From e1cbd193e6122295a21f83adbf7025d415013133 Mon Sep 17 00:00:00 2001 From: Zack Lee Date: Fri, 4 Apr 2025 22:27:20 +0900 Subject: [PATCH 1/8] Implement Add/Edit/Delete Segment List Modals --- .../core/segments/segments.data.service.ts | 25 ++++- .../src/app/core/segments/segments.service.ts | 14 +++ .../core/segments/store/segments.actions.ts | 42 ++++++++ .../core/segments/store/segments.effects.ts | 38 +++++++ .../app/core/segments/store/segments.model.ts | 9 +- .../core/segments/store/segments.reducer.ts | 99 ++++++++++++++++++- ...-flag-exclusions-section-card.component.ts | 27 +---- ...-flag-inclusions-section-card.component.ts | 2 +- ...rt-private-segment-list-modal.component.ts | 17 +++- .../segment-lists-section-card.component.ts | 38 ++++++- .../shared/services/common-dialog.service.ts | 46 ++++++++- .../projects/upgrade/src/assets/i18n/en.json | 3 + .../src/environments/environment-types.ts | 1 + 13 files changed, 316 insertions(+), 45 deletions(-) diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts index a91714f924..96902fbdc2 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts @@ -1,8 +1,16 @@ import { Inject, Injectable } from '@angular/core'; -import { SegmentFile, SegmentInput, SegmentsPaginationInfo, SegmentsPaginationParams } from './store/segments.model'; +import { + AddPrivateSegmentListRequest, + EditPrivateSegmentListRequest, + SegmentFile, + SegmentInput, + SegmentsPaginationInfo, + SegmentsPaginationParams, +} from './store/segments.model'; import { Observable } from 'rxjs'; import { HttpClient, HttpParams } from '@angular/common/http'; import { ENV, Environment } from '../../../environments/environment-types'; +import { FeatureFlagSegmentListDetails } from '../feature-flags/store/feature-flags.model'; @Injectable() export class SegmentsDataService { @@ -79,4 +87,19 @@ export class SegmentsDataService { const url = this.environment.api.validateSegmentsImport; return this.http.post(url, segments); } + + addSegmentList(list: AddPrivateSegmentListRequest): Observable { + const url = this.environment.api.addSegmentList; + return this.http.post(url, list); + } + + updateSegmentList(list: EditPrivateSegmentListRequest): Observable { + const url = `${this.environment.api.addSegmentList}/${list.segment.id}`; + return this.http.put(url, list); + } + + deleteSegmentList(segmentId: string) { + const url = `${this.environment.api.addSegmentList}/${segmentId}`; + return this.http.delete(url); + } } 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 c8257f2a82..e57d2e90ca 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.service.ts @@ -25,6 +25,8 @@ import { selectGlobalSortAs, } from './store/segments.selectors'; import { + AddPrivateSegmentListRequest, + EditPrivateSegmentListRequest, LIST_OPTION_TYPE, Segment, SegmentInput, @@ -220,4 +222,16 @@ export class SegmentsService { exportSegmentCSV(segmentIds: string[]): Observable { return this.segmentsDataService.exportSegmentCSV(segmentIds); } + + addPrivateSegmentList(list: AddPrivateSegmentListRequest) { + this.store$.dispatch(SegmentsActions.actionAddSegmentList({ list })); + } + + updatePrivateSegmentList(list: EditPrivateSegmentListRequest) { + this.store$.dispatch(SegmentsActions.actionUpdateSegmentList({ list })); + } + + deletePrivateSegmentList(segmentId: string) { + this.store$.dispatch(SegmentsActions.actionDeleteSegmentList({ segmentId })); + } } 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 32d161c3da..e246b0ecf0 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,7 @@ import { createAction, props } from '@ngrx/store'; import { + AddPrivateSegmentListRequest, + EditPrivateSegmentListRequest, Segment, SegmentInput, UpsertSegmentType, @@ -11,6 +13,7 @@ import { SORT_AS_DIRECTION, SEGMENT_SORT_KEY, } from '../../../../../../../../types/src/Experiment/enums'; +import { FeatureFlagSegmentListDetails } from '../../feature-flags/store/feature-flags.model'; export const actionFetchSegments = createAction('[Segments] Segments', props<{ fromStarting?: boolean }>()); @@ -124,3 +127,42 @@ export const actionSetGlobalSortingType = createAction( '[Global Segments] Set Sorting type', props<{ sortingType: SORT_AS_DIRECTION }>() ); + +export const actionAddSegmentList = createAction( + '[Segments] Add Segment List', + props<{ list: AddPrivateSegmentListRequest }>() +); + +export const actionAddSegmentListSuccess = createAction( + '[Segments] Add Segment List Success', + props<{ listResponse: FeatureFlagSegmentListDetails }>() +); + +export const actionAddSegmentListFailure = createAction('[Segments] Add Segment List Failure', props<{ error: any }>()); + +export const actionUpdateSegmentList = createAction( + '[Segments] Update Segment List', + props<{ list: EditPrivateSegmentListRequest }>() +); + +export const actionUpdateSegmentListSuccess = createAction( + '[Segments] Update Segment List Success', + props<{ listResponse: FeatureFlagSegmentListDetails }>() +); + +export const actionUpdateSegmentListFailure = createAction( + '[Segments] Update Segment List Failure', + props<{ error: any }>() +); + +export const actionDeleteSegmentList = createAction('[Segments] Delete Segment List', props<{ segmentId: string }>()); + +export const actionDeleteSegmentListSuccess = createAction( + '[Segments] Delete Segment List Success', + props<{ segmentId: string }>() +); + +export const actionDeleteSegmentListFailure = createAction( + '[Segments] Delete Segment List Failure', + props<{ error: any }>() +); 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 d87f8ea89e..38c44a3190 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 @@ -19,6 +19,7 @@ import { selectTotalSegments, } from './segments.selectors'; import JSZip from 'jszip'; +import { of } from 'rxjs'; @Injectable() export class SegmentsEffects { @@ -234,4 +235,41 @@ export class SegmentsEffects { element.click(); document.body.removeChild(element); } + + addSegmentList$ = createEffect(() => + this.actions$.pipe( + ofType(SegmentsActions.actionAddSegmentList), + switchMap((action) => { + return this.segmentsDataService.addSegmentList(action.list).pipe( + map((listResponse) => SegmentsActions.actionAddSegmentListSuccess({ listResponse })), + catchError((error) => of(SegmentsActions.actionAddSegmentListFailure({ error }))) + ); + }) + ) + ); + + updateSegmentList$ = createEffect(() => + this.actions$.pipe( + ofType(SegmentsActions.actionUpdateSegmentList), + switchMap((action) => { + return this.segmentsDataService.updateSegmentList(action.list).pipe( + map((listResponse) => SegmentsActions.actionUpdateSegmentListSuccess({ listResponse })), + catchError((error) => of(SegmentsActions.actionUpdateSegmentListFailure({ error }))) + ); + }) + ) + ); + + deleteSegmentList$ = createEffect(() => + this.actions$.pipe( + ofType(SegmentsActions.actionDeleteSegmentList), + map((action) => action.segmentId), + switchMap((segmentId) => { + return this.segmentsDataService.deleteSegmentList(segmentId).pipe( + map(() => SegmentsActions.actionDeleteSegmentListSuccess({ segmentId })), + catchError((error) => of(SegmentsActions.actionDeleteSegmentListFailure({ error }))) + ); + }) + ) + ); } 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 77ad548efd..f79a18b980 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 @@ -264,12 +264,7 @@ export interface UpsertPrivateSegmentListParams { sourceList: ParticipantListTableRow; sourceAppContext: string; action: UPSERT_PRIVATE_SEGMENT_LIST_ACTION; - flagId: string; -} - -export interface ImportListParams { - listType: FEATURE_FLAG_LIST_FILTER_MODE; - flagId: string; + id: string; } export enum LIST_OPTION_TYPE { @@ -317,7 +312,7 @@ export interface EditPrivateSegmentListDetails extends PrivateSegmentListRequest } export interface PrivateSegmentListRequest { - flagId: string; + id: string; enabled: boolean; listType: string; segment: AddPrivateSegmentListRequestDetails | EditPrivateSegmentListDetails; 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 f0c01cf2fa..7b1af9bc75 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 @@ -107,7 +107,104 @@ const reducer = createReducer( on(SegmentsActions.actionSetSortKey, (state, { sortKey }) => ({ ...state, sortKey })), on(SegmentsActions.actionSetSortingType, (state, { sortingType }) => ({ ...state, sortAs: sortingType })), on(SegmentsActions.actionDeleteSegmentSuccess, (state, { segment }) => adapter.removeOne(segment.id, state)), - on(SegmentsActions.actionSetIsLoadingSegments, (state, { isLoadingSegments }) => ({ ...state, isLoadingSegments })) + on(SegmentsActions.actionSetIsLoadingSegments, (state, { isLoadingSegments }) => ({ ...state, isLoadingSegments })), + + // Segment List Add Actions + on(SegmentsActions.actionAddSegmentList, (state) => ({ + ...state, + isLoadingSegments: true, + })), + on(SegmentsActions.actionAddSegmentListSuccess, (state, { listResponse }) => { + const { segment } = listResponse; + const parentSegmentId = segment?.id; // This should be the ID of the parent segment + const existingSegment = state.entities[parentSegmentId]; + + if (existingSegment) { + // Create updated subSegments array with the new list/segment + const updatedSubSegments = existingSegment.subSegments + ? [...existingSegment.subSegments, listResponse.segment] + : [listResponse.segment]; + + return adapter.updateOne( + { + id: parentSegmentId, + changes: { subSegments: updatedSubSegments }, + }, + { ...state, isLoadingSegments: false } + ); + } + + return { ...state, isLoadingSegments: false }; + }), + on(SegmentsActions.actionAddSegmentListFailure, (state) => ({ + ...state, + isLoadingSegments: false, + })), + + // Segment List Update Actions + on(SegmentsActions.actionUpdateSegmentList, (state) => ({ + ...state, + isLoadingSegments: true, + })), + on(SegmentsActions.actionUpdateSegmentListSuccess, (state, { listResponse }) => { + const { segment } = listResponse; + const parentSegmentId = segment?.id; // Parent segment ID + const existingSegment = state.entities[parentSegmentId]; + + if (existingSegment && existingSegment.subSegments) { + // Create updated subSegments array replacing the edited segment + const updatedSubSegments = existingSegment.subSegments.map((subSegment) => + subSegment.id === listResponse.segment.id ? listResponse.segment : subSegment + ); + + return adapter.updateOne( + { + id: parentSegmentId, + changes: { subSegments: updatedSubSegments }, + }, + { ...state, isLoadingSegments: false } + ); + } + + return { ...state, isLoadingSegments: false }; + }), + on(SegmentsActions.actionUpdateSegmentListFailure, (state) => ({ + ...state, + isLoadingSegments: false, + })), + + // Segment List Delete Actions + on(SegmentsActions.actionDeleteSegmentList, (state) => ({ + ...state, + isLoadingSegments: true, + })), + on(SegmentsActions.actionDeleteSegmentListSuccess, (state, { segmentId }) => { + // Find the parent segment that contains this subSegment + const parentSegmentId = Object.keys(state.entities).find((id) => + state.entities[id]?.subSegments?.some((subSegment) => subSegment.id === segmentId) + ); + + if (parentSegmentId) { + const parentSegment = state.entities[parentSegmentId]; + + // Filter out the deleted subSegment + const updatedSubSegments = parentSegment.subSegments.filter((subSegment) => subSegment.id !== segmentId); + + return adapter.updateOne( + { + id: parentSegmentId, + changes: { subSegments: updatedSubSegments }, + }, + { ...state, isLoadingSegments: false } + ); + } + + return { ...state, isLoadingSegments: false }; + }), + on(SegmentsActions.actionDeleteSegmentListFailure, (state) => ({ + ...state, + isLoadingSegments: false, + })) ); export function segmentsReducer(state: SegmentState | undefined, action: Action) { diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exclusions-section-card/feature-flag-exclusions-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-exclusions-section-card/feature-flag-exclusions-section-card.component.ts index 995187ea52..754f76899d 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exclusions-section-card/feature-flag-exclusions-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-exclusions-section-card/feature-flag-exclusions-section-card.component.ts @@ -6,7 +6,7 @@ import { } from '../../../../../../../shared-standalone-component-lib/components'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; -import { IMenuButtonItem, SEGMENT_TYPE } from 'upgrade_types'; +import { IMenuButtonItem } from 'upgrade_types'; import { FeatureFlagExclusionsTableComponent } from './feature-flag-exclusions-table/feature-flag-exclusions-table.component'; import { FeatureFlagsService } from '../../../../../../../core/feature-flags/feature-flags.service'; import { DialogService } from '../../../../../../../shared/services/common-dialog.service'; @@ -17,11 +17,7 @@ import { ParticipantListRowActionEvent, ParticipantListTableRow, } from '../../../../../../../core/feature-flags/store/feature-flags.model'; -import { - EditPrivateSegmentListDetails, - EditPrivateSegmentListRequest, - Segment, -} from '../../../../../../../core/segments/store/segments.model'; +import { Segment } from '../../../../../../../core/segments/store/segments.model'; import { UserPermission } from '../../../../../../../core/auth/store/auth.models'; import { Observable } from 'rxjs'; import { AuthService } from '../../../../../../../core/auth/auth.service'; @@ -119,25 +115,6 @@ export class FeatureFlagExclusionsSectionCardComponent { this.dialogService.openEditExcludeListModal(rowData, rowData.segment.context, flagId); } - createEditPrivateSegmentListDetails(segment: Segment): EditPrivateSegmentListDetails { - const editPrivateSegmentListDetails: EditPrivateSegmentListDetails = { - id: segment.id, - name: segment.name, - description: segment.description, - context: segment.context, - type: SEGMENT_TYPE.PRIVATE, - userIds: segment.individualForSegment.map((individual) => individual.userId), - groups: segment.groupForSegment, - subSegmentIds: segment.subSegments.map((subSegment) => subSegment.id), - }; - - return editPrivateSegmentListDetails; - } - - sendUpdateFeatureFlagExclusionRequest(request: EditPrivateSegmentListRequest): void { - this.featureFlagService.updateFeatureFlagExclusionPrivateSegmentList(request); - } - onDeleteExcludeList(segment: Segment): void { this.dialogService .openDeleteExcludeListModal(segment.name) 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-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-inclusions-section-card/feature-flag-inclusions-section-card.component.ts index f2987dac00..385b8342b1 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-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-inclusions-section-card/feature-flag-inclusions-section-card.component.ts @@ -209,7 +209,7 @@ export class FeatureFlagInclusionsSectionCardComponent { const list: EditPrivateSegmentListDetails = this.createEditPrivateSegmentListDetails(segment); const listRequest: EditPrivateSegmentListRequest = { - flagId, + id: flagId, enabled, listType, segment: list, diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts index 78991aee75..8f9296f734 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts @@ -92,6 +92,7 @@ export class UpsertPrivateSegmentListModalComponent { [ UPSERT_PRIVATE_SEGMENT_LIST_ACTION.ADD_FLAG_INCLUDE_LIST, UPSERT_PRIVATE_SEGMENT_LIST_ACTION.ADD_FLAG_EXCLUDE_LIST, + UPSERT_PRIVATE_SEGMENT_LIST_ACTION.ADD_SEGMENT_LIST, ].includes(this.config.params.action) ) { // Slight delay before opening the type select dropdown for a smoother UX @@ -295,7 +296,7 @@ export class UpsertPrivateSegmentListModalComponent { list = this.createRequestByListType(formData, listType); const listRequest: PrivateSegmentListRequest = { - flagId: this.config.params.flagId, + id: this.config.params.id, enabled: this.config.params.sourceList?.enabled || isExcludeList, // Maintain existing status for edits, default to false for new include lists, true for all exclude lists listType, segment: list, @@ -323,6 +324,12 @@ export class UpsertPrivateSegmentListModalComponent { case UPSERT_PRIVATE_SEGMENT_LIST_ACTION.EDIT_FLAG_EXCLUDE_LIST: this.sendUpdateFeatureFlagExclusionRequest(editRequest); break; + case UPSERT_PRIVATE_SEGMENT_LIST_ACTION.ADD_SEGMENT_LIST: + this.sendAddSegmentListRequest(addListRequest); + break; + case UPSERT_PRIVATE_SEGMENT_LIST_ACTION.EDIT_SEGMENT_LIST: + this.sendUpdateSegmentListRequest(editRequest); + break; } } @@ -375,6 +382,14 @@ export class UpsertPrivateSegmentListModalComponent { this.featureFlagService.updateFeatureFlagExclusionPrivateSegmentList(editListRequest); } + sendAddSegmentListRequest(addListRequest: AddPrivateSegmentListRequest): void { + this.segmentsService.addPrivateSegmentList(addListRequest); + } + + sendUpdateSegmentListRequest(editListRequest: EditPrivateSegmentListRequest): void { + this.segmentsService.updatePrivateSegmentList(editListRequest); + } + onDownloadRequested(values: string[]) { if (this.privateSegmentListForm.get('name').valid) { this.downloadValuesAsCSV(values, this.privateSegmentListForm.get('name').value); diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/segment-details-page/segment-details-page-content/segment-lists-section-card/segment-lists-section-card.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/segment-details-page/segment-details-page-content/segment-lists-section-card/segment-lists-section-card.component.ts index 8f8893e6b6..c9cc73a574 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/segment-details-page/segment-details-page-content/segment-lists-section-card/segment-lists-section-card.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/segment-details-page/segment-details-page-content/segment-lists-section-card/segment-lists-section-card.component.ts @@ -9,11 +9,18 @@ import { TranslateModule } from '@ngx-translate/core'; import { IMenuButtonItem } from 'upgrade_types'; import { SegmentsService } from '../../../../../../../core/segments/segments.service'; import { DialogService } from '../../../../../../../shared/services/common-dialog.service'; -import { Segment, SEGMENT_LIST_ACTIONS } from '../../../../../../../core/segments/store/segments.model'; +import { + ParticipantListTableRow, + Segment, + SEGMENT_LIST_ACTIONS, +} from '../../../../../../../core/segments/store/segments.model'; import { Observable } from 'rxjs'; import { AuthService } from '../../../../../../../core/auth/auth.service'; import { UserPermission } from '../../../../../../../core/auth/store/auth.models'; -import { ParticipantListRowActionEvent } from '../../../../../../../core/feature-flags/store/feature-flags.model'; +import { + PARTICIPANT_LIST_ROW_ACTION, + ParticipantListRowActionEvent, +} from '../../../../../../../core/feature-flags/store/feature-flags.model'; import { SegmentListsTableComponent } from './segment-lists-table/segment-lists-table.component'; @Component({ @@ -61,7 +68,7 @@ export class SegmentListsSectionCardComponent { } onAddListClick(appContext: string, segmentId: string) { - // this.dialogService.openAddListModal(appContext, segmentId); + this.dialogService.openAddListModal(appContext, segmentId); } onMenuButtonItemClick(event, segment: Segment) { @@ -82,7 +89,28 @@ export class SegmentListsSectionCardComponent { } onRowAction(event: ParticipantListRowActionEvent, segmentId: string): void { - // This will be implemented later when we implement the table component - console.log('Row action', event, segmentId); + switch (event.action) { + case PARTICIPANT_LIST_ROW_ACTION.EDIT: + this.onEditList(event.rowData, segmentId); + break; + case PARTICIPANT_LIST_ROW_ACTION.DELETE: + this.onDeleteList(event.rowData.segment); + break; + } + } + + onEditList(rowData: ParticipantListTableRow, flagId: string): void { + this.dialogService.openEditListModal(rowData, rowData.segment.context, flagId); + } + + onDeleteList(segment: Segment): void { + this.dialogService + .openDeleteExcludeListModal(segment.name) + .afterClosed() + .subscribe((confirmClicked) => { + if (confirmClicked) { + this.segmentsService.deletePrivateSegmentList(segment.id); + } + }); } } 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 c694f649d3..d6eb8f7130 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 @@ -190,7 +190,7 @@ export class DialogService { sourceList: null, sourceAppContext: appContext, action: UPSERT_PRIVATE_SEGMENT_LIST_ACTION.ADD_FLAG_INCLUDE_LIST, - flagId: flagId, + id: flagId, }, }; return this.openUpsertPrivateSegmentListModal(commonModalConfig); @@ -209,7 +209,7 @@ export class DialogService { sourceList, sourceAppContext: appContext, action: UPSERT_PRIVATE_SEGMENT_LIST_ACTION.EDIT_FLAG_INCLUDE_LIST, - flagId: flagId, + id: flagId, }, }; return this.openUpsertPrivateSegmentListModal(commonModalConfig); @@ -228,7 +228,7 @@ export class DialogService { sourceList: null, sourceAppContext: appContext, action: UPSERT_PRIVATE_SEGMENT_LIST_ACTION.ADD_FLAG_EXCLUDE_LIST, - flagId: flagId, + id: flagId, }, }; return this.openUpsertPrivateSegmentListModal(commonModalConfig); @@ -247,7 +247,45 @@ export class DialogService { sourceList, sourceAppContext: appContext, action: UPSERT_PRIVATE_SEGMENT_LIST_ACTION.EDIT_FLAG_EXCLUDE_LIST, - flagId: flagId, + id: flagId, + }, + }; + return this.openUpsertPrivateSegmentListModal(commonModalConfig); + } + + openAddListModal(appContext: string, segmentId: string) { + const commonModalConfig: CommonModalConfig = { + title: 'Add List', + nameHint: 'segments.upsert-list-modal.name-hint.text', + valuesLabel: 'segments.upsert-list-modal.values-label.text', + valuesPlaceholder: 'segments.upsert-list-modal.values-placeholder.text', + primaryActionBtnLabel: 'Create', + primaryActionBtnColor: 'primary', + cancelBtnLabel: 'Cancel', + params: { + sourceList: null, + sourceAppContext: appContext, + action: UPSERT_PRIVATE_SEGMENT_LIST_ACTION.ADD_SEGMENT_LIST, + id: segmentId, + }, + }; + return this.openUpsertPrivateSegmentListModal(commonModalConfig); + } + + openEditListModal(sourceList: ParticipantListTableRow, appContext: string, segmentId: string) { + const commonModalConfig: CommonModalConfig = { + title: 'Edit List', + nameHint: 'segments.upsert-list-modal.name-hint.text', + valuesLabel: 'segments.upsert-list-modal.values-label.text', + valuesPlaceholder: 'segments.upsert-list-modal.values-placeholder.text', + primaryActionBtnLabel: 'Save', + primaryActionBtnColor: 'primary', + cancelBtnLabel: 'Cancel', + params: { + sourceList, + sourceAppContext: appContext, + action: UPSERT_PRIVATE_SEGMENT_LIST_ACTION.EDIT_SEGMENT_LIST, + id: segmentId, }, }; return this.openUpsertPrivateSegmentListModal(commonModalConfig); diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index d645123567..1ade030928 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -509,6 +509,9 @@ "segments.import-segment-modal.compatibility-description.warning.text": "This JSON file can be imported, but it may contain outdated or missing properties/features. Please review the segment details post-import.", "segments.import-segment.error.message.text": "Invalid Segment JSON data", "segments.export-all-lists-design.confirmation-text.text": "Are you sure you want to export all lists (JSON)?", + "segments.upsert-list-modal.values-label.text": "Values", + "segments.upsert-list-modal.values-placeholder.text": "Values separated by commas", + "segments.upsert-list-modal.name-hint.text": "The name for this list.", "segments.details.lists.card.title.text": "Lists", "segments.details.lists.card.subtitle.text": "Define lists for this segment.", "segments.details.add-list.button.text": "Add List", diff --git a/frontend/projects/upgrade/src/environments/environment-types.ts b/frontend/projects/upgrade/src/environments/environment-types.ts index 595d0b8829..dac820664c 100644 --- a/frontend/projects/upgrade/src/environments/environment-types.ts +++ b/frontend/projects/upgrade/src/environments/environment-types.ts @@ -56,6 +56,7 @@ export interface APIEndpoints { importSegments: string; exportSegments: string; exportSegmentCSV: string; + addSegmentList: string; getGroupAssignmentStatus: string; stratification: string; } From 111e76d6375d85a3a8aa1679d34de8d0da640af6 Mon Sep 17 00:00:00 2001 From: Zack Lee Date: Tue, 8 Apr 2025 19:17:41 +0900 Subject: [PATCH 2/8] Update addSegmentList and updateSegmentList --- .../core/segments/segments.data.service.ts | 57 +++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts index 96902fbdc2..bcedcf0dde 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts @@ -2,12 +2,13 @@ import { Inject, Injectable } from '@angular/core'; import { AddPrivateSegmentListRequest, EditPrivateSegmentListRequest, + Segment, SegmentFile, SegmentInput, SegmentsPaginationInfo, SegmentsPaginationParams, } from './store/segments.model'; -import { Observable } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { HttpClient, HttpParams } from '@angular/common/http'; import { ENV, Environment } from '../../../environments/environment-types'; import { FeatureFlagSegmentListDetails } from '../feature-flags/store/feature-flags.model'; @@ -90,12 +91,60 @@ export class SegmentsDataService { addSegmentList(list: AddPrivateSegmentListRequest): Observable { const url = this.environment.api.addSegmentList; - return this.http.post(url, list); + + // Transform AddPrivateSegmentListRequest to ListInputValidator format + const transformedList = { + parentSegmentId: list.id, + name: list.segment.name, + description: list.segment.description, + context: list.segment.context, + type: list.segment.type, + userIds: list.segment.userIds, + groups: list.segment.groups, + subSegmentIds: list.segment.subSegmentIds, + listType: list.listType, + }; + + // Return type transformation - adapting Segment to FeatureFlagSegmentListDetails + return this.http.post(url, transformedList).pipe( + map((segment: Segment) => { + return { + segment: segment, + featureFlag: null, + enabled: list.enabled, + listType: list.listType, + } as FeatureFlagSegmentListDetails; + }) + ); } updateSegmentList(list: EditPrivateSegmentListRequest): Observable { - const url = `${this.environment.api.addSegmentList}/${list.segment.id}`; - return this.http.put(url, list); + const url = this.environment.api.segments; + + // Transform EditPrivateSegmentListRequest to SegmentInputValidator format + const transformedList = { + id: list.segment.id, + name: list.segment.name, + description: list.segment.description, + context: list.segment.context, + type: list.segment.type, + userIds: list.segment.userIds, + groups: list.segment.groups, + subSegmentIds: list.segment.subSegmentIds, + listType: list.listType, + }; + + // Return type transformation - adapting Segment to FeatureFlagSegmentListDetails + return this.http.post(url, transformedList).pipe( + map((segment: Segment) => { + return { + segment: segment, + featureFlag: null, + enabled: list.enabled, + listType: list.listType, + } as FeatureFlagSegmentListDetails; + }) + ); } deleteSegmentList(segmentId: string) { From 28e379482c1f8644b63e349ab47d91d4b8c03dd1 Mon Sep 17 00:00:00 2001 From: Zack Lee Date: Tue, 8 Apr 2025 21:10:11 +0900 Subject: [PATCH 3/8] Update flagId to id in FeatureFlagListValidator and update populateFormForEdit --- .../validators/FeatureFlagListValidator.ts | 2 +- .../src/api/services/FeatureFlagService.ts | 18 +++++++++--------- .../unit/services/FeatureFlagService.test.ts | 2 +- ...ert-private-segment-list-modal.component.ts | 1 + 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagListValidator.ts b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagListValidator.ts index 767b0677bc..1e9a057fe6 100644 --- a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagListValidator.ts +++ b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagListValidator.ts @@ -5,7 +5,7 @@ import { SegmentInputValidator } from './SegmentInputValidator'; export class FeatureFlagListValidator { @IsNotEmpty() @IsUUID() - public flagId: string; + public id: string; @IsDefined() @IsBoolean() diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index 013966e05b..c6b8d6c018 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -600,14 +600,14 @@ export class FeatureFlagService { const featureFlags = await manager .getRepository(FeatureFlag) - .findByIds(listsInput.map((listInput) => listInput.flagId)); + .findByIds(listsInput.map((listInput) => listInput.id)); const featureFlagSegmentInclusionOrExclusionArray = listsInput.map((listInput) => { const featureFlagSegmentInclusionOrExclusion = filterType === 'inclusion' ? new FeatureFlagSegmentInclusion() : new FeatureFlagSegmentExclusion(); featureFlagSegmentInclusionOrExclusion.enabled = listInput.enabled; featureFlagSegmentInclusionOrExclusion.listType = listInput.listType; - featureFlagSegmentInclusionOrExclusion.featureFlag = featureFlags.find((flag) => flag.id === listInput.flagId); + featureFlagSegmentInclusionOrExclusion.featureFlag = featureFlags.find((flag) => flag.id === listInput.id); featureFlagSegmentInclusionOrExclusion.segment = newSegments.find( (segment) => segment.id === listInput.segment.id ); @@ -679,23 +679,23 @@ export class FeatureFlagService { return await this.dataSource.transaction(async (transactionalEntityManager) => { // Find the existing record let existingRecord: FeatureFlagSegmentInclusion | FeatureFlagSegmentExclusion; - const featureFlag = await this.findOne(listInput.flagId); + const featureFlag = await this.findOne(listInput.id); if (filterType === FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION) { existingRecord = await this.featureFlagSegmentInclusionRepository.findOne({ - where: { featureFlag: { id: listInput.flagId }, segment: { id: listInput.segment.id } }, + where: { featureFlag: { id: listInput.id }, segment: { id: listInput.segment.id } }, relations: ['featureFlag', 'segment'], }); } else { existingRecord = await this.featureFlagSegmentExclusionRepository.findOne({ - where: { featureFlag: { id: listInput.flagId }, segment: { id: listInput.segment.id } }, + where: { featureFlag: { id: listInput.id }, segment: { id: listInput.segment.id } }, relations: ['featureFlag', 'segment'], }); } if (!existingRecord) { throw new Error( - `No existing ${filterType} record found for feature flag ${listInput.flagId} and segment ${listInput.segment.id}` + `No existing ${filterType} record found for feature flag ${listInput.id} and segment ${listInput.segment.id}` ); } @@ -961,7 +961,7 @@ export class FeatureFlagService { return { ...segmentInclusionList, enabled: false, - flagId: newFlag.id, + id: newFlag.id, segment: { ...segmentInclusionList.segment, userIds, subSegmentIds, groups }, }; }); @@ -981,7 +981,7 @@ export class FeatureFlagService { return { ...segmentExclusionList, - flagId: newFlag.id, + id: newFlag.id, segment: { ...segmentExclusionList.segment, userIds, subSegmentIds, groups }, }; }); @@ -1171,7 +1171,7 @@ export class FeatureFlagService { const listDoc: FeatureFlagListValidator = { ...list, enabled: false, - flagId: featureFlagId, + id: featureFlagId, segment: { ...list.segment, id: uuid() }, }; diff --git a/backend/packages/Upgrade/test/unit/services/FeatureFlagService.test.ts b/backend/packages/Upgrade/test/unit/services/FeatureFlagService.test.ts index 8e4f27ff34..f419bdc0be 100644 --- a/backend/packages/Upgrade/test/unit/services/FeatureFlagService.test.ts +++ b/backend/packages/Upgrade/test/unit/services/FeatureFlagService.test.ts @@ -86,7 +86,7 @@ describe('Feature Flag Service Testing', () => { const mockList = new FeatureFlagListValidator(); mockList.enabled = true; - mockList.flagId = mockFlag1.id; + mockList.id = mockFlag1.id; mockList.listType = 'individual'; mockList.segment = mockSegment; diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts index 8f9296f734..13b0cbe192 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts @@ -154,6 +154,7 @@ export class UpsertPrivateSegmentListModalComponent { ![ UPSERT_PRIVATE_SEGMENT_LIST_ACTION.EDIT_FLAG_INCLUDE_LIST, UPSERT_PRIVATE_SEGMENT_LIST_ACTION.EDIT_FLAG_EXCLUDE_LIST, + UPSERT_PRIVATE_SEGMENT_LIST_ACTION.EDIT_SEGMENT_LIST, ].includes(this.config.params.action) ) { return; From 5f16d4e96514811dfd7e21ccfa4c6403ec9d7ac5 Mon Sep 17 00:00:00 2001 From: Zack Lee Date: Tue, 8 Apr 2025 21:19:47 +0900 Subject: [PATCH 4/8] Add addSegmentList to environment files --- .../upgrade/src/environments/environment.beanstalk.prod.ts | 1 + frontend/projects/upgrade/src/environments/environment.bsnl.ts | 1 + .../projects/upgrade/src/environments/environment.demo.prod.ts | 1 + .../upgrade/src/environments/environment.local.example.ts | 1 + frontend/projects/upgrade/src/environments/environment.prod.ts | 1 + frontend/projects/upgrade/src/environments/environment.qa.ts | 1 + .../projects/upgrade/src/environments/environment.staging.ts | 1 + frontend/projects/upgrade/src/environments/environment.ts | 1 + 8 files changed, 8 insertions(+) diff --git a/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts b/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts index 582883c78b..0551faef6e 100644 --- a/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts @@ -74,6 +74,7 @@ export const environment: Environment = { importSegments: '/segments/import', exportSegments: '/segments/export/json', exportSegmentCSV: '/segments/export/csv', + addSegmentList: '/segments/list', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.bsnl.ts b/frontend/projects/upgrade/src/environments/environment.bsnl.ts index dd78eea55b..62ef159a58 100644 --- a/frontend/projects/upgrade/src/environments/environment.bsnl.ts +++ b/frontend/projects/upgrade/src/environments/environment.bsnl.ts @@ -74,6 +74,7 @@ export const environment: Environment = { importSegments: '/segments/import', exportSegments: '/segments/export/json', exportSegmentCSV: '/segments/export/csv', + addSegmentList: '/segments/list', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts index af3555e85e..1a5f52dcf2 100755 --- a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts @@ -74,6 +74,7 @@ export const environment: Environment = { importSegments: '/segments/import', exportSegments: '/segments/export/json', exportSegmentCSV: '/segments/export/csv', + addSegmentList: '/segments/list', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.local.example.ts b/frontend/projects/upgrade/src/environments/environment.local.example.ts index 4bdc3a4e9b..b63bdb605d 100755 --- a/frontend/projects/upgrade/src/environments/environment.local.example.ts +++ b/frontend/projects/upgrade/src/environments/environment.local.example.ts @@ -79,6 +79,7 @@ export const environment: Environment = { importSegments: '/segments/import', exportSegments: '/segments/export/json', exportSegmentCSV: '/segments/export/csv', + addSegmentList: '/segments/list', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.prod.ts b/frontend/projects/upgrade/src/environments/environment.prod.ts index 59c957f45c..2dd40b9d8e 100755 --- a/frontend/projects/upgrade/src/environments/environment.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.prod.ts @@ -74,6 +74,7 @@ export const environment: Environment = { importSegments: '/segments/import', exportSegments: '/segments/export/json', exportSegmentCSV: '/segments/export/csv', + addSegmentList: '/segments/list', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.qa.ts b/frontend/projects/upgrade/src/environments/environment.qa.ts index ad29a41c27..f107c7483c 100644 --- a/frontend/projects/upgrade/src/environments/environment.qa.ts +++ b/frontend/projects/upgrade/src/environments/environment.qa.ts @@ -74,6 +74,7 @@ export const environment: Environment = { importSegments: '/segments/import', exportSegments: '/segments/export/json', exportSegmentCSV: '/segments/export/csv', + addSegmentList: '/segments/list', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.staging.ts b/frontend/projects/upgrade/src/environments/environment.staging.ts index 499a6fb51e..5549faae13 100644 --- a/frontend/projects/upgrade/src/environments/environment.staging.ts +++ b/frontend/projects/upgrade/src/environments/environment.staging.ts @@ -74,6 +74,7 @@ export const environment: Environment = { importSegments: '/segments/import', exportSegments: '/segments/export/json', exportSegmentCSV: '/segments/export/csv', + addSegmentList: '/segments/list', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.ts b/frontend/projects/upgrade/src/environments/environment.ts index ab0c596d00..5fad43409f 100755 --- a/frontend/projects/upgrade/src/environments/environment.ts +++ b/frontend/projects/upgrade/src/environments/environment.ts @@ -80,6 +80,7 @@ export const environment = { importSegments: '/segments/import', exportSegments: '/segments/export/json', exportSegmentCSV: '/segments/export/csv', + addSegmentList: '/segments/list', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; From 1fb8128d43df8c745fd3392cc62029b4acfac5de Mon Sep 17 00:00:00 2001 From: Zack Lee Date: Tue, 8 Apr 2025 21:50:03 +0900 Subject: [PATCH 5/8] Add unit tests for segment list operations --- .../controllers/SegmentController.test.ts | 55 +++++++++++++++++++ .../controllers/mocks/SegmentServiceMock.ts | 12 +++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts b/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts index 0980189ee3..2bbd05516a 100644 --- a/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts +++ b/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts @@ -40,6 +40,18 @@ describe('Segment Controller Testing', () => { subSegmentIds: ['seg2'], }; + const listInputData = { + parentSegmentId: uuid(), + name: 'list1', + description: 'list description', + context: 'home', + type: 'private', + userIds: ['user1', 'user2'], + groups: [], + subSegmentIds: [], + listType: 'Individual', + }; + test('Get request for /api/segments', () => { return request(app) .get('/api/segments') @@ -111,4 +123,47 @@ describe('Segment Controller Testing', () => { .expect('Content-Type', /json/) .expect(200); }); + + test('Post request for /api/segments/list (addSegmentList)', () => { + return request(app) + .post('/api/segments/list') + .send(listInputData) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + + test('Post request for /api/segments (updateSegmentList)', () => { + const updateListData = { + id: uuid(), + name: 'updated list', + description: 'updated description', + context: 'home', + type: 'private', + userIds: ['user1', 'user2', 'user3'], + groups: [], + subSegmentIds: [], + listType: 'Individual', + }; + + return request(app) + .post('/api/segments') + .send(updateListData) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + + // TODO: Should be uncommented once the backend endpoint is implemented + // test('Delete request for /api/segments/list/:segmentId (deleteSegmentList)', () => { + // const segmentId = uuid(); + // const parentSegmentId = uuid(); + + // return request(app) + // .delete(`/api/segments/list/${segmentId}`) + // .send({ parentSegmentId }) + // .set('Accept', 'application/json') + // .expect('Content-Type', /json/) + // .expect(200); + // }); }); diff --git a/backend/packages/Upgrade/test/unit/controllers/mocks/SegmentServiceMock.ts b/backend/packages/Upgrade/test/unit/controllers/mocks/SegmentServiceMock.ts index 74b2e4c754..2bf2d28b83 100644 --- a/backend/packages/Upgrade/test/unit/controllers/mocks/SegmentServiceMock.ts +++ b/backend/packages/Upgrade/test/unit/controllers/mocks/SegmentServiceMock.ts @@ -1,7 +1,7 @@ import { Service } from 'typedi'; @Service() -export default class ExcludeServiceMock { +export default class SegmentServiceMock { public getAllSegments(): Promise<[]> { return Promise.resolve([]); } @@ -9,7 +9,7 @@ export default class ExcludeServiceMock { public getAllSegmentWithStatus(): Promise<[]> { return Promise.resolve([]); } - + public getSegmentById(id: string): Promise<[]> { return Promise.resolve([]); } @@ -53,4 +53,12 @@ export default class ExcludeServiceMock { public exportSegmentCSV(id: string): Promise<[]> { return Promise.resolve([]); } + + public addList(): Promise<[]> { + return Promise.resolve([]); + } + + public deleteList(): Promise<[]> { + return Promise.resolve([]); + } } From a9f087160cd1a753557d3bd29d3b5c513ad0757e Mon Sep 17 00:00:00 2001 From: Zack Lee Date: Tue, 8 Apr 2025 23:54:01 +0900 Subject: [PATCH 6/8] Update deleteSegmentList to include parentSegmentId in the request body --- .../controllers/SegmentController.test.ts | 23 +++++++++---------- .../core/segments/segments.data.service.ts | 4 ++-- .../src/app/core/segments/segments.service.ts | 9 ++++++-- .../core/segments/store/segments.actions.ts | 5 +++- .../core/segments/store/segments.effects.ts | 5 ++-- .../segment-lists-section-card.component.ts | 2 +- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts b/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts index 2bbd05516a..f0cb073a91 100644 --- a/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts +++ b/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts @@ -154,16 +154,15 @@ describe('Segment Controller Testing', () => { .expect(200); }); - // TODO: Should be uncommented once the backend endpoint is implemented - // test('Delete request for /api/segments/list/:segmentId (deleteSegmentList)', () => { - // const segmentId = uuid(); - // const parentSegmentId = uuid(); - - // return request(app) - // .delete(`/api/segments/list/${segmentId}`) - // .send({ parentSegmentId }) - // .set('Accept', 'application/json') - // .expect('Content-Type', /json/) - // .expect(200); - // }); + test('Delete request for /api/segments/list/:segmentId (deleteSegmentList)', () => { + const segmentId = uuid(); + const parentSegmentId = uuid(); + + return request(app) + .delete(`/api/segments/list/${segmentId}`) + .send({ parentSegmentId }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); }); diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts index bcedcf0dde..71b6afe97d 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts @@ -147,8 +147,8 @@ export class SegmentsDataService { ); } - deleteSegmentList(segmentId: string) { + deleteSegmentList(segmentId: string, parentSegmentId: string) { const url = `${this.environment.api.addSegmentList}/${segmentId}`; - return this.http.delete(url); + return this.http.delete(url, { body: { parentSegmentId } }); } } 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 e57d2e90ca..39ab68c67c 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.service.ts @@ -231,7 +231,12 @@ export class SegmentsService { this.store$.dispatch(SegmentsActions.actionUpdateSegmentList({ list })); } - deletePrivateSegmentList(segmentId: string) { - this.store$.dispatch(SegmentsActions.actionDeleteSegmentList({ segmentId })); + deletePrivateSegmentList(segmentId: string, parentSegmentId: string) { + this.store$.dispatch( + SegmentsActions.actionDeleteSegmentList({ + segmentId, + parentSegmentId, + }) + ); } } 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 e246b0ecf0..32d6194397 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 @@ -155,7 +155,10 @@ export const actionUpdateSegmentListFailure = createAction( props<{ error: any }>() ); -export const actionDeleteSegmentList = createAction('[Segments] Delete Segment List', props<{ segmentId: string }>()); +export const actionDeleteSegmentList = createAction( + '[Segments] Delete Segment List', + props<{ segmentId: string; parentSegmentId: string }>() +); export const actionDeleteSegmentListSuccess = createAction( '[Segments] Delete Segment List Success', 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 38c44a3190..c30de18e80 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 @@ -263,9 +263,8 @@ export class SegmentsEffects { deleteSegmentList$ = createEffect(() => this.actions$.pipe( ofType(SegmentsActions.actionDeleteSegmentList), - map((action) => action.segmentId), - switchMap((segmentId) => { - return this.segmentsDataService.deleteSegmentList(segmentId).pipe( + switchMap(({ segmentId, parentSegmentId }) => { + return this.segmentsDataService.deleteSegmentList(segmentId, parentSegmentId).pipe( map(() => SegmentsActions.actionDeleteSegmentListSuccess({ segmentId })), catchError((error) => of(SegmentsActions.actionDeleteSegmentListFailure({ error }))) ); diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/segment-details-page/segment-details-page-content/segment-lists-section-card/segment-lists-section-card.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/segment-details-page/segment-details-page-content/segment-lists-section-card/segment-lists-section-card.component.ts index c9cc73a574..f9f2c6147a 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/segment-details-page/segment-details-page-content/segment-lists-section-card/segment-lists-section-card.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/segment-details-page/segment-details-page-content/segment-lists-section-card/segment-lists-section-card.component.ts @@ -109,7 +109,7 @@ export class SegmentListsSectionCardComponent { .afterClosed() .subscribe((confirmClicked) => { if (confirmClicked) { - this.segmentsService.deletePrivateSegmentList(segment.id); + this.segmentsService.deletePrivateSegmentList(segment.id, this.data.id); } }); } From 879fc9febc220cfb48056d089f801b73cf8b7d63 Mon Sep 17 00:00:00 2001 From: Zack Lee Date: Wed, 9 Apr 2025 02:23:37 +0900 Subject: [PATCH 7/8] Close the Add List modal on Create button click --- .../components/common-modal/common-modal-config.ts | 1 + 1 file changed, 1 insertion(+) 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 d48cb898ae..e30c440378 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 @@ -4,4 +4,5 @@ import { environment } from '../../../../environments/environment'; export const ENDPOINTS_TO_INTERCEPT_FOR_MODAL_CLOSE = [ environment.api.addFlagInclusionList, environment.api.addFlagExclusionList, + environment.api.addSegmentList, ]; From c392fbc116cfd64bc3691c1e9d89ca99326ad7b4 Mon Sep 17 00:00:00 2001 From: Zack Lee Date: Wed, 9 Apr 2025 17:30:36 +0900 Subject: [PATCH 8/8] Add parentSegmentId to FeatureFlagSegmentListDetails --- .../src/app/core/feature-flags/store/feature-flags.model.ts | 1 + .../upgrade/src/app/core/segments/segments.data.service.ts | 2 ++ .../upsert-private-segment-list-modal.component.ts | 4 ++-- 3 files changed, 5 insertions(+), 2 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 311df9b2ff..1b16684bf8 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 @@ -50,6 +50,7 @@ export interface FeatureFlagSegmentListDetails { featureFlag: FeatureFlag; enabled: boolean; listType: MemberTypes | string; + parentSegmentId?: string; } export enum UPSERT_FEATURE_FLAG_ACTION { diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts index 71b6afe97d..965417d8ac 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts @@ -113,6 +113,7 @@ export class SegmentsDataService { featureFlag: null, enabled: list.enabled, listType: list.listType, + parentSegmentId: list.id, } as FeatureFlagSegmentListDetails; }) ); @@ -142,6 +143,7 @@ export class SegmentsDataService { featureFlag: null, enabled: list.enabled, listType: list.listType, + parentSegmentId: list.id, } as FeatureFlagSegmentListDetails; }) ); diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts index 13b0cbe192..0d0c139fcf 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts @@ -186,11 +186,11 @@ export class UpsertPrivateSegmentListModalComponent { determineValues(listType: string, segment: Segment): string[] { switch (listType) { case LIST_OPTION_TYPE.INDIVIDUAL: - return segment.individualForSegment.map((individual) => individual.userId); + return segment.individualForSegment?.map((individual) => individual.userId) || []; case LIST_OPTION_TYPE.SEGMENT: return []; default: - return segment.groupForSegment.map((group) => group.groupId); + return segment.groupForSegment?.map((group) => group.groupId) || []; } }