diff --git a/src/SIL.XForge.Scripture/ClientApp/e2e/workflows/onboarding-flow.ts b/src/SIL.XForge.Scripture/ClientApp/e2e/workflows/onboarding-flow.ts index e3f4fa1fe29..7e27e0c4ce8 100644 --- a/src/SIL.XForge.Scripture/ClientApp/e2e/workflows/onboarding-flow.ts +++ b/src/SIL.XForge.Scripture/ClientApp/e2e/workflows/onboarding-flow.ts @@ -2,8 +2,6 @@ import { expect } from 'npm:@playwright/test'; import { BrowserType, Page } from 'npm:playwright'; import { preset, ScreenshotContext } from '../e2e-globals.ts'; import { - disableFeatureFlag, - enableFeatureFlag, freshlyConnectProject, getNewBrowserForSideWork, installMouseFollower, @@ -40,9 +38,6 @@ export async function onboardingFlow( if (preset.showArrow) await installMouseFollower(page); const user = new UserEmulator(page); - await enableFeatureFlag(page, 'Show in-app onboarding form instead of external link'); - await disableFeatureFlag(page, 'Show developer tools'); - // Ensure project exists and is connected for this user await freshlyConnectProject(page, SIGNUP_PROJECT_SHORT_NAME); diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/pretranslation-signup-url.service.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/pretranslation-signup-url.service.spec.ts deleted file mode 100644 index 1fdc2a987f1..00000000000 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/pretranslation-signup-url.service.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { mock, when } from 'ts-mockito'; -import { ActivatedProjectService } from 'xforge-common/activated-project.service'; -import { UserDoc } from 'xforge-common/models/user-doc'; -import { configureTestingModule } from 'xforge-common/test-utils'; -import { UserService } from 'xforge-common/user.service'; -import { SFProjectProfileDoc } from '../../core/models/sf-project-profile-doc'; -import { PreTranslationSignupUrlService } from './pretranslation-signup-url.service'; - -describe('PreTranslationSignupUrlService', () => { - let service: PreTranslationSignupUrlService; - const mockActivatedProjectService: ActivatedProjectService = mock(ActivatedProjectService); - const mockUserService: UserService = mock(UserService); - - configureTestingModule(() => ({ - providers: [ - { provide: ActivatedProjectService, useMock: mockActivatedProjectService }, - { provide: UserService, useMock: mockUserService } - ] - })); - - beforeEach(() => { - service = TestBed.inject(PreTranslationSignupUrlService); - }); - - describe('generateSignupUrl', () => { - it('should generate the signup URL with the correct parameters', async () => { - const mockUserDoc: UserDoc = { data: { name: 'John', email: 'john@example.com' } } as UserDoc; - const mockProjectDoc: SFProjectProfileDoc = { - data: { shortName: 'project', writingSystem: { tag: 'eng' } } - } as SFProjectProfileDoc; - - when(mockUserService.getCurrentUser()).thenResolve(mockUserDoc); - when(mockActivatedProjectService.projectDoc).thenReturn(mockProjectDoc); - - const url = await service.generateSignupUrl(); - - expect(url).toBe( - 'https://app.smartsheet.com/b/form/305798a45a664d8585ac74e72241d8cc?Name=John&Email=john%40example.com&Paratext+Project+Short+Name=project&Project+Language+ISO+Code=eng' - ); - }); - - it('should generate the signup URL without parameters if user or project is undefined', async () => { - when(mockUserService.getCurrentUser()).thenResolve({ data: undefined } as UserDoc); - when(mockActivatedProjectService.projectDoc).thenReturn({ data: undefined } as SFProjectProfileDoc); - - const url = await service.generateSignupUrl(); - - expect(url).toBe('https://app.smartsheet.com/b/form/305798a45a664d8585ac74e72241d8cc'); - }); - - it('should omit language code if it is not a 3-letter code', async () => { - const mockUserDoc: UserDoc = { data: { name: 'John', email: 'john@example.com' } } as UserDoc; - const mockProjectDoc: SFProjectProfileDoc = { - data: { shortName: 'project', writingSystem: { tag: 'en' } } - } as SFProjectProfileDoc; - - when(mockUserService.getCurrentUser()).thenResolve(mockUserDoc); - when(mockActivatedProjectService.projectDoc).thenReturn(mockProjectDoc); - - const url = await service.generateSignupUrl(); - - expect(url).toBe( - 'https://app.smartsheet.com/b/form/305798a45a664d8585ac74e72241d8cc?Name=John&Email=john%40example.com&Paratext+Project+Short+Name=project' - ); - }); - - it('should omit email if it is a transparent authentication noreply email', async () => { - const mockUserDoc: UserDoc = { - data: { name: 'John', email: '6ymxqikh3mo5vmmu@users.noreply.scriptureforge.org' } - } as UserDoc; - const mockProjectDoc: SFProjectProfileDoc = { - data: { shortName: 'project', writingSystem: { tag: 'eng' } } - } as SFProjectProfileDoc; - - when(mockUserService.getCurrentUser()).thenResolve(mockUserDoc); - when(mockActivatedProjectService.projectDoc).thenReturn(mockProjectDoc); - - const url = await service.generateSignupUrl(); - expect(url).toBe( - 'https://app.smartsheet.com/b/form/305798a45a664d8585ac74e72241d8cc?Name=John&Paratext+Project+Short+Name=project&Project+Language+ISO+Code=eng' - ); - }); - }); -}); diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/pretranslation-signup-url.service.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/pretranslation-signup-url.service.ts deleted file mode 100644 index 2a73a4ce901..00000000000 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/pretranslation-signup-url.service.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Inject, Injectable, InjectionToken } from '@angular/core'; -import { User } from 'realtime-server/lib/esm/common/models/user'; -import { SFProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project'; -import { ActivatedProjectService } from 'xforge-common/activated-project.service'; -import { UserService } from 'xforge-common/user.service'; - -export interface PreTranslationSignupUrlConfig { - baseLink: string; - nameParam: string; - emailParam: string; - projectParam: string; - languageParam: string; -} - -export const PRE_TRANSLATION_SIGNUP_URL_CONFIG = new InjectionToken( - 'PRE_TRANSLATION_SIGNUP_LINK_CONFIG', - { - providedIn: 'root', - factory: () => ({ - baseLink: 'https://app.smartsheet.com/b/form/305798a45a664d8585ac74e72241d8cc', - nameParam: 'Name', - emailParam: 'Email', - projectParam: 'Paratext Project Short Name', - languageParam: 'Project Language ISO Code' - }) - } -); - -@Injectable({ - providedIn: 'root' -}) -export class PreTranslationSignupUrlService { - constructor( - private readonly activatedProject: ActivatedProjectService, - private readonly userService: UserService, - @Inject(PRE_TRANSLATION_SIGNUP_URL_CONFIG) private readonly linkConfig: PreTranslationSignupUrlConfig - ) {} - - async generateSignupUrl(): Promise { - const user: Readonly = (await this.userService.getCurrentUser()).data; - const project: Readonly = this.activatedProject.projectDoc?.data; - const languageCode: string | undefined = project?.writingSystem.tag; - - const url = new URL(this.linkConfig.baseLink); - const searchParams = new URLSearchParams(); - - // Omit the email if it's a transparent authentication noreply email - const userEmail = user?.email?.includes('@users.noreply.scriptureforge.org') ? undefined : user?.email; - - // Add parameters to the URL - if (user?.name) searchParams.set(this.linkConfig.nameParam, user.name); - if (userEmail) searchParams.set(this.linkConfig.emailParam, userEmail); - if (project?.shortName) searchParams.set(this.linkConfig.projectParam, project.shortName); - - // Only add language parameter if it's a 3-character code - if (languageCode?.length === 3) searchParams.set(this.linkConfig.languageParam, languageCode); - - url.search = searchParams.toString(); - return url.toString(); - } -} diff --git a/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/feature-flags/feature-flag.service.ts b/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/feature-flags/feature-flag.service.ts index 6c55420d610..2813a0288da 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/feature-flags/feature-flag.service.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/feature-flags/feature-flag.service.ts @@ -203,7 +203,7 @@ class StaticFeatureFlagStore implements IFeatureFlagStore { } } -class ServerOnlyFeatureFlag implements FeatureFlag { +class StaticFeatureFlag implements FeatureFlag { constructor( readonly key: string, readonly description: string, @@ -249,21 +249,24 @@ export class FeatureFlagService { this.featureFlagStore ); - readonly showNmtDrafting: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly showNmtDrafting: FeatureFlag = new StaticFeatureFlag( 'SHOW_NMT_DRAFTING', 'Show NMT drafting', 2, this.featureFlagStore ); - readonly allowForwardTranslationNmtDrafting: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly allowForwardTranslationNmtDrafting: FeatureFlag = new StaticFeatureFlag( 'ALLOW_FORWARD_TRANSLATION_NMT_DRAFTING', 'Allow Forward Translation NMT drafting', 3, this.featureFlagStore ); - readonly scriptureAudio: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly scriptureAudio: FeatureFlag = new StaticFeatureFlag( 'SCRIPTURE_AUDIO', 'Scripture audio', 4, @@ -291,78 +294,89 @@ export class FeatureFlagService { this.featureFlagStore ); - readonly machineInProcess: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly machineInProcess: FeatureFlag = new StaticFeatureFlag( 'MachineInProcess', 'Use In-Process Machine for Suggestions', 8, this.featureFlagStore ); - readonly serval: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly serval: FeatureFlag = new StaticFeatureFlag( 'Serval', 'Use Serval for Suggestions', 9, this.featureFlagStore ); - readonly useEchoForPreTranslation: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly useEchoForPreTranslation: FeatureFlag = new StaticFeatureFlag( 'UseEchoForPreTranslation', 'Allow Echo for Pre-Translation Drafting', 10, this.featureFlagStore ); - readonly allowFastTraining: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly allowFastTraining: FeatureFlag = new StaticFeatureFlag( 'ALLOW_FAST_TRAINING', 'Allow Fast Pre-Translation Training', 11, this.featureFlagStore ); - readonly uploadParatextZipForPreTranslation: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly uploadParatextZipForPreTranslation: FeatureFlag = new StaticFeatureFlag( 'UploadParatextZipForPreTranslation', 'Upload Paratext Zip Files for Pre-Translation Drafting', 12, this.featureFlagStore ); - readonly allowAdditionalTrainingSource: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly allowAdditionalTrainingSource: FeatureFlag = new StaticFeatureFlag( 'AllowAdditionalTrainingSource', 'Allow mixing in an additional training source', 13, this.featureFlagStore ); - readonly updatedLearningRateForServal: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly updatedLearningRateForServal: FeatureFlag = new StaticFeatureFlag( 'UpdatedLearningRateForServal', 'Updated Learning Rate For Serval', 14, this.featureFlagStore ); - readonly darkMode: FeatureFlag = new ServerOnlyFeatureFlag('DarkMode', 'Dark mode', 15, this.featureFlagStore); + readonly darkMode: FeatureFlag = new StaticFeatureFlag('DarkMode', 'Dark mode', 15, this.featureFlagStore); - readonly enableLynxInsights: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly enableLynxInsights: FeatureFlag = new StaticFeatureFlag( 'EnableLynxInsights', 'Enable Lynx insights', 16, this.featureFlagStore ); - readonly newDraftHistory: FeatureFlag = new ServerOnlyFeatureFlag( + /** @deprecated This feature flag is no longer used */ + readonly newDraftHistory: FeatureFlag = new StaticFeatureFlag( 'NewDraftHistory', 'Preview new draft history interface', 17, this.featureFlagStore ); - readonly usfmFormat: FeatureFlag = new ServerOnlyFeatureFlag('UsfmFormat', 'USFM Format', 18, this.featureFlagStore); + /** @deprecated This feature flag is no longer used */ + readonly usfmFormat: FeatureFlag = new StaticFeatureFlag('UsfmFormat', 'USFM Format', 18, this.featureFlagStore); - readonly inAppOnboardingForm: ObservableFeatureFlag = new FeatureFlagFromStorage( + /** @deprecated This feature flag is no longer used */ + readonly inAppOnboardingForm: FeatureFlag = new StaticFeatureFlag( 'InAppOnboardingForm', 'Show in-app onboarding form instead of external link', 19, - new StaticFeatureFlagStore(true) + this.featureFlagStore ); get featureFlags(): FeatureFlag[] { @@ -372,7 +386,7 @@ export class FeatureFlagService { get versionSuffix(): string { // Get the feature flags, sorted alphabetically const featureFlags: FeatureFlag[] = Object.values(this).filter( - value => value instanceof FeatureFlagFromStorage || value instanceof ServerOnlyFeatureFlag + value => value instanceof FeatureFlagFromStorage || value instanceof StaticFeatureFlag ); featureFlags.sort((a, b) => a.position - b.position); const versionNumber = this.getFeatureFlagVersion(featureFlags);