Skip to content

CLARIN-DSpace v9/Port #1299 (license label management) to the v9 base - #1510

Merged
milanmajchrak merged 3 commits into
dtq-dev-9-basefrom
ufal/port-1299-9-base
Sep 10, 2026
Merged

CLARIN-DSpace v9/Port #1299 (license label management) to the v9 base#1510
milanmajchrak merged 3 commits into
dtq-dev-9-basefrom
ufal/port-1299-9-base

Conversation

@milanmajchrak

Copy link
Copy Markdown
Collaborator

What

Ports dtq-dev PR #1299 (66a278c822, "license management") onto dtq-dev-9-base. Sync card FE-23; also closes card PB-09 and restores the spec noted in PB-10.

/licenses/manage-table gains a License Labels section with per-row Edit and Delete. Delete of a label that some licence references is disabled with a tooltip; the frontend derives that by crawling every licence page via byNameLike (100/page), and the authoritative guard stays the backend's 400 "is in use and cannot be deleted" (ClarinLicenseLabelRestRepository put / delete, already on 9-base). "Delete License" is disabled while bitstreams > 0. All five modals now open { centered: true }, and both licence modal templates lose the hand-rolled .modal > .modal-dialog > .modal-content chrome (and the modal-boy typo) that double-rendered inside ng-bootstrap's own modal window. The label form gains an edit mode — prefill, icon preview, "Remove current icon" — and extended becomes a real boolean.

Changes

Source commit: 66a278c822 (dtq-dev PR #1299, 14 files). No cherry-pick — the directory has diverged on 9-base (standalone components, @if/@for, BS5, [dsBtnDisabled]), so the file contents were taken from origin/dtq-dev and re-applied by hand.

File What
core/data/clarin/clarin-license-label-data.service.ts BaseDataServiceIdentifiableDataService, PutDataImpl/DeleteDataImpl wired next to the existing FindAllDataImpl/CreateDataImpl, put()/delete()/deleteByHref() delegates
core/shared/clarin/clarin-license-label-extended-serializer.ts accepts a real boolean, keeps the legacy 'Yes' string path
clarin-license-table.component.{ts,html,scss} labels section, inUseLabelIds, labelsRD$, labelUsageReady$, confirmDeleteLabel, editLabel, doUpdateLabel, ensureLicenseUsageLoaded, isSelectedLicenseInUse, isLabelInUse, fetchAllLicensePages, goToLastLabelsPage, .labels-actions-column
modal/define-license-form.component.{html,scss} de-wrapped; dead .modal { display: inline } rule deleted
modal/define-license-label-form.component.{ts,html,scss} edit mode, DomSanitizer + the existing secureImageData helper, clearIcon, boolean extended
src/assets/i18n/{en,cs}.json5 39 keys each
3 spec files + shared/testing/clarin-license-mock.ts see Tests

v9 adaptations (and why)

  • ConfirmationModalComponent takes name, not dso. The v9 component declares eight @Input()s and no dso; its template interpolates { dsoName: name }, so the {{ dsoName }} placeholders in the two confirm-dialog i18n keys work unchanged. modalRef.componentInstance.name = labelToDelete.label; — the DSpaceObject import from the v7 version is gone.
  • NgbTooltipModule added to imports[]. The component is standalone and there is no NgModule to inherit it from; an un-imported structural directive fails silently at runtime, so a missing entry here would ship a tooltip that never appears.
  • *ngIf/*ngFor@if/@for (@angular-eslint/template/prefer-control-flow is an error). *ngVar stays.
  • [disabled] on a <button>[dsBtnDisabled] (no-disabled-attribute-on-button), and selectedLicense == null!selectedLicense (template eqeqeq).
  • !(loading$ | async) && …(loading$ | async) === false && …: @angular-eslint/template/no-negated-async rejects the literal migration. loading$ is a BehaviorSubject<boolean> that always has a value, so the two are equivalent.
  • BS5: pr-1pe-1, mr-1me-1, custom-selectform-select, and form-groupmb-3 in the two de-wrapped modal templates (BS5 has no .form-group, so without this the fields lose their spacing once the wrappers are gone).
  • The PutDataImpl (6 ctor params), DeleteDataImpl (8) and IdentifiableDataService (7) signatures are identical on both branches, so the data-service wiring needed no argument changes.

One extra file the card did not list

clarin-license-table.component.spec.ts imports seven symbols from src/app/shared/testing/clarin-license-mock.ts, and that file does not exist on dtq-dev-9-base (git ls-tree -r origin/dtq-dev-9-base --name-only | grep clarin-license-mock → empty). It is restored here; without it the spec cannot compile.

Why

The v7 admin licence table can create labels but never edit or delete them, and deleting a label still referenced by a licence has to be discovered by hitting the backend error. Both licence modals also rendered their own modal chrome inside ng-bootstrap's, which collapsed .modal-content and pinned the inner position: fixed element to the top of the viewport instead of centring it.

Testing

$ npm run test:headless -- --include='src/app/clarin-licenses/**/*.spec.ts' --code-coverage=false
TOTAL: 43 SUCCESS

29 (table) + 10 (label form) + 4 (licence form). The licence-form spec is a restoration — 66a278c822 does not touch it; it exists on dtq-dev and was missing on 9-base (card PB-10).

PB-09 is folded in: the restored table spec keeps should reset pagination to page 1 when the search term changes and should not reset pagination when searching with the same term from cb7e1bb856. Both were checked against a reverted production tree, and they need different reverts — removing the reset kills the first while the second stays green, and only making the reset unconditional kills the second.

$ npm run lint:nobuild -- --quiet
All files pass linting.

$ npx madge --exclude '…' --circular --extensions ts ./
✔ No circular dependency found!

$ NODE_OPTIONS=--max-old-space-size=4096 npm run build:prod
exit 0   (no `error TS` / `Module not found` / `NG####` errors; only pre-existing warnings)

npm run sync-i18n was not run — its -t -i -o -d switches are dead code under Commander v14, so it ignores its target and rewrites 33 locale files. The 39 EN and 39 CS keys were edited by hand; git diff --stat shows exactly two i18n files and both blobs stay pure LF.

Two vacuous tests inherited from the v7 spec, both repaired

  1. should create new clarin license label and load table data was fakeAsync(...) with an untick'd fixture.whenStable().then(...), so none of its four expectations ever ran. 9-base karma sets failSpecWithNoExpectations: true, which surfaced it as Spec has no expectations. whenStable() alone does not fix it either — reading a Blob through FileReader is a real browser task the zone does not count as pending — so the spec now waits for the create call and then asserts.
  2. should not open confirmation modal when clicking disabled delete on linked label asserted not.toHaveBeenCalledWith(ConfirmationModalComponent) with one argument, while every real call passes two; the matcher could never match, so .not could never fail. Now matched against the real two-argument call.

Coverage gap found and closed

The serializer's boolean pass-through — the point of the extended: string → boolean change — is exercised by none of the 43 ported specs: removing it leaves all 43 green. Added core/shared/clarin/clarin-license-label-extended-serializer.spec.ts (4 it()), deliberately outside the src/app/clarin-licenses/** glob.

Manual testing

Needs an admin session on dev-6:8603 after deploy — LINDAT-124 (labels section; Delete disabled with tooltip for an in-use label and during the usage crawl; delete of an unused label → centred confirm modal → row disappears; Edit prefill + icon preview + "Remove current icon"; jump to the last page after "Define License Label"; "Delete License" disabled while bitstreams > 0), plus LINDAT-119 and the LINDAT-036 / 037 / 111 regressions. Negative probe: DELETE $REST/core/clarinlicenselabels/<in-use id> with an admin token → 400 "is in use and cannot be deleted".

🤖 Generated with Claude Code

milanmajchrak and others added 3 commits September 10, 2026 13:55
…icence table

Source: 66a278c (dtq-dev PR #1299)

Adds a License Labels section to /licenses/manage-table with per-row Edit and
Delete, guarded by a frontend usage crawl (the authoritative guard stays the
backend 400 "is in use and cannot be deleted"), de-wraps both licence modals so
ng-bootstrap owns the modal chrome, and centres all five modals.

v9 adaptations:
- ClarinLicenseLabelDataService: BaseDataService -> IdentifiableDataService with
  PutDataImpl/DeleteDataImpl (providedIn:'root' and the 9-base dataService
  import kept).
- ConfirmationModalComponent on v9 takes `name`, not `dso`.
- standalone imports[] gains NgbTooltipModule; *ngIf/*ngFor -> @if/@for;
  [disabled] -> [dsBtnDisabled]; custom-select -> form-select; form-group ->
  mb-3 in the two de-wrapped modal templates.
- Restores src/app/shared/testing/clarin-license-mock.ts, which the table spec
  imports and which is absent on 9-base.
- Restores define-license-form.component.spec.ts (PB-10) and keeps the two
  pagination-reset tests from cb7e1bb (PB-09).

Closes card FE-23 and PB-09.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-vacuous

The v7 spec asserted modalService.open was not called with a single argument,
which no real call site matches, so the assertion could never fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ializer boolean branch

The boolean pass-through added by 66a278c (extended: string -> boolean) is not
exercised by any of the 43 ported component specs - removing it leaves all 43 green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@milanmajchrak

Copy link
Copy Markdown
Collaborator Author

Local gate output (sync card FE-23 / PB-09, §6.3 order)

Branch ufal/port-1299-9-base, head 582f397e47, base dtq-dev-9-base b0c48c578e.

$ npm run lint:nobuild -- --quiet
Linting "dspace-angular"...
All files pass linting.

$ npx madge --exclude '(bitstream|bundle|collection|config-submission-form|eperson|item|version)\.model\.ts$' --circular --extensions ts ./
Processed 3364 files (49.6s) (165 warnings)
✔ No circular dependency found!

$ NODE_OPTIONS=--max-old-space-size=4096 npm run build:prod
EXIT=0
(grep 'error TS|Module not found|NG[0-9]{4}|^Error:' over the log → only 4 pre-existing NG8113
 *warnings*, all in files this PR does not touch)

$ npm run test:headless -- --include='src/app/clarin-licenses/**/*.spec.ts' --code-coverage=false
TOTAL: 43 SUCCESS

$ npm run test:headless -- --include='src/app/core/shared/clarin/clarin-license-label-extended-serializer.spec.ts' --code-coverage=false
TOTAL: 4 SUCCESS

Static parity checks against the port head

$ git show 582f397e47:src/app/core/data/clarin/clarin-license-label-data.service.ts \
  | grep -c "extends IdentifiableDataService<ClarinLicenseLabel>\|  put(object: ClarinLicenseLabel)\|  delete(objectId: string\|  deleteByHref(href: string"
4
$ git grep -c "typeof extended === 'boolean'" 582f397e47 -- …clarin-license-label-extended-serializer.ts
1
$ git grep -c "centered: true" 582f397e47 -- …clarin-license-table.component.ts
5
$ git grep -c "componentInstance.dso" 582f397e47 -- src/app/clarin-licenses/
(no match)
$ git grep -c "modal-dialog\|modal-boy\|modal-content\|display: inline" 582f397e47 -- …/clarin-license-table/modal
(no match)

Table-component markers, one grep per marker, all ≥ 1:
inUseLabelIds 5, labelsRD$ 5, labelUsageReady$ 7, confirmDeleteLabel 1, editLabel 4,
doUpdateLabel 3, ensureLicenseUsageLoaded 2, isSelectedLicenseInUse 2, isLabelInUse 1,
fetchAllLicensePages 3, goToLastLabelsPage 2.

i18n — all 39 keys added by 66a278c822 present once in en.json5 and at least once in cs.json5
(key-by-key loop, no MISSING line), and the diff touches exactly two locale files, both pure LF:

$ git diff --stat 582f397e47 -- src/assets/i18n
 src/assets/i18n/cs.json5 | 117 +++++++++
 src/assets/i18n/en.json5 |  39 +++
$ git cat-file blob 582f397e47:src/assets/i18n/cs.json5 | tr -cd '\r' | wc -c
0

Spec case counts on the port head (anchored regex — plain grep -c 'it(' over the table spec
returns 34 because it also matches submit(, edit(, init(, await():

$ git show 582f397e47:…/clarin-license-table.component.spec.ts       | grep -cE '^\s*it\('   → 29
$ git show 582f397e47:…/define-license-label-form.component.spec.ts  | grep -cE '^\s*it\('   → 10
$ git show 582f397e47:…/define-license-form.component.spec.ts        | grep -cE '^\s*it\('   →  4
$ git show 582f397e47:…/clarin-license-table.component.spec.ts \
    | grep -c 'should reset pagination to page 1 when the search term changes\|should not reset pagination when searching with the same term'   → 2

Negative controls (production code reverted, tests untouched)

Every restored test was checked against a deliberately broken tree; a summary of the ones that
matter most:

Production code reverted Went red
searchLicenses() reduced to this.loadAllLicenses() should reset pagination to page 1 when the search term changes
reset made unconditional (previousSearchTerm check dropped) should not reset pagination when searching with the same term
confirmDeleteLabel no longer sets componentInstance.name should open confirmation modal when confirmDeleteLabel is called
{ centered: true } removed from all 5 open() calls should open confirmation modal …; should open edit modal with the selected label …
isLabelInUse()false should disable delete button and expose tooltip for linked labels; should not open confirmation modal when clicking disabled delete on linked label
isSelectedLicenseInUse()false should disable delete button and expose tooltip when selected license has bitstreams; should not call delete when clicking disabled delete button
isEditModefalse 4 label-form edit-mode specs
clearIcon flag ignored should clear the icon when clearIcon is set and no new file is selected
goToLastLabelsPage() branch removed should jump to the last labels page after a successful create …
empty-state row no longer gated on hasSucceeded should not show labels empty-state row when labels request failed
!usageReady term dropped from [dsBtnDisabled] should disable delete on all rows until the usage crawl has finished
ensureLicenseUsageLoaded() cache guard removed should load full usage dataset only once across repeated table reloads
forceReload no longer invalidates the cache should force usage dataset reload when explicitly requested
icon path no longer calls createClarinLicenseLabel() should create new clarin license label and load table data
label split removed from DefineLicenseFormComponent should load and assign extended and non extended clarin license labels options …
serializer boolean pass-through removed nothing in the 43 → gap, closed by the new serializer spec

@milanmajchrak
milanmajchrak merged commit c453631 into dtq-dev-9-base Sep 10, 2026
9 checks passed
@milanmajchrak
milanmajchrak deleted the ufal/port-1299-9-base branch September 10, 2026 13:57
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