Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implemented signals for import-feature-flag #2086

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
40 changes: 40 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@
"@angular/platform-browser-dynamic": "^17.1.2",
"@angular/router": "^17.1.2",
"@danielmoncada/angular-datetime-picker": "^17.0.0",
"@ngrx/component-store": "^17.2.0",
"@ngrx/effects": "^17.1.0",
"@ngrx/entity": "^17.1.0",
"@ngrx/router-store": "^17.1.0",
"@ngrx/signals": "^17.2.0",
"@ngrx/store": "^17.1.0",
"@ngrx/store-devtools": "^17.1.0",
"@ngx-translate/core": "^15.0.0",
Expand All @@ -69,6 +71,7 @@
"lodash.isequal": "^4.5.0",
"ngx-skeleton-loader": "^9.0.0",
"rxjs": "7.8.1",
"signals": "^1.0.0",
"tslib": "^2.6.2",
"uuid": "^9.0.1",
"zone.js": "^0.14.3"
Expand Down Expand Up @@ -101,12 +104,12 @@
"jest-environment-jsdom": "^29.5.0",
"jest-preset-angular": "^13.1.4",
"npm-run-all": "^4.1.5",
"replace-in-file": "^6.1.0",
"rimraf": "^3.0.2",
"standard-version": "^9.5.0",
"ts-jest": "^29.0.8",
"ts-node": "~10.8.0",
"typescript": "~5.3.3",
"webpack-bundle-analyzer": "^4.6.1",
"replace-in-file": "^6.1.0"
"webpack-bundle-analyzer": "^4.6.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
selectSortKey,
selectSortAs,
selectAppContexts,
selectIsLoadingImportFeatureFlag,
selectFeatureFlagIds,
selectShouldShowWarningForSelectedFlag,
selectWarningStatusForAllFlags,
Expand Down Expand Up @@ -56,7 +55,6 @@ export class FeatureFlagsService {
isLoadingUpsertFeatureFlag$ = this.store$.pipe(select(selectIsLoadingUpsertFeatureFlag));
isDuplicateKeyFound$ = this.store$.pipe(select(selectDuplicateKeyFound));
isLoadingFeatureFlagDelete$ = this.store$.pipe(select(selectIsLoadingFeatureFlagDelete));
isLoadingImportFeatureFlag$ = this.store$.pipe(select(selectIsLoadingImportFeatureFlag));
isLoadingUpdateFeatureFlagStatus$ = this.store$.pipe(select(selectIsLoadingUpdateFeatureFlagStatus));
isLoadingUpsertPrivateSegmentList$ = this.store$.pipe(select(selectIsLoadingUpsertFeatureFlag));
allFeatureFlags$ = this.store$.pipe(select(selectAllFeatureFlagsSortedByDate));
Expand Down Expand Up @@ -152,10 +150,6 @@ export class FeatureFlagsService {
this.store$.dispatch(FeatureFlagsActions.actionDeleteFeatureFlag({ flagId }));
}

setIsLoadingImportFeatureFlag(isLoadingImportFeatureFlag: boolean) {
this.store$.dispatch(FeatureFlagsActions.actionSetIsLoadingImportFeatureFlag({ isLoadingImportFeatureFlag }));
}

emailFeatureFlagData(featureFlagId: string) {
this.store$.dispatch(FeatureFlagsActions.actionEmailFeatureFlagData({ featureFlagId }));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ export const actionUpdateFeatureFlagSuccess = createAction(

export const actionUpdateFeatureFlagFailure = createAction('[Feature Flags] Update Feature Flag Failure');

export const actionSetIsLoadingImportFeatureFlag = createAction(
'[Feature Flags] Set Is Loading for Flag Import',
props<{ isLoadingImportFeatureFlag: boolean }>()
);

export const actionEmailFeatureFlagData = createAction(
'[Feature Flags] Email Feature Flag Data',
props<{ featureFlagId: string }>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ export const FLAG_ROOT_DISPLAYED_COLUMNS = Object.values(FLAG_ROOT_COLUMN_NAMES)

export interface FeatureFlagState extends EntityState<FeatureFlag> {
isLoadingUpsertFeatureFlag: boolean;
isLoadingImportFeatureFlag: boolean;
isLoadingSelectedFeatureFlag: boolean;
isLoadingFeatureFlags: boolean;
isLoadingUpdateFeatureFlagStatus: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export const { selectIds, selectEntities, selectAll, selectTotal } = adapter.get

export const initialState: FeatureFlagState = adapter.getInitialState({
isLoadingUpsertFeatureFlag: false,
isLoadingImportFeatureFlag: false,
isLoadingFeatureFlags: false,
isLoadingUpdateFeatureFlagStatus: false,
isLoadingFeatureFlagDetail: false,
Expand Down Expand Up @@ -121,10 +120,6 @@ const reducer = createReducer(
...state,
isLoadingFeatureFlags,
})),
on(FeatureFlagsActions.actionSetIsLoadingImportFeatureFlag, (state, { isLoadingImportFeatureFlag }) => ({
...state,
isLoadingImportFeatureFlag,
})),
on(FeatureFlagsActions.actionSetSkipFlags, (state, { skipFlags }) => ({ ...state, skipFlags })),
on(FeatureFlagsActions.actionSetSearchKey, (state, { searchKey }) => ({ ...state, searchKey })),
on(FeatureFlagsActions.actionSetSearchString, (state, { searchString }) => ({ ...state, searchValue: searchString })),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,6 @@ export const selectFeatureFlagsListLength = createSelector(
(featureFlags) => featureFlags.length
);

export const selectIsLoadingImportFeatureFlag = createSelector(
selectFeatureFlagsState,
(state) => state.isLoadingImportFeatureFlag
);

export const selectIsLoadingUpdateFeatureFlagStatus = createSelector(
selectFeatureFlagsState,
(state) => state.isLoadingUpdateFeatureFlagStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ export class LocalStorageService {
ids: [],
entities: {},
isLoadingUpsertFeatureFlag: false,
isLoadingImportFeatureFlag: false,
isLoadingSelectedFeatureFlag: false,
isLoadingFeatureFlags: false,
isLoadingUpdateFeatureFlagStatus: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,19 @@ export interface UpsertPrivateSegmentListParams {
export interface ImportListParams {
listType: FEATURE_FLAG_LIST_FILTER_MODE;
flagId: string;
modelType?: MODEL_TYPE;
}

export enum LIST_OPTION_TYPE {
INDIVIDUAL = 'Individual',
SEGMENT = 'Segment',
}

export enum MODEL_TYPE {
LIST = 'List',
FEATURE_FLAG = 'Feature Flag',
}

export const PRIVATE_SEGMENT_LIST_FORM_FIELDS = {
LIST_TYPE: 'listType',
SEGMENT: 'segment',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { FeatureFlag, ValidateFeatureFlagError } from '../../../../../core/feature-flags/store/feature-flags.model';
import { FeatureFlagsDataService } from '../../../../../core/feature-flags/feature-flags.data.service';
import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { Observable, tap } from 'rxjs';
import { FEATURE_FLAG_LIST_FILTER_MODE, IFeatureFlagFile } from 'upgrade_types';
import { switchMap } from 'rxjs';
import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service';

type FeatureFlagState = {
isLoading: boolean;
isLoadingImportFeatureFlag: boolean;
importResults: { fileName: string; error: string | null }[];
validationErrors: ValidateFeatureFlagError[];
};

@Injectable()
export class FeatureFlagsStore extends ComponentStore<FeatureFlagState> {
constructor(
private featureFlagsDataService: FeatureFlagsDataService,
private featureFlagsService: FeatureFlagsService
) {
super({ isLoading: false, isLoadingImportFeatureFlag: false, importResults: [], validationErrors: [] });
}

// selectors
readonly isLoading = this.selectSignal((state) => state.isLoading);
readonly isLoadingImportFeatureFlag = this.selectSignal((state) => state.isLoadingImportFeatureFlag);
readonly importResults = this.selectSignal((state) => state.importResults);
readonly validationErrors = this.selectSignal((state) => state.validationErrors);

// computed
readonly featureFlagCount = this.selectSignal((state) => state.importResults.length);

// Updaters
readonly setFeatureFlags = this.updater((state, featureFlags: FeatureFlag[]) => ({
...state,
featureFlags,
}));

readonly setLoading = this.updater((state, isLoading: boolean) => ({
...state,
isLoading,
}));

readonly setLoadingImportFeatureFlag = this.updater((state, isLoadingImportFeatureFlag: boolean) => ({
...state,
isLoadingImportFeatureFlag,
}));

readonly setImportResults = this.updater((state, importResults: { fileName: string; error: string | null }[]) => ({
...state,
importResults,
}));

readonly setValidationErrors = this.updater((state, validationErrors: ValidateFeatureFlagError[]) => ({
...state,
validationErrors,
}));

// effects

// Effect for Import Feature Flags
readonly importFeatureFlags = this.effect((featureFlagFiles$: Observable<{ files: IFeatureFlagFile[] }>) => {
return featureFlagFiles$.pipe(
tap(() => this.setLoading(true)),
switchMap((featureFlagFiles) =>
this.featureFlagsDataService.importFeatureFlag(featureFlagFiles).pipe(
tap({
next: (importResults: { fileName: string; error: string | null }[]) => {
this.setImportResults(importResults);
this.setLoading(false);
this.featureFlagsService.fetchFeatureFlags(true);
},
error: (error) => {
console.log('Error importing feature flags', error);
// this.setError(error.message || 'An error occurred');
this.setLoading(false);
},
})
)
)
);
});

readonly importFeatureFlagList = this.effect(
(
params$: Observable<{
fileData: IFeatureFlagFile[];
flagId: string;
listType: FEATURE_FLAG_LIST_FILTER_MODE;
}>
) => {
return params$.pipe(
tap(() => this.setLoadingImportFeatureFlag(true)),
switchMap(({ fileData, flagId, listType }) =>
this.featureFlagsDataService.importFeatureFlagList(fileData, flagId, listType).pipe(
tap({
next: (importResults: { fileName: string; error: string | null }[]) => {
this.setImportResults(importResults);
this.setLoadingImportFeatureFlag(false);
},
error: (error) => {
console.error('Error importing feature flag list:', error);
this.setLoadingImportFeatureFlag(false);
},
})
)
)
);
}
);

readonly validateFeatureFlags = this.effect((featureFlagFiles$: Observable<IFeatureFlagFile[]>) => {
return featureFlagFiles$.pipe(
tap(() => this.setLoading(true)),
switchMap((files) =>
this.featureFlagsDataService.validateFeatureFlag({ files }).pipe(
tap({
next: (validationErrors: ValidateFeatureFlagError[]) => {
this.setValidationErrors(validationErrors);
this.setLoading(false);
},
error: (error) => {
console.error('Error during validation:', error);
this.setLoading(false);
},
})
)
)
);
});

readonly validateFeatureFlagList = this.effect(
(
params$: Observable<{
fileData: IFeatureFlagFile[];
flagId: string;
listType: FEATURE_FLAG_LIST_FILTER_MODE;
}>
) => {
return params$.pipe(
tap(() => this.setLoading(true)),
switchMap(({ fileData, flagId, listType }) =>
this.featureFlagsDataService.validateFeatureFlagList(fileData, flagId, listType).pipe(
tap({
next: (validationErrors: ValidateFeatureFlagError[]) => {
this.setValidationErrors(validationErrors);
this.setLoading(false);
},
error: (error) => {
console.error('Error validating feature flag list:', error);
this.setLoading(false);
},
})
)
)
);
}
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
(primaryActionBtnClicked)="importFiles()"
>
<div class="drag-drop-container">
<ng-container *ngIf="!(isLoadingImportFeatureFlag$ | async); else loadingSpinner">
<ng-container *ngIf="(uploadedFileCount | async) < 1; else validationTable">
<ng-container *ngIf="!(isLoadingImportFeatureFlag$()); else loadingSpinner">
<ng-container *ngIf="uploadedFileCount() < 1; else validationTable">
<app-common-import-container
fileType=".json"
buttonLabel="Choose JSON"
Expand Down
Loading
Loading