feat(integration-registry): 16 leaf providers (mega-leaf — depends on umbrella stack)#1514
Conversation
… external router (umbrella PR 1/N) First slice of the pluggable-integration-registry umbrella (#1307). Lands the foundation that every leaf change in waves W1-W3 depends on: the provider contract, the registry, external-routing plumbing, and the query-time storage-strategy helper. No built-in provider migration yet (tasks 12-17); no schema validator refactor yet (tasks 7-11); no frontend wiring yet (tasks 25+). Tasks completed: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6 (6/69 of the umbrella). What this adds: - lib/Service/Integration/IntegrationProvider.php — 15-method interface per design.md normative contract. Storage strategies: magic-column / link-table / external / query-time. - lib/Service/Integration/AbstractIntegrationProvider.php — base class with sensible defaults; mutation methods (get / create / update / delete) default to NotImplementedException so list-only and query-time providers don't have to spell that out. - lib/Service/Integration/IntegrationRegistry.php — explicit addProvider() registration with collision detection (AD-13) and external-source rejection (AD-4). Spec called for DI-tag-based discovery, but modern Nextcloud doesn't expose IAppContainer::queryAll(<tag>) as public API. Switched to explicit addProvider() at app bootstrap with identical semantics — documented in the file's class docblock + in tasks.md. - lib/Service/Integration/ExternalIntegrationRouter.php — dispatches storage='external' CRUD calls through OpenConnector. Surfaces failures via ProviderUnavailableException with a 3-way cause classification (openconnector-down / openconnector-source-missing / upstream-service-down) per AD-23. Includes probe() for admin health reporting. - lib/Service/Integration/QueryTimeContract.php — codifies the 2 s render timeout (AD-22) and the HTTP 501 envelope builder for the ObjectsController to consume when tasks 7-11 refactor it. - lib/Exception/NotImplementedException.php — thrown by query-time / list-only providers' unsupported CRUD methods. - lib/Exception/ProviderUnavailableException.php — carries the 3-way cause classification + a getDetails() helper that produces the `{cause: …}` payload the UI renders. - lib/AppInfo/Application.php — new private registerIntegrationRegistry() phase wires the registry and the router as shared per-request singletons. - tests/Unit/Service/Integration/*Test.php — 25 unit tests across 4 classes covering addProvider() validation (collision + external source), getEnabled() filtering, AbstractIntegrationProvider's NotImplementedException defaults, ExternalIntegrationRouter's failure-mode classification, and QueryTimeContract's HTTP envelope shape. All green. What's still pending (rest of the umbrella): - Schema validator refactor (tasks 7-11): Schema::validateLinkedTypesValue consumes IntegrationRegistry::listIds; deprecation of VALID_LINKED_TYPES + LinkedEntityService::TYPE_COLUMN_MAP. - Built-in providers (tasks 12-17): 5 BuiltinProviders/*Provider.php classes wrapping the existing files/notes/tasks/tags/audit-trail integrations. - Routes + controller + capabilities (tasks 18-22): IntegrationsController, ObjectsController sub-resource dispatch via registry, OCS capabilities block. - Admin UI + frontend registry + 3 missing widgets + CnObjectSidebar / CnDashboardPage / CnDetailPage / CnFormDialog / CnDetailGrid refactor (tasks 23-46). - Tests / CI parity gate / scaffold script / ADR + docs / translations (tasks 47-67). - E2E acceptance verification (tasks 68-71). Spec adjustment: tasks.md and plan.json record the DI-tag → explicit addProvider() pivot explicitly so the next session (or hydra builder) doesn't trip on it. Refs: #1307
…urveillance (umbrella PR 2/N) Second slice of #1307 — completes tasks 7-11 of the umbrella (Backend — Schema validator refactor). Builds on PR 1's IntegrationRegistry + ExternalIntegrationRouter foundation. Stacked PR — base is feature/1307/pluggable-integration-registry; GitHub will retarget to development once PR 1 merges. Tasks completed in this slice (5/69 → cumulative 11/69): - 2.1 Schema::validateLinkedTypesValue() now consults both VALID_LINKED_TYPES (deprecated fallback) AND IntegrationRegistry::listIds(). Registry resolved lazily via \OC::$server->get() since Schema is a Nextcloud Entity, not a service. Falls back to fallback-only when container isn't booted (unit tests). AD-5 backwards-compat preserved: existing schemas with 'mail' / 'calendar' / 'talk' / 'deck' still validate while the leaves land. - 2.2 VALID_LINKED_TYPES marked @deprecated with pointers to the registry + cleanup follow-up. - 2.3 LinkedEntityService::TYPE_COLUMN_MAP marked @deprecated. - 2.4 PropertyReferenceTypeValidator — new opt-in service that validates the `referenceType: <integration-id>` marker on schema property definitions (AD-18). Kept as a standalone validator so existing schema validation paths don't change; CnFormDialog / CnDetailGrid wire it in tasks 25-46. - 2.5 LogDanglingLinkedTypes repair step — registered under <install> + <post-migration> in info.xml. Scans every schema, logs WARNING for any linkedTypes value not yet registered. Strictly informational; never throws, never modifies data. Net new files: - lib/Service/Integration/PropertyReferenceTypeValidator.php - lib/Repair/LogDanglingLinkedTypes.php - tests/Unit/Service/Integration/PropertyReferenceTypeValidatorTest.php Modified: - lib/Db/Schema.php — validator + deprecation note - lib/Service/LinkedEntityService.php — deprecation note on TYPE_COLUMN_MAP - lib/Service/Integration/IntegrationRegistry.php — added isValidIntegrationId() helper consumed by the new validator - lib/AppInfo/Application.php — DI bindings for the new validator service + the repair step - appinfo/info.xml — repair-steps block adds LogDanglingLinkedTypes - openspec/changes/pluggable-integration-registry/tasks.md - openspec/changes/pluggable-integration-registry/plan.json Unit tests: - 34 tests, 48 assertions — all green (9 new for PropertyReferenceTypeValidator + the 25 from PR 1) Built-in providers (tasks 12-17) intentionally deferred to PR 3 to keep this PR reviewable as one coherent slice (the validator + the dangling-id surveillance form one story). Refs: #1307
…tions (umbrella PR 3/N) Third slice of #1307 — completes tasks 12-17 of the umbrella. Stacked on PR #1468 (schema validator refactor), which is itself stacked on PR #1467 (foundation). Tasks completed in this slice (6/69 → cumulative 17/69): - 3.1 FilesProvider wraps FileService (magic-column). - 3.2 NotesProvider wraps NoteService (link-table, full CRUD). - 3.3 TasksProvider wraps TaskService (link-table, CalDAV, full CRUD; composite {calendarId}/{taskUri} entity ids). - 3.4 TagsProvider wraps the NC system tag manager (link-table; read via ISystemTagObjectMapper::getTagIdsForObjects). - 3.5 AuditTrailProvider wraps AuditTrailMapper (query-time, AD-22 — read-only by design; mutation methods inherit NotImplementedException from AbstractIntegrationProvider). - 3.6 All five register via addProvider() at Application::boot() time through a new bootBuiltinIntegrationProviders() helper. The DI bindings live in registerBuiltinIntegrationProviders() so each provider's wrapped service is resolved lazily. Spec deviations flagged inline in tasks.md + plan.json: - FilesProvider + TagsProvider keep their write paths at the existing FileController / TagsController routes for now. Mutation methods throw NotImplementedException with a pointer to the umbrella's controller refactor (tasks 18-22). Users see the right tab; the underlying write API stays where it is until the controller layer catches up. Surface change is zero. - AuditTrailProvider intentionally inherits the abstract base's mutation defaults — audit-trail entries are immutable by construction; per AD-22 query-time providers MUST throw on create/update/delete. - The referenceType: <id> declarations the spec calls for on the frontend registry side are deferred to tasks 25-30 (when each provider gets its JS registry counterpart). The PHP-side providers are complete. Net new files: - lib/Service/Integration/BuiltinProviders/FilesProvider.php - lib/Service/Integration/BuiltinProviders/NotesProvider.php - lib/Service/Integration/BuiltinProviders/TasksProvider.php - lib/Service/Integration/BuiltinProviders/TagsProvider.php - lib/Service/Integration/BuiltinProviders/AuditTrailProvider.php - tests/Unit/Service/Integration/BuiltinProvidersMetadataTest.php Modified: - lib/AppInfo/Application.php — new registerBuiltinIntegrationProviders + bootBuiltinIntegrationProviders helpers - openspec/changes/pluggable-integration-registry/tasks.md - openspec/changes/pluggable-integration-registry/plan.json Unit tests: - 45 tests, 80 assertions — all green (11 new for the 5 providers + the 34 from PR 1 & 2) Refs: #1307
…brella PR 4/N) Fourth slice of #1307 — completes tasks 18-22 of the umbrella (Backend — Routes, Controller, Capabilities). Stacked on PR #1469 (built-in providers), itself stacked on #1468 (schema validator) and #1467 (foundation). Tasks completed in this slice (5/69 → cumulative 22/69): - 4.1 IntegrationsController — read-only API over the registry. GET /api/integrations (with `group` and `enabled` filter params), GET /api/integrations/{id}. Role-redacted descriptor per AD-17 — every authed user sees public fields (id, label, icon, group, enabled, storageStrategy, surfaces); admins additionally get requiresPermission, openConnectorSource, authStatus. Non-admin fields are omitted (not null-stubbed) so absence matches non-existence. - 4.2 Object-scoped sub-resource dispatch via the registry — new dedicated ObjectIntegrationsController owns /api/objects/{register}/{schema}/{id}/integrations/{integrationId}[/{entityId}] (GET / POST / PUT / DELETE). Additive — ObjectsController (2400+ lines) stays untouched. Error translation per AD-22 / AD-23: NotImplementedException → 501 with QueryTimeContract envelope ProviderUnavailableException → 503 with details.cause payload unknown integration id → 404 other Throwable → 500 (real exception logged, generic message returned per ADR-005). - 4.3 Routes wired in appinfo/routes.php — both the discovery API and the object sub-resource dispatch. - 4.4 IntegrationsCapability — surfaces the registry through /ocs/v2.php/cloud/capabilities, role-redacted per AD-17. Spec said 'Update lib/Service/CapabilitiesService.php'; OR's capability pattern uses one ICapability class per concern (see UrnCapability), so the new file lives in lib/Capabilities/. Same end shape, idiomatic structure. - 4.5 Registered via $context->registerCapability() in Application::register() — mirrors the existing UrnCapability pattern. No appinfo/info.xml change needed (OR doesn't carry capability declarations there). Net new files: - lib/Controller/IntegrationsController.php - lib/Controller/ObjectIntegrationsController.php - lib/Capabilities/IntegrationsCapability.php - tests/Unit/Controller/ObjectIntegrationsControllerTest.php Modified: - appinfo/routes.php — 7 new routes (2 discovery + 5 sub-resource) - lib/AppInfo/Application.php — DI bindings for the new controllers and the capability; registerCapability() call - openspec/changes/pluggable-integration-registry/tasks.md - openspec/changes/pluggable-integration-registry/plan.json Unit tests: - 52 tests, 95 assertions — all green inside the openregister-postgres dev container (7 new for ObjectIntegrationsController dispatch + error translation; the 45 from PRs 1-3 still pass) Spec deviations flagged inline: 1. Sub-resource dispatch lives in a NEW ObjectIntegrationsController rather than refactoring the 2400-line ObjectsController. Additive, zero regression risk on existing routes. 2. Capability advertisement uses one ICapability class (IntegrationsCapability) registered via registerCapability(), mirroring UrnCapability. Spec called for editing CapabilitiesService — OR doesn't have that file as the canonical integration point; the per-concern ICapability pattern is idiomatic for this codebase. 3. info.xml capability section unchanged — OR registers capabilities in code, not XML. Refs: #1307
…PR 5/N)
Renders the OpenRegister → "Integrations" admin section with a row per
registered IntegrationProvider:
- id / label / group / storage strategy
- required Nextcloud app + isInstalled status
- isEnabled() result
- authStatus / status / message from probe() (external) or health() (native)
- Configure deep-link into OpenConnector's source-edit screen
(external providers only)
- Test connection URL (external providers only)
Per AD-15, OpenRegister hosts the unified surface; credential flows
stay in OpenConnector — this page only links out. Native providers
report through provider->health(); external providers route through
ExternalIntegrationRouter::probe() so failure messages match what
runtime callers see.
Implements tasks 5.1–5.3.
Files:
- lib/Settings/IntegrationsAdminSettings.php (new) — ISettings
implementation, builds rendered rows from IntegrationRegistry
- templates/settings/integrations-admin.php (new) — server-rendered
table with status badges (ok / unavailable / unknown), translation
via $l throughout
- appinfo/info.xml — registers the additional <admin> entry
- lib/AppInfo/Application.php — DI binding for IntegrationsAdminSettings
- tests/Unit/Settings/IntegrationsAdminSettingsTest.php (new) — 5 tests
covering section/priority stability, row rendering, configure-URL
presence for external providers, absence for native providers, and
router routing for external probes
- openspec/changes/pluggable-integration-registry/{tasks.md,plan.json}
— mark 5.1–5.3 done
Spec deviation (documented in tasks.md):
Spec called for "edit CapabilitiesService" — there's no such
integration point on master. Capability is exposed via the idiomatic
ICapability class added in umbrella PR 4 (IntegrationsCapability),
not via this settings change.
Tests:
./vendor/bin/phpunit --configuration phpunit-unit.xml \
tests/Unit/Settings/IntegrationsAdminSettingsTest.php
-> 5 tests, 21 assertions, OK
…g sync (umbrella PR 10b) The openregister-side companion to the nextcloud-vue PRs (#202/#204/ #209/#210/#211). No production-code change here — docs, a scaffold script, and the umbrella's task-tracking sync. - docs/Integrations/pluggable-integration-registry.md (new) — "How to add an integration": the five built-ins table, storage strategies, the scaffold quickstart, a worked walkthrough (PHP provider → JS tab+widget → registration → surfaces light up → admin/health), and a reference section - scripts/scaffold-integration.sh (new) — `scaffold-integration.sh <id> [<Label>]` generates `openspec/changes/integration-<id>/` with proposal.md, tasks.md (within the ADR-028 cap), hydra.json (depends_on: pluggable-integration-registry), a PHP provider stub and a JS registration stub. Validates the id is kebab-case; derives the PascalCase class name; refuses to overwrite. - README.md — "Integrations" section now leads with the pluggable integration registry, pointing at the developer guide - openspec/changes/pluggable-integration-registry/{tasks.md, plan.json} — mark tasks 6.x / 7.x / 8.x / 9.x / 10.x / 12.1-12.2- 12.4 / 13.x / 14.x / 15.x done, with the cross-repo PR references inline; documents the spec deviations (listByGroup → inline filter; check-integration-parity.js not .sh; parity step folded into code-quality.yml not a separate workflow); flags the two explicitly-deferred items (hydra quality-gate hook 12.3, ADR-019 authoring — both separate hydra-repo PRs) Implements tasks 13.1-13.2 and 14.2-14.3 of the umbrella, and syncs the tracking for everything landed in the nextcloud-vue PRs. Smoke-tested: `scaffold-integration.sh scaffoldtest "Scaffold Test"` generates the expected tree; plan.json stays valid JSON.
…ed (leaf, backend) The PHP half of the `integration-xwiki` leaf (issue #1326, depends_on: pluggable-integration-registry) — the worked external-storage example. `XwikiProvider` declares storage `external`, group `external`, OpenConnector source `xwiki`, and delegates all CRUD to `ExternalIntegrationRouter` (AD-4) — it carries no HTTP client and no credentials; those live on the OpenConnector source (HTTP Basic or OAuth2, customer-dependent — AD-15). Per the leaf design.md: - AD-1: `get()` round-trips the page's rendered HTML under `content`; the @conduction/nextcloud-vue widget strips it to text + ~500 chars for the detail-page preview — macros are never executed in NC. - AD-2: `create()` passes `reference` through (a full XWiki URL or a `Space.Page` path); the OpenConnector source resolves both to a canonical reference. - AD-3: `normalizeRow()` ensures every row carries a `breadcrumb` (from the source, or derived from space + title) so the UI can disambiguate same-titled pages in different spaces. Files: - lib/Service/Integration/Providers/XwikiProvider.php (new) — metadata getters + authRequirements (type: external, configuredVia: openconnector) + isEnabled (mirrors IAppManager::isInstalled 'openconnector') + list/get/create/update/delete (all via router->call with the {register, schema, object} context) + health (defers to router->probe) + normalizeList/normalizeRow shaping - lib/AppInfo/Application.php — DI service registration for XwikiProvider + add it to bootBuiltinIntegrationProviders()'s list so it self-registers with the IntegrationRegistry at boot - docs/Integrations/xwiki-openconnector-source.yaml (new) — the OpenConnector source template to import (the repo's config/ dir is gitignored, so it lives under docs/Integrations/) - docs/Integrations/xwiki.md (new) — user-facing setup + usage guide - tests/Unit/Service/Integration/Providers/XwikiProviderTest.php (new) — 10 tests, 38 assertions: metadata matches the leaf spec, authRequirements shape, isEnabled mirrors OpenConnector install, list/get/create/update/delete delegate with the right method + path + context and normalise rows (breadcrumb fallback, id/title/ space/page/url/content mapping, bare-array response), health defers to probe Quality (verified in the nextcloud container): - composer phpcs (phpcs.xml) — 0 errors, 0 warnings - composer phpmd (phpmd.xml) — clean - composer psalm — no errors - phpunit (phpunit-unit.xml) XwikiProviderTest — 10 passed, 38 assertions - php -l Application.php — no syntax errors
…+ spec deviations
Syncs the integration-xwiki leaf tasks.md to reality:
- backend (XwikiProvider + DI registration + source-config template
+ user guide + unit tests) — done in this branch
- frontend (CnXwikiTab + CnXwikiCard + registration + tests) — done
in ConductionNL/nextcloud-vue#213
- acceptance: link-by-URL / reference-property / "macros not
executed in preview" are covered by the component tests; the live
E2E (against a running XWiki via an OpenConnector source) is
deferred until the umbrella + leaf PRs merge and deploy
Spec deviations recorded inline:
- `requiredApp` is `'openconnector'` (not `null`) — more accurate
for the admin UI: the integration genuinely needs that app
- the OpenConnector source-config template lives at
`docs/Integrations/xwiki-openconnector-source.yaml`, not
`config/openconnector-sources/xwiki.yaml` (the repo's `config/`
dir is gitignored)
- the provider is registered via `addProvider()` at boot (not a DI
tag) — consistent with the umbrella's registration mechanism
- `check-integration-parity.js` (Node, repo convention) not `.sh`
…ve XWiki REST verification
Spun up xwiki:lts 17.10 locally, created a test page via REST
(PUT /rest/wikis/xwiki/spaces/Sandbox/pages/IntegrationTest) and
inspected the page representation. Findings folded into the source
template:
- XWiki is deployed at the ROOT context here — REST is at `/rest/`,
not `/xwiki/rest/`. (The template's `location` is the wiki's REST
base URL, so this is just a deployment note.)
- The REST page representation's `content` field is RAW xwiki/2.1
syntax, NOT rendered HTML. For a clean AD-1 text preview the
source should fetch the rendered body from
`GET /bin/get/{Space}/{Page}?xpage=plain` (XWiki renders macros
server-side in its own sandbox — never inside Nextcloud) and map
that to `content`. Mapping `content` straight from the REST field
is still safe (macro markup is inert text) — documented both ways.
- XWiki exposes a native breadcrumb at `hierarchy.items[].label`
("xwiki" / "Sandbox" / "IntegrationTest") — map `breadcrumb` from
it (AD-3). Without it XwikiProvider derives a coarse one from
space + title.
- Confirmed the other mappings: `id` → `reference`
("xwiki:Space.Page"), `title`, `space`, `name` → `page`,
`xwikiAbsoluteUrl` → `url`.
No PHP change needed — XwikiProvider::normalizeRow() already reads
`breadcrumb` (with the space+title fallback) and `content` (with the
`renderedContent` fallback), so it works against either mapping.
…s-built implementation
Brings the leaf's design.md and proposal.md in line with what
actually shipped (tasks.md was already synced):
- design.md: AD-1 records the verified XWiki REST shape (the REST
`content` field is raw xwiki/2.1 syntax — the rendered body comes
from `/bin/get/{Space}/{Page}?xpage=plain`, macros run server-side
in XWiki's sandbox, never in NC). AD-3 records that `breadcrumb`
maps from XWiki's native `hierarchy.items[].label`. "Files
Affected" updated to the real paths/structure. New "Implementation
deviations" table (requiredApp `openconnector` not `null`; source
template under `docs/Integrations/` not `config/`; `addProvider()`
at boot not a DI tag; `check-integration-parity.js` not `.sh`).
Softened the "established by integration-openproject" line — the
external-storage machinery comes from the umbrella, this is the
first external leaf.
- proposal.md: `Required NC app` → `openconnector` (with the
rationale); acceptance criteria checked off against the component/
unit tests that cover them, with the live runtime check called out
as deploy-gated.
Doc-only; no code change.
… / searchResults)
normalizeList() previously only un-wrapped `{ results: [...] }` / `{ items: [...] }`
(a purpose-built OpenConnector source) and otherwise iterated the response's *values*
as if they were rows — which mangles XWiki's own REST shapes. Now it also recognises
`pageSummaries` (space listing: GET /rest/wikis/<wiki>/spaces/<Space>/pages) and
`searchResults` (search), uses a bare list as-is, and returns an empty list for an
assoc envelope with no rows key (instead of garbage rows). normalizeRow() gains a
last-resort `url` fallback that pulls the `rel=".../rel/page"` href out of XWiki's
`links` array (present on page summaries / search hits) when the source didn't map a
flat URL field. This lets the `xwiki` integration work against an OpenConnector source
pointed straight at XWiki's REST API, not just a custom proxy endpoint.
Tests: +2 (raw pageSummaries envelope incl. the links-based url fallback; assoc
envelope with no rows key → []).
Relates to #1307 (umbrella) / #1326 (xwiki leaf).
…t: application/json
Three follow-ups that make the `xwiki` provider work against a stock
OpenConnector source (verified end-to-end against a live XWiki):
- ExternalIntegrationRouter::decodeResponse() — OpenConnector's CallService
returns a CallLog whose getResponse() is { statusCode, headers, body, encoding };
the upstream payload is the (usually JSON) `body` string. Unwrap + JSON-decode
it (base64 bodies decoded first) instead of serialising the whole CallLog —
previously the provider only ever saw the CallLog metadata, so list() returned [].
- ExternalIntegrationRouter::assertUpstreamOk() — a >= 400 upstream status (carried
on the CallLog) now throws ProviderUnavailableException rather than letting an
error page leak through as rows; 401/403 → the new CAUSE_PROVIDER_AUTH so the
UI shows the reconnect banner.
- XwikiProvider — every call now carries `Accept: application/json` (XWiki's REST
API negotiates XML by default; writes also declare a JSON Content-Type). A
purpose-built source that already pins these is unaffected.
- ProviderUnavailableException::CAUSE_PROVIDER_AUTH constant added (the JS side —
CnXwikiTab.degradedMessage — already treats `provider-auth` as a reconnect cause).
Tests: ExternalIntegrationRouterTest +4 (CallLog body unwrap, base64 body, 401→provider-auth,
500→upstream-down); XwikiProviderTest assertions updated for the new request headers.
phpunit: 21 green.
Relates to #1307 (umbrella) / #1326 (xwiki leaf) / ConductionNL/openconnector#755.
… NC-app stubs + OpenProject) — mega-leaf Implements the PHP IntegrationProvider classes for every leaf currently spec'd alongside the umbrella registry (xwiki was already in place). The mega-leaf branches off feature/1326/integration-xwiki so the leaves can land together once the umbrella stack (#1467 → #1490) merges to development. Per-leaf depth ships what's tractable in one mega-PR; the rest is honest TODO that follow-up leaves can flesh out without rewriting: Backend-shipped (Calendar, Contacts, Deck, Email) — wrap existing services through the registry contract: - CalendarProvider → CalendarEventService (list, delete via "calendarId/eventUri" composite); create/update remain on the dedicated CalendarEventsController. - ContactsProvider → ContactService (list, update role, unlink); create() inherits NotImplementedException since the link-existing- vs-create-new flows are owned by ContactsController. - DeckProvider → DeckCardService (list, linkOrCreateCard, unlinkCard). - EmailProvider → EmailService (list with limit/page filters, linkEmail, unlinkEmail). External (OpenProject) — mirrors XwikiProvider, routes all CRUD through ExternalIntegrationRouter against the OpenConnector "openproject" source. Surfaces HAL+JSON envelope rows from _embedded.elements and normalises to the {id, title, status, url} contract. NC-app-backed greenfield (Activity, Analytics, Bookmarks, Collectives, Cospend, Flow, Forms, Maps, Photos, Polls, Talk, Time-tracker) — provider registers the registry surface today (id, label, icon, group, requiredApp, storage), gates on IAppManager::isInstalled() for the named app, and returns [] from list(); mutation methods inherit NotImplementedException from AbstractIntegrationProvider until the wrapped service + link table ship in per-leaf follow-ups. health() reports unavailable when the NC app is missing. SharesProvider — NC core (no required app), query-time storage; delete() delegates to OCP\Share\IManager::deleteShare(); list() stub until the file-link → share-filter glue lands. Wiring: lib/AppInfo/Application.php - registerBuiltinIntegrationProviders() — registers all 16 new provider services in the DI container. - bootBuiltinIntegrationProviders() — adds them to the providerClasses list so they self-register with the IntegrationRegistry on boot. Tests: tests/Unit/Service/Integration/Providers/LeafProvidersMetadataTest.php - Per-provider contract metadata + delegation assertions for Calendar, Contacts, Deck, Email, OpenProject, Shares. - Data-provider-driven contract test covering all 12 greenfield stubs (asserts id/label/icon/group/requiredApp/storage and the list()=[] + isEnabled gate behaviour). Tasks.md updates: ticks the Provider-creation + DI-registration checkboxes across all 18 integration-* changes (the unit-test + service/controller/migration/vue tasks remain open for the follow-up leaves). PHPCS strict: providers + new test file are clean (0 errors). Three pre-existing errors remain in Application.php from the umbrella PR (InlineComment cap-letter + named-param sniff on lines that pre-date this branch) — left untouched.
Visually verified end-to-end via decidesk (15 May)The full chain now renders in a dev container after porting this PR's PHP providers + nc-vue#230's Vue registrations + decidesk#205's main.js wiring (Pinia fix) into the running instance.
Tab content currently returns 503 because the See decidesk#205 comment for the full verification trace. |
…f API Playwright smoke
Two additions on top of the mega-leaf provider PR:
### docs/Integrations/
- leaf-system.md (NEW) — architectural overview of the pluggable
integration registry. <LeafGrid> of all 18+5 leaves with metadata
headers, the three-piece wiring walk-through (PHP provider, JS
registration, app-side activation), storage strategy reference,
requiredApp gating, collision policy (AD-13), surface fallback
(AD-19).
- 18 per-leaf pages (xwiki rewrite + 17 new) in the design-system's
'integrations.html' pattern: <LeafCard> metadata header + <Pair>
system-pair diagram + What-it-does + Setup numbered steps +
Configuration table + Authentication callout (where relevant) +
Troubleshooting + Related links.
Backend-shipped (full content): calendar, contacts, deck, email,
openproject, shares, xwiki.
Provider-stub (compact, marks 'pending follow-up'): activity,
analytics, bookmarks, collectives, cospend, flow, forms, maps,
photos, polls, talk, time-tracker.
- Deck.md renamed to deck.md (lowercase for URL slug consistency
with all other leaves).
- index.md restructured: 'Leaf integrations' section listed first
by group, then OR push events, then LLM + automation integrations.
Each per-leaf page uses <LeafCard> + <Pair> from
@conduction/docusaurus-preset (see design-system commit 70a0a65 for
the new LeafCard component).
### tests/e2e/integration-registry.spec.ts
Playwright smoke covering the OR side of the chain:
- OCS /cloud/capabilities exposes all 24 providers with shape
- every provider declares the documented metadata (group, storage,
surfaces array, authStatus object)
- 18 per-leaf sub-resource probes:
GET /api/objects/{r}/{s}/{o}/integrations/{leaf-id} returns
<500 for every advertised leaf (200 / 503 acceptable; 5xx is a
provider crash)
- 200 returns standard envelope shape (results | items | array)
- 503 carries a structured cause body
- unknown provider id 4xx (not 5xx)
- every advertised provider declares all 4 AD-19 surfaces
Tests skip cleanly when the registry isn't wired (older deploys),
so the spec is safe to run against partial deploys.
Verified locally: 31 / 31 tests pass on the dev container.
### Writing style
All pages follow design-system/CLAUDE.md: short sentences, sentence-
case headings, no em-dashes, je/jij tone (where pages have NL —
these are English-only for now), one claim per bullet, banned-word
list respected.
Summary
Ships the PHP
IntegrationProviderclasses for every leaf currently spec'd alongside the pluggable integration registry — 16 new providers + DI wiring + smoke test — so the registry surfaces every documented integration once the umbrella stack lands.Blocked behind: #1467 → #1468 → #1469 → #1473 → #1475 → #1490 → #1493 (umbrella + xwiki). This branch is based on `feature/1326/integration-xwiki` so it stacks on top of the umbrella; rebase onto `development` after those merge.
What's in the box (16 leaves)
"NC-app stub" = the Provider class registers the registry surface (sidebar slot, admin UI presence, OCS caps entry) gated on `IAppManager`, but `list()` returns `[]` until the wrapped service + link table land in per-leaf follow-up PRs. Mutation methods inherit `NotImplementedException` from `AbstractIntegrationProvider`. This is intentional honest scaffolding rather than fake-shipping CRUD that doesn't work.
What's NOT in this PR (deferred to per-leaf follow-ups)
Each leaf's `tasks.md` has Provider-creation + DI-registration boxes ticked; the remainder stays open.
Wiring
`lib/AppInfo/Application.php`:
Tests
`tests/Unit/Service/Integration/Providers/LeafProvidersMetadataTest.php`:
Quality
Test plan
Follow-ups
One per-leaf PR per stub will flesh out the wrapped service + link-table migration + Vue components + i18n + acceptance verification. They can land in any order once this merges.