Skip to content

Commit 0856704

Browse files
rubenvdlindeConduction Release Bot
andauthored
refactor(facets): translate the four facet dimensions as one set (#524)
* refactor(facets): translate the four facet dimensions as one set `referentiecomponent` -> referenceComponent, `standaard` -> standard, `applicatieservice` -> applicationService, `domein` -> domain. 84 substitutions across 10 files. All four or none. They are the endpoint's query parameters AND its response keys, and FacetController, FacetService, the frontend FACET_DIMENSIONS, the store and the gemma-faceted-search e2e spec each hold their own copy. Translating one of four silently broke filtering in tranche 1: a filter on a dimension nobody sends renders a page with no filters and no error. Whole quoted tokens only, so `standaard_gemma` and `standaarden` — which are different words — are untouched. TWO FILES DELIBERATELY EXCLUDED, both of which a blanket rename would have corrupted: * SettingsService's only match is `'domein' => $emailSettings ['mailgunDomain']` — Mailgun's domain, nothing to do with facets. * RenameDutchCatalogColumns is a column MAP; its `'standaard' => 'standard'` entry is data, and rewriting it would leave `'standard' => 'standard'`, an identity that migrates nothing while reporting success. That map is also the evidence this rename was overdue: the COLUMN moved to `standard` already, so the facet dimension had been lagging behind the property it filters on. Checked before renaming: none of the four is a schema key (so no ImportHandler slug migration is involved) and none feeds a derived `<type>_schema` config key — FacetService resolves those through SettingsService::LEGACY_SCHEMA_KEY, not from the dimension name. Unit suite failing set IDENTICAL to development (1 both sides, 0 introduced); the 22 Facet tests pass. * fix(facets): move the dimension in every shape, not just quoted tokens The first pass renamed whole QUOTED tokens, which left three shapes behind and CI's jest run found them: * bare object keys — `activeFilters.referentiecomponent`, `domein: []` * the URL-ENCODED form — `referentiecomponent%5B%5D=A` inside a longer string literal * the route-query form `_gf_referentiecomponent`. `\b` does NOT match after an underscore, so the word-boundary pass skipped every one of them, and `setFiltersFromQuery` returned {} — the store silently restored no filters at all. The real schema fields are deliberately NOT the dimension names and stay put: `referentieComponenten` and `standaardVersies` are different identifiers, which the negative lookahead protects. Also restores a HISTORICAL comment the rename made self-contradictory. It described the tranche-1 bug — frontend sending `standard[]` to a backend reading `standaard[]` — and translating its Dutch spelling turned it into "sending standard[] to a backend that only reads standard[]". The Dutch words are the point of that sentence. jest 120/120; the 22 Facet PHPUnit tests pass. * style: prettier the facet store spec Format-only; the rename left a call spanning lines prettier joins. jest 23/23 on that file. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
1 parent 785b9f2 commit 0856704

12 files changed

Lines changed: 173 additions & 176 deletions

File tree

lib/Controller/FacetController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function __construct(
6262
*
6363
* Query Parameters:
6464
* - search (string): Free-text query narrowing the candidate set.
65-
* - referentiecomponent[], standaard[], applicatieservice[], domein[] (string[]):
65+
* - referenceComponent[], standard[], applicationService[], domain[] (string[]):
6666
* Currently-selected facet values per dimension.
6767
* - organization (string): Optional organisation override.
6868
*
@@ -144,7 +144,7 @@ public function getFacets(string $schema): JSONResponse {
144144
* @return array<string,array> Raw filters keyed by dimension.
145145
*/
146146
private function parseFilters(): array {
147-
$dimensions = ['referentiecomponent', 'standaard', 'applicatieservice', 'domein'];
147+
$dimensions = ['referenceComponent', 'standard', 'applicationService', 'domain'];
148148
$filters = [];
149149

150150
foreach ($dimensions as $dimension) {

lib/Service/FacetService.php

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
/**
44
* Facet Service for SoftwareCatalog
55
*
6-
* Aggregates GEMMA-dimension facet counts (referentiecomponent, standaard,
7-
* applicatieservice, domein) across the `module` and `dienst` listings,
6+
* Aggregates GEMMA-dimension facet counts (referenceComponent, standard,
7+
* applicationService, domain) across the `module` and `dienst` listings,
88
* scoped to the caller's RBAC/tenant context and combinable with the
99
* existing free-text search.
1010
*
@@ -39,8 +39,8 @@
3939
*
4040
* Architecture (see design.md): resolves a bounded, RBAC-scoped candidate
4141
* object set for the requested schema (`module` or `dienst`), builds a
42-
* per-object map of GEMMA dimension values (resolving `domein` and
43-
* `applicatieservice` via linked `element` lookups through the existing
42+
* per-object map of GEMMA dimension values (resolving `domain` and
43+
* `applicationService` via linked `element` lookups through the existing
4444
* `ArchiMateService`), then computes disjunctive ("self-count not narrowed
4545
* by its own selection") facet counts over that map.
4646
*
@@ -89,7 +89,7 @@ class FacetService {
8989
private const MAX_BASE_PAGES = 5;
9090

9191
/**
92-
* Bounded limit for element/relation lookup queries (domein/applicatieservice resolution).
92+
* Bounded limit for element/relation lookup queries (domain/applicationService resolution).
9393
*/
9494
private const ELEMENT_LOOKUP_LIMIT = 1000;
9595

@@ -105,15 +105,15 @@ class FacetService {
105105
*
106106
* @var string[]
107107
*/
108-
private const DIMENSIONS = ['referentiecomponent', 'standaard', 'applicatieservice', 'domein'];
108+
private const DIMENSIONS = ['referenceComponent', 'standard', 'applicationService', 'domain'];
109109

110110
/**
111111
* Constructor for FacetService.
112112
*
113113
* @param ContainerInterface $container PSR-11 container interface (for lazy ObjectService lookup).
114114
* @param SettingsService $settingsService Settings service for voorzieningen register/schema configuration.
115115
* @param ArchiMateService $archiMateService Reused for bounded `element`/`relationship` lookups
116-
* (domein/applicatieservice resolution) instead of
116+
* (domain/applicationService resolution) instead of
117117
* duplicating AMEF register/schema resolution logic.
118118
* @param ViewQueryBuilder $queryBuilder Reused for the `_search` query-param convention.
119119
* @param IUserSession $userSession User session service for current user/RBAC cache-key context.
@@ -138,16 +138,16 @@ public function __construct(
138138
*
139139
* @param string $schema `module` or `dienst`.
140140
* @param array $filters Currently-selected facet values keyed by dimension,
141-
* e.g. `['referentiecomponent' => ['Zaakregistratiecomponent']]`.
141+
* e.g. `['referenceComponent' => ['Zaakregistratiecomponent']]`.
142142
* @param string|null $search Free-text query narrowing the candidate set.
143143
* @param string|null $organization Optional organisation override (mirrors
144144
* view-enrichment-api's `organization` parameter).
145145
*
146146
* @throws InvalidArgumentException When `$schema` is not supported.
147147
* @throws RuntimeException When OpenRegister's ObjectService is unavailable.
148148
*
149-
* @return array{referentiecomponent: array, standaard: array, applicatieservice: array,
150-
* domein: array, _meta: array{totalMatched: int, processingTimeMs: float, cached: bool,
149+
* @return array{referenceComponent: array, standard: array, applicationService: array,
150+
* domain: array, _meta: array{totalMatched: int, processingTimeMs: float, cached: bool,
151151
* matchedObjectIds: string[]}}
152152
*
153153
* @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-endpoint-returns-gemma-dimension-counts
@@ -232,8 +232,8 @@ private function assertSupportedSchema(string $schema): void {
232232
* @param string|null $normalizedSearch Normalized free-text query.
233233
* @param string|null $organization Optional organisation override.
234234
*
235-
* @return array{referentiecomponent: array, standaard: array, applicatieservice: array,
236-
* domein: array, _meta: array{totalMatched: int, processingTimeMs: float, cached: bool,
235+
* @return array{referenceComponent: array, standard: array, applicationService: array,
236+
* domain: array, _meta: array{totalMatched: int, processingTimeMs: float, cached: bool,
237237
* matchedObjectIds: string[]}}
238238
*/
239239
private function computeFacetsForRequest(
@@ -276,8 +276,8 @@ private function computeFacetsForRequest(
276276
// `matchedObjectIds` — the RBAC/filter/search-scoped object id set this
277277
// response's counts describe (proposal.md's Approach: "the RBAC-filtered
278278
// object IDs needed to drive the index page's existing list query").
279-
// Several dimensions (`domein`, `applicatieservice`, and `referentiecomponent`/
280-
// `standaard` by display NAME) are not directly filterable on the
279+
// Several dimensions (`domain`, `applicationService`, and `referenceComponent`/
280+
// `standard` by display NAME) are not directly filterable on the
281281
// `module`/`dienst` schema itself, so the frontend narrows its own object
282282
// list via `{ id: matchedObjectIds }` rather than re-deriving an
283283
// equivalent filter from the facet selection. Already bounded — this id
@@ -634,8 +634,8 @@ private function fetchModulesByIdentifiers(ObjectService $objectService, array $
634634

635635
/**
636636
* Build the per-object GEMMA dimension value map: for every base object,
637-
* the set of referentiecomponent/standaard names it carries directly, and
638-
* the set of domein/applicatieservice names resolved transitively via its
637+
* the set of referenceComponent/standard names it carries directly, and
638+
* the set of domain/applicationService names resolved transitively via its
639639
* linked `element` objects.
640640
*
641641
* @param array<string,array<int,array>> $modulesByObjectId Object id => module objects.
@@ -645,7 +645,7 @@ private function fetchModulesByIdentifiers(ObjectService $objectService, array $
645645
* @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-endpoint-returns-gemma-dimension-counts
646646
*/
647647
private function buildDimensionValueMap(array $modulesByObjectId): array {
648-
// Pass 1: direct fields (referentiecomponent identifiers + standaard identifiers).
648+
// Pass 1: direct fields (referenceComponent identifiers + standard identifiers).
649649
$refCompIdsByObjId = [];
650650
$stdValsByObjId = [];
651651
$allRefCompIds = [];
@@ -669,12 +669,12 @@ private function buildDimensionValueMap(array $modulesByObjectId): array {
669669

670670
$allRefCompIds = array_values(array_unique($allRefCompIds));
671671

672-
// Pass 2: resolve referentiecomponent elements themselves (for the
673-
// referentiecomponent facet's display value + the `domein` field).
672+
// Pass 2: resolve referenceComponent elements themselves (for the
673+
// referenceComponent facet's display value + the `domain` field).
674674
$elementsById = $this->resolveElementsByIdentifier(identifiers: $allRefCompIds);
675675

676-
// Pass 3: resolve applicatieservice elements reachable via a `relation`
677-
// touching one of the referentiecomponent elements.
676+
// Pass 3: resolve applicationService elements reachable via a `relation`
677+
// touching one of the referenceComponent elements.
678678
$appSvcNmByRefComp = $this->resolveApplicatieservicesForReferentiecomponenten(
679679
refCompIds: $allRefCompIds
680680
);
@@ -700,9 +700,9 @@ private function buildDimensionValueMap(array $modulesByObjectId): array {
700700
fallbackIdentifier: $refCompId
701701
);
702702

703-
$domein = $element['domein'] ?? null;
704-
if (is_string($domein) === true && trim($domein) !== '') {
705-
$domeinValues[] = trim($domein);
703+
$domain = $element['domain'] ?? null;
704+
if (is_string($domain) === true && trim($domain) !== '') {
705+
$domeinValues[] = trim($domain);
706706
}
707707

708708
foreach (($appSvcNmByRefComp[$refCompId] ?? []) as $appSvcName) {
@@ -711,10 +711,10 @@ private function buildDimensionValueMap(array $modulesByObjectId): array {
711711
}
712712

713713
$dimValsByObjId[$objectId] = [
714-
'referentiecomponent' => array_values(array_unique($refCompNames)),
715-
'standaard' => $stdValsByObjId[$objectId] ?? [],
716-
'domein' => array_values(array_unique($domeinValues)),
717-
'applicatieservice' => array_values(array_unique($appSvcValues)),
714+
'referenceComponent' => array_values(array_unique($refCompNames)),
715+
'standard' => $stdValsByObjId[$objectId] ?? [],
716+
'domain' => array_values(array_unique($domeinValues)),
717+
'applicationService' => array_values(array_unique($appSvcValues)),
718718
];
719719
}//end foreach
720720

@@ -764,16 +764,16 @@ private function resolveElementsByIdentifier(array $identifiers): array {
764764
}//end resolveElementsByIdentifier()
765765

766766
/**
767-
* Resolve, for each referentiecomponent identifier, the distinct display
767+
* Resolve, for each referenceComponent identifier, the distinct display
768768
* names of `Applicatieservice`-typed `element` objects reachable via a
769769
* `relation` object touching it (either endpoint) — the module schema has
770-
* no direct applicatieservice link, so this two-hop lookup mirrors the
770+
* no direct applicationService link, so this two-hop lookup mirrors the
771771
* relationship-resolution pattern `ViewService`/`ArchiMateService` already
772-
* perform for referentiecomponent overlays (design.md trade-offs).
772+
* perform for referenceComponent overlays (design.md trade-offs).
773773
*
774-
* @param array $refCompIds Distinct referentiecomponent element identifiers.
774+
* @param array $refCompIds Distinct referenceComponent element identifiers.
775775
*
776-
* @return array<string,string[]> Referentiecomponent identifier => applicatieservice names.
776+
* @return array<string,string[]> Referentiecomponent identifier => applicationService names.
777777
*
778778
* @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-endpoint-returns-gemma-dimension-counts
779779
* @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-queries-must-be-bounded
@@ -791,7 +791,7 @@ private function resolveApplicatieservicesForReferentiecomponenten(array $refCom
791791
);
792792
} catch (\Exception $e) {
793793
$this->logger->warning(
794-
message: 'FacetService: failed to resolve relationships for applicatieservice facet',
794+
message: 'FacetService: failed to resolve relationships for applicationService facet',
795795
context: ['error' => $e->getMessage()]
796796
);
797797
return [];
@@ -818,12 +818,12 @@ private function resolveApplicatieservicesForReferentiecomponenten(array $refCom
818818

819819
/**
820820
* For every `relation` object, record the OTHER endpoint (source or
821-
* target) whenever one side matches a referentiecomponent identifier —
821+
* target) whenever one side matches a referenceComponent identifier —
822822
* relations are undirected for this lookup's purposes (either endpoint
823823
* order counts).
824824
*
825825
* @param array $relations Bounded relation objects.
826-
* @param array $refCompIds Distinct referentiecomponent element identifiers.
826+
* @param array $refCompIds Distinct referenceComponent element identifiers.
827827
*
828828
* @return array<string,string[]> Referentiecomponent identifier => other-endpoint identifiers.
829829
*/
@@ -851,13 +851,13 @@ private function collectRelationEndpoints(array $relations, array $refCompIds):
851851
}//end collectRelationEndpoints()
852852

853853
/**
854-
* Filter each referentiecomponent's other-endpoint identifiers down to
854+
* Filter each referenceComponent's other-endpoint identifiers down to
855855
* `Applicatieservice`-typed elements and resolve their display names.
856856
*
857857
* @param array $otherEpsByRefComp Referentiecomponent identifier => other-endpoint identifiers.
858858
* @param array $elementsById Resolved element identifier => element object.
859859
*
860-
* @return array<string,string[]> Referentiecomponent identifier => applicatieservice names.
860+
* @return array<string,string[]> Referentiecomponent identifier => applicationService names.
861861
*/
862862
private function mapEndpointsToApplicatieserviceNames(array $otherEpsByRefComp, array $elementsById): array {
863863
$result = [];

src/services/facets.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,17 @@ import { generateUrl } from '@nextcloud/router'
2121
/** GEMMA facet dimensions this client supports, in display order. */
2222
export const FACET_DIMENSIONS = [
2323
// WIRE NAMES, and they must stay as a SET. FacetController and FacetService
24-
// both declare ['referentiecomponent', 'standaard', 'applicatieservice',
25-
// 'domein'] — these strings are the query parameters and the response keys of
26-
// this app's own facet endpoint. The vocabulary pass translated exactly ONE of
27-
// the four, so the frontend started sending `standard[]` to a backend that
28-
// only reads `standaard[]`: facet filtering by standard silently returned
29-
// everything, with no error on either side.
30-
'referentiecomponent',
31-
'standaard',
32-
'applicatieservice',
33-
'domein',
24+
// both declare ['referenceComponent', 'standard', 'applicationService',
25+
// 'domain'] — these strings are the query parameters and the response keys of
26+
// this app's own facet endpoint. They were Dutch until the vocabulary pass
27+
// moved all four together; an earlier tranche translated exactly ONE of
28+
// them, so the frontend sent `standard[]` to a backend still reading
29+
// `standaard[]` and facet filtering by standard silently returned
30+
// everything, with no error on either side. That is why they move as a SET.
31+
'referenceComponent',
32+
'standard',
33+
'applicationService',
34+
'domain',
3435
]
3536

3637
/**
@@ -39,7 +40,7 @@ export const FACET_DIMENSIONS = [
3940
* convention and the spec's documented query shape).
4041
*
4142
* @param {object} options Request options.
42-
* @param {object} [options.filters] Selected facet values keyed by dimension: `{ referentiecomponent: ['A', 'B'] }`.
43+
* @param {object} [options.filters] Selected facet values keyed by dimension: `{ referenceComponent: ['A', 'B'] }`.
4344
* @param {string} [options.search] Free-text query.
4445
* @param {string} [options.organization] Organisation override.
4546
* @return {URLSearchParams} The query parameters.
@@ -80,7 +81,7 @@ export function buildFacetQueryParams({
8081
*
8182
* @param {string} schema `module` or `dienst`.
8283
* @param {object} [options] See `buildFacetQueryParams()`.
83-
* @return {Promise<object>} The facet response: `{ referentiecomponent, standaard, applicatieservice, domein, _meta }`.
84+
* @return {Promise<object>} The facet response: `{ referenceComponent, standard, applicationService, domain, _meta }`.
8485
*
8586
* @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-endpoint-returns-gemma-dimension-counts
8687
*/

src/services/facets.spec.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ jest.mock('@nextcloud/router', () => ({
2929
describe('facets.FACET_DIMENSIONS', () => {
3030
it('lists all four GEMMA dimensions', () => {
3131
expect(FACET_DIMENSIONS).toEqual([
32-
'referentiecomponent',
33-
'standaard',
34-
'applicatieservice',
35-
'domein',
32+
'referenceComponent',
33+
'standard',
34+
'applicationService',
35+
'domain',
3636
])
3737
})
3838
})
@@ -41,30 +41,30 @@ describe('facets.buildFacetQueryParams', () => {
4141
it('builds repeated dimension[]= params for array-valued filters', () => {
4242
const params = buildFacetQueryParams({
4343
filters: {
44-
referentiecomponent: [
44+
referenceComponent: [
4545
'Zaakregistratiecomponent',
4646
'Klantcontactcomponent',
4747
],
4848
},
4949
})
50-
expect(params.getAll('referentiecomponent[]')).toEqual([
50+
expect(params.getAll('referenceComponent[]')).toEqual([
5151
'Zaakregistratiecomponent',
5252
'Klantcontactcomponent',
5353
])
5454
})
5555

5656
it('omits a dimension entirely when its filter value is not an array', () => {
5757
const params = buildFacetQueryParams({
58-
filters: { referentiecomponent: 'not-an-array' },
58+
filters: { referenceComponent: 'not-an-array' },
5959
})
60-
expect(params.has('referentiecomponent[]')).toBe(false)
60+
expect(params.has('referenceComponent[]')).toBe(false)
6161
})
6262

6363
it('drops blank/whitespace-only values within a dimension', () => {
6464
const params = buildFacetQueryParams({
65-
filters: { standaard: ['StUF-ZKN', '', ' '] },
65+
filters: { standard: ['StUF-ZKN', '', ' '] },
6666
})
67-
expect(params.getAll('standaard[]')).toEqual(['StUF-ZKN'])
67+
expect(params.getAll('standard[]')).toEqual(['StUF-ZKN'])
6868
})
6969

7070
it('sets search only when non-blank', () => {
@@ -94,16 +94,16 @@ describe('facets.fetchFacets', () => {
9494
it('requests GET /apps/softwarecatalog/api/facets/{schema} with the encoded schema and query params', async () => {
9595
axios.get.mockResolvedValue({
9696
data: {
97-
referentiecomponent: [],
98-
standaard: [],
99-
applicatieservice: [],
100-
domein: [],
97+
referenceComponent: [],
98+
standard: [],
99+
applicationService: [],
100+
domain: [],
101101
_meta: {},
102102
},
103103
})
104104

105105
await fetchFacets('module', {
106-
filters: { referentiecomponent: ['A'] },
106+
filters: { referenceComponent: ['A'] },
107107
search: 'zaak',
108108
})
109109

@@ -112,7 +112,7 @@ describe('facets.fetchFacets', () => {
112112
)
113113
const [calledUrl] = axios.get.mock.calls[0]
114114
expect(calledUrl).toContain('/apps/softwarecatalog/api/facets/module?')
115-
expect(calledUrl).toContain('referentiecomponent%5B%5D=A')
115+
expect(calledUrl).toContain('referenceComponent%5B%5D=A')
116116
expect(calledUrl).toContain('search=zaak')
117117
})
118118

@@ -127,10 +127,10 @@ describe('facets.fetchFacets', () => {
127127

128128
it('returns the response body', async () => {
129129
const body = {
130-
referentiecomponent: [{ value: 'A', label: 'A', count: 3 }],
131-
standaard: [],
132-
applicatieservice: [],
133-
domein: [],
130+
referenceComponent: [{ value: 'A', label: 'A', count: 3 }],
131+
standard: [],
132+
applicationService: [],
133+
domain: [],
134134
_meta: { totalMatched: 3 },
135135
}
136136
axios.get.mockResolvedValue({ data: body })

0 commit comments

Comments
 (0)