From 70627ff0eaac3846184dd8da2393f5b3d774ab17 Mon Sep 17 00:00:00 2001 From: Cecilia Krum Date: Wed, 17 Jun 2026 16:35:12 -0500 Subject: [PATCH 1/2] PER-10637: Show displayTime in file list --- .../file-list-item.component.html | 18 ++++-- .../file-list-item.component.spec.ts | 27 ++++++++- src/app/shared/pipes/pr-date.pipe.spec.ts | 56 +++++++++++++++++++ src/app/shared/pipes/pr-date.pipe.ts | 5 ++ 4 files changed, 100 insertions(+), 6 deletions(-) diff --git a/src/app/file-browser/components/file-list-item/file-list-item.component.html b/src/app/file-browser/components/file-list-item/file-list-item.component.html index ec76d890c..4fe7f52c3 100644 --- a/src/app/file-browser/components/file-list-item/file-list-item.component.html +++ b/src/app/file-browser/components/file-list-item/file-list-item.component.html @@ -90,10 +90,14 @@
@if (!showAccess || !item.ShareArchiveVO) { - {{ item.displayDT | prDate: item.TimezoneVO : 'date' }} + {{ + item.displayTime || item.displayDT + | prDate: item.TimezoneVO : 'date' + }} @if (item.dataStatus > 0) { {{ - item.displayDT | prDate: item.TimezoneVO : 'time' + item.displayTime || item.displayDT + | prDate: item.TimezoneVO : 'time' }} } @@ -112,12 +116,18 @@
@if (item.dataStatus > 0) {
- {{ item.displayDT | prDate: item.TimezoneVO : 'date' }} + {{ + item.displayTime || item.displayDT + | prDate: item.TimezoneVO : 'date' + }}
} @if (item.dataStatus > 0) {
- {{ item.displayDT | prDate: item.TimezoneVO : 'time' }} + {{ + item.displayTime || item.displayDT + | prDate: item.TimezoneVO : 'time' + }}
}
diff --git a/src/app/file-browser/components/file-list-item/file-list-item.component.spec.ts b/src/app/file-browser/components/file-list-item/file-list-item.component.spec.ts index 1e78be409..b670014b7 100644 --- a/src/app/file-browser/components/file-list-item/file-list-item.component.spec.ts +++ b/src/app/file-browser/components/file-list-item/file-list-item.component.spec.ts @@ -25,8 +25,8 @@ class MockItemTypeIconPipe implements PipeTransform { @Pipe({ name: 'prDate' }) export class MockPrDatePipe implements PipeTransform { - transform(value: any, format?: string): string { - return `mocked-date${format ? `-${format}` : ''}`; + transform(value: any, ...args: any[]): string { + return String(value ?? ''); } } @@ -342,4 +342,27 @@ describe('FileListItemComponent', () => { expect(component.recordThumbnailUrl).toBe('https://example.com/thumb.jpg'); }); + + it('should display displayTime instead of displayDT when displayTime is set', () => { + component.item.displayTime = '2020-06-10'; + component.item.displayDT = '2023-01-01T00:00:00.000Z'; + fixture.detectChanges(); + + const secondRowDate = + fixture.nativeElement.querySelector('.second-row span')?.textContent; + + expect(secondRowDate).toContain('2020-06-10'); + expect(secondRowDate).not.toContain('2023-01-01'); + }); + + it('should display displayDT when displayTime is not set', () => { + component.item.displayTime = undefined; + component.item.displayDT = '2023-01-01T00:00:00.000Z'; + fixture.detectChanges(); + + const secondRowDate = + fixture.nativeElement.querySelector('.second-row span')?.textContent; + + expect(secondRowDate).toContain('2023-01-01T00:00:00.000Z'); + }); }); diff --git a/src/app/shared/pipes/pr-date.pipe.spec.ts b/src/app/shared/pipes/pr-date.pipe.spec.ts index b65031384..0676b7c38 100644 --- a/src/app/shared/pipes/pr-date.pipe.spec.ts +++ b/src/app/shared/pipes/pr-date.pipe.spec.ts @@ -1,5 +1,13 @@ +import { TimezoneVOData } from '@models'; import { PrDatePipe } from './pr-date.pipe'; +const cdtTimezone: TimezoneVOData = { + dstAbbrev: 'CDT', + dstOffset: '-05:00', + stdAbbrev: 'CST', + stdOffset: '-06:00', +}; + describe('PrDatePipe', () => { it('create an instance', () => { const pipe = new PrDatePipe(); @@ -7,6 +15,54 @@ describe('PrDatePipe', () => { expect(pipe).toBeTruthy(); }); + it('returns undefined for empty string', () => { + const pipe = new PrDatePipe(); + + expect(pipe.transform('')).toBeUndefined(); + }); + + it('returns undefined for null', () => { + const pipe = new PrDatePipe(); + + expect(pipe.transform(null as any)).toBeUndefined(); + }); + + describe('date-only strings (no T)', () => { + it('returns the date formatted from UTC', () => { + const pipe = new PrDatePipe(); + + expect(pipe.transform('2020-06-10')).toBe('2020-06-10'); + }); + + it('returns the date when part is date', () => { + const pipe = new PrDatePipe(); + + expect(pipe.transform('2020-06-10', undefined, 'date')).toBe( + '2020-06-10', + ); + }); + + it('returns undefined when part is time', () => { + const pipe = new PrDatePipe(); + + expect(pipe.transform('2020-06-10', undefined, 'time')).toBeUndefined(); + }); + + it('does not shift date by timezone offset', () => { + const pipe = new PrDatePipe(); + + expect(pipe.transform('2020-06-10', cdtTimezone, 'date')).toBe( + '2020-06-10', + ); + }); + + it('returns undefined for time part even with a timezone', () => { + const pipe = new PrDatePipe(); + + expect(pipe.transform('2020-06-10', cdtTimezone, 'time')).toBeUndefined(); + }); + }); + // it('transforms a DST date in Pacific time', () => { // const pipe = new PrDatePipe(); // const displayDT = formatDateISOString('2017-05-13T16:36:29.000000'); diff --git a/src/app/shared/pipes/pr-date.pipe.ts b/src/app/shared/pipes/pr-date.pipe.ts index 96e38ab8a..5bc2877bf 100644 --- a/src/app/shared/pipes/pr-date.pipe.ts +++ b/src/app/shared/pipes/pr-date.pipe.ts @@ -35,6 +35,11 @@ export class PrDatePipe implements PipeTransform { return; } + if (typeof dtString === 'string' && !dtString.includes('T')) { + if (part === 'time') return; + return moment.utc(dtString).format(MOMENT_DATE_FORMAT.date); + } + const dt = moment.utc(dtString); let outputDt: moment.Moment; From a3a5de27dbaec82b3ad0223ad3be9a9805153f3e Mon Sep 17 00:00:00 2001 From: aasandei-vsp Date: Fri, 19 Jun 2026 15:47:43 +0300 Subject: [PATCH 2/2] Show interval start date for folders in file list Folder displayTime values are EDTF intervals (start/end), which the prDate pipe cannot parse and rendered as "Invalid date". Decompose the interval and display only the start date, falling back to displayDT when there is no resolvable start. Add EdtfService.getEdtfIntervalStartDate to extract the interval start (returning non-interval values unchanged and an empty string for open starts), and a startDisplayTime getter on the file list item that uses it with the displayDT fallback. Issue: PER-10637 --- .../file-list-item.component.html | 18 ++------- .../file-list-item.component.spec.ts | 24 ++++++++++++ .../file-list-item.component.ts | 9 +++++ .../edtf-service/edtf.service.spec.ts | 38 +++++++++++++++++++ .../services/edtf-service/edtf.service.ts | 13 +++++++ 5 files changed, 88 insertions(+), 14 deletions(-) diff --git a/src/app/file-browser/components/file-list-item/file-list-item.component.html b/src/app/file-browser/components/file-list-item/file-list-item.component.html index 4fe7f52c3..4e0d8e311 100644 --- a/src/app/file-browser/components/file-list-item/file-list-item.component.html +++ b/src/app/file-browser/components/file-list-item/file-list-item.component.html @@ -90,14 +90,10 @@
@if (!showAccess || !item.ShareArchiveVO) { - {{ - item.displayTime || item.displayDT - | prDate: item.TimezoneVO : 'date' - }} + {{ startDisplayTime | prDate: item.TimezoneVO : 'date' }} @if (item.dataStatus > 0) { {{ - item.displayTime || item.displayDT - | prDate: item.TimezoneVO : 'time' + startDisplayTime | prDate: item.TimezoneVO : 'time' }} } @@ -116,18 +112,12 @@
@if (item.dataStatus > 0) {
- {{ - item.displayTime || item.displayDT - | prDate: item.TimezoneVO : 'date' - }} + {{ startDisplayTime | prDate: item.TimezoneVO : 'date' }}
} @if (item.dataStatus > 0) {
- {{ - item.displayTime || item.displayDT - | prDate: item.TimezoneVO : 'time' - }} + {{ startDisplayTime | prDate: item.TimezoneVO : 'time' }}
}
diff --git a/src/app/file-browser/components/file-list-item/file-list-item.component.spec.ts b/src/app/file-browser/components/file-list-item/file-list-item.component.spec.ts index b670014b7..8a9e6b825 100644 --- a/src/app/file-browser/components/file-list-item/file-list-item.component.spec.ts +++ b/src/app/file-browser/components/file-list-item/file-list-item.component.spec.ts @@ -365,4 +365,28 @@ describe('FileListItemComponent', () => { expect(secondRowDate).toContain('2023-01-01T00:00:00.000Z'); }); + + it('should display only the start date when displayTime is an EDTF interval', () => { + component.item.displayTime = '2020-06-10/2026-06-15'; + component.item.displayDT = '2023-01-01T00:00:00.000Z'; + fixture.detectChanges(); + + const secondRowDate = + fixture.nativeElement.querySelector('.second-row span')?.textContent; + + expect(secondRowDate).toContain('2020-06-10'); + expect(secondRowDate).not.toContain('2026-06-15'); + expect(secondRowDate).not.toContain('2023-01-01'); + }); + + it('should fall back to displayDT when the EDTF interval has an open start', () => { + component.item.displayTime = '../2026-06-15'; + component.item.displayDT = '2023-01-01T00:00:00.000Z'; + fixture.detectChanges(); + + const secondRowDate = + fixture.nativeElement.querySelector('.second-row span')?.textContent; + + expect(secondRowDate).toContain('2023-01-01T00:00:00.000Z'); + }); }); diff --git a/src/app/file-browser/components/file-list-item/file-list-item.component.ts b/src/app/file-browser/components/file-list-item/file-list-item.component.ts index 21312a7d5..bceed689e 100644 --- a/src/app/file-browser/components/file-list-item/file-list-item.component.ts +++ b/src/app/file-browser/components/file-list-item/file-list-item.component.ts @@ -33,6 +33,7 @@ import { } from '@root/app/models'; import { DataStatus } from '@models/data-status.enum'; import { EditService } from '@core/services/edit/edit.service'; +import { EdtfService } from '@shared/services/edtf-service/edtf.service'; import { RecordResponse, FolderResponse, @@ -247,8 +248,16 @@ export class FileListItemComponent private storage: StorageService, @Inject(DOCUMENT) private document: Document, private shareLinksService: ShareLinksService, + private edtfService: EdtfService, ) {} + get startDisplayTime(): string { + return ( + this.edtfService.getEdtfIntervalStartDate(this.item.displayTime) || + this.item.displayDT + ); + } + async ngOnInit() { this.recordThumbnailUrl = GetThumbnail(this.item); const date = new Date(this.item.displayDT); diff --git a/src/app/shared/services/edtf-service/edtf.service.spec.ts b/src/app/shared/services/edtf-service/edtf.service.spec.ts index 2f86e5f4a..400dc5e1c 100644 --- a/src/app/shared/services/edtf-service/edtf.service.spec.ts +++ b/src/app/shared/services/edtf-service/edtf.service.spec.ts @@ -1283,4 +1283,42 @@ describe('EdtfService', () => { expect(result).toBe(edtfString); }); }); + + describe('getEdtfIntervalStartDate', () => { + it('should return the start date of an interval', () => { + expect(service.getEdtfIntervalStartDate('1985-05-20/1990-06-15')).toBe( + '1985-05-20', + ); + }); + + it('should preserve the wall-clock time and offset of the start', () => { + expect( + service.getEdtfIntervalStartDate( + '2020-06-10T06:58:00-05:00/2026-06-15T00:00:00Z', + ), + ).toBe('2020-06-10T06:58:00-05:00'); + }); + + it('should return a non-interval value unchanged', () => { + expect(service.getEdtfIntervalStartDate('1985-05-20')).toBe('1985-05-20'); + }); + + it('should return an empty string for an empty input', () => { + expect(service.getEdtfIntervalStartDate('')).toBe(''); + }); + + it('should return an empty string for an undefined input', () => { + expect(service.getEdtfIntervalStartDate(undefined)).toBe(''); + }); + + it('should return an empty string for an open-ended start', () => { + expect(service.getEdtfIntervalStartDate('../1990-06-15')).toBe(''); + }); + + it('should return the start when the end is open-ended', () => { + expect(service.getEdtfIntervalStartDate('1985-05-20/..')).toBe( + '1985-05-20', + ); + }); + }); }); diff --git a/src/app/shared/services/edtf-service/edtf.service.ts b/src/app/shared/services/edtf-service/edtf.service.ts index 8cc7dd34d..14ed0f385 100644 --- a/src/app/shared/services/edtf-service/edtf.service.ts +++ b/src/app/shared/services/edtf-service/edtf.service.ts @@ -102,6 +102,19 @@ export class EdtfService { } } + getEdtfIntervalStartDate(edtfString: string | undefined): string { + if (!edtfString) { + return ''; + } + + if (!edtfString.includes('/')) { + return edtfString; + } + + const [startPart] = edtfString.split('/'); + return startPart === '..' ? '' : (startPart ?? ''); + } + private parseInterval(edtfString: string): DateTimeModel | null { const [startPart, endPart] = edtfString.split('/');