feat(integrations): 5 built-in JS integration registrations#210
feat(integrations): 5 built-in JS integration registrations#210rubenvdlinde wants to merge 3 commits into
Conversation
… PR 9 — part 1)
Mirrors OpenRegister's built-in PHP `IntegrationProvider`s on the JS
side. Each descriptor maps a built-in onto its existing
`CnObjectSidebar` tab plus a compact widget for dashboard / detail
surfaces, with `referenceType === id` for the AD-18 crossover.
id order group tab widget
------------ ----- ----- ---------------- ----------------------
files 1 core CnFilesTab CnFilesCard
notes 2 core CnNotesTab CnNotesCard (adapter)
tags 3 core CnTagsTab CnTagsCard
tasks 4 core CnTasksTab CnTasksCard (adapter)
audit-trail 5 core CnAuditTrailTab CnAuditTrailCard
id / order / icon / group match the PHP providers exactly so PHP + JS
stay in lockstep. `CnNotesCard` / `CnTasksCard` predate the registry
and take `registerId` / `schemaId` props, so a thin adapter component
in the builtin module renames `register` / `schema` (the convention
the surface components forward) — no change to those existing cards.
`registerBuiltinIntegrations(registry?)` registers all five onto a
registry (default: the singleton) and is what OpenRegister's main
bundle calls once at bootstrap, after `installIntegrationRegistry()`.
It SKIPS any id already registered — quietly, no collision warning —
so a consuming app can pre-register an id to override a built-in
(collision policy: first wins). It's idempotent and returns the ids
it newly registered.
Implements tasks 7.1–7.6 (the 5 JS registrations + reference-property
declarations) of the umbrella.
Files:
- src/integrations/builtin/{files,notes,tags,tasks,audit-trail}.js
(new) — one descriptor per built-in; notes/tasks include the
prop-rename adapter
- src/integrations/builtin/index.js (new) — `builtinIntegrations`
array + `registerBuiltinIntegrations()`
- src/integrations/index.js + src/index.js — export
`builtinIntegrations` + `registerBuiltinIntegrations`
- docs/utilities/builtin-integrations.md,
register-builtin-integrations.md (new)
- tests/integrations/builtin.spec.js (new) — 9 tests: descriptor
shape (ids, order, icon, group, referenceType, tab+widget
presence), registerBuiltinIntegrations (registers all five,
skip-on-collision keeping the pre-registered one, idempotent,
defaults to singleton)
Tests: 25 integration tests (registry + composable + builtin), all
green. Full suite: 71 suites, 1011 passing, 4 pre-existing failures
(CnMapWidget + CnAiChatPanel, unrelated, also red on origin/beta).
node scripts/check-docs.js — only pre-existing CnAi* / useAi* / default
gaps remain. npm run lint — no new errors.
There was a problem hiding this comment.
[BLOCKER] Hardcoded apiBase default '/apps/openregister/api' in CnNotesCardAdapter
The apiBase prop default is hardcoded to '/apps/openregister/api'. This breaks any deployment where the app is installed under a sub-path or uses a different route prefix. The value should either be injected by the caller (no default, or '') and documented as required, or derived at runtime via generateUrl() from @nextcloud/router. The same issue exists in tasks.js.
There was a problem hiding this comment.
[BLOCKER] Hardcoded apiBase default '/apps/openregister/api' in CnTasksCardAdapter
Same issue as notes.js: apiBase defaults to '/apps/openregister/api'. Fix: remove the default or use generateUrl('openregister', 'api') from @nextcloud/router so the path is resolved against the actual Nextcloud installation base URL.
There was a problem hiding this comment.
[CONCERN] register() result checked against null instead of truthiness — undefined slips through
if (result !== null) will push the descriptor id if register() returns undefined (e.g. on a validation failure that returns void rather than null). Use if (result) or if (result != null) to cover both null and undefined, or add a comment citing the spec that guarantees null-on-failure.
There was a problem hiding this comment.
[CONCERN] objectId prop typed [String, Number] but adapter always casts to String — misleading
The objectId prop is typed [String, Number] but the render function unconditionally calls String(this.objectId). The Number type is misleading — it implies Number is a valid passthrough, but it gets coerced. Either accept only String (and document that callers must stringify) or pass the raw value and let CnNotesCard handle coercion. The same pattern exists in CnTasksCardAdapter.
There was a problem hiding this comment.
[CONCERN] objectId prop typed [String, Number] but adapter always casts to String (tasks)
Same as the notes adapter: objectId: { type: [String, Number], default: '' } but the render always does String(this.objectId). Align the declared type with the actual behaviour.
There was a problem hiding this comment.
[CONCERN] Individual descriptor exports widen the public API surface unintentionally
filesIntegration, notesIntegration, tagsIntegration, tasksIntegration, and auditTrailIntegration are re-exported from the barrel. The docs only describe builtinIntegrations (the array) and registerBuiltinIntegrations (the function) as public API. Exporting the five individual descriptors locks the library into maintaining each object's identity and shape as semver-breaking changes forever. If intentionally public, document them. If not, remove them from the barrel.
There was a problem hiding this comment.
[CONCERN] surface and objectType props silently dropped by adapter — no pass-through or warning
Both CnNotesCardAdapter and CnTasksCardAdapter declare surface and objectType as accepted props but never forward them to the wrapped card. If a future version gains surface support, the adapter will silently swallow the value. Consider forwarding all $attrs via v-bind or adding a dev-mode warning. CnTasksCardAdapter also lacks the inline comment present in CnNotesCardAdapter noting these are ignored.
WilcoLouwerse
left a comment
There was a problem hiding this comment.
Review
🔴 Blockers (2)
- Hardcoded apiBase default '/apps/openregister/api' in CnNotesCardAdapter —
src/integrations/builtin/notes.js:283 - Hardcoded apiBase default '/apps/openregister/api' in CnTasksCardAdapter —
src/integrations/builtin/tasks.js:393
🟡 Concerns (5)
- register() result checked against null instead of truthiness — undefined slips through —
src/integrations/builtin/index.js:236 - objectId prop typed [String, Number] but adapter always casts to String — misleading —
src/integrations/builtin/notes.js:280 - objectId prop typed [String, Number] but adapter always casts to String (tasks) —
src/integrations/builtin/tasks.js:390 - Individual descriptor exports widen the public API surface unintentionally —
src/integrations/index.js:437 - surface and objectType props silently dropped by adapter — no pass-through or warning —
src/integrations/builtin/notes.js:281
🟢 Minor (3)
- Doc table omits defaultSize column despite mentioning it in prose (
docs/utilities/builtin-integrations.md:25)
The descriptor shape description listsdefaultSizeas part of the contract, but the summary table only hasid | order | group | tab | widget. Adding adefaultSizecolumn would help consumers who read it for layout purposes —tagsuses{ w: 4, h: 2 }while all others use{ w: 4, h: 3 }. - Test file mixes require() (CJS) with ESM source — may mask transform issues (
tests/integrations/builtin.spec.js:464)
The test usesrequire()while the source files use ES moduleexportsyntax. If the Jest/Babel transform is misconfigured, tests silently pass against the wrong module version. Preferimportstatements for consistency with the source. - Docs don't explain why collision warning is suppressed for builtin registrations (
docs/utilities/register-builtin-integrations.md:52)
The doc states builtins skip any already-registered id quietly without the dev-mode collision warning, but doesn't explain why. A one-sentence rationale (to avoid noise for the common bootstrap pattern) would prevent future maintainers from "fixing" the intentionalhas()guard.
Reviewed by WilcoLouwerse via automated batch review.
Part of the openregister pluggable-integration-registry umbrella. Cross-repo PR 9/N. Stacked on #209 → #204 → #202 →
beta; GitHub auto-retargets as upstream merges.Summary
Mirrors OpenRegister's five built-in PHP
IntegrationProviders on the JS side, soCnObjectSidebar(registry mode),CnDashboardPage, andCnDetailPageactually have something to render. Implements tasks 7.1–7.6 of the umbrella; the frontend-test tasks (10.1–10.4) are covered cumulatively across PRs #202 / #204 / #209 / this one (registry unit tests, widget tests, surface-component tests, built-in registration tests).filescoreCnFilesTabCnFilesCardnotescoreCnNotesTabCnNotesCard(adapter)tagscoreCnTagsTabCnTagsCardtaskscoreCnTasksTabCnTasksCard(adapter)audit-trailcoreCnAuditTrailTabCnAuditTrailCardid/order/icon/groupmatch the PHP providers exactly so PHP + JS stay in lockstep. Each declaresreferenceType === idfor the AD-18 crossover.Adapter note:
CnNotesCard/CnTasksCardpredate the registry and takeregisterId/schemaIdprops, whereas the surface components forward the OpenRegister-conventionregister/schema. Thenotes/tasksbuiltins wrap their card in a tiny render-fn adapter that renames the props — those existing components are untouched.registerBuiltinIntegrations(registry?)What OpenRegister's main bundle calls once at bootstrap (after
installIntegrationRegistry()). Skips any id already registered — quietly, no collision warning — so a consuming app can pre-register an id to override a built-in (collision policy: first wins). Idempotent; returns the ids it newly registered.Files
src/integrations/builtin/{files,notes,tags,tasks,audit-trail}.js(new)src/integrations/builtin/index.js(new) —builtinIntegrationsarray +registerBuiltinIntegrations()src/integrations/index.js+src/index.js— public exportsdocs/utilities/builtin-integrations.md,register-builtin-integrations.md(new)tests/integrations/builtin.spec.js(new) — 9 testsTest plan
npx jest tests/integrations→ 25 tests (registry + composable + builtin), greenCnMapWidget+CnAiChatPanel, unrelated, also red onorigin/beta)node scripts/check-docs.js— only pre-existing CnAi* / useAi* /defaultgaps remainnpm run lint— no new errorsinstallIntegrationRegistry()+registerBuiltinIntegrations()and the 5 built-ins should appear inCnObjectSidebar(useRegistry) and be available astype: 'integration'widgets