Skip to content

feat(integrations): 5 built-in JS integration registrations#210

Open
rubenvdlinde wants to merge 3 commits into
feature/integration-surfacesfrom
feature/integration-builtins
Open

feat(integrations): 5 built-in JS integration registrations#210
rubenvdlinde wants to merge 3 commits into
feature/integration-surfacesfrom
feature/integration-builtins

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Part of the openregister pluggable-integration-registry umbrella. Cross-repo PR 9/N. Stacked on #209#204#202beta; GitHub auto-retargets as upstream merges.

Summary

Mirrors OpenRegister's five built-in PHP IntegrationProviders on the JS side, so CnObjectSidebar (registry mode), CnDashboardPage, and CnDetailPage actually 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).

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. Each declares referenceType === id for the AD-18 crossover.

Adapter note: CnNotesCard / CnTasksCard predate the registry and take registerId / schemaId props, whereas the surface components forward the OpenRegister-convention register / schema. The notes / tasks builtins 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) — builtinIntegrations array + registerBuiltinIntegrations()
  • src/integrations/index.js + src/index.js — public exports
  • docs/utilities/builtin-integrations.md, register-builtin-integrations.md (new)
  • tests/integrations/builtin.spec.js (new) — 9 tests

Test plan

  • npx jest tests/integrations → 25 tests (registry + composable + builtin), 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
  • Live smoke-test: after this merges, OpenRegister's bundle calls installIntegrationRegistry() + registerBuiltinIntegrations() and the 5 built-ins should appear in CnObjectSidebar (useRegistry) and be available as type: 'integration' widgets

… 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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread src/integrations/index.js
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown

@WilcoLouwerse WilcoLouwerse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

🔴 Blockers (2)

🟡 Concerns (5)

🟢 Minor (3)

  • Doc table omits defaultSize column despite mentioning it in prose (docs/utilities/builtin-integrations.md:25)
    The descriptor shape description lists defaultSize as part of the contract, but the summary table only has id | order | group | tab | widget. Adding a defaultSize column would help consumers who read it for layout purposes — tags uses { 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 uses require() while the source files use ES module export syntax. If the Jest/Babel transform is misconfigured, tests silently pass against the wrong module version. Prefer import statements 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 intentional has() guard.

Reviewed by WilcoLouwerse via automated batch review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants