diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.html
index 62fe1fe401..44d7c57507 100644
--- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.html
+++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.html
@@ -8,10 +8,15 @@
{{ lastCompletedBuildMessage }}
/>
}
- @if (historicalBuilds.length > 0) {
+ @if (savedHistoricalBuilds.length > 0) {
{{ t("previously_generated_drafts") }}
- @for (entry of historicalBuilds; track entry.id) {
+ @for (entry of savedHistoricalBuilds; track entry.id) {
}
}
+ @if (showOlderDraftsNotSupportedWarning && nonActiveBuilds.length > 0) {
+ {{
+ t("older_drafts_not_available", { date: draftHistoryCutOffDateFormatted })
+ }}
+ }
diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.spec.ts
index 4d8e55bc0c..9514f0a803 100644
--- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.spec.ts
+++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.spec.ts
@@ -47,7 +47,7 @@ describe('DraftHistoryListComponent', () => {
it('should handle a missing build history', () => {
const env = new TestEnvironment(undefined);
expect(env.component.history).toEqual([]);
- expect(env.component.historicalBuilds).toEqual([]);
+ expect(env.component.savedHistoricalBuilds).toEqual([]);
expect(env.component.isBuildActive).toBe(false);
expect(env.component.latestBuild).toBeUndefined();
expect(env.component.lastCompletedBuildMessage).toBe('');
@@ -57,7 +57,7 @@ describe('DraftHistoryListComponent', () => {
it('should handle an empty build history', () => {
const env = new TestEnvironment([]);
expect(env.component.history).toEqual([]);
- expect(env.component.historicalBuilds).toEqual([]);
+ expect(env.component.savedHistoricalBuilds).toEqual([]);
expect(env.component.isBuildActive).toBe(false);
expect(env.component.latestBuild).toBeUndefined();
expect(env.component.lastCompletedBuildMessage).toBe('');
@@ -66,10 +66,13 @@ describe('DraftHistoryListComponent', () => {
it('should handle completed and active builds', fakeAsync(() => {
const activeBuild = { state: BuildStates.Active } as BuildDto;
- const completedBuild = { state: BuildStates.Completed } as BuildDto;
+ const completedBuild = {
+ state: BuildStates.Completed,
+ additionalInfo: { dateRequested: '2025-08-01T12:00:00.000Z' }
+ } as BuildDto;
const env = new TestEnvironment([completedBuild, activeBuild]);
expect(env.component.history).toEqual([activeBuild, completedBuild]);
- expect(env.component.historicalBuilds).toEqual([completedBuild]);
+ expect(env.component.savedHistoricalBuilds).toEqual([completedBuild]);
expect(env.component.isBuildActive).toBe(true);
expect(env.component.latestBuild).toBeUndefined();
expect(env.component.lastCompletedBuildMessage).toBe('');
@@ -80,7 +83,7 @@ describe('DraftHistoryListComponent', () => {
const buildHistory = [{ state: BuildStates.Active } as BuildDto];
const env = new TestEnvironment(buildHistory);
expect(env.component.history).toEqual(buildHistory);
- expect(env.component.historicalBuilds).toEqual([]);
+ expect(env.component.savedHistoricalBuilds).toEqual([]);
expect(env.component.isBuildActive).toBe(true);
expect(env.component.latestBuild).toBeUndefined();
expect(env.component.lastCompletedBuildMessage).toBe('');
@@ -92,7 +95,7 @@ describe('DraftHistoryListComponent', () => {
const buildHistory = [build];
const env = new TestEnvironment(buildHistory);
expect(env.component.history).toEqual(buildHistory);
- expect(env.component.historicalBuilds).toEqual([]);
+ expect(env.component.savedHistoricalBuilds).toEqual([]);
expect(env.component.isBuildActive).toBe(false);
expect(env.component.latestBuild).toBe(build);
expect(env.component.lastCompletedBuildMessage).not.toBe('');
@@ -100,11 +103,14 @@ describe('DraftHistoryListComponent', () => {
}));
it('should handle just one completed build', fakeAsync(() => {
- const build = { state: BuildStates.Completed } as BuildDto;
+ const build = {
+ state: BuildStates.Completed,
+ additionalInfo: { dateGenerated: '2025-08-01T12:00:00.000Z' }
+ } as BuildDto;
const buildHistory = [build];
const env = new TestEnvironment(buildHistory);
expect(env.component.history).toEqual(buildHistory);
- expect(env.component.historicalBuilds).toEqual([]);
+ expect(env.component.savedHistoricalBuilds).toEqual([]);
expect(env.component.isBuildActive).toBe(false);
expect(env.component.latestBuild).toBe(build);
expect(env.component.lastCompletedBuildMessage).not.toBe('');
@@ -116,7 +122,7 @@ describe('DraftHistoryListComponent', () => {
const buildHistory = [build];
const env = new TestEnvironment(buildHistory);
expect(env.component.history).toEqual(buildHistory);
- expect(env.component.historicalBuilds).toEqual([]);
+ expect(env.component.savedHistoricalBuilds).toEqual([]);
expect(env.component.isBuildActive).toBe(false);
expect(env.component.latestBuild).toBe(build);
expect(env.component.lastCompletedBuildMessage).not.toBe('');
@@ -139,19 +145,107 @@ describe('DraftHistoryListComponent', () => {
expect(env.component.history).toEqual([newBuild, build]);
}));
+ it('should filter draft history and hide builds that are not saved to the database', fakeAsync(() => {
+ const build = {
+ state: BuildStates.Completed,
+ additionalInfo: { dateRequested: '2025-08-01T12:00:00.000Z' }
+ } as BuildDto;
+ const olderBuild = {
+ state: BuildStates.Completed
+ } as BuildDto;
+ const buildHistory = [olderBuild, build];
+ const env = new TestEnvironment(buildHistory);
+
+ // Ensure that the warning shows, even though the project ID is invalid.
+ env.component.showOlderDraftsNotSupportedWarning = true;
+ env.fixture.detectChanges();
+
+ expect(env.component.history).toEqual(buildHistory);
+ expect(env.component.savedHistoricalBuilds).toEqual([]);
+ expect(env.component.isBuildActive).toBe(false);
+ expect(env.component.latestBuild).toBe(build);
+ expect(env.component.lastCompletedBuildMessage).not.toBe('');
+ expect(env.component.nonActiveBuilds).toEqual(buildHistory);
+ expect(env.olderDraftsMessage).not.toBeNull();
+ }));
+
+ it('should show history with faulted and canceled builds', fakeAsync(() => {
+ const build = {
+ id: 'completed',
+ state: BuildStates.Completed,
+ additionalInfo: { dateRequested: '2025-08-01T12:00:00.000Z' }
+ } as BuildDto;
+ const canceled = {
+ id: 'canceled',
+ state: BuildStates.Canceled,
+ additionalInfo: { dateRequested: '2025-07-02T12:00:00.000Z' }
+ } as BuildDto;
+ const faulted = {
+ id: 'faulted',
+ state: BuildStates.Faulted,
+ additionalInfo: { dateRequested: '2025-07-01T12:00:00.000Z' }
+ } as BuildDto;
+ const buildHistory = [faulted, canceled, build];
+ const env = new TestEnvironment(buildHistory);
+ expect(env.component.history).toEqual(buildHistory);
+ expect(env.component.savedHistoricalBuilds).toEqual([canceled, faulted]);
+ expect(env.component.isBuildActive).toBe(false);
+ expect(env.component.latestBuild).toBe(build);
+ expect(env.component.lastCompletedBuildMessage).not.toBe('');
+ expect(env.component.nonActiveBuilds).toEqual(buildHistory);
+ }));
+
+ it('should not show the drafts not supported warning for projects created after 4 June 2025', fakeAsync(() => {
+ const build = {
+ state: BuildStates.Completed,
+ additionalInfo: { dateRequested: '2025-08-01T12:00:00.000Z' }
+ } as BuildDto;
+ const olderBuild = {
+ state: BuildStates.Completed,
+ additionalInfo: { dateRequested: '2025-08-01T12:00:00.000Z' }
+ } as BuildDto;
+ const buildHistory = [olderBuild, build];
+ // ObjectID was created at 2025-06-03T00:00:00.000Z
+ const env = new TestEnvironment(buildHistory, '683e3b000000000000000000');
+ expect(env.component.history).toEqual(buildHistory);
+ expect(env.component.nonActiveBuilds).toEqual(buildHistory);
+ expect(env.olderDraftsMessage).not.toBeNull();
+ }));
+
+ it('should show the drafts not supported warning for projects created before 4 June 2025', fakeAsync(() => {
+ const build = {
+ state: BuildStates.Completed,
+ additionalInfo: { dateRequested: '2025-08-01T12:00:00.000Z' }
+ } as BuildDto;
+ const olderBuild = {
+ state: BuildStates.Completed,
+ additionalInfo: { dateRequested: '2025-08-01T12:00:00.000Z' }
+ } as BuildDto;
+ const buildHistory = [olderBuild, build];
+ // ObjectID was created at 2025-06-05T00:00:00.000Z
+ const env = new TestEnvironment(buildHistory, '6840de000000000000000000');
+ expect(env.component.history).toEqual(buildHistory);
+ expect(env.component.nonActiveBuilds).toEqual(buildHistory);
+ expect(env.olderDraftsMessage).toBeNull();
+ }));
+
class TestEnvironment {
component: DraftHistoryListComponent;
fixture: ComponentFixture;
- constructor(buildHistory: BuildDto[] | undefined) {
- when(mockedActivatedProjectService.projectId$).thenReturn(of('project01'));
+ constructor(buildHistory: BuildDto[] | undefined, projectId: string = 'project01') {
+ when(mockedActivatedProjectService.projectId$).thenReturn(of(projectId));
when(mockedActivatedProjectService.changes$).thenReturn(of(undefined)); // Required for DraftPreviewBooksComponent
- when(mockedDraftGenerationService.getBuildHistory('project01')).thenReturn(new BehaviorSubject(buildHistory));
+ when(mockedDraftGenerationService.getBuildHistory(projectId)).thenReturn(new BehaviorSubject(buildHistory));
when(mockedFeatureFlagsService.usfmFormat).thenReturn(createTestFeatureFlag(true));
this.fixture = TestBed.createComponent(DraftHistoryListComponent);
this.component = this.fixture.componentInstance;
this.fixture.detectChanges();
}
+
+ get olderDraftsMessage(): HTMLElement {
+ return this.fixture.nativeElement.querySelector('.older-drafts');
+ }
}
});
diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.ts
index 617789fc51..1e2c72a144 100644
--- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.ts
+++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.ts
@@ -1,12 +1,15 @@
import { Component, DestroyRef } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { TranslocoModule, TranslocoService } from '@ngneat/transloco';
+import ObjectID from 'bson-objectid';
import { take } from 'rxjs';
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
+import { I18nService } from 'xforge-common/i18n.service';
import { filterNullish, quietTakeUntilDestroyed } from 'xforge-common/util/rxjs-util';
import { ProjectNotificationService } from '../../../core/project-notification.service';
import { BuildDto } from '../../../machine-api/build-dto';
import { BuildStates } from '../../../machine-api/build-states';
+import { NoticeComponent } from '../../../shared/notice/notice.component';
import { activeBuildStates } from '../draft-generation';
import { DraftGenerationService } from '../draft-generation.service';
import { DraftHistoryEntryComponent } from './draft-history-entry/draft-history-entry.component';
@@ -14,23 +17,31 @@ import { DraftHistoryEntryComponent } from './draft-history-entry/draft-history-
@Component({
selector: 'app-draft-history-list',
standalone: true,
- imports: [MatIconModule, DraftHistoryEntryComponent, TranslocoModule],
+ imports: [MatIconModule, DraftHistoryEntryComponent, TranslocoModule, NoticeComponent],
templateUrl: './draft-history-list.component.html',
styleUrl: './draft-history-list.component.scss'
})
export class DraftHistoryListComponent {
+ showOlderDraftsNotSupportedWarning: boolean = false;
history: BuildDto[] = [];
+ // This is just after SFv5.33.0 was released
+ readonly draftHistoryCutOffDate: Date = new Date('2025-06-03T21:00:00Z');
+ readonly draftHistoryCutOffDateFormatted: string = this.i18n.formatDate(this.draftHistoryCutOffDate);
constructor(
activatedProject: ActivatedProjectService,
private destroyRef: DestroyRef,
private readonly draftGenerationService: DraftGenerationService,
projectNotificationService: ProjectNotificationService,
- private readonly transloco: TranslocoService
+ private readonly transloco: TranslocoService,
+ private readonly i18n: I18nService
) {
activatedProject.projectId$
.pipe(quietTakeUntilDestroyed(destroyRef), filterNullish(), take(1))
.subscribe(async projectId => {
+ // Determine whether to show or hide the older drafts warning
+ this.showOlderDraftsNotSupportedWarning =
+ ObjectID.isValid(projectId) && new ObjectID(projectId).getTimestamp() < this.draftHistoryCutOffDate;
// Initially load the history
this.loadHistory(projectId);
// Start the connection to SignalR
@@ -75,14 +86,23 @@ export class DraftHistoryListComponent {
}
}
- get historicalBuilds(): BuildDto[] {
- return this.latestBuild == null ? this.nonActiveBuilds : this.nonActiveBuilds.slice(1);
- }
-
get isBuildActive(): boolean {
return this.history.some(entry => activeBuildStates.includes(entry.state)) ?? false;
}
+ get savedHistoricalBuilds(): BuildDto[] {
+ // The requested date, if not set for the build in MongoDB, will be set based on the build id in the Machine API
+ return this.historicalBuilds.filter(
+ entry =>
+ entry.additionalInfo?.dateRequested != null &&
+ new Date(entry.additionalInfo.dateRequested) > this.draftHistoryCutOffDate
+ );
+ }
+
+ private get historicalBuilds(): BuildDto[] {
+ return this.latestBuild == null ? this.nonActiveBuilds : this.nonActiveBuilds.slice(1);
+ }
+
loadHistory(projectId: string): void {
this.draftGenerationService
.getBuildHistory(projectId)
diff --git a/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json b/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json
index 29c8903270..ca6947209a 100644
--- a/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json
+++ b/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json
@@ -287,6 +287,7 @@
"draft_canceled": "The draft was canceled",
"draft_completed": "The draft is ready",
"draft_faulted": "The draft failed",
+ "older_drafts_not_available": "Older drafts requested before {{ date }} are not available.",
"previously_generated_drafts": "Previously generated drafts"
},
"draft_preview_books": {