From b17eb32c3841ba6eb1831ded4c2ea312fc8f4dca Mon Sep 17 00:00:00 2001 From: MatusBeke Date: Mon, 20 Jul 2026 14:52:59 +0200 Subject: [PATCH 1/5] Show a per-bitstream embargo-date badge in the CLARIN file listing Fixes dataquest-dev/dspace-customers#823 (ZCU-DATA portion). ZCU-DATA's Item View file listing doesn't go through the standard file-download-link/AccessStatusBadgeComponent path used on ZCU-PUB (dataquest-dev/dspace-angular#1378) - it's a separate, CLARIN-specific component chain (clarin-files-section -> preview-section -> file-description) backed by a completely different REST endpoint (metadatabitstreams/search/byHandle) that returns a flat DTO, not a HAL resource with resolvable links. This is new implementation, not a port. Change set: - MetadataBitstream model gains status/embargoDate fields, matching what the backend companion PR (dataquest-dev/DSpace#1380) now embeds directly in that endpoint's response - no separate resolution step needed, unlike the HAL-link approach on ZCU-PUB. - file-description.component.html renders the same visual badge (badge badge-secondary, embargo.listelement.badge i18n key) used on ZCU-PUB, for a consistent look, next to the file name. - New item.bitstream.showAccessStatuses config flag (defaults to false, same reasoning as ZCU-PUB: flip only once the backend PR is deployed). - i18n copy matches ZCU-PUB/vanilla: "Embargo until {{ date }}" (en) / "Embargo do {{ date }}" (cs). Test evidence: npx ng lint --quiet -> All files pass linting. npx ng build --configuration production -> build succeeded, no budget warnings npx ng test (file-description.component.spec.ts) -> 5/5 SUCCESS, including coverage for: badge hidden by default, badge shown when embargoed + flag on, badge hidden when embargoed but flag off. --- .../core/metadata/metadata-bitstream.model.ts | 14 ++++ .../file-description.component.html | 2 + .../file-description.component.spec.ts | 70 +++++++++++++++---- .../file-description.component.ts | 7 ++ src/assets/i18n/cs.json5 | 2 + src/assets/i18n/en.json5 | 2 + src/config/default-app-config.ts | 5 +- src/config/item-config.interface.ts | 2 + src/environments/environment.test.ts | 4 +- 9 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/app/core/metadata/metadata-bitstream.model.ts b/src/app/core/metadata/metadata-bitstream.model.ts index c5eed0594a4..5d89c0a2a85 100644 --- a/src/app/core/metadata/metadata-bitstream.model.ts +++ b/src/app/core/metadata/metadata-bitstream.model.ts @@ -80,6 +80,20 @@ export class MetadataBitstream extends ListableObject implements HALResource { @autoserialize canPreview: boolean; + /** + * The access status of this bitstream (e.g. "open.access", "embargo", "restricted"). + * Unlike the standard Bitstream HAL resource, this endpoint embeds the status directly + * rather than exposing it via a resolvable accessStatus link. + */ + @autoserialize + status: string; + + /** + * The date this bitstream's embargo lifts. Only set when status is "embargo". + */ + @autoserialize + embargoDate: string; + /** * The {@link HALLink}s for this MetadataField */ diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html index 4d4e95b0b49..b487a282cfe 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html @@ -9,6 +9,8 @@
{{'item.file.description.name' | translate}}
{{ fileInput.name }} + {{ 'embargo.listelement.badge' | translate: { date: fileInput.embargoDate } }}
{{'item.file.description.size' | translate}}
diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts index 19cd1bf778f..1256e14fa67 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts @@ -12,6 +12,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HALEndpointService } from '../../../../../core/shared/hal-endpoint.service'; import { FileSizePipe } from '../../../../../shared/utils/file-size-pipe'; +import { environment } from '../../../../../../environments/environment'; describe('FileDescriptionComponent', () => { let component: FileDescriptionComponent; @@ -47,11 +48,7 @@ describe('FileDescriptionComponent', () => { }).compileComponents(); }); - beforeEach(() => { - fixture = TestBed.createComponent(FileDescriptionComponent); - component = fixture.componentInstance; - - // Mock the input value + function createFileInput(overrides: Partial = {}): MetadataBitstream { const fileInput = new MetadataBitstream(); fileInput.id = 123; fileInput.name = 'testFile'; @@ -66,20 +63,65 @@ describe('FileDescriptionComponent', () => { self: { href: '' }, schema: { href: '' }, }; + return Object.assign(fileInput, overrides); + } + + describe('by default', () => { + beforeEach(() => { + fixture = TestBed.createComponent(FileDescriptionComponent); + component = fixture.componentInstance; + component.fileInput = createFileInput(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); - component.fileInput = fileInput; + it('should display the file name', () => { + const fileNameElement = fixture.debugElement.query( + By.css('.file-content dd') + ).nativeElement; + expect(fileNameElement.textContent).toContain('testFile'); + }); - fixture.detectChanges(); + it('should not show the embargo badge', () => { + const badge = fixture.debugElement.query(By.css('span.badge')); + expect(badge).toBeNull(); + }); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('when the bitstream is embargoed and the feature flag is on', () => { + beforeEach(() => { + environment.item.bitstream.showAccessStatuses = true; + fixture = TestBed.createComponent(FileDescriptionComponent); + component = fixture.componentInstance; + component.fileInput = createFileInput({ status: 'embargo', embargoDate: '2050-01-01' }); + fixture.detectChanges(); + }); + + afterEach(() => { + environment.item.bitstream.showAccessStatuses = false; + }); + + it('should show the embargo badge', () => { + const badge = fixture.debugElement.query(By.css('span.badge')); + expect(badge).not.toBeNull(); + expect(badge.nativeElement.textContent).toContain('embargo.listelement.badge'); + }); }); - it('should display the file name', () => { - const fileNameElement = fixture.debugElement.query( - By.css('.file-content dd') - ).nativeElement; - expect(fileNameElement.textContent).toContain('testFile'); + describe('when the bitstream is embargoed but the feature flag is off', () => { + beforeEach(() => { + fixture = TestBed.createComponent(FileDescriptionComponent); + component = fixture.componentInstance; + component.fileInput = createFileInput({ status: 'embargo', embargoDate: '2050-01-01' }); + fixture.detectChanges(); + }); + + it('should not show the embargo badge', () => { + const badge = fixture.debugElement.query(By.css('span.badge')); + expect(badge).toBeNull(); + }); }); }); diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts index cb2a2bf2ed1..eaee710a66b 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts @@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core'; import { MetadataBitstream } from 'src/app/core/metadata/metadata-bitstream.model'; import { HALEndpointService } from '../../../../../core/shared/hal-endpoint.service'; import {Router} from '@angular/router'; +import { environment } from 'src/environments/environment'; const allowedPreviewFormats = ['text/plain', 'text/html', 'application/zip']; @Component({ @@ -13,6 +14,12 @@ export class FileDescriptionComponent { MIME_TYPE_IMAGES_PATH = '/assets/images/mime/'; MIME_TYPE_DEFAULT_IMAGE_NAME = 'application-octet-stream.png'; + /** + * Whether to show the embargo-date badge for a restricted bitstream. Same feature flag as + * the standard file-download-link/AccessStatusBadgeComponent path on other customer instances. + */ + showAccessStatus = environment.item.bitstream.showAccessStatuses; + @Input() fileInput: MetadataBitstream; diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 4d0ec104948..9ae830d2672 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -8397,4 +8397,6 @@ // "item.page.cc.license.disclaimer": "Except where otherwised noted, this item's license is described as", // TODO New key - Add a translation "item.page.cc.license.disclaimer": "Except where otherwised noted, this item's license is described as", + // "embargo.listelement.badge": "Embargo until {{ date }}", + "embargo.listelement.badge": "Embargo do {{ date }}", } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 1ae3aa647db..062f3e6f876 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5930,4 +5930,6 @@ "navbar.about.service-integrations": "Service integrations", "navbar.about.project-partnership": "Project partnerships", + + "embargo.listelement.badge": "Embargo until {{ date }}", } diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 6f05dedb112..e1578b2da74 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -285,7 +285,10 @@ export class DefaultAppConfig implements AppConfig { // Number of entries in the bitstream list in the item view page. // Rounded to the nearest size in the list of selectable sizes on the // settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. - pageSize: 5 + pageSize: 5, + // Show the bitstream embargo-date badge. + // Keep this false until the backend accessStatus support (DSpace#1380) is deployed. + showAccessStatuses: false } }; diff --git a/src/config/item-config.interface.ts b/src/config/item-config.interface.ts index 35cb5260aea..f3141f77238 100644 --- a/src/config/item-config.interface.ts +++ b/src/config/item-config.interface.ts @@ -12,5 +12,7 @@ export interface ItemConfig extends Config { // Rounded to the nearest size in the list of selectable sizes on the // settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. pageSize: number; + // Show the bitstream embargo-date badge + showAccessStatuses: boolean; } } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 8a6a5d1fccb..46164ae5da4 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -254,7 +254,9 @@ export const environment: BuildConfig = { // Number of entries in the bitstream list in the item view page. // Rounded to the nearest size in the list of selectable sizes on the // settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. - pageSize: 5 + pageSize: 5, + // Show the bitstream embargo-date badge + showAccessStatuses: false } }, collection: { From 71760d2d3f750e318a10dd77542fe704de1f0932 Mon Sep 17 00:00:00 2001 From: MatusBeke Date: Tue, 21 Jul 2026 09:18:18 +0200 Subject: [PATCH 2/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../file-description/file-description.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts index eaee710a66b..ee754501013 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts @@ -18,7 +18,9 @@ export class FileDescriptionComponent { * Whether to show the embargo-date badge for a restricted bitstream. Same feature flag as * the standard file-download-link/AccessStatusBadgeComponent path on other customer instances. */ - showAccessStatus = environment.item.bitstream.showAccessStatuses; + get showAccessStatus(): boolean { + return environment.item.bitstream.showAccessStatuses; + } @Input() fileInput: MetadataBitstream; From e04c31cf5358c88601e587e3aa94acf46669f983 Mon Sep 17 00:00:00 2001 From: MatusBeke Date: Tue, 21 Jul 2026 09:18:25 +0200 Subject: [PATCH 3/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../file-description/file-description.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html index b487a282cfe..b29c5b33787 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html @@ -9,8 +9,8 @@
{{'item.file.description.name' | translate}}
{{ fileInput.name }} - {{ 'embargo.listelement.badge' | translate: { date: fileInput.embargoDate } }} + {{ 'embargo.listelement.badge' | translate: { date: fileInput.embargoDate } }}
{{'item.file.description.size' | translate}}
From 6154fb3bad987cfd411d278302362fd49731cc2c Mon Sep 17 00:00:00 2001 From: MatusBeke Date: Tue, 21 Jul 2026 10:28:17 +0200 Subject: [PATCH 4/5] Show lock icon next to embargoed bitstream name Matches the existing lock-icon pattern used on ZCU-PUB's file-download-link component for restricted bitstreams. --- .../file-description.component.html | 1 + .../file-description.component.spec.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html index b29c5b33787..984d96c5e7b 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html @@ -8,6 +8,7 @@
{{'item.file.description.name' | translate}}
+ {{ fileInput.name }} {{ 'embargo.listelement.badge' | translate: { date: fileInput.embargoDate } }} diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts index 1256e14fa67..cef0d185c44 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts @@ -89,6 +89,11 @@ describe('FileDescriptionComponent', () => { const badge = fixture.debugElement.query(By.css('span.badge')); expect(badge).toBeNull(); }); + + it('should not show the lock icon', () => { + const lockIcon = fixture.debugElement.query(By.css('.file-content dd i.fa-lock')); + expect(lockIcon).toBeNull(); + }); }); describe('when the bitstream is embargoed and the feature flag is on', () => { @@ -109,6 +114,11 @@ describe('FileDescriptionComponent', () => { expect(badge).not.toBeNull(); expect(badge.nativeElement.textContent).toContain('embargo.listelement.badge'); }); + + it('should show the lock icon next to the file name', () => { + const lockIcon = fixture.debugElement.query(By.css('.file-content dd i.fa-lock')); + expect(lockIcon).not.toBeNull(); + }); }); describe('when the bitstream is embargoed but the feature flag is off', () => { @@ -123,5 +133,10 @@ describe('FileDescriptionComponent', () => { const badge = fixture.debugElement.query(By.css('span.badge')); expect(badge).toBeNull(); }); + + it('should not show the lock icon', () => { + const lockIcon = fixture.debugElement.query(By.css('.file-content dd i.fa-lock')); + expect(lockIcon).toBeNull(); + }); }); }); From 3dfd44a22d7496b150f5ac1dfcf902dd014eb558 Mon Sep 17 00:00:00 2001 From: MatusBeke Date: Tue, 21 Jul 2026 10:51:49 +0200 Subject: [PATCH 5/5] Make embargoed file name a clickable link, matching ZCU-PUB visual style 1:1 The lock icon now inherits link color (blue) by living inside an anchor that triggers downloadFile(), exactly mirroring the file-download-link component's content structure used on ZCU-PUB. Non-embargoed file names are unaffected (still plain text). --- .../file-description/file-description.component.html | 11 +++++++---- .../file-description.component.spec.ts | 6 ++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html index 984d96c5e7b..5e2f5abe13a 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html @@ -8,10 +8,13 @@
{{'item.file.description.name' | translate}}
- - {{ fileInput.name }} - {{ 'embargo.listelement.badge' | translate: { date: fileInput.embargoDate } }} + + + {{ fileInput.name }} + + {{ 'embargo.listelement.badge' | translate: { date: fileInput.embargoDate } }} + + {{ fileInput.name }}
{{'item.file.description.size' | translate}}
diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts index cef0d185c44..210dca9999c 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.spec.ts @@ -119,6 +119,12 @@ describe('FileDescriptionComponent', () => { const lockIcon = fixture.debugElement.query(By.css('.file-content dd i.fa-lock')); expect(lockIcon).not.toBeNull(); }); + + it('should render the file name as a clickable link, matching the ZCU-PUB pattern', () => { + const link = fixture.debugElement.query(By.css('.file-content dd a')); + expect(link).not.toBeNull(); + expect(link.nativeElement.textContent).toContain('testFile'); + }); }); describe('when the bitstream is embargoed but the feature flag is off', () => {