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

consolidate add-edit-duplicate modals #1732

Merged
merged 16 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
FeatureFlag,
FeatureFlagsPaginationInfo,
FeatureFlagsPaginationParams,
UpdateFeatureFlagRequest,
UpdateFeatureFlagStatusRequest,
} from './store/feature-flags.model';
import { Observable } from 'rxjs';
import { FEATURE_FLAG_STATUS, FILTER_MODE } from '../../../../../../../types/src';

@Injectable()
export class FeatureFlagsDataService {
Expand All @@ -18,32 +18,30 @@ export class FeatureFlagsDataService {
fetchFeatureFlagsPaginated(params: FeatureFlagsPaginationParams): Observable<FeatureFlagsPaginationInfo> {
const url = this.environment.api.getPaginatedFlags;
return this.http.post<FeatureFlagsPaginationInfo>(url, params);
// mock
// // return of({ nodes: mockFeatureFlags, total: 2 }).pipe(delay(2000));
}

fetchFeatureFlagById(id: string) {
const url = `${this.environment.api.featureFlag}/${id}`;
return this.http.get(url);
}

addFeatureFlag(params: AddFeatureFlagRequest): Observable<FeatureFlag> {
const url = this.environment.api.featureFlag;
return this.http.post<FeatureFlag>(url, params);
}

updateFeatureFlagStatus(params: UpdateFeatureFlagStatusRequest): Observable<FeatureFlag> {
const url = this.environment.api.updateFlagStatus;
return this.http.post<FeatureFlag>(url, params);
}

deleteFeatureFlag(id: string) {
const url = `${this.environment.api.featureFlag}/${id}`;
return this.http.delete(url);
addFeatureFlag(flag: AddFeatureFlagRequest): Observable<FeatureFlag> {
const url = this.environment.api.featureFlag;
return this.http.post<FeatureFlag>(url, flag);
}

updateFeatureFlag(flag: FeatureFlag): Observable<FeatureFlag> {
updateFeatureFlag(flag: UpdateFeatureFlagRequest): Observable<FeatureFlag> {
const url = `${this.environment.api.featureFlag}/${flag.id}`;
return this.http.put<FeatureFlag>(url, flag);
}

deleteFeatureFlag(id: string) {
const url = `${this.environment.api.featureFlag}/${id}`;
return this.http.delete(url);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,21 @@ import {
selectIsLoadingSelectedFeatureFlag,
selectSortKey,
selectSortAs,
selectFeatureFlagListTypeOptions,
selectAppContexts,
} from './store/feature-flags.selectors';
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,
LIST_OPTION_TYPE,
UpdateFeatureFlagStatusRequest,
AddFeatureFlagRequest,
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 {
Expand All @@ -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));
Expand All @@ -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(),
Expand All @@ -71,20 +75,18 @@ 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));
selectedFlagOverviewDetails = this.store$.pipe(select(selectFeatureFlagOverviewDetails));
selectedFeatureFlag$ = this.store$.pipe(select(selectSelectedFeatureFlag));
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),
Expand All @@ -95,46 +97,6 @@ export class FeatureFlagsService {
select(selectFeatureFlagExclusions),
map((exclusions) => exclusions.length)
);
// note: this comes from experiment service!
selectFeatureFlagListTypeOptions$ = this.store$.pipe(
Yagnik56 marked this conversation as resolved.
Show resolved Hide resolved
select(selectContextMetaData),
withLatestFrom(this.store$.pipe(select(selectSelectedFeatureFlag))),
map(([contextMetaData, flag]) => {
// TODO: straighten out contextmetadata and it's selectors with a dedicated service
const flagAppContext = flag?.context?.[0];
const groupTypes = contextMetaData?.contextMetadata?.[flagAppContext]?.GROUP_TYPES ?? [];
const groupTypeSelectOptions = this.formatGroupTypes(groupTypes as string[]);
const listOptionTypes = [
{
value: LIST_OPTION_TYPE.SEGMENT,
viewValue: LIST_OPTION_TYPE.SEGMENT,
},
{
value: LIST_OPTION_TYPE.INDIVIDUAL,
viewValue: LIST_OPTION_TYPE.INDIVIDUAL,
},
...groupTypeSelectOptions,
];

return listOptionTypes;
})
);

formatGroupTypes(groupTypes: string[]): { value: string; viewValue: string }[] {
if (Array.isArray(groupTypes) && groupTypes.length > 0) {
Yagnik56 marked this conversation as resolved.
Show resolved Hide resolved
return groupTypes.map((groupType) => {
return { value: groupType, viewValue: 'Group: "' + groupType + '"' };
});
} else {
return [];
}
}

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 }));
Expand All @@ -152,7 +114,7 @@ export class FeatureFlagsService {
this.store$.dispatch(FeatureFlagsActions.actionAddFeatureFlag({ addFeatureFlagRequest }));
}

updateFeatureFlag(flag: FeatureFlag) {
updateFeatureFlag(flag: UpdateFeatureFlagRequest) {
this.store$.dispatch(FeatureFlagsActions.actionUpdateFeatureFlag({ flag }));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { createAction, props } from '@ngrx/store';
import { AddFeatureFlagRequest, FeatureFlag, UpdateFeatureFlagStatusRequest } from './feature-flags.model';
import {
FeatureFlag,
UpdateFeatureFlagStatusRequest,
AddFeatureFlagRequest,
UpdateFeatureFlagRequest,
} from './feature-flags.model';
import { FLAG_SEARCH_KEY, FLAG_SORT_KEY, SORT_AS_DIRECTION } from 'upgrade_types';

export const actionFetchFeatureFlags = createAction(
Expand Down Expand Up @@ -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: UpdateFeatureFlagRequest }>()
);

export const actionUpdateFeatureFlagSuccess = createAction(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,56 @@
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 { 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;
}

// 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;
status: FEATURE_FLAG_STATUS;
filterMode: FILTER_MODE;
description?: string;
context: string[];
tags: string[];
featureFlagSegmentInclusion: PrivateSegment | EmptyPrivateSegment;
featureFlagSegmentExclusion: PrivateSegment | EmptyPrivateSegment;
status: FEATURE_FLAG_STATUS;
filterMode: FILTER_MODE;
featureFlagSegmentInclusion: FeatureFlagSegmentListDetails[];
featureFlagSegmentExclusion: FeatureFlagSegmentListDetails[];
}

export interface FeatureFlagsPaginationInfo {
nodes: FeatureFlag[];
total: number;
skip: number;
take: number;
// 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 UpdateFeatureFlagRequest extends AddFeatureFlagRequest {
readonly id: string;
}

export interface AddFeatureFlagRequest {
name: string;
key: string;
description: string;
status: FEATURE_FLAG_STATUS;
context: string[];
tags: string[];
featureFlagSegmentInclusion: PrivateSegment | EmptyPrivateSegment;
featureFlagSegmentExclusion: PrivateSegment | EmptyPrivateSegment;
filterMode: FILTER_MODE;
danoswaltCL marked this conversation as resolved.
Show resolved Hide resolved
export interface FeatureFlagSegmentListDetails {
segment: Segment;
featureFlag: FeatureFlag;
enabled: boolean;
listType: MemberTypes | string;
}

export interface UpdateFeatureFlagStatusRequest {
flagId: string;
status: FEATURE_FLAG_STATUS;
export enum UPSERT_FEATURE_FLAG_ACTION {
ADD = 'add',
EDIT = 'edit',
DUPLICATE = 'duplicate',
}

export interface UpsertFeatureFlagParams {
sourceFlag: FeatureFlag;
action: UPSERT_FEATURE_FLAG_ACTION;
}

export enum UPSERT_FEATURE_FLAG_LIST_ACTION {
Expand All @@ -49,13 +59,20 @@ export enum UPSERT_FEATURE_FLAG_LIST_ACTION {
}

export interface UpsertFeatureFlagListParams {
sourceList: any; // TODO define me
sourceList: FeatureFlagSegmentListDetails;
action: UPSERT_FEATURE_FLAG_LIST_ACTION;
}

export enum LIST_OPTION_TYPE {
INDIVIDUAL = 'Individual',
SEGMENT = 'Segment',
export interface FeatureFlagsPaginationInfo {
nodes: FeatureFlag[];
total: number;
skip: number;
take: number;
}

export interface UpdateFeatureFlagStatusRequest {
flagId: string;
status: FEATURE_FLAG_STATUS;
}

export interface FeatureFlagFormData {
Expand All @@ -76,8 +93,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 {
Expand Down
Loading
Loading