From fb02e12db236cbd4fbfb6131928b54db1d493c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sun, 28 May 2023 13:39:48 +0200 Subject: [PATCH] Add state to comment, webhook bug fixing --- app/controllers/api/functions.php | 2 +- app/controllers/api/vcs.php | 86 +++++++++++----- app/workers/builds.php | 27 +++-- composer.lock | 4 +- .../Utopia/Response/Model/Deployment.php | 2 +- src/Appwrite/Vcs/Comment.php | 98 +++++++++++++++++++ 6 files changed, 178 insertions(+), 41 deletions(-) create mode 100644 src/Appwrite/Vcs/Comment.php diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index db009eb9e7..67dfc426bf 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1091,7 +1091,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId') } $build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')); - $deployment->setAttribute('status', $build->getAttribute('status', 'processing')); + $deployment->setAttribute('status', $build->getAttribute('status', 'waiting')); $deployment->setAttribute('buildStderr', $build->getAttribute('stderr', '')); $deployment->setAttribute('buildStdout', $build->getAttribute('stdout', '')); diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 851ea1d872..207912db5b 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -12,6 +12,7 @@ use Utopia\VCS\Adapter\Git\GitHub; use Appwrite\Extend\Exception; use Appwrite\Network\Validator\Host; use Appwrite\Utopia\Database\Validator\Queries\Installations; +use Appwrite\Vcs\Comment; use Utopia\Database\Query; use Utopia\Database\ID; use Utopia\Database\Permission; @@ -30,7 +31,7 @@ App::get('/v1/vcs/github/installations') ->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY) ->label('sdk.response.type', Response::CONTENT_TYPE_HTML) ->label('sdk.methodType', 'webAuth') - ->param('redirect', '', fn($clients) => new Host($clients), 'URL to redirect back to your Git authorization. Only console hostnames are allowed.', true, ['clients']) + ->param('redirect', '', fn ($clients) => new Host($clients), 'URL to redirect back to your Git authorization. Only console hostnames are allowed.', true, ['clients']) ->inject('response') ->inject('project') ->action(function (string $redirect, Response $response, Document $project) { @@ -225,24 +226,22 @@ App::get('v1/vcs/github/installations/:installationId/repositories/:repositoryId $response->dynamic(new Document([ 'branches' => \array_map(function ($branch) { - return [ 'name' => $branch ]; + return ['name' => $branch]; }, $branches), 'total' => \count($branches), ]), Response::MODEL_BRANCH_LIST); }); -$createGitDeployments = function (array $vcsRepos, string $branchName, string $SHA, Database $dbForConsole, callable $getProjectDB, Request $request) { +$createGitDeployments = function (array $vcsRepos, string $branchName, string $SHA, string $commentId, Database $dbForConsole, callable $getProjectDB, Request $request) { foreach ($vcsRepos as $resource) { $resourceType = $resource->getAttribute('resourceType'); if ($resourceType === "function") { $projectId = $resource->getAttribute('projectId'); - //TODO: Why is Authorization::skip needed? $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); $dbForProject = $getProjectDB($project); $functionId = $resource->getAttribute('resourceId'); - //TODO: Why is Authorization::skip needed? $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); $deploymentId = ID::unique(); $vcsRepoId = $resource->getId(); @@ -256,6 +255,18 @@ $createGitDeployments = function (array $vcsRepos, string $branchName, string $S $activate = true; } + if (empty($commentId)) { + $latestDeployment = Authorization::skip(fn () => $dbForProject->findOne('deployments', [ + Query::equal('vcsRepositoryId', [$vcsRepoId]), + Query::equal('branch', [$branchName]), + Query::equal('resourceType', ['functions']), + Query::orderDesc('$createdAt'), + ])); + if ($latestDeployment !== false && !$latestDeployment->isEmpty()) { + $commentId = $latestDeployment->getAttribute('vcsCommentId', ''); + } + } + $deployment = $dbForProject->createDocument('deployments', new Document([ '$id' => $deploymentId, '$permissions' => [ @@ -273,6 +284,7 @@ $createGitDeployments = function (array $vcsRepos, string $branchName, string $S 'vcsInstallationInternalId' => $vcsInstallationInternalId, 'vcsRepositoryId' => $vcsRepoId, 'vcsRepositoryInternalId' => $vcsRepoInternalId, + 'vcsCommentId' => $commentId, 'branch' => $branchName, 'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]), 'activate' => $activate, @@ -326,7 +338,7 @@ App::post('/v1/vcs/github/incomingwebhook') Query::limit(100), ]); - $createGitDeployments($vcsRepos, $branchName, $SHA, $dbForConsole, $getProjectDB, $request); + $createGitDeployments($vcsRepos, $branchName, $SHA, '', $dbForConsole, $getProjectDB, $request); } elseif ($event == $github::EVENT_INSTALLATION) { if ($parsedPayload["action"] == "deleted") { // TODO: Use worker for this job instead (update function as well) @@ -365,30 +377,50 @@ App::post('/v1/vcs/github/incomingwebhook') Query::orderDesc('$createdAt') ]); - if (\count($vcsRepos) === 0) { - $createGitDeployments($vcsRepos, $branchName, '', $dbForConsole, $getProjectDB, $request); - } + if (\count($vcsRepos) !== 0) { + $comment = new Comment(); - // TODO: Use for loop instead - $vcsRepo = $vcsRepos[0]; + foreach ($vcsRepos as $vcsRepo) { + $projectId = $vcsRepo->getAttribute('projectId'); + $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $dbForProject = $getProjectDB($project); - $projectId = $vcsRepo->getAttribute('projectId'); - //TODO: Why is Authorization::skip needed? - $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); - $dbForProject = $getProjectDB($project); - $vcsRepoId = $vcsRepo->getId(); - $deployment = Authorization::skip(fn () => $dbForProject->findOne('deployments', [ - Query::equal('vcsRepositoryId', [$vcsRepoId]), - Query::equal('branch', [$branchName]), - Query::orderDesc('$createdAt'), - ])); + $vcsRepoId = $vcsRepo->getId(); - if ($deployment !== false && !$deployment->isEmpty()) { - $buildId = $deployment->getAttribute('buildId'); - $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId)); - $buildStatus = $build->getAttribute('status'); - $comment = "| Build Status |\r\n | --------------- |\r\n | $buildStatus |"; - $github->addComment($owner, $repositoryName, $pullRequestNumber, $comment); + $deployment = Authorization::skip(fn () => $dbForProject->findOne('deployments', [ + Query::equal('vcsRepositoryId', [$vcsRepoId]), + Query::equal('branch', [$branchName]), + Query::equal('resourceType', ['functions']), + Query::orderDesc('$createdAt'), + ])); + + if (!$deployment || $deployment->isEmpty()) { + $function = Authorization::skip(fn () => $dbForProject->findOne('functions', [ + Query::equal('vcsRepositoryId', [$vcsRepoId]), + Query::orderDesc('$createdAt'), + ])); + $build = new Document([]); + } else { + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))); + $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + } + + if (!$function || $function->isEmpty()) { + continue; + } + + $status = !$build || $build->isEmpty() ? 'waiting' : $build->getAttribute('status', 'waiting'); + $deploymentId = !$build || $build->isEmpty() ? '' : $build->getAttribute('deploymentId', ''); + + $comment->addBuild($project, $function, $status, $deploymentId); + } + + $commentId = ''; + if (!$comment->isEmpty()) { + $commentId = $github->createComment($owner, $repositoryName, $pullRequestNumber, $comment->generateComment()); + } + + $createGitDeployments($vcsRepos, $branchName, '', $commentId, $dbForConsole, $getProjectDB, $request); } } } diff --git a/app/workers/builds.php b/app/workers/builds.php index e5f4165205..04858d29e3 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -7,6 +7,7 @@ use Appwrite\Resque\Worker; use Appwrite\Utopia\Response\Model\Deployment; use Executor\Executor; use Appwrite\Usage\Stats; +use Appwrite\Vcs\Comment; use Utopia\Database\DateTime; use Utopia\App; use Utopia\CLI\Console; @@ -163,9 +164,10 @@ class BuildsV1 extends Worker } $commentId = $deployment->getAttribute('vcsCommentId'); if ($commentId) { - $comment = "| Build Status |\r\n | --------------- |\r\n | Processing |"; - - $github->updateComment($owner, $repositoryName, $commentId, $comment); + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $commentId)); + $comment->addBuild($project, $function, 'processing', $deployment->getId()); + $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); } } else { $build = $dbForProject->createDocument('builds', new Document([ @@ -201,8 +203,10 @@ class BuildsV1 extends Worker if ($isVcsEnabled) { $commentId = $deployment->getAttribute('vcsCommentId'); if ($commentId) { - $comment = "| Build Status |\r\n | --------------- |\r\n | Building |"; - $github->updateComment($owner, $repositoryName, $commentId, $comment); + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $commentId)); + $comment->addBuild($project, $function, 'building', $deployment->getId()); + $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); } } @@ -303,15 +307,16 @@ class BuildsV1 extends Worker $build->setAttribute('stdout', $response['stdout']); if ($isVcsEnabled) { - $status = 'ready'; if ($SHA !== "" && $owner !== "") { $github->updateCommitStatus($repositoryName, $SHA, $owner, "success", "Deployment is successful!", $targetUrl, "Appwrite Deployment"); } $commentId = $deployment->getAttribute('vcsCommentId'); if ($commentId) { - $comment = "| Build Status |\r\n | --------------- |\r\n | $status |"; - $github->updateComment($owner, $repositoryName, $commentId, $comment); + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $commentId)); + $comment->addBuild($project, $function, 'ready', $deployment->getId()); + $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); } } @@ -355,8 +360,10 @@ class BuildsV1 extends Worker $commentId = $deployment->getAttribute('vcsCommentId'); if ($commentId) { - $comment = "| Build Status |\r\n | --------------- |\r\n | $status |"; - $github->updateComment($owner, $repositoryName, $commentId, $comment); + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $commentId)); + $comment->addBuild($project, $function, 'failed', $deployment->getId()); + $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); } } } finally { diff --git a/composer.lock b/composer.lock index d949f65d8f..4471de23f0 100644 --- a/composer.lock +++ b/composer.lock @@ -3090,7 +3090,7 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "1331193f6ba0443a87f4fd665a8f91dc07817370" + "reference": "9215381b4fde95e26ab1f01d5add6d2d428d6fd7" }, "require": { "adhocore/jwt": "^1.1", @@ -3135,7 +3135,7 @@ "utopia", "vcs" ], - "time": "2023-05-26T08:41:54+00:00" + "time": "2023-05-28T10:14:37+00:00" }, { "name": "utopia-php/websocket", diff --git a/src/Appwrite/Utopia/Response/Model/Deployment.php b/src/Appwrite/Utopia/Response/Model/Deployment.php index 050b4e7a1a..58bd432a19 100644 --- a/src/Appwrite/Utopia/Response/Model/Deployment.php +++ b/src/Appwrite/Utopia/Response/Model/Deployment.php @@ -66,7 +66,7 @@ class Deployment extends Model ]) ->addRule('status', [ 'type' => self::TYPE_STRING, - 'description' => 'The deployment status. Possible values are "processing", "building", "pending", "ready", and "failed".', + 'description' => 'The deployment status. Possible values are "processing", "building", "waiting", "ready", and "failed".', 'default' => '', 'example' => 'ready', ]) diff --git a/src/Appwrite/Vcs/Comment.php b/src/Appwrite/Vcs/Comment.php new file mode 100644 index 0000000000..46f34029f7 --- /dev/null +++ b/src/Appwrite/Vcs/Comment.php @@ -0,0 +1,98 @@ +builds) === 0; + } + + public function addBuild(Document $project, Document $function, string $buildStatus, string $deploymentId): void + { + // Unique index + $id = $project->getId() . '_' . $function->getId(); + + $this->builds[$id] = [ + 'projectName' => $project->getAttribute('name'), + 'projectId' => $project->getId(), + 'functionName' => $function->getAttribute('name'), + 'functionId' => $function->getId(), + 'buildStatus' => $buildStatus, + 'deploymentId' => $deploymentId + ]; + } + + public function generateComment(): string + { + $json = \json_encode($this->builds); + + $text = $this->statePrefix . \base64_encode($json) . "\n\n"; + + $projects = []; + + foreach ($this->builds as $id => $build) { + if (!\array_key_exists($build['projectId'], $projects)) { + $projects[$build['projectId']] = [ + 'name' => $build['projectName'], + 'functions' => [] + ]; + } + + $projects[$build['projectId']]['functions'][$build['functionId']] = [ + 'name' => $build['functionName'], + 'status' => $build['buildStatus'], + 'deploymentId' => $build['deploymentId'] + ]; + } + + $text .= "> Bored? Read random [Wikipedia Page](https://en.wikipedia.org/wiki/Special:Random).\n\n"; + + foreach ($projects as $projectId => $project) { + $text .= "### {$project['name']} `{$projectId}`\n\n"; + $text .= "| Function | Status | Execute |\n"; + $text .= "| :- | :- | :- |\n"; + + foreach ($project['functions'] as $functionId => $function) { + $status = match ($function['status']) { + 'waiting' => '⌛ Waiting', + 'processing' => '🤔 Processing', + 'building' => '🛠️ Building', + 'ready' => '✅ Ready', + 'failed' => '❌ Failed', + }; + + $execute = $function['status'] === 'ready' ? "[HTTP](#) \\| [Console](#)" : '_Build must be ready first_'; + + $text .= "| **{$function['name']}** `{$functionId}` | {$status} ([Logs](#)) | {$execute} |\n"; + } + + $text .= "\n"; + } + + return $text; + } + + public function parseComment(string $comment): self + { + $state = \explode("\n", $comment)[0] ?? ''; + $state = substr($state, strlen($this->statePrefix)); + + $json = \base64_decode($state); + + $builds = \json_decode($json, true); + $this->builds = $builds; + + return $this; + } +}