diff --git a/frontend/projects/upgrade/src/app/core/core.module.ts b/frontend/projects/upgrade/src/app/core/core.module.ts index fc348eccf5..3fe0a94d2f 100755 --- a/frontend/projects/upgrade/src/app/core/core.module.ts +++ b/frontend/projects/upgrade/src/app/core/core.module.ts @@ -30,7 +30,6 @@ import { ENV, Environment } from '../../environments/environment-types'; import { environment } from '../../environments/environment'; import { ExperimentDesignStepperModule } from './experiment-design-stepper/experiment-design-stepper.module'; import { StratificationFactorsModule } from './stratification-factors/stratification-factors.module'; -import { CloseModalInterceptor } from './http-interceptors/close-modal.interceptor'; export { TitleService, AppState, LocalStorageService, selectRouterState, NotificationService }; @@ -79,7 +78,6 @@ export function HttpLoaderFactory(http: HttpClient, environment: Environment) { { provide: HTTP_INTERCEPTORS, useClass: HttpErrorInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: HttpAuthInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: HttpCancelInterceptor, multi: true }, - { provide: HTTP_INTERCEPTORS, useClass: CloseModalInterceptor, multi: true }, { provide: ErrorHandler, useClass: AppErrorHandler }, { provide: RouterStateSerializer, useClass: CustomSerializer }, provideHttpClient(withInterceptorsFromDi()), diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts index 2d85183a4b..eb32d8edaf 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts @@ -38,8 +38,7 @@ import { UpdateFeatureFlagStatusRequest, FeatureFlagLocalStorageKeys, } from './store/feature-flags.model'; -import { filter, map, pairwise } from 'rxjs'; -import isEqual from 'lodash.isequal'; +import { map } from 'rxjs'; import { selectCurrentUserEmail } from '../auth/store/auth.selectors'; import { AddPrivateSegmentListRequest, EditPrivateSegmentListRequest } from '../segments/store/segments.model'; import { LocalStorageService } from '../local-storage/local-storage.service'; @@ -68,38 +67,6 @@ export class FeatureFlagsService { sortAs$ = this.store$.pipe(select(selectSortAs)); shouldShowWarningForSelectedFlag$ = this.store$.pipe(select(selectShouldShowWarningForSelectedFlag)); warningStatusForAllFlags$ = this.store$.pipe(select(selectWarningStatusForAllFlags)); - - hasFeatureFlagsCountChanged$ = this.allFeatureFlags$.pipe( - pairwise(), - filter(([prevEntities, currEntities]) => prevEntities.length !== currEntities.length) - ); - - selectedFeatureFlagStatusChange$ = this.store$.pipe( - select(selectSelectedFeatureFlag), - pairwise(), - filter(([prev, curr]) => prev.status !== curr.status) - ); - selectedFeatureFlagFilterModeChange$ = this.store$.pipe( - select(selectSelectedFeatureFlag), - pairwise(), - filter(([prev, curr]) => prev.filterMode !== curr.filterMode) - ); - // Observable to check if selectedFeatureFlag is removed from the store - isSelectedFeatureFlagRemoved$ = this.store$.pipe( - select(selectSelectedFeatureFlag), - pairwise(), - filter(([prev, curr]) => prev && !curr) - ); - - isSelectedFeatureFlagUpdated$ = this.store$.pipe( - select(selectSelectedFeatureFlag), - pairwise(), - filter(([prev, curr]) => { - return prev && curr && !isEqual(prev, curr); - }), - map(([, curr]) => curr) - ); - selectedFlagOverviewDetails = this.store$.pipe(select(selectFeatureFlagOverviewDetails)); selectedFeatureFlag$ = this.store$.pipe(select(selectSelectedFeatureFlag)); searchParams$ = this.store$.pipe(select(selectSearchFeatureFlagParams)); diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts index 43aaf8b03c..646edfd029 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts @@ -20,6 +20,8 @@ import { selectCurrentUser } from '../../auth/store/auth.selectors'; import { CommonExportHelpersService } from '../../../shared/services/common-export-helpers.service'; import { of } from 'rxjs'; import { SERVER_ERROR } from 'upgrade_types'; +import { CommonModalEventsService } from '../../../shared/services/common-modal-event.service'; + @Injectable() export class FeatureFlagsEffects { constructor( @@ -28,7 +30,8 @@ export class FeatureFlagsEffects { private featureFlagsDataService: FeatureFlagsDataService, private router: Router, private notificationService: NotificationService, - private commonExportHelpersService: CommonExportHelpersService + private commonExportHelpersService: CommonExportHelpersService, + private commonModalEvents: CommonModalEventsService ) {} fetchFeatureFlags$ = createEffect(() => @@ -98,7 +101,10 @@ export class FeatureFlagsEffects { ofType(FeatureFlagsActions.actionAddFeatureFlag), switchMap((action) => { return this.featureFlagsDataService.addFeatureFlag(action.addFeatureFlagRequest).pipe( - map((response) => FeatureFlagsActions.actionAddFeatureFlagSuccess({ response })), + map((response) => { + this.commonModalEvents.forceCloseModal(); + return FeatureFlagsActions.actionAddFeatureFlagSuccess({ response }); + }), tap(({ response }) => { this.router.navigate(['/featureflags', 'detail', response.id]); }), @@ -119,6 +125,7 @@ export class FeatureFlagsEffects { switchMap((action) => { return this.featureFlagsDataService.updateFeatureFlag(action.flag).pipe( map((response) => { + this.commonModalEvents.forceCloseModal(); return FeatureFlagsActions.actionUpdateFeatureFlagSuccess({ response }); }), catchError((res) => { @@ -138,6 +145,7 @@ export class FeatureFlagsEffects { switchMap((action) => { return this.featureFlagsDataService.updateFeatureFlagStatus(action.updateFeatureFlagStatusRequest).pipe( map((response) => { + this.commonModalEvents.forceCloseModal(); return FeatureFlagsActions.actionUpdateFeatureFlagStatusSuccess({ response }); }), catchError(() => [FeatureFlagsActions.actionUpdateFeatureFlagStatusFailure()]) @@ -168,6 +176,7 @@ export class FeatureFlagsEffects { switchMap((id) => this.featureFlagsDataService.deleteFeatureFlag(id).pipe( map((data: any) => { + this.commonModalEvents.forceCloseModal(); this.router.navigate(['/featureflags']); return FeatureFlagsActions.actionDeleteFeatureFlagSuccess({ flag: data[0] }); }), @@ -182,7 +191,10 @@ export class FeatureFlagsEffects { ofType(FeatureFlagsActions.actionAddFeatureFlagInclusionList), switchMap((action) => { return this.featureFlagsDataService.addInclusionList(action.list).pipe( - map((listResponse) => FeatureFlagsActions.actionAddFeatureFlagInclusionListSuccess({ listResponse })), + map((listResponse) => { + this.commonModalEvents.forceCloseModal(); + return FeatureFlagsActions.actionAddFeatureFlagInclusionListSuccess({ listResponse }); + }), catchError((error) => of(FeatureFlagsActions.actionAddFeatureFlagInclusionListFailure({ error }))) ); }) @@ -194,7 +206,10 @@ export class FeatureFlagsEffects { ofType(FeatureFlagsActions.actionUpdateFeatureFlagInclusionList), switchMap((action) => { return this.featureFlagsDataService.updateInclusionList(action.list).pipe( - map((listResponse) => FeatureFlagsActions.actionUpdateFeatureFlagInclusionListSuccess({ listResponse })), + map((listResponse) => { + this.commonModalEvents.forceCloseModal(); + return FeatureFlagsActions.actionUpdateFeatureFlagInclusionListSuccess({ listResponse }); + }), catchError((error) => of(FeatureFlagsActions.actionUpdateFeatureFlagInclusionListFailure({ error }))) ); }) @@ -219,7 +234,10 @@ export class FeatureFlagsEffects { ofType(FeatureFlagsActions.actionAddFeatureFlagExclusionList), switchMap((action) => { return this.featureFlagsDataService.addExclusionList(action.list).pipe( - map((listResponse) => FeatureFlagsActions.actionAddFeatureFlagExclusionListSuccess({ listResponse })), + map((listResponse) => { + this.commonModalEvents.forceCloseModal(); + return FeatureFlagsActions.actionAddFeatureFlagExclusionListSuccess({ listResponse }); + }), catchError((error) => of(FeatureFlagsActions.actionAddFeatureFlagExclusionListFailure({ error }))) ); }) @@ -231,7 +249,10 @@ export class FeatureFlagsEffects { ofType(FeatureFlagsActions.actionUpdateFeatureFlagExclusionList), switchMap((action) => { return this.featureFlagsDataService.updateExclusionList(action.list).pipe( - map((listResponse) => FeatureFlagsActions.actionUpdateFeatureFlagExclusionListSuccess({ listResponse })), + map((listResponse) => { + this.commonModalEvents.forceCloseModal(); + return FeatureFlagsActions.actionUpdateFeatureFlagExclusionListSuccess({ listResponse }); + }), catchError((error) => of(FeatureFlagsActions.actionUpdateFeatureFlagExclusionListFailure({ error }))) ); }) diff --git a/frontend/projects/upgrade/src/app/core/http-interceptors/close-modal.interceptor.ts b/frontend/projects/upgrade/src/app/core/http-interceptors/close-modal.interceptor.ts deleted file mode 100644 index acfb0e8ed8..0000000000 --- a/frontend/projects/upgrade/src/app/core/http-interceptors/close-modal.interceptor.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http'; -import { MatDialog } from '@angular/material/dialog'; -import { Observable } from 'rxjs'; -import { tap } from 'rxjs/operators'; -import { ENDPOINTS_TO_INTERCEPT_FOR_MODAL_CLOSE } from '../../shared-standalone-component-lib/components/common-modal/common-modal-config'; - -@Injectable() -export class CloseModalInterceptor implements HttpInterceptor { - constructor(private dialog: MatDialog) {} - - intercept(req: HttpRequest, next: HttpHandler): Observable> { - return next.handle(req).pipe( - tap((event) => { - if (event instanceof HttpResponse) { - const shouldCloseModal = ENDPOINTS_TO_INTERCEPT_FOR_MODAL_CLOSE.some((endpoint) => - event.url.includes(endpoint) - ); - if (shouldCloseModal && event.status === 200) { - this.dialog.closeAll(); - } - } - }) - ); - } -} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.ts index fbb513241d..19b97b079c 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.ts @@ -21,7 +21,6 @@ export class DeleteFeatureFlagModalComponent { selectedFlag$ = this.featureFlagsService.selectedFeatureFlag$; inputValue = ''; subscriptions = new Subscription(); - isSelectedFeatureFlagRemoved$ = this.featureFlagsService.isSelectedFeatureFlagRemoved$; isLoadingFeatureFlagDelete$ = this.featureFlagsService.isLoadingFeatureFlagDelete$; private inputSubject: BehaviorSubject = new BehaviorSubject(''); @@ -41,27 +40,12 @@ export class DeleteFeatureFlagModalComponent { public dialogRef: MatDialogRef ) {} - ngOnInit(): void { - this.listenForSelectedFeatureFlagDeletion(); - } - onInputChange(value: string): void { this.inputSubject.next(value); } - listenForSelectedFeatureFlagDeletion(): void { - this.subscriptions = this.isSelectedFeatureFlagRemoved$.subscribe(() => this.closeModal()); - } - onPrimaryActionBtnClicked(flagId: string) { this.featureFlagsService.deleteFeatureFlag(flagId); - } - - closeModal() { this.dialogRef.close(); } - - ngOnDestroy() { - this.subscriptions.unsubscribe(); - } } diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.ts index 616f77ee5c..7213829810 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.ts @@ -29,7 +29,6 @@ import { ExperimentService } from '../../../../../core/experiments/experiments.s import { CommonTextHelpersService } from '../../../../../shared/services/common-text-helpers.service'; import isEqual from 'lodash.isequal'; import { CommonModalConfig } from '../../../../../shared-standalone-component-lib/components/common-modal/common-modal.types'; -import { Router } from '@angular/router'; @Component({ selector: 'upsert-add-feature-flag-modal', @@ -51,7 +50,6 @@ import { Router } from '@angular/router'; }) export class UpsertFeatureFlagModalComponent { isLoadingUpsertFeatureFlag$ = this.featureFlagsService.isLoadingUpsertFeatureFlag$; - isSelectedFeatureFlagUpdated$ = this.featureFlagsService.isSelectedFeatureFlagUpdated$; selectedFlag$ = this.featureFlagsService.selectedFeatureFlag$; appContexts$ = this.featureFlagsService.appContexts$; isDuplicateKeyFound$ = this.featureFlagsService.isDuplicateKeyFound$; @@ -72,7 +70,6 @@ export class UpsertFeatureFlagModalComponent { @Inject(MAT_DIALOG_DATA) public config: CommonModalConfig, public dialog: MatDialog, - private router: Router, private formBuilder: FormBuilder, private featureFlagsService: FeatureFlagsService, private experimentService: ExperimentService, @@ -84,7 +81,6 @@ export class UpsertFeatureFlagModalComponent { this.experimentService.fetchContextMetaData(); this.createFeatureFlagForm(); this.listenOnKeyChangesToRemoveWarning(); - this.listenForFeatureFlagGetUpdated(); this.listenOnNameChangesToUpdateKey(); this.listenForIsInitialFormValueChanged(); this.listenForPrimaryButtonDisabled(); @@ -181,15 +177,6 @@ export class UpsertFeatureFlagModalComponent { this.subscriptions.add(this.isPrimaryButtonDisabled$.subscribe()); } - // Close the modal once the feature flag list length changes, as that indicates actual success - listenForFeatureFlagGetUpdated(): void { - this.subscriptions.add( - this.isSelectedFeatureFlagUpdated$.subscribe(() => { - this.closeModal(); - }) - ); - } - listenForDuplicateKey() { this.subscriptions.add( this.isDuplicateKeyFound$.subscribe((isDuplicate) => { diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal-config.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal-config.ts deleted file mode 100644 index d48cb898ae..0000000000 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal-config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { environment } from '../../../../environments/environment'; - -// see close-modal.interceptor.ts -export const ENDPOINTS_TO_INTERCEPT_FOR_MODAL_CLOSE = [ - environment.api.addFlagInclusionList, - environment.api.addFlagExclusionList, -]; diff --git a/frontend/projects/upgrade/src/app/shared/services/common-modal-event.service.ts b/frontend/projects/upgrade/src/app/shared/services/common-modal-event.service.ts new file mode 100644 index 0000000000..bf7e365fb5 --- /dev/null +++ b/frontend/projects/upgrade/src/app/shared/services/common-modal-event.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Subject } from 'rxjs'; + +export interface CloseModalEvent { + identifier?: string; + data?: any; +} + +@Injectable({ + providedIn: 'root', +}) +export class CommonModalEventsService { + public closeModalEvent$ = new Subject(); + + constructor(private dialog: MatDialog) {} + + // raise an event with optional identifer or data to allow components to handle this event + public emitCloseModalEvent(event: CloseModalEvent = {}): void { + console.debug('CloseModalEvent:', event); + this.closeModalEvent$.next(event); + } + + // it is generally safe to force close modal if component is not handling the close event + public forceCloseModal() { + this.dialog.closeAll(); + } +}