2026-05-01 01:40:27 +00:00
< ? php
2026-05-11 11:40:06 +00:00
namespace Appwrite\Advisor\Validator ;
2026-05-01 01:40:27 +00:00
use Utopia\Validator ;
2026-05-01 02:27:45 +00:00
class CTAs extends Validator
2026-05-01 01:40:27 +00:00
{
refactor(insights): metadata-only CTAs, platform DB, reports parent
Address review feedback on PR #12194:
- Pivot CTAs to pure descriptors (id/label/action/params). Drop the
server-side execution layer: Action interface, registry, the
databases.indexes.create CTA action, the params validator, the
/v1/insights/:id/ctas/:id/executions endpoint, the InsightCTAExecution
model, the INSIGHT_CTA_* errors, and the corresponding events. The
console invokes the existing public API directly with the descriptor's
action + params.
- Restore Databases\Indexes\Action.php to its pre-CTA shape and inline
the index-create body back into Create.php (the createIndex helper
was added solely for CTA reuse).
- Move insights collection from project DB to platform DB and add a
parent reports collection alongside it. Insights carry projectId /
projectInternalId for tenant scoping and an optional reportId for
grouping. List endpoints filter by projectInternalId; Get/Update/
Delete also enforce project ownership before touching the document.
- New Reports module with full CRUD (Create/Get/XList/Update/Delete),
Report response model, Reports query validator, REPORT_NOT_FOUND /
REPORT_ALREADY_EXISTS errors, reports.read / reports.write scopes,
and reports.* event tree. Delete cascades to child insights.
- Update.php now mutates the loaded document via setAttribute (instead
of passing a partial new Document), reuses CTAsValidator (instead of
the looser ArrayList<JSON> + isset check), and rejects duplicate CTA
ids.
- Create.php enforces unique CTA ids during normalization.
- CTAsValidator gained a configurable maxCount (default 16) so the
Create path matches the Update path and the DB column size, and
oversized payloads return a clean 400.
- Validator\Queries\Insights adds status and reportId to
ALLOWED_ATTRIBUTES so dismissal / report workflows are filterable.
- Realtime channel parser guards $parts[1] for both insights and
reports event names.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:46:07 +00:00
public const MAX_COUNT_DEFAULT = 16 ;
refactor(insights): drop CTA `key` field
`key` was a leftover from when CTAs were embedded JSON — there's no
remaining reason to require analyzers to invent a within-insight
identifier. The execution layer is gone (no `cta.key` event format),
insights are immutable from the user side (analyzers re-ingest by
delete + recreate, so idempotent matching never happens), and `label`
already covers human-facing identification. The console can group/sort
CTAs by `service`+`method` if needed.
- Schema: drop `key` attribute and the UNIQUE
`(insightInternalId, key)` index from insightCTAs. Required fields
are now `label`, `service`, `method` (+ optional `params`).
- Validator no longer requires `key`. Drop the dup-key normalization
loop in the manager Create endpoint — there's no semantic
uniqueness to enforce.
- Response model: `InsightCTA` keeps `$id` + standard headers,
`insightId` backref, and the four functional fields.
- E2E: drop sampleCTA's `$key` parameter, drop the
testCreateRejectsDuplicateCTAIds test entirely, rename empty-fields
test to testCreateRejectsCTAWithEmptyLabel and update the missing-
fields tests to drop `key` from their payloads.
- Unit tests rewritten to drop `key`.
- Comment on the `insights.ctas` virtual attribute updated to
reference the renamed `insightCTAs` collection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 06:29:40 +00:00
protected string $message = 'Value must be an array of CTA descriptors. Each entry must define `label`, `service`, `method`, and an optional `params` object.' ;
2026-05-12 13:42:59 +00:00
protected array $allowedServices ;
protected array $allowedMethods ;
2026-05-01 01:40:27 +00:00
2026-05-12 13:42:59 +00:00
public function __construct (
protected int $maxCount = self :: MAX_COUNT_DEFAULT ,
? array $allowedServices = null ,
? array $allowedMethods = null ,
) {
$this -> allowedServices = $allowedServices ? ? ADVISOR_CTA_SERVICES ;
$this -> allowedMethods = $allowedMethods ? ? ADVISOR_CTA_METHODS ;
refactor(insights): metadata-only CTAs, platform DB, reports parent
Address review feedback on PR #12194:
- Pivot CTAs to pure descriptors (id/label/action/params). Drop the
server-side execution layer: Action interface, registry, the
databases.indexes.create CTA action, the params validator, the
/v1/insights/:id/ctas/:id/executions endpoint, the InsightCTAExecution
model, the INSIGHT_CTA_* errors, and the corresponding events. The
console invokes the existing public API directly with the descriptor's
action + params.
- Restore Databases\Indexes\Action.php to its pre-CTA shape and inline
the index-create body back into Create.php (the createIndex helper
was added solely for CTA reuse).
- Move insights collection from project DB to platform DB and add a
parent reports collection alongside it. Insights carry projectId /
projectInternalId for tenant scoping and an optional reportId for
grouping. List endpoints filter by projectInternalId; Get/Update/
Delete also enforce project ownership before touching the document.
- New Reports module with full CRUD (Create/Get/XList/Update/Delete),
Report response model, Reports query validator, REPORT_NOT_FOUND /
REPORT_ALREADY_EXISTS errors, reports.read / reports.write scopes,
and reports.* event tree. Delete cascades to child insights.
- Update.php now mutates the loaded document via setAttribute (instead
of passing a partial new Document), reuses CTAsValidator (instead of
the looser ArrayList<JSON> + isset check), and rejects duplicate CTA
ids.
- Create.php enforces unique CTA ids during normalization.
- CTAsValidator gained a configurable maxCount (default 16) so the
Create path matches the Update path and the DB column size, and
oversized payloads return a clean 400.
- Validator\Queries\Insights adds status and reportId to
ALLOWED_ATTRIBUTES so dismissal / report workflows are filterable.
- Realtime channel parser guards $parts[1] for both insights and
reports event names.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:46:07 +00:00
}
2026-05-01 01:40:27 +00:00
public function getDescription () : string
{
return $this -> message ;
}
public function isArray () : bool
{
return true ;
}
public function getType () : string
{
return self :: TYPE_ARRAY ;
}
public function isValid ( $value ) : bool
{
if ( ! \is_array ( $value )) {
return false ;
}
refactor(insights): metadata-only CTAs, platform DB, reports parent
Address review feedback on PR #12194:
- Pivot CTAs to pure descriptors (id/label/action/params). Drop the
server-side execution layer: Action interface, registry, the
databases.indexes.create CTA action, the params validator, the
/v1/insights/:id/ctas/:id/executions endpoint, the InsightCTAExecution
model, the INSIGHT_CTA_* errors, and the corresponding events. The
console invokes the existing public API directly with the descriptor's
action + params.
- Restore Databases\Indexes\Action.php to its pre-CTA shape and inline
the index-create body back into Create.php (the createIndex helper
was added solely for CTA reuse).
- Move insights collection from project DB to platform DB and add a
parent reports collection alongside it. Insights carry projectId /
projectInternalId for tenant scoping and an optional reportId for
grouping. List endpoints filter by projectInternalId; Get/Update/
Delete also enforce project ownership before touching the document.
- New Reports module with full CRUD (Create/Get/XList/Update/Delete),
Report response model, Reports query validator, REPORT_NOT_FOUND /
REPORT_ALREADY_EXISTS errors, reports.read / reports.write scopes,
and reports.* event tree. Delete cascades to child insights.
- Update.php now mutates the loaded document via setAttribute (instead
of passing a partial new Document), reuses CTAsValidator (instead of
the looser ArrayList<JSON> + isset check), and rejects duplicate CTA
ids.
- Create.php enforces unique CTA ids during normalization.
- CTAsValidator gained a configurable maxCount (default 16) so the
Create path matches the Update path and the DB column size, and
oversized payloads return a clean 400.
- Validator\Queries\Insights adds status and reportId to
ALLOWED_ATTRIBUTES so dismissal / report workflows are filterable.
- Realtime channel parser guards $parts[1] for both insights and
reports event names.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:46:07 +00:00
if ( \count ( $value ) > $this -> maxCount ) {
$this -> message = " A maximum of { $this -> maxCount } CTAs are allowed per insight. " ;
return false ;
}
2026-05-01 01:40:27 +00:00
foreach ( $value as $entry ) {
if ( ! \is_array ( $entry )) {
return false ;
}
2026-05-09 00:06:09 +00:00
$maxLengths = [ 'label' => 256 , 'service' => 64 , 'method' => 64 ];
foreach ( $maxLengths as $required => $maxLength ) {
2026-05-01 01:40:27 +00:00
if ( ! isset ( $entry [ $required ]) || ! \is_string ( $entry [ $required ]) || $entry [ $required ] === '' ) {
return false ;
}
2026-05-09 00:06:09 +00:00
if ( \strlen ( $entry [ $required ]) > $maxLength ) {
$this -> message = " CTA ` { $required } ` must not exceed { $maxLength } characters. " ;
return false ;
}
2026-05-01 01:40:27 +00:00
}
2026-05-12 13:42:59 +00:00
if ( ! empty ( $this -> allowedServices ) && ! \in_array ( $entry [ 'service' ], $this -> allowedServices , true )) {
$this -> message = " CTA `service` must be one of: " . \implode ( ', ' , $this -> allowedServices ) . '.' ;
return false ;
}
if ( ! empty ( $this -> allowedMethods ) && ! \in_array ( $entry [ 'method' ], $this -> allowedMethods , true )) {
$this -> message = " CTA `method` must be one of: " . \implode ( ', ' , $this -> allowedMethods ) . '.' ;
return false ;
}
2026-05-01 01:40:27 +00:00
if ( isset ( $entry [ 'params' ]) && ! \is_array ( $entry [ 'params' ]) && ! \is_object ( $entry [ 'params' ])) {
return false ;
}
}
return true ;
}
}