From 9c9c581f65981fc8060eee59e9d99c4aa2ba62ed Mon Sep 17 00:00:00 2001 From: Benjamin Blanchard Date: Fri, 20 Sep 2024 16:55:41 -0400 Subject: [PATCH 1/2] Include feature flags in segment status --- .../FeatureFlagSegmentExclusionRepository.ts | 2 +- .../FeatureFlagSegmentInclusionRepository.ts | 2 +- .../src/api/services/SegmentService.ts | 41 ++++++++++- .../test/unit/services/SegmentService.test.ts | 71 +++++++++++++++++-- .../local-storage/local-storage.service.ts | 2 + .../src/app/core/segments/segments.service.ts | 4 ++ .../core/segments/store/segments.actions.ts | 10 ++- .../segments/store/segments.effects.spec.ts | 4 ++ .../core/segments/store/segments.effects.ts | 2 + .../app/core/segments/store/segments.model.ts | 17 +++++ .../segments/store/segments.reducer.spec.ts | 2 + .../core/segments/store/segments.reducer.ts | 15 +++- .../segments/store/segments.selectors.spec.ts | 22 ++++++ .../core/segments/store/segments.selectors.ts | 10 +++ .../segment-experiment-list.component.html | 22 +++--- .../segment-experiment-list.component.scss | 8 +-- .../segment-experiment-list.component.ts | 50 ++++++++++++- .../pipes/experiment-state.pipe.spec.ts | 17 +++++ .../app/shared/pipes/experiment-state.pipe.ts | 10 ++- .../projects/upgrade/src/assets/i18n/en.json | 6 +- 20 files changed, 284 insertions(+), 33 deletions(-) diff --git a/backend/packages/Upgrade/src/api/repositories/FeatureFlagSegmentExclusionRepository.ts b/backend/packages/Upgrade/src/api/repositories/FeatureFlagSegmentExclusionRepository.ts index 0fa0eb7373..e274e8eb25 100644 --- a/backend/packages/Upgrade/src/api/repositories/FeatureFlagSegmentExclusionRepository.ts +++ b/backend/packages/Upgrade/src/api/repositories/FeatureFlagSegmentExclusionRepository.ts @@ -38,7 +38,7 @@ export class FeatureFlagSegmentExclusionRepository extends Repository { const connection = this.dataSource.manager.connection; const segmentsDataWithStatus = await connection.transaction(async () => { - const [allExperimentSegmentsInclusion, allExperimentSegmentsExclusion] = await Promise.all([ + const [ + allExperimentSegmentsInclusion, + allExperimentSegmentsExclusion, + allFeatureFlagSegmentsInclusion, + allFeatureFlagSegmentsExclusion, + ] = await Promise.all([ this.getExperimentSegmentInclusionData(), this.getExperimentSegmentExclusionData(), + this.getFeatureFlagSegmentInclusionData(), + this.getFeatureFlagSegmentExclusionData(), ]); const segmentMap = new Map(); @@ -183,6 +196,20 @@ export class SegmentService { }); } + if (allFeatureFlagSegmentsInclusion) { + allFeatureFlagSegmentsInclusion.forEach((ele) => { + collectSegmentIds(ele.segment.id); + ele.segment.subSegments.forEach((subSegment) => collectSegmentIds(subSegment.id)); + }); + } + + if (allFeatureFlagSegmentsExclusion) { + allFeatureFlagSegmentsExclusion.forEach((ele) => { + collectSegmentIds(ele.segment.id); + ele.segment.subSegments.forEach((subSegment) => collectSegmentIds(subSegment.id)); + }); + } + const segmentsDataWithStatus = segmentsData.map((segment) => { if (segment.id === globalExcludeSegment.id) { return { ...segment, status: SEGMENT_STATUS.GLOBAL }; @@ -197,6 +224,8 @@ export class SegmentService { segmentsData: segmentsDataWithStatus, experimentSegmentInclusionData: allExperimentSegmentsInclusion, experimentSegmentExclusionData: allExperimentSegmentsExclusion, + featureFlagSegmentInclusionData: allFeatureFlagSegmentsInclusion, + featureFlagSegmentExclusionData: allFeatureFlagSegmentsExclusion, }; }); @@ -213,6 +242,16 @@ export class SegmentService { return queryBuilder; } + public async getFeatureFlagSegmentExclusionData() { + const queryBuilder = await this.featureFlagSegmentExclusionRepository.getFeatureFlagSegmentExclusionData(); + return queryBuilder; + } + + public async getFeatureFlagSegmentInclusionData() { + const queryBuilder = await this.featureFlagSegmentInclusionRepository.getFeatureFlagSegmentInclusionData(); + return queryBuilder; + } + public upsertSegment(segment: SegmentInputValidator, logger: UpgradeLogger): Promise { logger.info({ message: `Upsert segment => ${JSON.stringify(segment, undefined, 2)}` }); return this.addSegmentDataInDB(segment, logger); diff --git a/backend/packages/Upgrade/test/unit/services/SegmentService.test.ts b/backend/packages/Upgrade/test/unit/services/SegmentService.test.ts index 75e62b8020..8c8d821321 100644 --- a/backend/packages/Upgrade/test/unit/services/SegmentService.test.ts +++ b/backend/packages/Upgrade/test/unit/services/SegmentService.test.ts @@ -9,18 +9,24 @@ import { IndividualForSegmentRepository } from '../../../src/api/repositories/In import { GroupForSegmentRepository } from '../../../src/api/repositories/GroupForSegmentRepository'; import { ExperimentSegmentExclusionRepository } from '../../../src/api/repositories/ExperimentSegmentExclusionRepository'; import { ExperimentSegmentInclusionRepository } from '../../../src/api/repositories/ExperimentSegmentInclusionRepository'; +import { FeatureFlagSegmentExclusionRepository } from '../../../src/api/repositories/FeatureFlagSegmentExclusionRepository'; +import { FeatureFlagSegmentInclusionRepository } from '../../../src/api/repositories/FeatureFlagSegmentInclusionRepository'; import { CacheService } from '../../../src/api/services/CacheService'; import { SegmentFile, SegmentInputValidator } from '../../../src/api/controllers/validators/SegmentInputValidator'; import { IndividualForSegment } from '../../../src/api/models/IndividualForSegment'; import { GroupForSegment } from '../../../src/api/models/GroupForSegment'; import { Experiment } from '../../../src/api/models/Experiment'; -import { SEGMENT_TYPE, SERVER_ERROR, EXPERIMENT_STATE } from 'upgrade_types'; +import { FeatureFlag } from '../../../src/api/models/FeatureFlag'; +import { SEGMENT_TYPE, SERVER_ERROR, EXPERIMENT_STATE, FEATURE_FLAG_STATUS } from 'upgrade_types'; import { configureLogger } from '../../utils/logger'; import { ExperimentSegmentExclusion } from '../../../src/api/models/ExperimentSegmentExclusion'; import { ExperimentSegmentInclusion } from '../../../src/api/models/ExperimentSegmentInclusion'; +import { FeatureFlagSegmentExclusion } from '../../../src/api/models/FeatureFlagSegmentExclusion'; +import { FeatureFlagSegmentInclusion } from '../../../src/api/models/FeatureFlagSegmentInclusion'; import { Container } from '../../../src/typeorm-typedi-extensions'; const exp = new Experiment(); +const ff = new FeatureFlag(); const seg2 = new Segment(); const seg1 = new Segment(); const segValSegment = new Segment(); @@ -28,6 +34,7 @@ const logger = new UpgradeLogger(); const segmentArr = [seg1, seg2]; const segVal = new SegmentInputValidator(); const include = [{ segment: seg1, experiment: exp }]; +const ff_include = [{ segment: seg1, featureFlag: ff }]; const segValImportFile: SegmentFile = { fileName: 'seg1.json', fileContent: '', @@ -65,6 +72,8 @@ describe('Segment Service Testing', () => { Segment, ExperimentSegmentExclusion, ExperimentSegmentInclusion, + FeatureFlagSegmentExclusion, + FeatureFlagSegmentInclusion, ], synchronize: true, }); @@ -75,6 +84,8 @@ describe('Segment Service Testing', () => { exp.id = 'exp1'; exp.state = EXPERIMENT_STATE.ENROLLING; + ff.id = 'ff1'; + ff.status = FEATURE_FLAG_STATUS.ENABLED; seg2.subSegments = []; seg2.id = 'seg2'; seg2.subSegments = []; @@ -133,6 +144,8 @@ describe('Segment Service Testing', () => { GroupForSegmentRepository, ExperimentSegmentExclusionRepository, ExperimentSegmentInclusionRepository, + FeatureFlagSegmentExclusionRepository, + FeatureFlagSegmentInclusionRepository, CacheService, SegmentRepository, { @@ -190,6 +203,36 @@ describe('Segment Service Testing', () => { })), }, }, + { + provide: getRepositoryToken(FeatureFlagSegmentExclusionRepository), + useValue: { + findOne: jest.fn().mockResolvedValue(seg1), + save: jest.fn().mockResolvedValue(seg1), + delete: jest.fn(), + getFeatureFlagSegmentExclusionData: jest.fn().mockResolvedValue(ff_include), + createQueryBuilder: jest.fn(() => ({ + insert: jest.fn().mockReturnThis(), + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue(segmentArr), + })), + }, + }, + { + provide: getRepositoryToken(FeatureFlagSegmentInclusionRepository), + useValue: { + findOne: jest.fn().mockResolvedValue(seg1), + save: jest.fn().mockResolvedValue(seg1), + delete: jest.fn(), + getFeatureFlagSegmentInclusionData: jest.fn().mockResolvedValue(ff_include), + createQueryBuilder: jest.fn(() => ({ + insert: jest.fn().mockReturnThis(), + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue(segmentArr), + })), + }, + }, { provide: getRepositoryToken(IndividualForSegmentRepository), useValue: { @@ -254,7 +297,7 @@ describe('Segment Service Testing', () => { expect(segments).toEqual([seg1]); }); - it('should return all segments with status for enrolling experiments', async () => { + it('should return all segments with status for enrolling experiments and feature flags', async () => { const res = { segmentsData: [ { @@ -269,12 +312,14 @@ describe('Segment Service Testing', () => { ], experimentSegmentExclusionData: [{ experiment: exp, segment: seg1 }], experimentSegmentInclusionData: [{ experiment: exp, segment: seg1 }], + featureFlagSegmentExclusionData: [{ featureFlag: ff, segment: seg1 }], + featureFlagSegmentInclusionData: [{ featureFlag: ff, segment: seg1 }], }; const segments = await service.getAllSegmentWithStatus(logger); expect(segments).toEqual(res); }); - it('should return all segments with status for not enrolling experiments', async () => { + it('should return all segments with status for not enrolling experiments and feature flags', async () => { exp.state = EXPERIMENT_STATE.ENROLLMENT_COMPLETE; const res = { segmentsData: [ @@ -290,6 +335,8 @@ describe('Segment Service Testing', () => { ], experimentSegmentExclusionData: [{ experiment: exp, segment: seg1 }], experimentSegmentInclusionData: [{ experiment: exp, segment: seg1 }], + featureFlagSegmentExclusionData: [{ featureFlag: ff, segment: seg1 }], + featureFlagSegmentInclusionData: [{ featureFlag: ff, segment: seg1 }], }; const segments = await service.getAllSegmentWithStatus(logger); expect(segments).toEqual(res); @@ -324,21 +371,33 @@ describe('Segment Service Testing', () => { ], experimentSegmentExclusionData: [{ experiment: exp, segment: seg1 }], experimentSegmentInclusionData: [{ experiment: exp, segment: seg1 }], + featureFlagSegmentExclusionData: [{ featureFlag: ff, segment: seg1 }], + featureFlagSegmentInclusionData: [{ featureFlag: ff, segment: seg1 }], }; const segments = await service.getAllSegmentWithStatus(logger); expect(segments).toEqual(res); }); - it('should return segment exclusion data', async () => { + it('should return experiment segment exclusion data', async () => { const segments = await service.getExperimentSegmentExclusionData(); expect(segments).toEqual(include); }); - it('should return segment inclusion data', async () => { + it('should return experiment segment inclusion data', async () => { const segments = await service.getExperimentSegmentInclusionData(); expect(segments).toEqual(include); }); + it('should return feture flag segment exclusion data', async () => { + const segments = await service.getFeatureFlagSegmentExclusionData(); + expect(segments).toEqual(ff_include); + }); + + it('should return feature flag segment inclusion data', async () => { + const segments = await service.getFeatureFlagSegmentInclusionData(); + expect(segments).toEqual(ff_include); + }); + it('should upsert a segment', async () => { const segments = await service.upsertSegment(segVal, logger); expect(segments).toEqual(seg1); @@ -426,4 +485,4 @@ describe('Segment Service Testing', () => { await service.exportSegments([seg1.id], logger); }).rejects.toThrow(new Error(SERVER_ERROR.QUERY_FAILED)); }); -}); \ No newline at end of file +}); diff --git a/frontend/projects/upgrade/src/app/core/local-storage/local-storage.service.ts b/frontend/projects/upgrade/src/app/core/local-storage/local-storage.service.ts index e5883ea40e..4aaf08d8b7 100755 --- a/frontend/projects/upgrade/src/app/core/local-storage/local-storage.service.ts +++ b/frontend/projects/upgrade/src/app/core/local-storage/local-storage.service.ts @@ -57,6 +57,8 @@ export class LocalStorageService { isLoadingSegments: false, allExperimentSegmentsInclusion: null, allExperimentSegmentsExclusion: null, + allFeatureFlagSegmentsInclusion: null, + allFeatureFlagSegmentsExclusion: null, searchKey: segmentSearchKey as SEGMENT_SEARCH_KEY, searchString: segmentSearchString || null, sortKey: (segmentSortKey as SEGMENT_SORT_KEY) || SEGMENT_SORT_KEY.NAME, diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.service.ts b/frontend/projects/upgrade/src/app/core/segments/segments.service.ts index d2c17b6dcd..a25de0df65 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.service.ts @@ -8,6 +8,8 @@ import { selectSelectedSegment, selectExperimentSegmentsInclusion, selectExperimentSegmentsExclusion, + selectFeatureFlagSegmentsInclusion, + selectFeatureFlagSegmentsExclusion, selectSegmentById, selectSearchString, selectSearchKey, @@ -47,6 +49,8 @@ export class SegmentsService { selectSegmentSortAs$ = this.store$.pipe(select(selectSortAs)); allExperimentSegmentsInclusion$ = this.store$.pipe(select(selectExperimentSegmentsInclusion)); allExperimentSegmentsExclusion$ = this.store$.pipe(select(selectExperimentSegmentsExclusion)); + allFeatureFlagSegmentsExclusion$ = this.store$.pipe(select(selectFeatureFlagSegmentsExclusion)); + allFeatureFlagSegmentsInclusion$ = this.store$.pipe(select(selectFeatureFlagSegmentsInclusion)); selectSearchSegmentParams(): Observable> { return combineLatest([this.selectSearchKey$, this.selectSearchString$]).pipe( diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.actions.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.actions.ts index 0ccc32f9a6..c7234c3dd5 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.actions.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.actions.ts @@ -1,5 +1,11 @@ import { createAction, props } from '@ngrx/store'; -import { Segment, SegmentInput, UpsertSegmentType, experimentSegmentInclusionExclusionData } from './segments.model'; +import { + Segment, + SegmentInput, + UpsertSegmentType, + experimentSegmentInclusionExclusionData, + featureFlagSegmentInclusionExclusionData, +} from './segments.model'; import { SEGMENT_SEARCH_KEY, SORT_AS_DIRECTION, @@ -14,6 +20,8 @@ export const actionFetchSegmentsSuccess = createAction( segments: Segment[]; experimentSegmentInclusion: experimentSegmentInclusionExclusionData[]; experimentSegmentExclusion: experimentSegmentInclusionExclusionData[]; + featureFlagSegmentInclusion: featureFlagSegmentInclusionExclusionData[]; + featureFlagSegmentExclusion: featureFlagSegmentInclusionExclusionData[]; }>() ); diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.spec.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.spec.ts index ca9079206f..c65f2663a0 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.spec.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.spec.ts @@ -61,6 +61,8 @@ describe('SegmentsEffects', () => { segmentsData: [{ ...mockSegment }], experimentSegmentInclusionData: [], experimentSegmentExclusionData: [], + featureFlagSegmentInclusionData: [], + featureFlagSegmentExclusionData: [], }) ); selectAllSegments.setResult([{ ...mockSegment }]); @@ -69,6 +71,8 @@ describe('SegmentsEffects', () => { segments: [{ ...mockSegment }], experimentSegmentInclusion: [], experimentSegmentExclusion: [], + featureFlagSegmentInclusion: [], + featureFlagSegmentExclusion: [], }); service.fetchSegments$.subscribe((result) => { diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts index b7fc857558..635a7d9293 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts @@ -30,6 +30,8 @@ export class SegmentsEffects { segments: data.segmentsData, experimentSegmentInclusion: data.experimentSegmentInclusionData, experimentSegmentExclusion: data.experimentSegmentExclusionData, + featureFlagSegmentInclusion: data.featureFlagSegmentInclusionData, + featureFlagSegmentExclusion: data.featureFlagSegmentExclusionData, }) ), catchError(() => [SegmentsActions.actionFetchSegmentsFailure()]) 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 a7beade03c..65078e1dfc 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 @@ -54,6 +54,21 @@ export interface experimentSegmentInclusionExclusionData { }; } +export interface featureFlagSegmentInclusionExclusionData { + createdAt: string; + updatedAt: string; + versionNumber: number; + featureFlag: { + name: string; + context: any[]; + status: string; + }; + segment: { + id: string; + subSegments: any[]; + }; +} + export interface Group { groupId: string; type: string; @@ -107,6 +122,8 @@ export interface SegmentState extends EntityState { // TODO: remove any allExperimentSegmentsInclusion: any; allExperimentSegmentsExclusion: any; + allFeatureFlagSegmentsInclusion: any; + allFeatureFlagSegmentsExclusion: any; searchKey: SEGMENT_SEARCH_KEY; searchString: string; sortKey: SEGMENT_SORT_KEY; diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.reducer.spec.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.reducer.spec.ts index 9287e229e1..ba24517bf4 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.reducer.spec.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.reducer.spec.ts @@ -32,6 +32,8 @@ describe('SegmentsReducer', () => { segments: [], experimentSegmentExclusion: [], experimentSegmentInclusion: [], + featureFlagSegmentExclusion: [], + featureFlagSegmentInclusion: [], }); const newState = segmentsReducer(previousState, testAction); diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.reducer.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.reducer.ts index af29a45243..1522861b8e 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.reducer.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.reducer.ts @@ -16,6 +16,8 @@ export const initialState: SegmentState = adapter.getInitialState({ isLoadingSegments: false, allExperimentSegmentsInclusion: null, allExperimentSegmentsExclusion: null, + allFeatureFlagSegmentsInclusion: null, + allFeatureFlagSegmentsExclusion: null, searchKey: SEGMENT_SEARCH_KEY.ALL, searchString: null, sortKey: SEGMENT_SORT_KEY.NAME, @@ -30,12 +32,23 @@ const reducer = createReducer( })), on( SegmentsActions.actionFetchSegmentsSuccess, - (state, { segments, experimentSegmentExclusion, experimentSegmentInclusion }) => { + ( + state, + { + segments, + experimentSegmentExclusion, + experimentSegmentInclusion, + featureFlagSegmentInclusion, + featureFlagSegmentExclusion, + } + ) => { const newState = { ...state, segments, allExperimentSegmentsInclusion: experimentSegmentInclusion, allExperimentSegmentsExclusion: experimentSegmentExclusion, + allFeatureFlagSegmentsInclusion: featureFlagSegmentInclusion, + allFeatureFlagSegmentsExclusion: featureFlagSegmentExclusion, }; return adapter.upsertMany(segments, { ...newState, isLoadingSegments: false }); } diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.selectors.spec.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.selectors.spec.ts index 3f13c2ac73..cefa386b06 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.selectors.spec.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.selectors.spec.ts @@ -39,6 +39,28 @@ describe('SegmentSelectors', () => { }); }); + describe('#selectFeatureFlagSegmentsInclusion', () => { + it('should return any from allFeatureFlagSegmentsInclusion', () => { + const previousState = { ...mockState }; + previousState.allFeatureFlagSegmentsInclusion = []; + + const result = SegmentSelectors.selectFeatureFlagSegmentsInclusion.projector(previousState); + + expect(result).toEqual([]); + }); + }); + + describe('#selectFeatureFlagSegmentsExclusion', () => { + it('should return any from allFeatureFlagSegmentsExclusion', () => { + const previousState = { ...mockState }; + previousState.allFeatureFlagSegmentsExclusion = []; + + const result = SegmentSelectors.selectFeatureFlagSegmentsExclusion.projector(previousState); + + expect(result).toEqual([]); + }); + }); + describe('#selectSelectedSegment', () => { it('should return undefined from allExperimentSegmentsExclusion if segmentId is not an entity key', () => { const previousState = { ...mockState }; diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.selectors.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.selectors.ts index 1a674d6e2a..d3a5125433 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.selectors.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.selectors.ts @@ -27,6 +27,16 @@ export const selectExperimentSegmentsExclusion = createSelector( (state) => state.allExperimentSegmentsExclusion ); +export const selectFeatureFlagSegmentsInclusion = createSelector( + selectSegmentsState, + (state) => state.allFeatureFlagSegmentsInclusion +); + +export const selectFeatureFlagSegmentsExclusion = createSelector( + selectSegmentsState, + (state) => state.allFeatureFlagSegmentsExclusion +); + export const selectSelectedSegment = createSelector( selectRouterState, selectSegmentsState, diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.html b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.html index 91833587d7..1c20163707 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.html @@ -1,7 +1,7 @@
- {{ 'segments.segment-experiment-list-title.text' | translate: { segmentName: data.segment.name } }} + {{ 'segments.segment-experiment-list-title.text' | translate: { numberOfUses: segmentsExperimentList?.length } }} @@ -19,6 +19,16 @@ + + + + {{ 'segments.segment-experiment-list-type.text' | translate }} + + + {{ element.type }} + + + @@ -60,16 +70,6 @@ - - - - {{ 'segments.segment-experiment-list-context.text' | translate }} - - - {{ element.experimentContext }} - - - diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.scss index 8a57151f6f..950fdaabf5 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.scss +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.scss @@ -52,12 +52,12 @@ $experiment-state-light-color: #8f9bb3; flex: 0 0 30%; justify-content: left; } - .cdk-column-experimentState { - flex: 0 0 30%; + .cdk-column-type { + flex: 0 0 15%; justify-content: left; } - .cdk-column-experimentContext { - flex: 0 0 15%; + .cdk-column-experimentState { + flex: 0 0 30%; justify-content: left; } .cdk-column-usedList { diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.ts index e46c691631..baae6d3105 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.ts @@ -11,12 +11,16 @@ import { EXPERIMENT_STATE } from '../../../../../../core/experiments/store/exper changeDetection: ChangeDetectionStrategy.OnPush, }) export class SegmentExperimentListComponent implements OnInit { - segmentExperimentListDisplayedColumns = ['experimentName', 'experimentState', 'experimentContext', 'usedList']; + segmentExperimentListDisplayedColumns = ['experimentName', 'type', 'experimentState', 'usedList']; segment: any; allExperimentSegmentsInclusionSub: Subscription; allExperimentSegmentsExclusionSub: Subscription; + allFeatureFlagSegmentsInclusionSub: Subscription; + allFeatureFlagSegmentsExclusionSub: Subscription; allExperimentSegmentsInclusion = []; allExperimentSegmentsExclusion = []; + allFeatureFlagSegmentsInclusion = []; + allFeatureFlagSegmentsExclusion = []; segmentsExperimentList = []; constructor( @@ -44,6 +48,14 @@ export class SegmentExperimentListComponent implements OnInit { this.allExperimentSegmentsExclusion = ele; }); + this.allFeatureFlagSegmentsInclusionSub = this.segmentsService.allFeatureFlagSegmentsInclusion$.subscribe((ele) => { + this.allFeatureFlagSegmentsInclusion = ele; + }); + + this.allFeatureFlagSegmentsExclusionSub = this.segmentsService.allFeatureFlagSegmentsExclusion$.subscribe((ele) => { + this.allFeatureFlagSegmentsExclusion = ele; + }); + if (this.allExperimentSegmentsInclusion) { this.allExperimentSegmentsInclusion.forEach((ele) => { const subSegments = ele.segment.subSegments; @@ -51,8 +63,8 @@ export class SegmentExperimentListComponent implements OnInit { if (subSegment.id === this.segment.id) { this.segmentsExperimentList.push({ experimentName: ele.experiment.name, + type: 'Experiment', experimentState: ele.experiment.state, - experimentContext: ele.experiment.context, usedList: 'Inclusion', }); } @@ -67,8 +79,40 @@ export class SegmentExperimentListComponent implements OnInit { if (subSegment.id === this.segment.id) { this.segmentsExperimentList.push({ experimentName: ele.experiment.name, + type: 'Experiment', experimentState: ele.experiment.state, - experimentContext: ele.experiment.context, + usedList: 'Exclusion', + }); + } + }); + }); + } + + if (this.allFeatureFlagSegmentsInclusion) { + this.allFeatureFlagSegmentsInclusion.forEach((ele) => { + const subSegments = ele.segment.subSegments; + subSegments.forEach((subSegment) => { + if (subSegment.id === this.segment.id) { + this.segmentsExperimentList.push({ + experimentName: ele.featureFlag.name, + type: 'Feature Flag', + experimentState: ele.featureFlag.status, + usedList: 'Inclusion', + }); + } + }); + }); + } + + if (this.allFeatureFlagSegmentsExclusion) { + this.allFeatureFlagSegmentsExclusion.forEach((ele) => { + const subSegments = ele.segment.subSegments; + subSegments.forEach((subSegment) => { + if (subSegment.id === this.segment.id) { + this.segmentsExperimentList.push({ + experimentName: ele.featureFlag.name, + type: 'Feature Flag', + experimentState: ele.featureFlag.status, usedList: 'Exclusion', }); } diff --git a/frontend/projects/upgrade/src/app/shared/pipes/experiment-state.pipe.spec.ts b/frontend/projects/upgrade/src/app/shared/pipes/experiment-state.pipe.spec.ts index d3ba91f6b7..c549654e9d 100644 --- a/frontend/projects/upgrade/src/app/shared/pipes/experiment-state.pipe.spec.ts +++ b/frontend/projects/upgrade/src/app/shared/pipes/experiment-state.pipe.spec.ts @@ -1,5 +1,6 @@ import { ExperimentStatePipe, ExperimentStatePipeType } from './experiment-state.pipe'; import { EXPERIMENT_STATE } from 'upgrade_types'; +import { FEATURE_FLAG_STATUS } from 'upgrade_types'; describe('ExperimentStatePipe', () => { const experimentStatePipe = new ExperimentStatePipe(); @@ -32,6 +33,14 @@ describe('ExperimentStatePipe', () => { expect(experimentStatePipe.transform(EXPERIMENT_STATE.ARCHIVED)).toBe('Archived'); }); + it('should return Inactive State', () => { + expect(experimentStatePipe.transform(FEATURE_FLAG_STATUS.DISABLED)).toBe('Disabled'); + }); + + it('should return Archived State', () => { + expect(experimentStatePipe.transform(FEATURE_FLAG_STATUS.ENABLED)).toBe('Enabled'); + }); + it('should return #000 color for Preview State', () => { expect(experimentStatePipe.transform(EXPERIMENT_STATE.PREVIEW, ExperimentStatePipeType.COLOR)).toBe('#000'); }); @@ -61,4 +70,12 @@ describe('ExperimentStatePipe', () => { it('should return #fd9099 color for Archived State', () => { expect(experimentStatePipe.transform(EXPERIMENT_STATE.ARCHIVED, ExperimentStatePipeType.COLOR)).toBe('#fd9099'); }); + + it('should return #d8d8d8 color for Disabled Status', () => { + expect(experimentStatePipe.transform(FEATURE_FLAG_STATUS.DISABLED, ExperimentStatePipeType.COLOR)).toBe('#d8d8d8'); + }); + + it('should return #fd9099 color for Archived State', () => { + expect(experimentStatePipe.transform(FEATURE_FLAG_STATUS.ENABLED, ExperimentStatePipeType.COLOR)).toBe('#7b9cff'); + }); }); diff --git a/frontend/projects/upgrade/src/app/shared/pipes/experiment-state.pipe.ts b/frontend/projects/upgrade/src/app/shared/pipes/experiment-state.pipe.ts index 7fe5d486f4..cf134c78d6 100644 --- a/frontend/projects/upgrade/src/app/shared/pipes/experiment-state.pipe.ts +++ b/frontend/projects/upgrade/src/app/shared/pipes/experiment-state.pipe.ts @@ -1,5 +1,6 @@ import { Pipe, PipeTransform } from '@angular/core'; import { EXPERIMENT_STATE } from '../../core/experiments/store/experiments.model'; +import { FEATURE_FLAG_STATUS } from 'upgrade_types'; export enum ExperimentStatePipeType { TEXT = 'text', @@ -10,7 +11,10 @@ export enum ExperimentStatePipeType { name: 'experimentState', }) export class ExperimentStatePipe implements PipeTransform { - transform(experimentState: EXPERIMENT_STATE, type: ExperimentStatePipeType = ExperimentStatePipeType.TEXT): any { + transform( + experimentState: EXPERIMENT_STATE | FEATURE_FLAG_STATUS, + type: ExperimentStatePipeType = ExperimentStatePipeType.TEXT + ): any { switch (experimentState) { case EXPERIMENT_STATE.PREVIEW: return type === ExperimentStatePipeType.TEXT ? 'Preview' : '#000'; @@ -26,6 +30,10 @@ export class ExperimentStatePipe implements PipeTransform { return type === ExperimentStatePipeType.TEXT ? 'Cancelled' : '#ff0000'; case EXPERIMENT_STATE.ARCHIVED: return type === ExperimentStatePipeType.TEXT ? 'Archived' : '#fd9099'; + case FEATURE_FLAG_STATUS.ENABLED: + return type === ExperimentStatePipeType.TEXT ? 'Enabled' : '#7b9cff'; + case FEATURE_FLAG_STATUS.DISABLED: + return type === ExperimentStatePipeType.TEXT ? 'Disabled' : '#fd9099'; } } } diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index aecdcc301e..eee91b5594 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -462,10 +462,10 @@ "segments.import-segment.text": "IMPORT SEGMENT", "segments.import-segment.message.text": "Select the JSON file(s) to import segments:", "segments.import-segment.error.message.text": "Invalid Segment JSON data", - "segments.segment-experiment-list-title.text": "Experiments using '{{ segmentName }}' ", + "segments.segment-experiment-list-title.text": "Used by ({{ numberOfUses }}) ", "segments.segment-experiment-list-name.text": "Name", - "segments.segment-experiment-list-state.text": "State", - "segments.segment-experiment-list-context.text": "Context", + "segments.segment-experiment-list-state.text": "Status", + "segments.segment-experiment-list-type.text": "Type", "segments.segment-experiment-list-usedList.text": "Used List", "segments.segment-experiment-list-zeroState.text": "No Data", "stratifications.global-members-factor.text": "FACTOR", From 6cabe2cd42a16f68dd01b56586ee02f1ff731285 Mon Sep 17 00:00:00 2001 From: Benjamin Blanchard Date: Mon, 23 Sep 2024 14:22:01 -0400 Subject: [PATCH 2/2] review changes --- .../segment-experiment-list.component.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.scss index 950fdaabf5..1a8da23d74 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.scss +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/segment-experiment-list/segment-experiment-list.component.scss @@ -53,15 +53,15 @@ $experiment-state-light-color: #8f9bb3; justify-content: left; } .cdk-column-type { - flex: 0 0 15%; + flex: 0 0 20%; justify-content: left; } .cdk-column-experimentState { - flex: 0 0 30%; + flex: 0 0 20%; justify-content: left; } .cdk-column-usedList { - flex: 0 0 15%; + flex: 0 0 20%; justify-content: left; } @@ -82,8 +82,8 @@ $experiment-state-light-color: #8f9bb3; display: inline-block; margin-right: 6px; border-radius: 50%; - width: 12px; - height: 12px; + min-width: 12px; + min-height: 12px; } .icons {