diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index ddb6717f7e..be44627167 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -2782,6 +2782,17 @@ return [ 'array' => false, 'filters' => [], ], + [ + // Possible values: active, dismissed + '$id' => ID::custom('status'), + 'type' => Database::VAR_STRING, + 'size' => 16, + 'signed' => true, + 'required' => true, + 'default' => 'active', + 'array' => false, + 'filters' => [], + ], [ // Possible values: databases, collections, sites, functions '$id' => ID::custom('resourceType'), diff --git a/app/config/events.php b/app/config/events.php index 576962fbe0..3b4d636471 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -440,14 +440,6 @@ return [ 'delete' => [ '$description' => 'This event triggers when an insight is deleted.', ], - 'dismissals' => [ - '$model' => Response::MODEL_INSIGHT, - '$resource' => true, - '$description' => 'This event triggers on any insight dismissal event.', - 'create' => [ - '$description' => 'This event triggers when an insight is dismissed.', - ], - ], 'ctas' => [ '$model' => Response::MODEL_INSIGHT_CTA, '$resource' => true, diff --git a/app/init/constants.php b/app/init/constants.php index 44b51bd6d9..5a4da73988 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -453,6 +453,15 @@ const INSIGHT_SEVERITIES = [ INSIGHT_SEVERITY_CRITICAL, ]; +// Insight statuses +const INSIGHT_STATUS_ACTIVE = 'active'; +const INSIGHT_STATUS_DISMISSED = 'dismissed'; + +const INSIGHT_STATUSES = [ + INSIGHT_STATUS_ACTIVE, + INSIGHT_STATUS_DISMISSED, +]; + // Insight CTA actions const INSIGHT_CTA_ACTION_DATABASES_CREATE_INDEX = 'databases.createIndex'; diff --git a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Create.php b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Create.php index cddcfd6738..9b6c17ad75 100644 --- a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Create.php +++ b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Create.php @@ -111,6 +111,7 @@ class Create extends Action '$id' => $insightId, 'type' => $type, 'severity' => $severity, + 'status' => INSIGHT_STATUS_ACTIVE, 'resourceType' => $resourceType, 'resourceId' => $resourceId, 'resourceInternalId' => $resourceInternalId, diff --git a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Dismissal/Create.php b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Dismissal/Create.php deleted file mode 100644 index 6430e35746..0000000000 --- a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Dismissal/Create.php +++ /dev/null @@ -1,87 +0,0 @@ -setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/insights/:insightId/dismissals') - ->desc('Create insight dismissal') - ->groups(['api', 'insights']) - ->label('scope', 'insights.write') - ->label('event', 'insights.[insightId].dismissals.create') - ->label('resourceType', RESOURCE_TYPE_INSIGHTS) - ->label('audits.event', 'insight.dismissal.create') - ->label('audits.resource', 'insight/{response.$id}') - ->label('abuse-key', 'projectId:{projectId},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'insights', - group: 'insights', - name: 'createDismissal', - description: <<param('insightId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Insight ID.', false, ['dbForProject']) - ->inject('response') - ->inject('user') - ->inject('dbForProject') - ->inject('queueForEvents') - ->callback($this->action(...)); - } - - public function action( - string $insightId, - Response $response, - Document $user, - Database $dbForProject, - Event $queueForEvents - ) { - $insight = $dbForProject->getDocument('insights', $insightId); - - if ($insight->isEmpty()) { - throw new Exception(Exception::INSIGHT_NOT_FOUND); - } - - $insight = $dbForProject->updateDocument('insights', $insight->getId(), new Document([ - 'dismissedAt' => DateTime::now(), - 'dismissedBy' => $user->getId(), - ])); - - $queueForEvents->setParam('insightId', $insight->getId()); - - $response->dynamic($insight, Response::MODEL_INSIGHT); - } -} diff --git a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Update.php b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Update.php index 47480eb980..300fa19cf1 100644 --- a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Update.php +++ b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Update.php @@ -9,6 +9,7 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\UID; @@ -61,12 +62,14 @@ class Update extends Action )) ->param('insightId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Insight ID.', false, ['dbForProject']) ->param('severity', null, new Nullable(new WhiteList(INSIGHT_SEVERITIES, true)), 'Insight severity. One of `info`, `warning`, `critical`.', true) + ->param('status', null, new Nullable(new WhiteList(INSIGHT_STATUSES, true)), 'Insight status. Set to `dismissed` to dismiss the insight, `active` to undo a dismissal.', true) ->param('title', null, new Nullable(new Text(256)), 'Short, human-readable title.', true) ->param('summary', null, new Nullable(new Text(4096, 0)), 'Markdown summary describing the insight.', true) ->param('payload', null, new Nullable(new JSON()), 'Type-specific structured payload.', true) ->param('ctas', null, new Nullable(new ArrayList(new JSON(), 16)), 'Array of call-to-action descriptors.', true) ->param('analyzedAt', null, new Nullable(new DatetimeValidator()), 'Time the insight was analyzed in ISO 8601 format.', true) ->inject('response') + ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') ->callback($this->action(...)); @@ -75,12 +78,14 @@ class Update extends Action public function action( string $insightId, ?string $severity, + ?string $status, ?string $title, ?string $summary, ?array $payload, ?array $ctas, ?string $analyzedAt, Response $response, + Document $user, Database $dbForProject, Event $queueForEvents ) { @@ -95,6 +100,16 @@ class Update extends Action if ($severity !== null) { $changes['severity'] = $severity; } + if ($status !== null && $status !== $insight->getAttribute('status')) { + $changes['status'] = $status; + if ($status === INSIGHT_STATUS_DISMISSED) { + $changes['dismissedAt'] = DateTime::now(); + $changes['dismissedBy'] = $user->getId(); + } else { + $changes['dismissedAt'] = null; + $changes['dismissedBy'] = ''; + } + } if ($title !== null) { $changes['title'] = $title; } diff --git a/src/Appwrite/Platform/Modules/Insights/Services/Http.php b/src/Appwrite/Platform/Modules/Insights/Services/Http.php index 48f52ca7e9..433df62865 100644 --- a/src/Appwrite/Platform/Modules/Insights/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Insights/Services/Http.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Insights\Services; use Appwrite\Platform\Modules\Insights\Http\CTA\Execution\Create as CreateInsightCTAExecution; use Appwrite\Platform\Modules\Insights\Http\Insights\Create as CreateInsight; use Appwrite\Platform\Modules\Insights\Http\Insights\Delete as DeleteInsight; -use Appwrite\Platform\Modules\Insights\Http\Insights\Dismissal\Create as CreateInsightDismissal; use Appwrite\Platform\Modules\Insights\Http\Insights\Get as GetInsight; use Appwrite\Platform\Modules\Insights\Http\Insights\Update as UpdateInsight; use Appwrite\Platform\Modules\Insights\Http\Insights\XList as ListInsights; @@ -22,7 +21,6 @@ class Http extends Service $this->addAction(ListInsights::getName(), new ListInsights()); $this->addAction(UpdateInsight::getName(), new UpdateInsight()); $this->addAction(DeleteInsight::getName(), new DeleteInsight()); - $this->addAction(CreateInsightDismissal::getName(), new CreateInsightDismissal()); $this->addAction(CreateInsightCTAExecution::getName(), new CreateInsightCTAExecution()); } } diff --git a/src/Appwrite/Utopia/Response/Model/Insight.php b/src/Appwrite/Utopia/Response/Model/Insight.php index 1c567f8c72..c1e437696c 100644 --- a/src/Appwrite/Utopia/Response/Model/Insight.php +++ b/src/Appwrite/Utopia/Response/Model/Insight.php @@ -47,6 +47,12 @@ class Insight extends Model 'default' => 'info', 'example' => 'warning', ]) + ->addRule('status', [ + 'type' => self::TYPE_STRING, + 'description' => 'Insight status. One of active, dismissed.', + 'default' => 'active', + 'example' => 'active', + ]) ->addRule('resourceType', [ 'type' => self::TYPE_STRING, 'description' => 'Type of the resource the insight is about. Plural noun, e.g. databases, sites, functions.', diff --git a/tests/e2e/Services/Insights/InsightsBase.php b/tests/e2e/Services/Insights/InsightsBase.php index 84bcd97c2d..178eaddc4a 100644 --- a/tests/e2e/Services/Insights/InsightsBase.php +++ b/tests/e2e/Services/Insights/InsightsBase.php @@ -129,20 +129,31 @@ trait InsightsBase /** * @depends testUpdate */ - public function testCreateDismissal(array $data): array + public function testDismissViaUpdate(array $data): array { $insightId = $data['insightId']; - $response = $this->client->call(Client::METHOD_POST, '/insights/' . $insightId . '/dismissals', $this->serverHeaders()); + $response = $this->client->call(Client::METHOD_PATCH, '/insights/' . $insightId, $this->serverHeaders(), [ + 'status' => 'dismissed', + ]); $this->assertSame(200, $response['headers']['status-code']); + $this->assertSame('dismissed', $response['body']['status']); $this->assertNotEmpty($response['body']['dismissedAt']); + $undismiss = $this->client->call(Client::METHOD_PATCH, '/insights/' . $insightId, $this->serverHeaders(), [ + 'status' => 'active', + ]); + + $this->assertSame(200, $undismiss['headers']['status-code']); + $this->assertSame('active', $undismiss['body']['status']); + $this->assertEmpty($undismiss['body']['dismissedAt']); + return $data; } /** - * @depends testCreateDismissal + * @depends testDismissViaUpdate */ public function testCreateCTAExecution(array $data): void {