From e480c59a2044a7c6d0ff1d323042e6159ab8cd5b Mon Sep 17 00:00:00 2001 From: danoswaltCL Date: Wed, 3 Jul 2024 18:02:09 -0400 Subject: [PATCH 01/10] consolidate add-edit-duplicate modals --- .../feature-flags.data.service.ts | 20 ++- .../feature-flags/feature-flags.service.ts | 16 +- .../store/feature-flags.actions.ts | 9 +- .../store/feature-flags.model.ts | 33 +++- .../store/feature-flags.reducer.ts | 66 ++++---- .../add-feature-flag-modal.component.scss | 33 ---- .../add-feature-flag-modal.component.ts | 151 ------------------ .../edit-feature-flag-modal.component.html | 45 ------ .../upsert-feature-flag-modal.component.html} | 10 +- .../upsert-feature-flag-modal.component.scss} | 4 +- ...psert-feature-flag-modal.component.spec.ts | 22 +++ .../upsert-feature-flag-modal.component.ts} | 85 +++++----- ...erview-details-section-card.component.html | 2 +- ...overview-details-section-card.component.ts | 15 +- ...ture-flag-root-section-card.component.html | 1 + .../common-modal/common-modal-config.ts | 5 +- .../shared/services/common-dialog.service.ts | 56 +++++-- .../projects/upgrade/src/assets/i18n/en.json | 8 +- 18 files changed, 218 insertions(+), 363 deletions(-) delete mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.scss delete mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts delete mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.html rename frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/{add-feature-flag-modal/add-feature-flag-modal.component.html => upsert-feature-flag-modal/upsert-feature-flag-modal.component.html} (83%) rename frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/{edit-feature-flag-modal/edit-feature-flag-modal.component.scss => upsert-feature-flag-modal/upsert-feature-flag-modal.component.scss} (88%) create mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.spec.ts rename frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/{edit-feature-flag-modal/edit-feature-flag-modal.component.ts => upsert-feature-flag-modal/upsert-feature-flag-modal.component.ts} (66%) diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts index dd9757c9e4..94ff92140f 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts @@ -6,10 +6,10 @@ import { FeatureFlag, FeatureFlagsPaginationInfo, FeatureFlagsPaginationParams, + ModifyFeatureFlagRequest, UpdateFeatureFlagStatusRequest, } from './store/feature-flags.model'; import { Observable } from 'rxjs'; -import { FEATURE_FLAG_STATUS, FILTER_MODE } from '../../../../../../../types/src'; @Injectable() export class FeatureFlagsDataService { @@ -18,8 +18,6 @@ export class FeatureFlagsDataService { fetchFeatureFlagsPaginated(params: FeatureFlagsPaginationParams): Observable { const url = this.environment.api.getPaginatedFlags; return this.http.post(url, params); - // mock - // // return of({ nodes: mockFeatureFlags, total: 2 }).pipe(delay(2000)); } fetchFeatureFlagById(id: string) { @@ -27,23 +25,23 @@ export class FeatureFlagsDataService { return this.http.get(url); } + updateFeatureFlagStatus(params: UpdateFeatureFlagStatusRequest): Observable { + const url = this.environment.api.updateFlagStatus; + return this.http.post(url, params); + } + addFeatureFlag(params: AddFeatureFlagRequest): Observable { const url = this.environment.api.featureFlag; return this.http.post(url, params); } - updateFeatureFlagStatus(params: UpdateFeatureFlagStatusRequest): Observable { - const url = this.environment.api.updateFlagStatus; - return this.http.post(url, params); + updateFeatureFlag(flag: ModifyFeatureFlagRequest): Observable { + const url = `${this.environment.api.featureFlag}/${flag.id}`; + return this.http.put(url, flag); } deleteFeatureFlag(id: string) { const url = `${this.environment.api.featureFlag}/${id}`; return this.http.delete(url); } - - updateFeatureFlag(flag: FeatureFlag): Observable { - const url = `${this.environment.api.featureFlag}/${flag.id}`; - return this.http.put(url, flag); - } } 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 abed8ae865..db550536d2 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 @@ -23,7 +23,11 @@ import { import * as FeatureFlagsActions from './store/feature-flags.actions'; import { actionFetchContextMetaData } from '../experiments/store/experiments.actions'; import { FLAG_SEARCH_KEY, FLAG_SORT_KEY, SORT_AS_DIRECTION } from 'upgrade_types'; -import { AddFeatureFlagRequest, FeatureFlag, UpdateFeatureFlagStatusRequest } from './store/feature-flags.model'; +import { + UpdateFeatureFlagStatusRequest, + AddFeatureFlagRequest, + ModifyFeatureFlagRequest, +} from './store/feature-flags.model'; import { ExperimentService } from '../experiments/experiments.service'; import { filter, map, pairwise } from 'rxjs'; @@ -86,11 +90,11 @@ export class FeatureFlagsService { map((exclusions) => exclusions.length) ); - convertNameStringToKey(name:string):string { - let upperCaseString = name.trim().toUpperCase(); - let key = upperCaseString.replace(/ /g, '_'); + convertNameStringToKey(name: string): string { + const upperCaseString = name.trim().toUpperCase(); + const key = upperCaseString.replace(/ /g, '_'); return key; -} + } fetchFeatureFlags(fromStarting?: boolean) { this.store$.dispatch(FeatureFlagsActions.actionFetchFeatureFlags({ fromStarting })); @@ -108,7 +112,7 @@ export class FeatureFlagsService { this.store$.dispatch(FeatureFlagsActions.actionAddFeatureFlag({ addFeatureFlagRequest })); } - updateFeatureFlag(flag: FeatureFlag) { + updateFeatureFlag(flag: ModifyFeatureFlagRequest) { this.store$.dispatch(FeatureFlagsActions.actionUpdateFeatureFlag({ flag })); } diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts index da27abcadc..07eb65724b 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts @@ -1,5 +1,10 @@ import { createAction, props } from '@ngrx/store'; -import { AddFeatureFlagRequest, FeatureFlag, UpdateFeatureFlagStatusRequest } from './feature-flags.model'; +import { + FeatureFlag, + UpdateFeatureFlagStatusRequest, + AddFeatureFlagRequest, + ModifyFeatureFlagRequest, +} from './feature-flags.model'; import { FLAG_SEARCH_KEY, FLAG_SORT_KEY, SORT_AS_DIRECTION } from 'upgrade_types'; export const actionFetchFeatureFlags = createAction( @@ -48,7 +53,7 @@ export const actionDeleteFeatureFlagFailure = createAction('[Feature Flags] Dele export const actionUpdateFeatureFlag = createAction( '[Feature Flags] Update Feature Flag', - props<{ flag: FeatureFlag }>() + props<{ flag: ModifyFeatureFlagRequest }>() ); export const actionUpdateFeatureFlagSuccess = createAction( diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts index 4429f7f9c2..a6b2629790 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts @@ -15,8 +15,7 @@ export interface FeatureFlag { filterMode: FILTER_MODE; context: string[]; tags: string[]; - featureFlagSegmentInclusion: PrivateSegment | EmptyPrivateSegment; - featureFlagSegmentExclusion: PrivateSegment | EmptyPrivateSegment; + enabled: boolean; } export interface FeatureFlagsPaginationInfo { @@ -29,13 +28,32 @@ export interface FeatureFlagsPaginationInfo { export interface AddFeatureFlagRequest { name: string; key: string; - description: string; - status: FEATURE_FLAG_STATUS; + description?: string; context: string[]; tags: string[]; - featureFlagSegmentInclusion: PrivateSegment | EmptyPrivateSegment; - featureFlagSegmentExclusion: PrivateSegment | EmptyPrivateSegment; - filterMode: FILTER_MODE; +} + +export interface ModifyFeatureFlagRequest { + id: string; + name?: string; + key?: string; + description?: string; + status?: FEATURE_FLAG_STATUS; + filterMode?: FILTER_MODE; + context?: string[]; + tags?: string[]; + enabled?: boolean; +} + +export enum UpsertModalAction { + ADD = 'add', + EDIT = 'edit', + DUPLICATE = 'duplicate', +} + +export interface UpsertModalParams { + sourceFlag: FeatureFlag; + action: UpsertModalAction; } export interface UpdateFeatureFlagStatusRequest { @@ -43,6 +61,7 @@ export interface UpdateFeatureFlagStatusRequest { status: FEATURE_FLAG_STATUS; } +export const DuplicateFeatureFlagSuffix = '_COPY_CHANGE_ME'; export interface FeatureFlagFormData { name: string; key: string; diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.reducer.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.reducer.ts index 896b1b6668..ab066d0e39 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.reducer.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.reducer.ts @@ -26,6 +26,7 @@ export const initialState: FeatureFlagState = adapter.getInitialState({ const reducer = createReducer( initialState, + // Feature Flags Fetching Actions on(FeatureFlagsActions.actionFetchFeatureFlags, (state) => ({ ...state, isLoadingFeatureFlags: true, @@ -43,6 +44,12 @@ const reducer = createReducer( }); }), on(FeatureFlagsActions.actionFetchFeatureFlagsFailure, (state) => ({ ...state, isLoadingFeatureFlags: false })), + + // Feature Flag Detail Actions + on(FeatureFlagsActions.actionFetchFeatureFlagById, (state) => ({ + ...state, + isLoadingSelectedFeatureFlag: true, + })), on(FeatureFlagsActions.actionFetchFeatureFlagByIdSuccess, (state, { flag }) => { return adapter.upsertOne(flag, { ...state, @@ -53,24 +60,23 @@ const reducer = createReducer( ...state, isLoadingSelectedFeatureFlag: false, })), - on(FeatureFlagsActions.actionSetIsLoadingFeatureFlags, (state, { isLoadingFeatureFlags }) => ({ + + // Feature Flag Upsert Actions (Add/Update both = upsert result) + on(FeatureFlagsActions.actionAddFeatureFlag, FeatureFlagsActions.actionUpdateFeatureFlag, (state) => ({ ...state, - isLoadingFeatureFlags, + isLoadingUpsertFeatureFlag: true, })), - on(FeatureFlagsActions.actionAddFeatureFlag, (state) => ({ ...state, isLoadingUpsertFeatureFlag: true })), - on(FeatureFlagsActions.actionAddFeatureFlagSuccess, (state, { response }) => { - return adapter.addOne(response, { - ...state, - isLoadingUpsertFeatureFlag: false, - }); - }), - on(FeatureFlagsActions.actionUpdateFeatureFlag, (state) => ({ ...state, isLoadingUpsertFeatureFlag: true })), - on(FeatureFlagsActions.actionUpdateFeatureFlagSuccess, (state, { response }) => { - return adapter.upsertOne(response, { - ...state, - isLoadingUpsertFeatureFlag: false, - }); - }), + on( + FeatureFlagsActions.actionUpdateFeatureFlagSuccess, + FeatureFlagsActions.actionAddFeatureFlagSuccess, + (state, { response }) => adapter.upsertOne(response, { ...state, isLoadingUpsertFeatureFlag: false }) + ), + on(FeatureFlagsActions.actionAddFeatureFlagFailure, FeatureFlagsActions.actionUpdateFeatureFlagFailure, (state) => ({ + ...state, + isLoadingUpsertFeatureFlag: false, + })), + + // Feature Flag Delete Actions on(FeatureFlagsActions.actionDeleteFeatureFlag, (state) => ({ ...state, isLoadingFeatureFlagDelete: true })), on(FeatureFlagsActions.actionDeleteFeatureFlagSuccess, (state, { flag }) => { return adapter.removeOne(flag.id, { @@ -82,17 +88,8 @@ const reducer = createReducer( ...state, isLoadingFeatureFlagDelete: false, })), - on(FeatureFlagsActions.actionUpdateFeatureFlagFailure, (state) => ({ ...state, isLoadingUpsertFeatureFlag: false })), - on(FeatureFlagsActions.actionAddFeatureFlagFailure, (state) => ({ ...state, isLoadingUpsertFeatureFlag: false })), - on(FeatureFlagsActions.actionSetSkipFlags, (state, { skipFlags }) => ({ ...state, skipFlags })), - on(FeatureFlagsActions.actionSetSearchKey, (state, { searchKey }) => ({ ...state, searchKey })), - on(FeatureFlagsActions.actionSetSearchString, (state, { searchString }) => ({ ...state, searchValue: searchString })), - on(FeatureFlagsActions.actionSetSortKey, (state, { sortKey }) => ({ ...state, sortKey })), - on(FeatureFlagsActions.actionSetSortingType, (state, { sortingType }) => ({ ...state, sortAs: sortingType })), - on(FeatureFlagsActions.actionSetActiveDetailsTabIndex, (state, { activeDetailsTabIndex }) => ({ - ...state, - activeDetailsTabIndex, - })), + + // Feature Flag Status Update Actions on(FeatureFlagsActions.actionUpdateFeatureFlagStatus, (state) => ({ ...state, isLoadingUpdateFeatureFlagStatus: true, @@ -108,9 +105,20 @@ const reducer = createReducer( ...state, isLoadingUpdateFeatureFlagStatus: true, })), - on(FeatureFlagsActions.actionFetchFeatureFlagById, (state) => ({ + + // UI State Update Actions + on(FeatureFlagsActions.actionSetIsLoadingFeatureFlags, (state, { isLoadingFeatureFlags }) => ({ ...state, - isLoadingSelectedFeatureFlag: true, + isLoadingFeatureFlags, + })), + on(FeatureFlagsActions.actionSetSkipFlags, (state, { skipFlags }) => ({ ...state, skipFlags })), + on(FeatureFlagsActions.actionSetSearchKey, (state, { searchKey }) => ({ ...state, searchKey })), + on(FeatureFlagsActions.actionSetSearchString, (state, { searchString }) => ({ ...state, searchValue: searchString })), + on(FeatureFlagsActions.actionSetSortKey, (state, { sortKey }) => ({ ...state, sortKey })), + on(FeatureFlagsActions.actionSetSortingType, (state, { sortingType }) => ({ ...state, sortAs: sortingType })), + on(FeatureFlagsActions.actionSetActiveDetailsTabIndex, (state, { activeDetailsTabIndex }) => ({ + ...state, + activeDetailsTabIndex, })) ); diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.scss deleted file mode 100644 index 6e5a2f4a4f..0000000000 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.scss +++ /dev/null @@ -1,33 +0,0 @@ -:host ::ng-deep .add-feature-flag-modal-container { - display: flex; - flex-direction: column; - align-items: space-between; - width: 100%; - height: 600px; -} - -.title { - font-size: 18px; - font-weight: bold; -} - -.mat-mdc-form-field { - width: 100%; -} - -.mat-mdc-form-field:not(:last-child) { - margin-bottom: 20px; -} - -.form-hint { - color: var(--grey-5); -} - -.learn-more-link { - color: var(--blue); - text-decoration: none; - - &:hover { - text-decoration: underline; - } -} \ No newline at end of file diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts deleted file mode 100644 index 6ad0cd4d42..0000000000 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { - CommonModalComponent, - CommonTagsInputComponent, -} from '../../../../../shared-standalone-component-lib/components'; -import { - MAT_DIALOG_DATA, - MatDialog, - MatDialogActions, - MatDialogClose, - MatDialogContent, - MatDialogRef, - MatDialogTitle, -} from '@angular/material/dialog'; -import { CommonModalConfig } from '../../../../../shared-standalone-component-lib/components/common-modal/common-modal-config'; -import { CommonModule, NgTemplateOutlet } from '@angular/common'; -import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIcon } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service'; -import { CommonFormHelpersService } from '../../../../../shared/services/common-form-helpers.service'; -import { FEATURE_FLAG_STATUS, SEGMENT_TYPE, FILTER_MODE } from '../../../../../../../../../../types/src'; -import { AddFeatureFlagRequest, FeatureFlagFormData } from '../../../../../core/feature-flags/store/feature-flags.model'; -import { Subscription } from 'rxjs'; -import { TranslateModule } from '@ngx-translate/core'; -import { ExperimentService } from '../../../../../core/experiments/experiments.service'; - -@Component({ - selector: 'app-add-feature-flag-modal', - standalone: true, - imports: [ - CommonModalComponent, - MatCardModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatDialogTitle, - MatDialogContent, - MatDialogActions, - MatDialogClose, - MatSelectModule, - CommonModule, - NgTemplateOutlet, - MatIcon, - ReactiveFormsModule, - TranslateModule, - CommonTagsInputComponent, - ], - templateUrl: './add-feature-flag-modal.component.html', - styleUrl: './add-feature-flag-modal.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AddFeatureFlagModalComponent { - isLoadingUpsertFeatureFlag$ = this.featureFlagsService.isLoadingUpsertFeatureFlag$; - appContexts$ = this.featureFlagsService.appContexts$; - featureFlagsListLengthChange$ = this.featureFlagsService.featureFlagsListLengthChange$; - subscriptions = new Subscription(); - - featureFlagForm: FormGroup; - - constructor( - @Inject(MAT_DIALOG_DATA) - public config: CommonModalConfig, - public dialog: MatDialog, - private formBuilder: FormBuilder, - private featureFlagsService: FeatureFlagsService, - private experimentService: ExperimentService, - private formHelpersService: CommonFormHelpersService, - public dialogRef: MatDialogRef - ) {} - - ngOnInit(): void { - this.experimentService.fetchContextMetaData(); - this.buildForm(); - this.listenForFeatureFlagListLengthChanges(); - this.listenOnNameChangesToUpdateKey(); - } - - buildForm(): void { - this.featureFlagForm = this.formBuilder.group({ - name: ['', Validators.required], - key: ['', Validators.required], - description: [''], - appContext: ['', Validators.required], - tags: [[]], - }); - } - - // Close the modal once the feature flag list length changes, as that indicates actual success - listenForFeatureFlagListLengthChanges(): void { - this.subscriptions = this.featureFlagsListLengthChange$.subscribe(() => this.closeModal()); - } - - listenOnNameChangesToUpdateKey(): void { - this.featureFlagForm.get('name')?.valueChanges.subscribe((name) => { - const keyControl = this.featureFlagForm.get('key'); - if (keyControl && !keyControl.dirty) { - keyControl.setValue(this.featureFlagsService.convertNameStringToKey(name)); - } - }); - } - - onPrimaryActionBtnClicked(): void { - if (this.featureFlagForm.valid) { - // Handle extra form validation logic here? - this.createAddFeatureFlagRequest(); - } else { - // If the form is invalid, manually mark all form controls as touched - this.formHelpersService.triggerTouchedToDisplayErrors(this.featureFlagForm); - } - } - - createAddFeatureFlagRequest(): void { - // temporarily use any until tags feature is added - const { name, key, description, appContext, tags }: FeatureFlagFormData = this.featureFlagForm.value; - - const addFeatureFlagRequest: AddFeatureFlagRequest = { - name, - key, - description, - status: FEATURE_FLAG_STATUS.DISABLED, - context: [appContext], - tags: tags, // it is now an array of strings - featureFlagSegmentInclusion: { - segment: { - type: SEGMENT_TYPE.PRIVATE, - }, - }, - featureFlagSegmentExclusion: { - segment: { - type: SEGMENT_TYPE.PRIVATE, - }, - }, - filterMode: FILTER_MODE.INCLUDE_ALL, - }; - - this.featureFlagsService.addFeatureFlag(addFeatureFlagRequest); - } - - closeModal() { - this.dialogRef.close(); - } - - ngOnDestroy() { - this.subscriptions.unsubscribe(); - } -} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.html deleted file mode 100644 index a9096fff44..0000000000 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.html +++ /dev/null @@ -1,45 +0,0 @@ -
- -
- - Name - - {{ 'feature-flags.add-flag-modal.name-hint.text' | translate }} - - - - Key - - {{ 'feature-flags.add-flag-modal.key-hint.text' | translate - }}Learn more - - - - Description (optional) - - - - - App Context - - {{ context }} - - {{ 'feature-flags.add-flag-modal.app-context-hint.text' | translate - }}Learn more - - - -
-
-
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.html similarity index 83% rename from frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html rename to frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.html index d743ce3824..eac2ad9665 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.html @@ -1,4 +1,4 @@ -
+
Name - {{ 'feature-flags.add-flag-modal.name-hint.text' | translate }} + {{ + 'feature-flags.upsert-flag-modal.name-hint.text' | translate + }} Key {{ 'feature-flags.add-flag-modal.key-hint.text' | translate + >{{ 'feature-flags.upsert-flag-modal.key-hint.text' | translate }}Learn more @@ -34,7 +36,7 @@ {{ context }} {{ 'feature-flags.add-flag-modal.app-context-hint.text' | translate + >{{ 'feature-flags.upsert-flag-modal.app-context-hint.text' | translate }}Learn more diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.scss similarity index 88% rename from frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.scss rename to frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.scss index 099d6902c9..2263e52e20 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.scss +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.scss @@ -1,4 +1,4 @@ -:host ::ng-deep .edit-feature-flag-modal-container { +:host ::ng-deep .upsert-feature-flag-modal-container { display: flex; flex-direction: column; align-items: space-between; @@ -30,4 +30,4 @@ &:hover { text-decoration: underline; } -} \ No newline at end of file +} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.spec.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.spec.ts new file mode 100644 index 0000000000..05ad142e44 --- /dev/null +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UpsertFeatureFlagModalComponent } from './upsert-feature-flag-modal.component'; + +describe('UpsertFeatureFlagModalComponent', () => { + let component: UpsertFeatureFlagModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UpsertFeatureFlagModalComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UpsertFeatureFlagModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-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 similarity index 66% rename from frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts rename to frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.ts index 4487b22bb8..cf89756fe7 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-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 @@ -23,13 +23,21 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service'; import { CommonFormHelpersService } from '../../../../../shared/services/common-form-helpers.service'; -import { FeatureFlag, FeatureFlagFormData } from '../../../../../core/feature-flags/store/feature-flags.model'; +import { + FeatureFlag, + FeatureFlagFormData, + AddFeatureFlagRequest, + UpsertModalParams, + UpsertModalAction, + DuplicateFeatureFlagSuffix, + ModifyFeatureFlagRequest, +} from '../../../../../core/feature-flags/store/feature-flags.model'; import { Subscription } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { ExperimentService } from '../../../../../core/experiments/experiments.service'; @Component({ - selector: 'edit-add-feature-flag-modal', + selector: 'upsert-add-feature-flag-modal', standalone: true, imports: [ CommonModalComponent, @@ -49,68 +57,58 @@ import { ExperimentService } from '../../../../../core/experiments/experiments.s TranslateModule, CommonTagsInputComponent, ], - templateUrl: './edit-feature-flag-modal.component.html', - styleUrl: './edit-feature-flag-modal.component.scss', + templateUrl: './upsert-feature-flag-modal.component.html', + styleUrl: './upsert-feature-flag-modal.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class EditFeatureFlagModalComponent { +export class UpsertFeatureFlagModalComponent { isLoadingUpsertFeatureFlag$ = this.featureFlagsService.isLoadingUpsertFeatureFlag$; isSelectedFeatureFlagUpdated$ = this.featureFlagsService.isSelectedFeatureFlagUpdated$; selectedFlag$ = this.featureFlagsService.selectedFeatureFlag$; appContexts$ = this.featureFlagsService.appContexts$; subscriptions = new Subscription(); - flag: FeatureFlag; featureFlagForm: FormGroup; constructor( @Inject(MAT_DIALOG_DATA) - public config: CommonModalConfig, + public config: CommonModalConfig, public dialog: MatDialog, private formBuilder: FormBuilder, private featureFlagsService: FeatureFlagsService, private experimentService: ExperimentService, private formHelpersService: CommonFormHelpersService, - public dialogRef: MatDialogRef + public dialogRef: MatDialogRef ) {} ngOnInit(): void { this.experimentService.fetchContextMetaData(); - this.buildForm(); - this.initializeFormValues(); + this.createFeatureFlagForm(); this.listenForFeatureFlagGetUpdated(); this.listenOnNameChangesToUpdateKey(); } - buildForm(): void { + createFeatureFlagForm(): void { + const { sourceFlag, action } = this.config.params; + + const initialValues: FeatureFlag = { ...sourceFlag }; + + if (sourceFlag && action === UpsertModalAction.DUPLICATE) { + initialValues.name += DuplicateFeatureFlagSuffix; + initialValues.key += DuplicateFeatureFlagSuffix; + } + this.featureFlagForm = this.formBuilder.group({ - name: ['', Validators.required], - key: ['', Validators.required], - description: [''], - appContext: ['', Validators.required], - tags: [], + name: [initialValues.name || '', Validators.required], + key: [initialValues.key || '', Validators.required], + description: [initialValues.description || ''], + appContext: [initialValues.context?.[0] || '', Validators.required], + tags: [initialValues.tags || []], }); } - initializeFormValues(): void { - this.subscriptions.add( - this.selectedFlag$.subscribe((selectedFlag) => { - this.flag = selectedFlag; - if (selectedFlag) { - this.featureFlagForm.patchValue({ - name: selectedFlag.name, - key: selectedFlag.key, - description: selectedFlag.description, - appContext: selectedFlag.context ? selectedFlag.context[0] : '', - tags: selectedFlag.tags, - }); - } - }) - ); - } - listenOnNameChangesToUpdateKey(): void { - this.featureFlagForm.get('name')?.valueChanges.subscribe((name) => { + this.subscriptions = this.featureFlagForm.get('name')?.valueChanges.subscribe((name) => { const keyControl = this.featureFlagForm.get('key'); if (keyControl && !keyControl.dirty) { keyControl.setValue(this.featureFlagsService.convertNameStringToKey(name)); @@ -125,27 +123,30 @@ export class EditFeatureFlagModalComponent { onPrimaryActionBtnClicked(): void { if (this.featureFlagForm.valid) { - // Handle extra form validation logic here? - this.updateFeatureFlagRequest(); + // Handle extra frontend form validation logic here? + this.createRequest(this.config.params.action, this.config.params.sourceFlag); } else { // If the form is invalid, manually mark all form controls as touched this.formHelpersService.triggerTouchedToDisplayErrors(this.featureFlagForm); } } - updateFeatureFlagRequest(): void { + createRequest(action: UpsertModalAction, sourceFlag?: FeatureFlag): void { const { name, key, description, appContext, tags }: FeatureFlagFormData = this.featureFlagForm.value; - - this.flag = { - ...this.flag, + const flagRequest = { name, key, description, context: [appContext], - tags: tags, // it is now an array of strings + tags, }; - this.featureFlagsService.updateFeatureFlag(this.flag); + if (action === UpsertModalAction.ADD || action === UpsertModalAction.DUPLICATE) { + this.featureFlagsService.addFeatureFlag(flagRequest); + } else if (action === UpsertModalAction.EDIT && sourceFlag) { + const updatedFlag = { ...sourceFlag, ...flagRequest }; + this.featureFlagsService.updateFeatureFlag(updatedFlag); + } } closeModal() { diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-section-card.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-section-card.component.html index 217493197a..82118def5e 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-section-card.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-section-card.component.html @@ -19,7 +19,7 @@ [menuButtonItems]="menuButtonItems" [isSectionCardExpanded]="isSectionCardExpanded" (slideToggleChange)="onSlideToggleChange($event)" - (menuButtonItemClick)="onMenuButtonItemClick($event)" + (menuButtonItemClick)="onMenuButtonItemClick($event, flag)" (sectionCardExpandChange)="onSectionCardExpandChange($event)" > diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-section-card.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-section-card.component.ts index f78f82a1ff..8df07b4ed2 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-section-card.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-section-card.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { CommonSectionCardActionButtonsComponent, CommonSectionCardComponent, @@ -12,6 +12,7 @@ import { FEATURE_FLAG_STATUS, IMenuButtonItem } from 'upgrade_types'; import { CommonModule } from '@angular/common'; import { CommonSectionCardOverviewDetailsComponent } from '../../../../../../../shared-standalone-component-lib/components/common-section-card-overview-details/common-section-card-overview-details.component'; import { DialogService } from '../../../../../../../shared/services/common-dialog.service'; +import { FeatureFlag } from '../../../../../../../core/feature-flags/store/feature-flags.model'; @Component({ selector: 'app-feature-flag-overview-details-section-card', standalone: true, @@ -34,13 +35,11 @@ export class FeatureFlagOverviewDetailsSectionCardComponent { menuButtonItems: IMenuButtonItem[] = [ { name: 'Edit', disabled: false }, { name: 'Delete', disabled: false }, + { name: 'Duplicate', disabled: false }, ]; isSectionCardExpanded = true; - constructor( - private dialogService: DialogService, - private featureFlagService: FeatureFlagsService, - ) {} + constructor(private dialogService: DialogService, private featureFlagService: FeatureFlagsService) {} get FEATURE_FLAG_STATUS() { return FEATURE_FLAG_STATUS; @@ -72,11 +71,13 @@ export class FeatureFlagOverviewDetailsSectionCardComponent { this.dialogService.openDisableFeatureFlagConfirmModel(); } - onMenuButtonItemClick(event) { + onMenuButtonItemClick(event: 'Edit' | 'Delete' | 'Duplicate', flag: FeatureFlag) { if (event === 'Delete') { this.dialogService.openDeleteFeatureFlagModal(); } else if (event === 'Edit') { - this.dialogService.openEditFeatureFlagModal(); + this.dialogService.openEditFeatureFlagModal(flag); + } else if (event === 'Duplicate') { + this.dialogService.openDuplicateFeatureFlagModal(flag); } } diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.html index c7c574f264..36dc297154 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.html @@ -14,6 +14,7 @@ [showPrimaryButton]="true" [primaryButtonText]="'feature-flags.add-feature-flag.text' | translate" [menuButtonItems]="menuButtonItems" + [showMenuButton]="true" [isSectionCardExpanded]="isSectionCardExpanded" (primaryButtonClick)="onAddFeatureFlagButtonClick()" (menuButtonItemClick)="onMenuButtonItemClick($event)" 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 index b6d1aeb704..1bb4bb305e 100644 --- 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 @@ -1,11 +1,12 @@ import { MatDialogConfig } from '@angular/material/dialog'; -export interface CommonModalConfig { +export interface CommonModalConfig { title: string; cancelBtnLabel?: string; primaryActionBtnLabel?: string; - primaryActionBtnColor?: string; // TODO mat-button enum? or just string? + primaryActionBtnColor?: string; hideFooter?: boolean; + params?: ParamsType; } export interface CommonDialogMatDialogConfig extends MatDialogConfig { diff --git a/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts b/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts index eedd21cfa2..0c19663688 100644 --- a/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts +++ b/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@angular/core'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { MatConfirmDialogComponent } from '../components/mat-confirm-dialog/mat-confirm-dialog.component'; -import { AddFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component'; import { CommonModalConfig } from '../../shared-standalone-component-lib/components/common-modal/common-modal-config'; import { DeleteFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component'; import { UpdateFlagStatusConfirmationModalComponent } from '../../features/dashboard/feature-flags/modals/update-flag-status-confirmation-modal/update-flag-status-confirmation-modal.component'; -import { EditFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component'; +import { UpsertFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component'; +import { FeatureFlag, UpsertModalAction, UpsertModalParams } from '../../core/feature-flags/store/feature-flags.model'; @Injectable({ providedIn: 'root', @@ -13,6 +13,7 @@ import { EditFeatureFlagModalComponent } from '../../features/dashboard/feature- export class DialogService { constructor(private dialog: MatDialog) {} + // This method is used in the older ui openConfirmDialog() { return this.dialog.open(MatConfirmDialogComponent, { width: 'auto', @@ -20,36 +21,47 @@ export class DialogService { }); } + // Common modal flags ---------------------------------------- // openAddFeatureFlagModal() { const commonModalConfig: CommonModalConfig = { title: 'Add Feature Flag', primaryActionBtnLabel: 'Add', primaryActionBtnColor: 'primary', cancelBtnLabel: 'Cancel', + params: { + sourceFlag: null, + action: UpsertModalAction.ADD, + }, }; - const config: MatDialogConfig = { - data: commonModalConfig, - width: '656px', - autoFocus: 'first-heading', - disableClose: true, - }; - return this.dialog.open(AddFeatureFlagModalComponent, config); + return this.openUpsertFeatureFlagModal(commonModalConfig); } - openEditFeatureFlagModal() { - const commonModalConfig: CommonModalConfig = { + openEditFeatureFlagModal(flag: FeatureFlag) { + const commonModalConfig: CommonModalConfig = { title: 'Edit Feature Flag', primaryActionBtnLabel: 'Save', primaryActionBtnColor: 'primary', cancelBtnLabel: 'Cancel', + params: { + sourceFlag: { ...flag }, + action: UpsertModalAction.EDIT, + }, }; - const config: MatDialogConfig = { - data: commonModalConfig, - width: '656px', - autoFocus: 'first-heading', - disableClose: true, + return this.openUpsertFeatureFlagModal(commonModalConfig); + } + + openDuplicateFeatureFlagModal(flag: FeatureFlag) { + const commonModalConfig: CommonModalConfig = { + title: 'Duplicate Feature Flag', + primaryActionBtnLabel: 'Add', + primaryActionBtnColor: 'primary', + cancelBtnLabel: 'Cancel', + params: { + sourceFlag: { ...flag }, + action: UpsertModalAction.DUPLICATE, + }, }; - return this.dialog.open(EditFeatureFlagModalComponent, config); + return this.openUpsertFeatureFlagModal(commonModalConfig); } openEnableFeatureFlagConfirmModel() { @@ -74,6 +86,16 @@ export class DialogService { return this.openUpdateFlagStatusConfirmationModal(disableFlagStatusModalConfig); } + openUpsertFeatureFlagModal(commonModalConfig: CommonModalConfig) { + const config: MatDialogConfig = { + data: commonModalConfig, + width: '656px', + autoFocus: 'first-heading', + disableClose: true, + }; + return this.dialog.open(UpsertFeatureFlagModalComponent, config); + } + openUpdateFlagStatusConfirmationModal(commonModalConfig: CommonModalConfig) { const config: MatDialogConfig = { data: commonModalConfig, diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index c646543e96..53ac771839 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -354,10 +354,10 @@ "feature-flags.no-flags-in-table.text": "No feature flags is defined. Create a new feature flag.", "feature-flags.delete-flag.message.text": "Type the name of feature flag to confirm deletion:", "feature-flags.delete-flag.input-placeholder.text": "Feature flag name", - "feature-flags.add-flag.text": "ADD FLAG", - "feature-flags.add-flag-modal.name-hint.text": "The name for this feature flag.", - "feature-flags.add-flag-modal.key-hint.text": "A unique key used to retrieve this feature flag from the client application.", - "feature-flags.add-flag-modal.app-context-hint.text": "The App Context indicates where the experiment will run, known to UpGrade.", + "feature-flags.upsert-flag.text": "ADD FLAG", + "feature-flags.upsert-flag-modal.name-hint.text": "The name for this feature flag.", + "feature-flags.upsert-flag-modal.key-hint.text": "A unique key used to retrieve this feature flag from the client application.", + "feature-flags.upsert-flag-modal.app-context-hint.text": "The App Context indicates where the feature flag will run, known to UpGrade.", "feature-flags.delete-modal.warning-message.text": "Are you sure you want to delete ", "feature-flags.details.inclusions.card.no-data-row.text": "No Include Lists defined. The feature flag will not be applied to any participants.", "feature-flags.details.inclusions.card.title.text": "Include Lists", From 491d63ad21d8064b3b1e061fe51b684ca32107f6 Mon Sep 17 00:00:00 2001 From: danoswaltCL Date: Wed, 17 Jul 2024 13:25:49 -0400 Subject: [PATCH 02/10] adjustments to backend updates, code cleanup --- .../feature-flags/feature-flags.service.ts | 9 +- .../store/feature-flags.model.ts | 69 ++++++------ .../store/feature-flags.selectors.ts | 100 ++++++------------ .../app/core/segments/store/segments.model.ts | 7 +- .../upsert-feature-flag-modal.component.ts | 62 ++++++++--- .../shared/services/common-dialog.service.ts | 12 ++- .../services/common-text-helpers.service.ts | 56 ++++++++++ 7 files changed, 185 insertions(+), 130 deletions(-) create mode 100644 frontend/projects/upgrade/src/app/shared/services/common-text-helpers.service.ts 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 7957347dcd..8f5b820241 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 @@ -21,6 +21,7 @@ import { selectIsLoadingSelectedFeatureFlag, selectSortKey, selectSortAs, + selectFeatureFlagListTypeOptions, } from './store/feature-flags.selectors'; import * as FeatureFlagsActions from './store/feature-flags.actions'; import { actionFetchContextMetaData } from '../experiments/store/experiments.actions'; @@ -73,6 +74,8 @@ export class FeatureFlagsService { map(([prev, curr]) => curr) ); + selectFeatureFlagListTypeOptions$ = this.store$.pipe(select(selectFeatureFlagListTypeOptions)); + selectedFlagOverviewDetails = this.store$.pipe(select(selectFeatureFlagOverviewDetails)); selectedFeatureFlag$ = this.store$.pipe(select(selectSelectedFeatureFlag)); searchParams$ = this.store$.pipe(select(selectSearchFeatureFlagParams)); @@ -94,12 +97,6 @@ export class FeatureFlagsService { map((exclusions) => exclusions.length) ); - convertNameStringToKey(name: string): string { - const upperCaseString = name.trim().toUpperCase(); - const key = upperCaseString.replace(/ /g, '_'); - return key; - } - fetchFeatureFlags(fromStarting?: boolean) { this.store$.dispatch(FeatureFlagsActions.actionFetchFeatureFlags({ fromStarting })); } diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts index 5f3db24611..69e3ad993e 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts @@ -1,51 +1,48 @@ import { AppState } from '../../core.state'; import { EntityState } from '@ngrx/entity'; import { FEATURE_FLAG_STATUS, FILTER_MODE, FLAG_SORT_KEY, SEGMENT_TYPE, SORT_AS_DIRECTION } from 'upgrade_types'; -import { GroupForSegment, IndividualForSegment, Segment } from '../../segments/store/segments.model'; +import { GroupForSegment, IndividualForSegment, MemberTypes, Segment } from '../../segments/store/segments.model'; -export interface FeatureFlag { +// This obviously is a more global type, but for now we're not about to refactor all of the things, so I'm just putting it here so I can create some more dev-friendly types to catch the small differences between some of these formats +export interface GeneralCRUDResponseFields { createdAt: string; updatedAt: string; versionNumber: number; - id: string; - name: string; - key: string; - description: string; - status: FEATURE_FLAG_STATUS; - filterMode: FILTER_MODE; - context: string[]; - tags: string[]; - enabled: boolean; } -export interface FeatureFlagsPaginationInfo { - nodes: FeatureFlag[]; - total: number; - skip: number; - take: number; -} - -export interface AddFeatureFlagRequest { +// Fields belonging to the FeatureFlag entity itself that are not part of the CRUD response +export interface BaseFeatureFlag { + id?: string; name: string; key: string; description?: string; context: string[]; tags: string[]; + status: FEATURE_FLAG_STATUS; + filterMode: FILTER_MODE; + featureFlagSegmentInclusion: FeatureFlagSegmentListDetails[]; + featureFlagSegmentExclusion: FeatureFlagSegmentListDetails[]; } -export interface ModifyFeatureFlagRequest { - id: string; - name?: string; - key?: string; - description?: string; - status?: FEATURE_FLAG_STATUS; - filterMode?: FILTER_MODE; - context?: string[]; - tags?: string[]; - enabled?: boolean; +// Feature Flag entity = base + db-generated fields (we depend on createdOn for sorting, for instance, but it's not truly part of the feature flag base) +export type FeatureFlag = BaseFeatureFlag & GeneralCRUDResponseFields; + +// Currently there is no difference between these types, but they semantically different and could diverge later +export type AddFeatureFlagRequest = BaseFeatureFlag; + +// so that we can throw an error if we try to update the id +export interface ModifyFeatureFlagRequest extends AddFeatureFlagRequest { + readonly id: string; +} + +export interface FeatureFlagSegmentListDetails { + segment: Segment; + featureFlag: FeatureFlag; + enabled: boolean; + listType: MemberTypes | string; } -export enum UpsertModalAction { +export enum UPSERT_MODAL_ACTION { ADD = 'add', EDIT = 'edit', DUPLICATE = 'duplicate', @@ -53,7 +50,14 @@ export enum UpsertModalAction { export interface UpsertModalParams { sourceFlag: FeatureFlag; - action: UpsertModalAction; + action: UPSERT_MODAL_ACTION; +} + +export interface FeatureFlagsPaginationInfo { + nodes: FeatureFlag[]; + total: number; + skip: number; + take: number; } export interface UpdateFeatureFlagStatusRequest { @@ -80,8 +84,7 @@ export interface EmptyPrivateSegment { }; } -export type AnySegmentType = Segment | PrivateSegment | EmptyPrivateSegment | GroupForSegment | IndividualForSegment; - +// TODO: This should be probably be a part of env config export const NUMBER_OF_FLAGS = 20; interface IFeatureFlagsSearchParams { diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts index aed58f5721..36510549c7 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts @@ -1,20 +1,10 @@ import { createSelector, createFeatureSelector } from '@ngrx/store'; -import { - AnySegmentType, - EmptyPrivateSegment, - FLAG_SEARCH_KEY, - FeatureFlag, - FeatureFlagState, - ParticipantListTableRow, - PrivateSegment, -} from './feature-flags.model'; +import { FLAG_SEARCH_KEY, FeatureFlag, FeatureFlagState, ParticipantListTableRow } from './feature-flags.model'; import { selectRouterState } from '../../core.state'; import { selectAll } from './feature-flags.reducer'; -import { GroupForSegment, IndividualForSegment, Segment } from '../../segments/store/segments.model'; -import { - FEATURE_FLAG_PARTICIPANT_LIST_KEY, - FEATURE_FLAG_STATUS, -} from '../../../../../../../../types/src/Experiment/enums'; +import { MemberTypes } from '../../segments/store/segments.model'; +import { selectContextMetaData } from '../../experiments/store/experiments.selectors'; +import { CommonTextHelpersService } from '../../../shared/services/common-text-helpers.service'; export const selectFeatureFlagsState = createFeatureSelector('featureFlags'); @@ -129,68 +119,38 @@ export const selectIsLoadingFeatureFlagDelete = createSelector( (state) => state.isLoadingFeatureFlagDelete ); +// TODO: will need reimplementation in the list table stories export const selectFeatureFlagInclusions = createSelector( selectSelectedFeatureFlag, - (featureFlag: FeatureFlag): ParticipantListTableRow[] => - mapToParticipantTableRowStructure(featureFlag, FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE) + (featureFlag: FeatureFlag): ParticipantListTableRow[] => [] + // mapToParticipantTableRowStructure(featureFlag, FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE) ); export const selectFeatureFlagExclusions = createSelector( selectSelectedFeatureFlag, - (featureFlag: FeatureFlag): ParticipantListTableRow[] => - mapToParticipantTableRowStructure(featureFlag, FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE) + (featureFlag: FeatureFlag): ParticipantListTableRow[] => [] + // mapToParticipantTableRowStructure(featureFlag, FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE) ); -// TODO: can we get rid of this ui discovery work and have the backend do it? -function mapToParticipantTableRowStructure( - featureFlag: FeatureFlag, - key: FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE | FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE -): ParticipantListTableRow[] { - const privateSegment: PrivateSegment | EmptyPrivateSegment = featureFlag?.[key]; - - if (!privateSegment) return []; - - // make sure this is not an empty private segment - if ('groupForSegment' in privateSegment.segment) { - const groups: GroupForSegment[] = privateSegment.segment.groupForSegment || []; - const subSegments: Segment[] = privateSegment.segment.subSegments || []; - const individuals: IndividualForSegment[] = privateSegment.segment.individualForSegment || []; - - const groupsRow = groups.map(mapGroupToRow); - const subSegmentsRow = subSegments.map(mapSubSegmentToRow); - const individualsRow = individuals.map(mapIndividualToRow); - - const allSegments: ParticipantListTableRow[] = [...groupsRow, ...subSegmentsRow, ...individualsRow]; - - return allSegments; +export const selectFeatureFlagListTypeOptions = createSelector( + selectContextMetaData, + selectSelectedFeatureFlag, + (contextMetaData, flag) => { + const flagAppContext = flag?.context?.[0]; + const groupTypes = contextMetaData?.contextMetadata?.[flagAppContext]?.GROUP_TYPES ?? []; + const groupTypeSelectOptions = CommonTextHelpersService.formatGroupTypes(groupTypes as string[]); + const listOptionTypes = [ + { + value: MemberTypes.SEGMENT, + viewValue: MemberTypes.SEGMENT, + }, + { + value: MemberTypes.INDIVIDUAL, + viewValue: MemberTypes.INDIVIDUAL, + }, + ...groupTypeSelectOptions, + ]; + + return listOptionTypes; } - - return []; -} - -function mapGroupToRow(group: GroupForSegment): ParticipantListTableRow { - return { - name: group.groupId, - type: group.type + ' (group)', - values: '?', - status: '?', - }; -} - -function mapSubSegmentToRow(subSegment: Segment): ParticipantListTableRow { - return { - name: subSegment.id, - type: 'Segment', - values: '?', - status: '?', - }; -} - -function mapIndividualToRow(individual: IndividualForSegment): ParticipantListTableRow { - return { - name: individual.userId, - type: 'Individual', - values: '?', - status: '?', - }; -} +); diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.model.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.model.ts index ddc3fc9a6c..8f80a0aa39 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.model.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.model.ts @@ -53,9 +53,12 @@ export interface experimentSegmentInclusionExclusionData { }; } -export interface GroupForSegment { +export interface Group { groupId: string; type: string; +} + +export interface GroupForSegment extends Group { segmentId: string; } @@ -93,7 +96,7 @@ export interface SegmentInput { context: string; description: string; userIds: string[]; - groups: { groupId: string; type: string }[]; + groups: Group[]; subSegmentIds: string[]; type: SEGMENT_TYPE; } 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 cf89756fe7..66b0ac5bec 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 @@ -23,18 +23,20 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service'; import { CommonFormHelpersService } from '../../../../../shared/services/common-form-helpers.service'; +import { FEATURE_FLAG_STATUS, FILTER_MODE } from '../../../../../../../../../../types/src'; import { - FeatureFlag, - FeatureFlagFormData, AddFeatureFlagRequest, - UpsertModalParams, - UpsertModalAction, DuplicateFeatureFlagSuffix, + FeatureFlag, + FeatureFlagFormData, ModifyFeatureFlagRequest, + UPSERT_MODAL_ACTION, + UpsertModalParams, } from '../../../../../core/feature-flags/store/feature-flags.model'; import { Subscription } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { ExperimentService } from '../../../../../core/experiments/experiments.service'; +import { CommonTextHelpersService } from '../../../../../shared/services/common-text-helpers.service'; @Component({ selector: 'upsert-add-feature-flag-modal', @@ -93,7 +95,7 @@ export class UpsertFeatureFlagModalComponent { const initialValues: FeatureFlag = { ...sourceFlag }; - if (sourceFlag && action === UpsertModalAction.DUPLICATE) { + if (sourceFlag && action === UPSERT_MODAL_ACTION.DUPLICATE) { initialValues.name += DuplicateFeatureFlagSuffix; initialValues.key += DuplicateFeatureFlagSuffix; } @@ -111,7 +113,7 @@ export class UpsertFeatureFlagModalComponent { this.subscriptions = this.featureFlagForm.get('name')?.valueChanges.subscribe((name) => { const keyControl = this.featureFlagForm.get('key'); if (keyControl && !keyControl.dirty) { - keyControl.setValue(this.featureFlagsService.convertNameStringToKey(name)); + keyControl.setValue(CommonTextHelpersService.convertStringToFeatureFlagKeyFormat(name)); } }); } @@ -131,22 +133,52 @@ export class UpsertFeatureFlagModalComponent { } } - createRequest(action: UpsertModalAction, sourceFlag?: FeatureFlag): void { - const { name, key, description, appContext, tags }: FeatureFlagFormData = this.featureFlagForm.value; - const flagRequest = { + createRequest(action: UPSERT_MODAL_ACTION, sourceFlag?: FeatureFlag): void { + const formData: FeatureFlagFormData = this.featureFlagForm.value; + + if (action === UPSERT_MODAL_ACTION.ADD || action === UPSERT_MODAL_ACTION.DUPLICATE) { + this.createAddRequest(formData); + } else if (action === UPSERT_MODAL_ACTION.EDIT && sourceFlag) { + this.createEditRequest(formData, sourceFlag); + } else { + console.error('UpsertFeatureFlagModalComponent: createRequest: Invalid action or missing sourceFlag'); + } + } + + private createAddRequest({ name, key, description, appContext, tags }: FeatureFlagFormData): void { + const flagRequest: AddFeatureFlagRequest = { name, key, description, context: [appContext], tags, + status: FEATURE_FLAG_STATUS.DISABLED, + filterMode: FILTER_MODE.INCLUDE_ALL, + featureFlagSegmentInclusion: [], + featureFlagSegmentExclusion: [], }; - if (action === UpsertModalAction.ADD || action === UpsertModalAction.DUPLICATE) { - this.featureFlagsService.addFeatureFlag(flagRequest); - } else if (action === UpsertModalAction.EDIT && sourceFlag) { - const updatedFlag = { ...sourceFlag, ...flagRequest }; - this.featureFlagsService.updateFeatureFlag(updatedFlag); - } + this.featureFlagsService.addFeatureFlag(flagRequest); + } + + private createEditRequest( + { name, key, description, appContext, tags }: FeatureFlagFormData, + { id, status, filterMode, featureFlagSegmentInclusion, featureFlagSegmentExclusion }: FeatureFlag + ): void { + const flagRequest: ModifyFeatureFlagRequest = { + id, + name, + key, + description, + context: [appContext], + tags, + status, + filterMode, + featureFlagSegmentInclusion, + featureFlagSegmentExclusion, + }; + + this.featureFlagsService.updateFeatureFlag(flagRequest); } closeModal() { diff --git a/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts b/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts index 928040ba27..8e7056bba5 100644 --- a/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts +++ b/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts @@ -7,7 +7,11 @@ import { DeleteFeatureFlagModalComponent } from '../../features/dashboard/featur import { ImportFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component'; import { UpdateFlagStatusConfirmationModalComponent } from '../../features/dashboard/feature-flags/modals/update-flag-status-confirmation-modal/update-flag-status-confirmation-modal.component'; import { UpsertFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component'; -import { FeatureFlag, UpsertModalAction, UpsertModalParams } from '../../core/feature-flags/store/feature-flags.model'; +import { + FeatureFlag, + UPSERT_MODAL_ACTION, + UpsertModalParams, +} from '../../core/feature-flags/store/feature-flags.model'; @Injectable({ providedIn: 'root', @@ -32,7 +36,7 @@ export class DialogService { cancelBtnLabel: 'Cancel', params: { sourceFlag: null, - action: UpsertModalAction.ADD, + action: UPSERT_MODAL_ACTION.ADD, }, }; return this.openUpsertFeatureFlagModal(commonModalConfig); @@ -46,7 +50,7 @@ export class DialogService { cancelBtnLabel: 'Cancel', params: { sourceFlag: { ...flag }, - action: UpsertModalAction.EDIT, + action: UPSERT_MODAL_ACTION.EDIT, }, }; return this.openUpsertFeatureFlagModal(commonModalConfig); @@ -60,7 +64,7 @@ export class DialogService { cancelBtnLabel: 'Cancel', params: { sourceFlag: { ...flag }, - action: UpsertModalAction.DUPLICATE, + action: UPSERT_MODAL_ACTION.DUPLICATE, }, }; return this.openUpsertFeatureFlagModal(commonModalConfig); diff --git a/frontend/projects/upgrade/src/app/shared/services/common-text-helpers.service.ts b/frontend/projects/upgrade/src/app/shared/services/common-text-helpers.service.ts new file mode 100644 index 0000000000..7d59a17076 --- /dev/null +++ b/frontend/projects/upgrade/src/app/shared/services/common-text-helpers.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@angular/core'; + +/** + * A service class that provides common text formatting helpers. + * + * This class contains static methods to format group types, convert names to keys, etc. + * The methods are static so they can be used in pure functions like selectors without + * needing to create an instance of the service. + */ +@Injectable() +export class CommonTextHelpersService { + /** + * Formats an array of group types into an array of objects with value and viewValue properties. + * + * @param groupTypes - An array of group type strings, as defined in context-metadata. + * @returns An array of objects where each object contains a value and a viewValue property, or [] if groupTypes is falsy. + * + * The method is static so it can be used in pure functions like selectors without needing + * to create an instance of the service. + */ + public static formatGroupTypes(groupTypes: string[]): { value: string; viewValue: string }[] { + return ( + groupTypes?.map((groupType) => { + return { value: groupType, viewValue: CommonTextHelpersService.formatGroupTypeViewValue(groupType) }; + }) ?? [] + ); + } + + /** + * Formats a group type string into a view value string. + * + * @param groupType - A group type string. + * @returns A formatted view value string. + * + * The method is static so it can be used in pure functions like selectors without needing + * to create an instance of the service. + */ + public static formatGroupTypeViewValue(groupType: string): string { + return 'Group: "' + groupType + '"'; + } + + /** + * Converts a name string into a feature-flag permitted key format by trimming, converting to uppercase, and replacing spaces with underscores. + * + * @param str - A string to be formatted. + * @returns A key string. + * + * The method is static so it can be used in pure functions like selectors without needing + * to create an instance of the service. + */ + public static convertStringToFeatureFlagKeyFormat(str: string): string { + const upperCaseString = str.trim().toUpperCase(); + const key = upperCaseString.replace(/ /g, '_'); + return key; + } +} From 5ef49b55e0fdcbf8c6ad7a36830e088f575e6201 Mon Sep 17 00:00:00 2001 From: danoswaltCL Date: Wed, 17 Jul 2024 13:50:54 -0400 Subject: [PATCH 03/10] fixed messed up interfaces after merge --- .../store/feature-flags.model.ts | 18 +++++++++++---- .../upsert-feature-flag-modal.component.ts | 15 ++++++------ .../shared/services/common-dialog.service.ts | 23 +++++++++++-------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts index 69e3ad993e..a6484484b6 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts @@ -1,7 +1,7 @@ import { AppState } from '../../core.state'; import { EntityState } from '@ngrx/entity'; import { FEATURE_FLAG_STATUS, FILTER_MODE, FLAG_SORT_KEY, SEGMENT_TYPE, SORT_AS_DIRECTION } from 'upgrade_types'; -import { GroupForSegment, IndividualForSegment, MemberTypes, Segment } from '../../segments/store/segments.model'; +import { MemberTypes, Segment } from '../../segments/store/segments.model'; // This obviously is a more global type, but for now we're not about to refactor all of the things, so I'm just putting it here so I can create some more dev-friendly types to catch the small differences between some of these formats export interface GeneralCRUDResponseFields { @@ -42,15 +42,25 @@ export interface FeatureFlagSegmentListDetails { listType: MemberTypes | string; } -export enum UPSERT_MODAL_ACTION { +export enum UPSERT_FEATURE_FLAG_ACTION { ADD = 'add', EDIT = 'edit', DUPLICATE = 'duplicate', } -export interface UpsertModalParams { +export interface UpsertFeatureFlagParams { sourceFlag: FeatureFlag; - action: UPSERT_MODAL_ACTION; + action: UPSERT_FEATURE_FLAG_ACTION; +} + +export enum UPSERT_FEATURE_FLAG_LIST_ACTION { + ADD = 'add', + EDIT = 'edit', +} + +export interface UpsertFeatureFlagListParams { + sourceList: FeatureFlagSegmentListDetails; + action: UPSERT_FEATURE_FLAG_LIST_ACTION; } export interface FeatureFlagsPaginationInfo { 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 66b0ac5bec..6ad751d9ee 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 @@ -30,8 +30,9 @@ import { FeatureFlag, FeatureFlagFormData, ModifyFeatureFlagRequest, - UPSERT_MODAL_ACTION, - UpsertModalParams, + UPSERT_FEATURE_FLAG_ACTION, + UPSERT_FEATURE_FLAG_LIST_ACTION, + UpsertFeatureFlagParams, } from '../../../../../core/feature-flags/store/feature-flags.model'; import { Subscription } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; @@ -74,7 +75,7 @@ export class UpsertFeatureFlagModalComponent { constructor( @Inject(MAT_DIALOG_DATA) - public config: CommonModalConfig, + public config: CommonModalConfig, public dialog: MatDialog, private formBuilder: FormBuilder, private featureFlagsService: FeatureFlagsService, @@ -95,7 +96,7 @@ export class UpsertFeatureFlagModalComponent { const initialValues: FeatureFlag = { ...sourceFlag }; - if (sourceFlag && action === UPSERT_MODAL_ACTION.DUPLICATE) { + if (sourceFlag && action === UPSERT_FEATURE_FLAG_ACTION.DUPLICATE) { initialValues.name += DuplicateFeatureFlagSuffix; initialValues.key += DuplicateFeatureFlagSuffix; } @@ -133,12 +134,12 @@ export class UpsertFeatureFlagModalComponent { } } - createRequest(action: UPSERT_MODAL_ACTION, sourceFlag?: FeatureFlag): void { + createRequest(action: UPSERT_FEATURE_FLAG_ACTION, sourceFlag?: FeatureFlag): void { const formData: FeatureFlagFormData = this.featureFlagForm.value; - if (action === UPSERT_MODAL_ACTION.ADD || action === UPSERT_MODAL_ACTION.DUPLICATE) { + if (action === UPSERT_FEATURE_FLAG_ACTION.ADD || action === UPSERT_FEATURE_FLAG_ACTION.DUPLICATE) { this.createAddRequest(formData); - } else if (action === UPSERT_MODAL_ACTION.EDIT && sourceFlag) { + } else if (action === UPSERT_FEATURE_FLAG_ACTION.EDIT && sourceFlag) { this.createEditRequest(formData, sourceFlag); } else { console.error('UpsertFeatureFlagModalComponent: createRequest: Invalid action or missing sourceFlag'); diff --git a/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts b/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts index 79995754f8..7f51bcd5d9 100644 --- a/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts +++ b/frontend/projects/upgrade/src/app/shared/services/common-dialog.service.ts @@ -9,9 +9,12 @@ import { UpdateFlagStatusConfirmationModalComponent } from '../../features/dashb import { UpsertFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component'; import { FeatureFlag, - UPSERT_MODAL_ACTION, - UpsertModalParams, + UPSERT_FEATURE_FLAG_ACTION, + UPSERT_FEATURE_FLAG_LIST_ACTION, + UpsertFeatureFlagListParams, + UpsertFeatureFlagParams, } from '../../core/feature-flags/store/feature-flags.model'; +import { UpsertFeatureFlagListModalComponent } from '../../features/dashboard/feature-flags/modals/upsert-feature-flag-list-modal/upsert-feature-flag-list-modal.component'; @Injectable({ providedIn: 'root', @@ -36,35 +39,35 @@ export class DialogService { cancelBtnLabel: 'Cancel', params: { sourceFlag: null, - action: UPSERT_MODAL_ACTION.ADD, + action: UPSERT_FEATURE_FLAG_LIST_ACTION.ADD, }, }; return this.openUpsertFeatureFlagModal(commonModalConfig); } - openEditFeatureFlagModal(flag: FeatureFlag) { - const commonModalConfig: CommonModalConfig = { + openEditFeatureFlagModal(sourceFlag: FeatureFlag) { + const commonModalConfig: CommonModalConfig = { title: 'Edit Feature Flag', primaryActionBtnLabel: 'Save', primaryActionBtnColor: 'primary', cancelBtnLabel: 'Cancel', params: { - sourceFlag: { ...flag }, - action: UPSERT_MODAL_ACTION.EDIT, + sourceFlag: { ...sourceFlag }, + action: UPSERT_FEATURE_FLAG_ACTION.EDIT, }, }; return this.openUpsertFeatureFlagModal(commonModalConfig); } - openDuplicateFeatureFlagModal(flag: FeatureFlag) { + openDuplicateFeatureFlagModal(sourceFlag: FeatureFlag) { const commonModalConfig: CommonModalConfig = { title: 'Duplicate Feature Flag', primaryActionBtnLabel: 'Add', primaryActionBtnColor: 'primary', cancelBtnLabel: 'Cancel', params: { - sourceFlag: { ...flag }, - action: UPSERT_MODAL_ACTION.DUPLICATE, + sourceFlag: { ...sourceFlag }, + action: UPSERT_FEATURE_FLAG_ACTION.DUPLICATE, }, }; return this.openUpsertFeatureFlagModal(commonModalConfig); From 53e55d44ff46d1314fb540c1b9670d0c72c027f9 Mon Sep 17 00:00:00 2001 From: danoswaltCL Date: Thu, 18 Jul 2024 21:33:09 -0400 Subject: [PATCH 04/10] fix up better selectors and some refactor for readability --- .../feature-flags.data.service.ts | 4 +- .../feature-flags/feature-flags.service.ts | 22 ++-- .../store/feature-flags.actions.ts | 4 +- .../store/feature-flags.model.ts | 3 +- .../store/feature-flags.selectors.ts | 4 + .../upsert-feature-flag-modal.component.html | 2 +- .../upsert-feature-flag-modal.component.ts | 102 +++++++++++++----- ...feature-flag-inclusions-table.component.ts | 4 +- ...lag-root-section-card-table.component.html | 1 + 9 files changed, 99 insertions(+), 47 deletions(-) diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts index 94ff92140f..8294da4517 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts @@ -6,7 +6,7 @@ import { FeatureFlag, FeatureFlagsPaginationInfo, FeatureFlagsPaginationParams, - ModifyFeatureFlagRequest, + UpdateFeatureFlagRequest, UpdateFeatureFlagStatusRequest, } from './store/feature-flags.model'; import { Observable } from 'rxjs'; @@ -35,7 +35,7 @@ export class FeatureFlagsDataService { return this.http.post(url, params); } - updateFeatureFlag(flag: ModifyFeatureFlagRequest): Observable { + updateFeatureFlag(flag: UpdateFeatureFlagRequest): Observable { const url = `${this.environment.api.featureFlag}/${flag.id}`; return this.http.put(url, flag); } 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 15eab33f71..6542074908 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 @@ -22,6 +22,7 @@ import { selectSortKey, selectSortAs, selectFeatureFlagListTypeOptions, + selectAppContexts, } from './store/feature-flags.selectors'; import * as FeatureFlagsActions from './store/feature-flags.actions'; import { actionFetchContextMetaData } from '../experiments/store/experiments.actions'; @@ -29,11 +30,12 @@ import { FLAG_SEARCH_KEY, FLAG_SORT_KEY, SORT_AS_DIRECTION } from 'upgrade_types import { UpdateFeatureFlagStatusRequest, AddFeatureFlagRequest, - ModifyFeatureFlagRequest, + UpdateFeatureFlagRequest, } from './store/feature-flags.model'; import { ExperimentService } from '../experiments/experiments.service'; import { filter, map, pairwise, withLatestFrom } from 'rxjs'; import { selectContextMetaData } from '../experiments/store/experiments.selectors'; +import isEqual from 'lodash.isequal'; @Injectable() export class FeatureFlagsService { @@ -44,6 +46,7 @@ export class FeatureFlagsService { isLoadingSelectedFeatureFlag$ = this.store$.pipe(select(selectIsLoadingSelectedFeatureFlag)); isLoadingUpdateFeatureFlagStatus$ = this.store$.pipe(select(selectIsLoadingUpdateFeatureFlagStatus)); allFeatureFlags$ = this.store$.pipe(select(selectAllFeatureFlagsSortedByDate)); + appContexts$ = this.store$.pipe(select(selectAppContexts)); isAllFlagsFetched$ = this.store$.pipe(select(selectIsAllFlagsFetched)); searchString$ = this.store$.pipe(select(selectSearchString)); searchKey$ = this.store$.pipe(select(selectSearchKey)); @@ -52,10 +55,11 @@ export class FeatureFlagsService { isLoadingUpsertFeatureFlag$ = this.store$.pipe(select(selectIsLoadingUpsertFeatureFlag)); IsLoadingFeatureFlagDelete$ = this.store$.pipe(select(selectIsLoadingFeatureFlagDelete)); - featureFlagsListLengthChange$ = this.allFeatureFlags$.pipe( + hasFeatureFlagsCountChanged$ = this.allFeatureFlags$.pipe( pairwise(), filter(([prevEntities, currEntities]) => prevEntities.length !== currEntities.length) ); + selectedFeatureFlagStatusChange$ = this.store$.pipe( select(selectSelectedFeatureFlag), pairwise(), @@ -71,8 +75,10 @@ export class FeatureFlagsService { isSelectedFeatureFlagUpdated$ = this.store$.pipe( select(selectSelectedFeatureFlag), pairwise(), - filter(([prev, curr]) => prev && curr && JSON.stringify(prev) !== JSON.stringify(curr)), - map(([prev, curr]) => curr) + filter(([prev, curr]) => { + return prev && curr && !isEqual(prev, curr); + }), + map(([, curr]) => curr) ); selectFeatureFlagListTypeOptions$ = this.store$.pipe(select(selectFeatureFlagListTypeOptions)); @@ -81,11 +87,6 @@ export class FeatureFlagsService { searchParams$ = this.store$.pipe(select(selectSearchFeatureFlagParams)); selectRootTableState$ = this.store$.select(selectRootTableState); activeDetailsTabIndex$ = this.store$.pipe(select(selectActiveDetailsTabIndex)); - appContexts$ = this.experimentService.contextMetaData$.pipe( - map((contextMetaData) => { - return Object.keys(contextMetaData?.contextMetadata ?? []); - }) - ); selectFeatureFlagInclusions$ = this.store$.pipe(select(selectFeatureFlagInclusions)); selectFeatureFlagInclusionsLength$ = this.store$.pipe( select(selectFeatureFlagInclusions), @@ -96,7 +97,6 @@ export class FeatureFlagsService { select(selectFeatureFlagExclusions), map((exclusions) => exclusions.length) ); - // note: this comes from experiment service! fetchFeatureFlags(fromStarting?: boolean) { this.store$.dispatch(FeatureFlagsActions.actionFetchFeatureFlags({ fromStarting })); @@ -114,7 +114,7 @@ export class FeatureFlagsService { this.store$.dispatch(FeatureFlagsActions.actionAddFeatureFlag({ addFeatureFlagRequest })); } - updateFeatureFlag(flag: ModifyFeatureFlagRequest) { + updateFeatureFlag(flag: UpdateFeatureFlagRequest) { this.store$.dispatch(FeatureFlagsActions.actionUpdateFeatureFlag({ flag })); } diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts index 07eb65724b..4cddf07568 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts @@ -3,7 +3,7 @@ import { FeatureFlag, UpdateFeatureFlagStatusRequest, AddFeatureFlagRequest, - ModifyFeatureFlagRequest, + UpdateFeatureFlagRequest, } from './feature-flags.model'; import { FLAG_SEARCH_KEY, FLAG_SORT_KEY, SORT_AS_DIRECTION } from 'upgrade_types'; @@ -53,7 +53,7 @@ export const actionDeleteFeatureFlagFailure = createAction('[Feature Flags] Dele export const actionUpdateFeatureFlag = createAction( '[Feature Flags] Update Feature Flag', - props<{ flag: ModifyFeatureFlagRequest }>() + props<{ flag: UpdateFeatureFlagRequest }>() ); export const actionUpdateFeatureFlagSuccess = createAction( diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts index a6484484b6..897471b221 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.model.ts @@ -31,7 +31,7 @@ export type FeatureFlag = BaseFeatureFlag & GeneralCRUDResponseFields; export type AddFeatureFlagRequest = BaseFeatureFlag; // so that we can throw an error if we try to update the id -export interface ModifyFeatureFlagRequest extends AddFeatureFlagRequest { +export interface UpdateFeatureFlagRequest extends AddFeatureFlagRequest { readonly id: string; } @@ -75,7 +75,6 @@ export interface UpdateFeatureFlagStatusRequest { status: FEATURE_FLAG_STATUS; } -export const DuplicateFeatureFlagSuffix = '_COPY_CHANGE_ME'; export interface FeatureFlagFormData { name: string; key: string; diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts index 36510549c7..1dd299bb95 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts @@ -21,6 +21,10 @@ export const selectAllFeatureFlagsSortedByDate = createSelector(selectAllFeature }); }); +export const selectAppContexts = createSelector(selectContextMetaData, (contextMetaData) => + Object.keys(contextMetaData?.contextMetadata ?? []) +); + export const selectHasInitialFeatureFlagsDataLoaded = createSelector( selectFeatureFlagsState, (state) => state.hasInitialFeatureFlagsDataLoaded diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.html index eac2ad9665..8ffd4a7acd 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component.html @@ -4,7 +4,7 @@ [cancelBtnLabel]="config.cancelBtnLabel" [primaryActionBtnLabel]="config.primaryActionBtnLabel" [primaryActionBtnColor]="config.primaryActionBtnColor" - [primaryActionBtnDisabled]="isLoadingUpsertFeatureFlag$ | async" + [primaryActionBtnDisabled]="isPrimaryButtonDisabled$ | async" (primaryActionBtnClicked)="onPrimaryActionBtnClicked()" >
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 6ad751d9ee..a084a0b600 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 @@ -26,18 +26,17 @@ import { CommonFormHelpersService } from '../../../../../shared/services/common- import { FEATURE_FLAG_STATUS, FILTER_MODE } from '../../../../../../../../../../types/src'; import { AddFeatureFlagRequest, - DuplicateFeatureFlagSuffix, FeatureFlag, FeatureFlagFormData, - ModifyFeatureFlagRequest, + UpdateFeatureFlagRequest, UPSERT_FEATURE_FLAG_ACTION, - UPSERT_FEATURE_FLAG_LIST_ACTION, UpsertFeatureFlagParams, } from '../../../../../core/feature-flags/store/feature-flags.model'; -import { Subscription } from 'rxjs'; +import { BehaviorSubject, combineLatestWith, map, Observable, startWith, Subscription } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { ExperimentService } from '../../../../../core/experiments/experiments.service'; import { CommonTextHelpersService } from '../../../../../shared/services/common-text-helpers.service'; +import isEqual from 'lodash.isequal'; @Component({ selector: 'upsert-add-feature-flag-modal', @@ -69,7 +68,12 @@ export class UpsertFeatureFlagModalComponent { isSelectedFeatureFlagUpdated$ = this.featureFlagsService.isSelectedFeatureFlagUpdated$; selectedFlag$ = this.featureFlagsService.selectedFeatureFlag$; appContexts$ = this.featureFlagsService.appContexts$; + subscriptions = new Subscription(); + isInitialFormValueChanged$: Observable; + isPrimaryButtonDisabled$: Observable; + + initialFormValues$ = new BehaviorSubject(null); featureFlagForm: FormGroup; @@ -89,39 +93,85 @@ export class UpsertFeatureFlagModalComponent { this.createFeatureFlagForm(); this.listenForFeatureFlagGetUpdated(); this.listenOnNameChangesToUpdateKey(); + this.listenForIsInitialFormValueChanged(); + this.listenForPrimaryButtonDisabled(); } createFeatureFlagForm(): void { const { sourceFlag, action } = this.config.params; - - const initialValues: FeatureFlag = { ...sourceFlag }; - - if (sourceFlag && action === UPSERT_FEATURE_FLAG_ACTION.DUPLICATE) { - initialValues.name += DuplicateFeatureFlagSuffix; - initialValues.key += DuplicateFeatureFlagSuffix; - } + const initialValues = this.deriveInitialFormValues(sourceFlag, action); this.featureFlagForm = this.formBuilder.group({ - name: [initialValues.name || '', Validators.required], - key: [initialValues.key || '', Validators.required], - description: [initialValues.description || ''], - appContext: [initialValues.context?.[0] || '', Validators.required], - tags: [initialValues.tags || []], + name: [initialValues.name, Validators.required], + key: [initialValues.key, Validators.required], + description: [initialValues.description], + appContext: [initialValues.appContext, Validators.required], + tags: [initialValues.tags], }); + + this.initialFormValues$.next(this.featureFlagForm.value); + } + + deriveInitialFormValues(sourceFlag: FeatureFlag, action: string) { + const name = this.deriveName(sourceFlag, action); + const key = this.deriveKey(sourceFlag, action); + const description = this.deriveDescription(sourceFlag); + const appContext = this.deriveAppContext(sourceFlag); + const tags = this.deriveTags(sourceFlag); + + return { name, key, description, appContext, tags }; + } + + deriveName(sourceFlag: FeatureFlag, action: string): string { + return action === UPSERT_FEATURE_FLAG_ACTION.EDIT ? sourceFlag?.name : ''; + } + + deriveKey(sourceFlag: FeatureFlag, action: string): string { + return action === UPSERT_FEATURE_FLAG_ACTION.EDIT ? sourceFlag?.key : ''; + } + + deriveDescription(sourceFlag: FeatureFlag): string { + return sourceFlag?.description || ''; + } + + deriveAppContext(sourceFlag: FeatureFlag): string { + return sourceFlag?.context?.[0] || ''; + } + + deriveTags(sourceFlag: FeatureFlag): string[] { + return sourceFlag?.tags || []; } listenOnNameChangesToUpdateKey(): void { - this.subscriptions = this.featureFlagForm.get('name')?.valueChanges.subscribe((name) => { - const keyControl = this.featureFlagForm.get('key'); - if (keyControl && !keyControl.dirty) { - keyControl.setValue(CommonTextHelpersService.convertStringToFeatureFlagKeyFormat(name)); - } - }); + this.subscriptions.add( + this.featureFlagForm.get('name')?.valueChanges.subscribe((name) => { + const keyControl = this.featureFlagForm.get('key'); + if (keyControl && !keyControl.dirty) { + keyControl.setValue(CommonTextHelpersService.convertStringToFeatureFlagKeyFormat(name)); + } + }) + ); + } + + listenForIsInitialFormValueChanged() { + this.isInitialFormValueChanged$ = this.featureFlagForm.valueChanges.pipe( + startWith(this.featureFlagForm.value), + map(() => !isEqual(this.featureFlagForm.value, this.initialFormValues$.value)) + ); + this.subscriptions.add(this.isInitialFormValueChanged$.subscribe()); + } + + listenForPrimaryButtonDisabled() { + this.isPrimaryButtonDisabled$ = this.isLoadingUpsertFeatureFlag$.pipe( + combineLatestWith(this.isInitialFormValueChanged$), + map(([isLoading, isInitialFormValueChanged]) => isLoading || !isInitialFormValueChanged) + ); + 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 = this.isSelectedFeatureFlagUpdated$.subscribe(() => this.closeModal()); + this.subscriptions.add(this.isSelectedFeatureFlagUpdated$.subscribe(() => this.closeModal())); } onPrimaryActionBtnClicked(): void { @@ -146,7 +196,7 @@ export class UpsertFeatureFlagModalComponent { } } - private createAddRequest({ name, key, description, appContext, tags }: FeatureFlagFormData): void { + createAddRequest({ name, key, description, appContext, tags }: FeatureFlagFormData): void { const flagRequest: AddFeatureFlagRequest = { name, key, @@ -162,11 +212,11 @@ export class UpsertFeatureFlagModalComponent { this.featureFlagsService.addFeatureFlag(flagRequest); } - private createEditRequest( + createEditRequest( { name, key, description, appContext, tags }: FeatureFlagFormData, { id, status, filterMode, featureFlagSegmentInclusion, featureFlagSegmentExclusion }: FeatureFlag ): void { - const flagRequest: ModifyFeatureFlagRequest = { + const flagRequest: UpdateFeatureFlagRequest = { id, name, key, diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-inclusions-section-card/feature-flag-inclusions-table/feature-flag-inclusions-table.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-inclusions-section-card/feature-flag-inclusions-table/feature-flag-inclusions-table.component.ts index e7f2714fa8..bec582f87a 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-inclusions-section-card/feature-flag-inclusions-table/feature-flag-inclusions-table.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-inclusions-section-card/feature-flag-inclusions-table/feature-flag-inclusions-table.component.ts @@ -14,9 +14,7 @@ import { tap } from 'rxjs'; imports: [CommonDetailsParticipantListTableComponent, CommonModule, TranslateModule], }) export class FeatureFlagInclusionsTableComponent { - @Input() dataSource$ = this.featureFlagService.selectFeatureFlagInclusions$.pipe( - tap((data) => console.log('>> inclusions table component', data)) - ); + @Input() dataSource$ = this.featureFlagService.selectFeatureFlagInclusions$; isLoading$ = this.featureFlagService.isLoadingSelectedFeatureFlag$; constructor(private featureFlagService: FeatureFlagsService) {} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html index b8686c982d..d757e417b4 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html @@ -17,6 +17,7 @@ +

{{ flag | json }}

Date: Thu, 18 Jul 2024 21:34:09 -0400 Subject: [PATCH 05/10] remove debugging thing --- .../feature-flag-root-section-card-table.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html index d757e417b4..b8686c982d 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html @@ -17,7 +17,6 @@ -

{{ flag | json }}

Date: Sat, 20 Jul 2024 00:45:04 +0900 Subject: [PATCH 06/10] Update Feature Flags Modal Style (#1769) --- .../delete-feature-flag-modal.component.html | 62 +++++++------- .../delete-feature-flag-modal.component.scss | 23 ++---- .../import-feature-flag-modal.component.html | 45 +++++----- .../import-feature-flag-modal.component.scss | 20 ++--- ...g-status-confirmation-modal.component.html | 63 ++++++-------- ...g-status-confirmation-modal.component.scss | 29 +++---- ...ert-feature-flag-list-modal.component.html | 38 ++++----- ...ert-feature-flag-list-modal.component.scss | 14 ---- .../upsert-feature-flag-modal.component.html | 82 +++++++++---------- .../upsert-feature-flag-modal.component.scss | 33 -------- .../experiment-overview.component.html | 24 +++--- .../experiment-post-condition.component.html | 4 +- .../common-import-container.component.html | 10 +-- .../common-import-container.component.scss | 40 ++++----- .../common-modal/common-modal.component.html | 32 ++++---- .../common-modal/common-modal.component.scss | 39 ++++----- .../common-tag-input.component.html | 11 +-- .../common-tag-input.component.scss | 52 ++++++------ .../shared/services/common-dialog.service.ts | 8 +- .../projects/upgrade/src/assets/i18n/en.json | 2 +- frontend/projects/upgrade/src/styles.scss | 21 +++++ .../upgrade/src/styles/variables.scss | 1 + 22 files changed, 283 insertions(+), 370 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html index 8c8e9e9f76..89ef0dfe9f 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html @@ -1,33 +1,31 @@ -