diff --git a/app/config/collections/platform.php b/app/config/collections/platform.php index 7c54da0f0a..ea65759876 100644 --- a/app/config/collections/platform.php +++ b/app/config/collections/platform.php @@ -2261,6 +2261,10 @@ $platformCollections = [ 'filters' => ['json'], ], [ + // Virtual attribute — CTAs live in their own `ctas` collection + // back-referenced by `insightInternalId`. The subQuery filter + // joins them in at read time, so consumers still see them + // embedded on the insight response. '$id' => ID::custom('ctas'), 'type' => Database::VAR_STRING, 'format' => '', @@ -2269,7 +2273,7 @@ $platformCollections = [ 'required' => false, 'default' => null, 'array' => false, - 'filters' => ['json'], + 'filters' => ['subQueryInsightCTAs'], ], [ '$id' => ID::custom('analyzedAt'), @@ -2364,6 +2368,140 @@ $platformCollections = [ ], ], ], + + 'ctas' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('ctas'), + 'name' => 'Insight CTAs', + 'attributes' => [ + [ + '$id' => ID::custom('projectInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('projectId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('insightInternalId'), + 'type' => Database::VAR_ID, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('insightId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + // Caller-supplied identifier, unique within the parent insight. + '$id' => ID::custom('key'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('label'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + // SDK namespace (databases / tablesDB / documentsDB / vectorsDB / …). + '$id' => ID::custom('service'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 64, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('method'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 64, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('params'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['json'], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_project'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectInternalId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + // Primary lookup — fetch all CTAs of an insight (subQuery filter). + '$id' => ID::custom('_key_project_insight'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectInternalId', 'insightInternalId'], + 'lengths' => [Database::LENGTH_KEY, 0], + 'orders' => [], + ], + [ + // Enforce per-insight key uniqueness at the DB layer. + '$id' => ID::custom('_key_insight_key'), + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['insightInternalId', 'key'], + 'lengths' => [0, Database::LENGTH_KEY], + 'orders' => [], + ], + ], + ], ]; // Organization API keys subquery diff --git a/app/init/database/filters.php b/app/init/database/filters.php index 5a65479424..3d9a20a24b 100644 --- a/app/init/database/filters.php +++ b/app/init/database/filters.php @@ -475,3 +475,17 @@ Database::addFilter( ])); } ); + +Database::addFilter( + 'subQueryInsightCTAs', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database->getAuthorization()->skip(fn () => $database + ->find('ctas', [ + Query::equal('insightInternalId', [$document->getSequence()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); diff --git a/src/Appwrite/Insights/Validator/CTAs.php b/src/Appwrite/Insights/Validator/CTAs.php index e7e9de8205..565b4be93f 100644 --- a/src/Appwrite/Insights/Validator/CTAs.php +++ b/src/Appwrite/Insights/Validator/CTAs.php @@ -8,7 +8,7 @@ class CTAs extends Validator { public const MAX_COUNT_DEFAULT = 16; - protected string $message = 'Value must be an array of CTA descriptors. Each entry must define `id`, `label`, `service`, `method`, and an optional `params` object.'; + protected string $message = 'Value must be an array of CTA descriptors. Each entry must define `key`, `label`, `service`, `method`, and an optional `params` object.'; public function __construct(protected int $maxCount = self::MAX_COUNT_DEFAULT) { @@ -45,7 +45,7 @@ class CTAs extends Validator return false; } - foreach (['id', 'label', 'service', 'method'] as $required) { + foreach (['key', 'label', 'service', 'method'] as $required) { if (!isset($entry[$required]) || !\is_string($entry[$required]) || $entry[$required] === '') { return false; } diff --git a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Delete.php b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Delete.php index 5e2d4b36fe..2f7974b965 100644 --- a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Delete.php +++ b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Delete.php @@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Query; use Utopia\Database\Validator\UID; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; @@ -76,6 +77,16 @@ class Delete extends Action throw new Exception(Exception::INSIGHT_NOT_FOUND); } + // Cascade delete child CTAs first. + $childCTAs = $dbForPlatform->find('ctas', [ + Query::equal('insightInternalId', [$insight->getSequence()]), + Query::limit(APP_LIMIT_COUNT), + ]); + + foreach ($childCTAs as $cta) { + $dbForPlatform->deleteDocument('ctas', $cta->getId()); + } + if (!$dbForPlatform->deleteDocument('insights', $insight->getId())) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove insight from DB'); } diff --git a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Update.php b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Update.php index 01469e64d2..a8cc5803a5 100644 --- a/src/Appwrite/Platform/Modules/Insights/Http/Insights/Update.php +++ b/src/Appwrite/Platform/Modules/Insights/Http/Insights/Update.php @@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Insights\Http\Insights; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Insights\Validator\CTAs as CTAsValidator; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -12,15 +11,20 @@ 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; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; -use Utopia\Validator\JSON; use Utopia\Validator\Nullable; -use Utopia\Validator\Text; use Utopia\Validator\WhiteList; +/** + * User-facing Update endpoint. + * + * Limited to user-controlled state: dismissal (status), and severity overrides. + * Analyzer-controlled fields (title, summary, payload, ctas, analyzedAt) flow + * through the manager-only Create endpoint — analyzers re-ingest by deleting + * the stale insight and submitting a fresh one. + */ class Update extends Action { use HTTP; @@ -50,7 +54,7 @@ class Update extends Action group: 'insights', name: 'update', description: <<param('insightId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Insight ID.', false, ['dbForPlatform']) ->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 CTAsValidator()), '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('project') @@ -80,11 +79,6 @@ class Update extends Action string $insightId, ?string $severity, ?string $status, - ?string $title, - ?string $summary, - ?array $payload, - ?array $ctas, - ?string $analyzedAt, Response $response, Document $user, Document $project, @@ -112,38 +106,6 @@ class Update extends Action $changes['dismissedBy'] = ''; } } - if ($title !== null) { - $changes['title'] = $title; - } - if ($summary !== null) { - $changes['summary'] = $summary; - } - if ($payload !== null) { - $changes['payload'] = $payload; - } - if ($ctas !== null) { - $seen = []; - $normalized = []; - foreach ($ctas as $cta) { - $ctaId = (string) $cta['id']; - if (isset($seen[$ctaId])) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'CTA `id` values must be unique within an insight.'); - } - $seen[$ctaId] = true; - - $normalized[] = [ - 'id' => $ctaId, - 'label' => (string) $cta['label'], - 'service' => (string) $cta['service'], - 'method' => (string) $cta['method'], - 'params' => $cta['params'] ?? new \stdClass(), - ]; - } - $changes['ctas'] = $normalized; - } - if ($analyzedAt !== null) { - $changes['analyzedAt'] = $analyzedAt; - } if ($changes !== []) { foreach ($changes as $key => $value) { diff --git a/src/Appwrite/Platform/Modules/Insights/Http/Manager/Insights/Create.php b/src/Appwrite/Platform/Modules/Insights/Http/Manager/Insights/Create.php index 5e909ab25e..6455899638 100644 --- a/src/Appwrite/Platform/Modules/Insights/Http/Manager/Insights/Create.php +++ b/src/Appwrite/Platform/Modules/Insights/Http/Manager/Insights/Create.php @@ -85,7 +85,7 @@ class Create extends Action ->param('title', '', new Text(256), 'Short, human-readable title.') ->param('summary', '', new Text(4096, 0), 'Markdown summary describing the insight.', true) ->param('payload', null, new Nullable(new JSON()), 'Type-specific structured payload.', true) - ->param('ctas', [], new CTAsValidator(), 'Array of call-to-action descriptors. Each must contain `id`, `label`, `service`, `method`, and an optional `params` object.', true) + ->param('ctas', [], new CTAsValidator(), 'Array of call-to-action descriptors. Each must contain `key` (unique within the insight), `label`, `service`, `method`, and an optional `params` object.', true) ->param('analyzedAt', null, new Nullable(new DatetimeValidator()), 'Time the insight was analyzed in ISO 8601 format. Defaults to now.', true) ->inject('response') ->inject('project') @@ -133,14 +133,14 @@ class Create extends Action $normalizedCTAs = []; foreach ($ctas as $cta) { - $ctaId = (string) $cta['id']; - if (isset($seen[$ctaId])) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'CTA `id` values must be unique within an insight.'); + $key = (string) $cta['key']; + if (isset($seen[$key])) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'CTA `key` values must be unique within an insight.'); } - $seen[$ctaId] = true; + $seen[$key] = true; $normalizedCTAs[] = [ - 'id' => $ctaId, + 'key' => $key, 'label' => (string) $cta['label'], 'service' => (string) $cta['service'], 'method' => (string) $cta['method'], @@ -167,7 +167,6 @@ class Create extends Action 'title' => $title, 'summary' => $summary, 'payload' => $payload, - 'ctas' => $normalizedCTAs, 'analyzedAt' => $analyzedAt, 'dismissedAt' => null, 'dismissedBy' => '', @@ -176,6 +175,25 @@ class Create extends Action throw new Exception(Exception::INSIGHT_ALREADY_EXISTS); } + foreach ($normalizedCTAs as $cta) { + $dbForPlatform->createDocument('ctas', new Document([ + '$id' => ID::unique(), + 'projectInternalId' => $project->getSequence(), + 'projectId' => $project->getId(), + 'insightInternalId' => $insight->getSequence(), + 'insightId' => $insight->getId(), + 'key' => $cta['key'], + 'label' => $cta['label'], + 'service' => $cta['service'], + 'method' => $cta['method'], + 'params' => $cta['params'], + ])); + } + + // Re-fetch so the subQueryInsightCTAs filter embeds the freshly-created + // CTA documents on the response — keeps a single round-trip for callers. + $insight = $dbForPlatform->getDocument('insights', $insight->getId()); + $queueForEvents->setParam('insightId', $insight->getId()); $response diff --git a/src/Appwrite/Platform/Modules/Insights/Http/Reports/Delete.php b/src/Appwrite/Platform/Modules/Insights/Http/Reports/Delete.php index f5bfe4a651..5560d3060c 100644 --- a/src/Appwrite/Platform/Modules/Insights/Http/Reports/Delete.php +++ b/src/Appwrite/Platform/Modules/Insights/Http/Reports/Delete.php @@ -84,6 +84,15 @@ class Delete extends Action ]); foreach ($childInsights as $insight) { + // Cascade through CTAs first. + $childCTAs = $dbForPlatform->find('ctas', [ + Query::equal('insightInternalId', [$insight->getSequence()]), + Query::limit(APP_LIMIT_COUNT), + ]); + foreach ($childCTAs as $cta) { + $dbForPlatform->deleteDocument('ctas', $cta->getId()); + } + $dbForPlatform->deleteDocument('insights', $insight->getId()); } diff --git a/src/Appwrite/Utopia/Response/Model/InsightCTA.php b/src/Appwrite/Utopia/Response/Model/InsightCTA.php index fbdecd9951..2fd493cd57 100644 --- a/src/Appwrite/Utopia/Response/Model/InsightCTA.php +++ b/src/Appwrite/Utopia/Response/Model/InsightCTA.php @@ -10,9 +10,33 @@ class InsightCTA extends Model public function __construct() { $this - ->addRule('id', [ + ->addRule('$id', [ 'type' => self::TYPE_STRING, - 'description' => 'CTA identifier, unique within the parent insight.', + 'description' => 'CTA document ID.', + 'default' => '', + 'example' => '5e5ea5c16897e', + ]) + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'CTA creation date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'CTA update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('insightId', [ + 'type' => self::TYPE_STRING, + 'description' => 'ID of the insight that owns this CTA.', + 'default' => '', + 'example' => '5e5ea5c16897e', + ]) + ->addRule('key', [ + 'type' => self::TYPE_STRING, + 'description' => 'Caller-supplied identifier, unique within the parent insight.', 'default' => '', 'example' => 'createIndex', ]) diff --git a/tests/e2e/Services/Insights/InsightsBase.php b/tests/e2e/Services/Insights/InsightsBase.php index a42fda8e6c..032dc2d7b4 100644 --- a/tests/e2e/Services/Insights/InsightsBase.php +++ b/tests/e2e/Services/Insights/InsightsBase.php @@ -84,10 +84,10 @@ trait InsightsBase * - `documentsDB` → service `documentsDB`, method `createIndex` (params use collectionId/attributes) * - `vectorsDB` → service `vectorsDB`, method `createIndex` (params use collectionId/attributes) */ - protected function sampleCTA(string $id = 'createIndex', string $engine = 'tablesDB'): array + protected function sampleCTA(string $key = 'createIndex', string $engine = 'tablesDB'): array { $base = [ - 'id' => $id, + 'key' => $key, 'label' => 'Create missing index', 'method' => 'createIndex', ]; @@ -349,11 +349,15 @@ trait InsightsBase $this->assertSame('orders', $insight['body']['parentResourceId']); $this->assertSame('Missing index on collection orders', $insight['body']['title']); $this->assertCount(1, $insight['body']['ctas']); - $this->assertSame('createIndex', $insight['body']['ctas'][0]['id']); + $this->assertSame('createIndex', $insight['body']['ctas'][0]['key']); + $this->assertSame($insightId, $insight['body']['ctas'][0]['insightId']); + $this->assertSame('Create missing index', $insight['body']['ctas'][0]['label']); $this->assertSame('tablesDB', $insight['body']['ctas'][0]['service']); $this->assertSame('createIndex', $insight['body']['ctas'][0]['method']); $this->assertSame('orders', $insight['body']['ctas'][0]['params']['tableId']); $this->assertSame(['status'], $insight['body']['ctas'][0]['params']['columns']); + $this->assertArrayHasKey('$id', $insight['body']['ctas'][0]); + $this->assertArrayHasKey('$createdAt', $insight['body']['ctas'][0]); $this->assertEmpty($insight['body']['dismissedAt']); $this->assertEmpty($insight['body']['dismissedBy']); @@ -480,8 +484,8 @@ trait InsightsBase 'resourceId' => 'main', 'title' => 'Should not be created', 'ctas' => [ - ['id' => 'dup', 'label' => 'A', 'service' => 'databases', 'method' => 'createIndex'], - ['id' => 'dup', 'label' => 'B', 'service' => 'databases', 'method' => 'createIndex'], + ['key' => 'dup', 'label' => 'A', 'service' => 'databases', 'method' => 'createIndex'], + ['key' => 'dup', 'label' => 'B', 'service' => 'databases', 'method' => 'createIndex'], ], ]); @@ -498,7 +502,7 @@ trait InsightsBase 'resourceId' => 'main', 'title' => 'Should not be created', 'ctas' => [ - ['id' => '', 'label' => 'Has empty id', 'service' => 'databases', 'method' => 'createIndex'], + ['key' => '', 'label' => 'Has empty id', 'service' => 'databases', 'method' => 'createIndex'], ], ]); @@ -514,7 +518,7 @@ trait InsightsBase 'resourceId' => 'main', 'title' => 'Should not be created', 'ctas' => [ - ['id' => 'createIndex', 'label' => 'Missing method', 'service' => 'tablesDB'], + ['key' => 'createIndex', 'label' => 'Missing method', 'service' => 'tablesDB'], ], ]); @@ -530,7 +534,7 @@ trait InsightsBase 'resourceId' => 'main', 'title' => 'Should not be created', 'ctas' => [ - ['id' => 'createIndex', 'label' => 'Missing service', 'method' => 'createIndex'], + ['key' => 'createIndex', 'label' => 'Missing service', 'method' => 'createIndex'], ], ]); @@ -542,7 +546,7 @@ trait InsightsBase $ctas = []; for ($i = 0; $i < 17; $i++) { $ctas[] = [ - 'id' => 'cta-' . $i, + 'key' => 'cta-' . $i, 'label' => 'CTA ' . $i, 'service' => 'databases', 'method' => 'createIndex', @@ -727,40 +731,6 @@ trait InsightsBase /** * @depends testUpdate */ - public function testUpdateRejectsDuplicateCTAIds(array $data): array - { - $response = $this->updateInsight($data['insightId'], [ - 'ctas' => [ - ['id' => 'dup', 'label' => 'A', 'service' => 'databases', 'method' => 'createIndex'], - ['id' => 'dup', 'label' => 'B', 'service' => 'databases', 'method' => 'createIndex'], - ], - ]); - - $this->assertSame(400, $response['headers']['status-code']); - $this->assertSame('general_argument_invalid', $response['body']['type']); - - return $data; - } - - /** - * @depends testUpdateRejectsDuplicateCTAIds - */ - public function testUpdateRejectsCTAWithEmptyFields(array $data): array - { - $response = $this->updateInsight($data['insightId'], [ - 'ctas' => [ - ['id' => '', 'label' => 'Has empty id', 'service' => 'databases', 'method' => 'createIndex'], - ], - ]); - - $this->assertSame(400, $response['headers']['status-code']); - - return $data; - } - - /** - * @depends testUpdateRejectsCTAWithEmptyFields - */ public function testDismissViaUpdate(array $data): array { $dismissed = $this->updateInsight($data['insightId'], ['status' => 'dismissed']); diff --git a/tests/unit/Insights/Validator/CTAsTest.php b/tests/unit/Insights/Validator/CTAsTest.php index 629cb06b57..fbc30cad81 100644 --- a/tests/unit/Insights/Validator/CTAsTest.php +++ b/tests/unit/Insights/Validator/CTAsTest.php @@ -28,7 +28,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $this->assertTrue($validator->isValid([[ - 'id' => 'createIndex', + 'key' => 'createIndex', 'label' => 'Create missing index', 'service' => 'tablesDB', 'method' => 'createIndex', @@ -44,7 +44,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $this->assertTrue($validator->isValid([[ - 'id' => 'createIndex', + 'key' => 'createIndex', 'label' => 'Create missing index', 'service' => 'tablesDB', 'method' => 'createIndex', @@ -55,10 +55,10 @@ class CTAsTest extends TestCase { $validator = new CTAs(); - $this->assertFalse($validator->isValid([['id' => 'x']])); - $this->assertFalse($validator->isValid([['id' => 'x', 'label' => 'y']])); - $this->assertFalse($validator->isValid([['id' => 'x', 'label' => 'y', 'service' => 'tablesDB']])); - $this->assertFalse($validator->isValid([['id' => 'x', 'label' => 'y', 'method' => 'createIndex']])); + $this->assertFalse($validator->isValid([['key' => 'x']])); + $this->assertFalse($validator->isValid([['key' => 'x', 'label' => 'y']])); + $this->assertFalse($validator->isValid([['key' => 'x', 'label' => 'y', 'service' => 'tablesDB']])); + $this->assertFalse($validator->isValid([['key' => 'x', 'label' => 'y', 'method' => 'createIndex']])); } public function testRejectsEntryWithEmptyStrings(): void @@ -66,7 +66,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $this->assertFalse($validator->isValid([[ - 'id' => '', + 'key' => '', 'label' => 'Create missing index', 'service' => 'tablesDB', 'method' => 'createIndex', @@ -78,7 +78,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $this->assertFalse($validator->isValid([[ - 'id' => 123, + 'key' => 123, 'label' => 'Create missing index', 'service' => 'tablesDB', 'method' => 'createIndex', @@ -90,7 +90,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $this->assertFalse($validator->isValid([[ - 'id' => 'createIndex', + 'key' => 'createIndex', 'label' => 'Create missing index', 'service' => 'tablesDB', 'method' => 'createIndex', @@ -113,7 +113,7 @@ class CTAsTest extends TestCase $entries = []; for ($i = 0; $i < 4; $i++) { $entries[] = [ - 'id' => 'cta-' . $i, + 'key' => 'cta-' . $i, 'label' => 'Label ' . $i, 'service' => 'tablesDB', 'method' => 'createIndex', @@ -131,7 +131,7 @@ class CTAsTest extends TestCase $entries = []; for ($i = 0; $i < 3; $i++) { $entries[] = [ - 'id' => 'cta-' . $i, + 'key' => 'cta-' . $i, 'label' => 'Label ' . $i, 'service' => 'tablesDB', 'method' => 'createIndex', @@ -146,7 +146,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $entry = [ - 'id' => 'createIndex', + 'key' => 'createIndex', 'label' => 'Create missing index', 'service' => 'tablesDB', 'method' => 'createIndex', @@ -161,7 +161,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $this->assertFalse($validator->isValid([[ - 'id' => 'createIndex', + 'key' => 'createIndex', 'label' => 'Create missing index', 'service' => '', 'method' => 'createIndex', @@ -173,7 +173,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $this->assertFalse($validator->isValid([[ - 'id' => 'createIndex', + 'key' => 'createIndex', 'label' => 'Create missing index', 'service' => 'tablesDB', 'method' => '', @@ -185,7 +185,7 @@ class CTAsTest extends TestCase $validator = new CTAs(); $this->assertFalse($validator->isValid([[ - 'id' => 'createIndex', + 'key' => 'createIndex', 'label' => '', 'service' => 'tablesDB', 'method' => 'createIndex', @@ -201,7 +201,7 @@ class CTAsTest extends TestCase $entries = []; for ($i = 0; $i < 16; $i++) { $entries[] = [ - 'id' => 'cta-' . $i, + 'key' => 'cta-' . $i, 'label' => 'Label ' . $i, 'service' => 'tablesDB', 'method' => 'createIndex', @@ -211,7 +211,7 @@ class CTAsTest extends TestCase $this->assertTrue($validator->isValid($entries)); $entries[] = [ - 'id' => 'cta-16', + 'key' => 'cta-16', 'label' => 'Label 16', 'service' => 'tablesDB', 'method' => 'createIndex',