Skip to content

CLARIN-DSpace v9/Port #1326 (scoped-search icon on community/collection pages) to the v9 base - #1508

Merged
milanmajchrak merged 1 commit into
dtq-dev-9-basefrom
ufal/port-1326-9-base
Sep 10, 2026
Merged

CLARIN-DSpace v9/Port #1326 (scoped-search icon on community/collection pages) to the v9 base#1508
milanmajchrak merged 1 commit into
dtq-dev-9-basefrom
ufal/port-1326-9-base

Conversation

@milanmajchrak

Copy link
Copy Markdown
Collaborator

What

Ports dataquest-dev/dspace-angular #1326 (fecfa07e5d, "[Port to dtq-dev] Add search icon to
community and collection", originally ufal/dspace-angular#109) to dtq-dev-9-base.
Sync card FE-22 (tranche T4).

The DSO action menu of a Community or Collection gains a scoped-search entry that opens
/search?spc.page=1&scope=<uuid> — search restricted to that container, on the first page.

Changes

File Change
shared/menu/providers/comcol-search.menu.ts new ComColSearchMenuProvider
shared/menu/providers/comcol-search.menu.spec.ts new, 2 cases
app.menus.ts registered first inside DsoOptionMenuProvider.withSubs([...]), .onRoute(COMMUNITY_PAGE, COLLECTION_PAGE)
shared/dso-page/dso-edit-menu/dso-edit-menu-section/…component.html [queryParams]="itemModel.queryParams" on the LINK anchor
…dso-edit-menu-section.component.spec.ts fixture gains queryParams, 7 → 8 cases

Why this is a rewrite, not a translation

On 7.x the entry was one more object in the array returned by DSOEditMenuResolver
(id: 'search-dso', index: 3). That resolver does not exist on v9 — the DSO edit menu is
assembled from providers registered in app.menus.ts:

$ git ls-tree origin/dtq-dev-9-base -- src/app/shared/dso-page/dso-edit-menu.resolver.ts
(empty)
$ git grep -c 'search-dso' origin/dtq-dev-9-base -- src
(no match)

So two of the source's four production/spec hunks have no target and are replaced by the new
provider plus its spec. What is dropped rather than translated, and why:

  • index: 3PartialMenuSection on v9 has no index field; ordering comes from the position
    inside withSubs([...]), so "first in the list" replaces it.
  • id: 'search-dso' and active: false — both optional on v9 and auto-assigned
    (AbstractMenuProvider.getAutomatedSectionId()); the closest sibling SubscribeMenuProvider sets
    neither.

LinkMenuItemModel.queryParams already exists on 9-base and is byte-identical with dspace-9.3, so
the model half is untouched — the missing piece was the one-line binding in the renderer. That is
also a vanilla gap worth noting: dso-edit-menu-section.component.html is byte-identical between
dtq-dev-9-base and dspace-9.3, so upstream a LINK section may carry queryParams in its model
and the DSO-edit renderer silently drops them.

No extra DI registration is needed — buildMenuStructure() pushes the provider class itself into the
Provider[] it returns, and MENUS is spread into app.config.ts.

UX change this makes, deliberately (owner decision O-4, 2026-09-10)

On v9 every MenuID.DSO_EDIT sub-provider is nested under DsoOptionMenuProvider
(alwaysRenderExpandable = true), so the magnifier is an entry inside the "⋮ Options" dropdown
rather than a button standing next to Subscribe/Edit as on 7.x. Manual scenario UNIVERSAL-021
needs rewording accordingly.

The new section is visible unconditionally (a scoped search was public on 7.x too, where the same
entry carried no authorization check). Today, for an anonymous visitor, Subscribe and Edit
are both visible: false, so hasSubSections$ is false and the whole dropdown — button included —
is @if-ed away:

$ curl -s http://dev-6.pc:8603/repository/communities/f1e6128f-… | tr '<' '\n<' | sed -n '322,324p'
ds-dso-edit-menu-expandable-section …>
!---->                       <-- the entire content
/ds-dso-edit-menu-expandable-section>
$ … | grep -c 'ngbDropdown'      -> 0

Adding an unconditionally visible section flips hasSubSections$ to true, so this PR makes the
"⋮ Options" button visible to logged-out visitors for the first time.
Verified locally by rendering
DsoEditMenuExpandableSectionComponent with and without the section the provider returns:

BEFORE hasSubSections=false dom=[<!--container-->]
AFTER  hasSubSections=true  dom=[<div … ngbdropdown … class="dso-button-menu mb-1 dropdown">
        <button … class="dropdown-toggle btn btn-dark btn-sm" aria-label="dso-edit-menu.header">
        <i class="fa-ellipsis-vertical fa-fw fas"></i></button>
        <ul … class="dso-edit-menu-dropdown p-1 dropdown-menu"><li …>
        <i aria-hidden="true" class="fa-fw fa-search fas"></i>…]

The repository owner has accepted this (decision O-4): the feature is unchanged, only the chrome
around it is new. No authorization gate was added.

Not ported, with a verdict

The source also adds community-page.component.spec.ts and collection-page.component.spec.ts.
Neither exists on dtq-dev-9-base or on dspace-9.3; both are written pre-standalone
(declarations: [], NO_ERRORS_SCHEMA, a faked ngOnInit), and their single assertion is
expect(query(By.css('ds-dso-edit-menu'))).toBeTruthy(), which does not exercise the scoped search
at all and is already implied by the DSO_EDIT menu existing. Rewriting two standalone TestBeds for a
tautology is cost without coverage, so they are skipped deliberately, not dropped silently.

One assertion had to be re-expressed for Angular 20

The source's new case asserts link.properties.queryParams.scope. On v9 that throws
TypeError: Cannot read properties of undefined (reading 'scope'): DebugElement.properties is now
the DOM element's property map (download, ping, rel, href, …), and RouterLink is a
standalone directive whose inputs are no longer mirrored there. Same intent, v9 mechanism:

const link = fixture.debugElement.query(By.css('a'));
expect(link.injector.get(RouterLink).queryParams).toEqual({ scope: 'test-scope-id' });

Testing

$ npm run test:headless -- --include='src/app/shared/menu/providers/comcol-search.menu.spec.ts' \
    --include='src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.spec.ts' \
    --code-coverage=false
TOTAL: 10 SUCCESS                # provider 2 + section 8 (7 before)

# regression over everything that renders these menus
$ npm run test:headless -- --include='src/app/shared/dso-page/**/*.spec.ts' --include='src/app/shared/menu/**/*.spec.ts' --code-coverage=false
TOTAL: 238 SUCCESS

$ npx ng lint --lint-file-patterns 'src/app/shared/menu/providers/comcol-search.menu*.ts' \
              --lint-file-patterns 'src/app/app.menus.ts' \
              --lint-file-patterns 'src/app/shared/dso-page/dso-edit-menu/**/*.{ts,html}'
5 problems (0 errors, 5 warnings)   # all pre-existing in old specs

Negative control (production code reverted, tests untouched):

  • remove [queryParams]="itemModel.queryParams" from the anchor → 1 FAILED
    (should bind queryParams on the link element);
  • change the provider's section to visible: false and scope: 'not-the-dso-uuid'2 FAILED
    (both provider cases).

All three new cases fail under one of the two, so none is vacuous.

Live verification on dev-6 (UNIVERSAL-021, reworded per the dropdown UX) is pending deployment.

Source: fecfa07e5d (dtq-dev PR #1326). Sync card: FE-22. Owner decision: O-4.

🤖 Generated with Claude Code

…tion

The DSO action menu of a Community or Collection gains a scoped-search entry
that opens /search restricted to that container, on the first page
(/search?spc.page=1&scope=<uuid>).

This is a rewrite, not a translation. On 7.x the entry was one more object in
the array returned by DSOEditMenuResolver (`id: 'search-dso'`, `index: 3`).
That resolver does not exist on v9 - the DSO edit menu is assembled from menu
providers registered in app.menus.ts - so the port is:

* new `ComColSearchMenuProvider extends DSpaceObjectPageMenuProvider`, modelled
  on the sibling `comcol-subscribe.menu.ts`, returning one LINK section
  { text: 'search.title', link: '/search',
    queryParams: { 'spc.page': '1', scope: dso.uuid }, icon: 'search' };
* registered FIRST inside `DsoOptionMenuProvider.withSubs([...])` with
  `.onRoute(MenuRoute.COMMUNITY_PAGE, MenuRoute.COLLECTION_PAGE)` - ordering on
  v9 comes from the position in withSubs([...]), which is why 7.x's `index: 3`
  is dropped rather than translated. `id: 'search-dso'` and `active: false` are
  dropped for the same reason: `PartialMenuSection.id` is optional and
  auto-assigned, and no sibling provider sets either;
* `[queryParams]="itemModel.queryParams"` added to the LINK anchor in
  dso-edit-menu-section.component.html. `LinkMenuItemModel.queryParams` already
  exists on 9-base (vanilla) but the DSO-edit renderer silently dropped it, so
  this one line is what makes the model half work.

The 7.x resolver-spec hunks have no target on v9 and are replaced by the new
provider spec (2 cases). The two page specs the source commit also adds
(community-page / collection-page) are deliberately NOT ported: they do not
exist on 9-base or on dspace-9.3, they are written pre-standalone, and their
single assertion is `expect(query(By.css('ds-dso-edit-menu'))).toBeTruthy()`,
which does not exercise this feature at all.

UX note, decided by the repository owner (decision O-4): on v9 every DSO_EDIT
sub-provider is nested under DsoOptionMenuProvider, so the magnifier is an entry
inside the "options" dropdown rather than a button next to Subscribe/Edit. The
new section is visible unconditionally (a scoped search was public on 7.x too),
which flips `hasSubSections$` from false to true for anonymous visitors and
therefore makes the "options" button itself visible to them for the first time.
That is accepted: the feature is unchanged, only the chrome around it is new.

Source: fecfa07 (dtq-dev PR #1326)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@milanmajchrak
milanmajchrak merged commit b0c48c5 into dtq-dev-9-base Sep 10, 2026
9 checks passed
@milanmajchrak
milanmajchrak deleted the ufal/port-1326-9-base branch September 10, 2026 11:09
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.

1 participant