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"
}
}
RidhamShah marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { featureFlagsReducer } from './store/feature-flags.reducer';
import { FeatureFlagsEffects } from './store/feature-flags.effects';
import { FeatureFlagsStore } from '../../features/dashboard/feature-flags/modals/import-feature-flag-modal/feature-flag.signal.store';

@NgModule({
declarations: [],
Expand All @@ -14,6 +15,6 @@ import { FeatureFlagsEffects } from './store/feature-flags.effects';
EffectsModule.forFeature([FeatureFlagsEffects]),
StoreModule.forFeature('featureFlags', featureFlagsReducer),
],
providers: [FeatureFlagsService, FeatureFlagsDataService],
providers: [FeatureFlagsService, FeatureFlagsDataService, FeatureFlagsStore],
})
export class FeatureFlagsModule {}
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