From 44305cdeb2d3abac3911971c15a5b85b17852acf Mon Sep 17 00:00:00 2001 From: Yagnik Date: Mon, 21 Oct 2024 15:02:02 +0530 Subject: [PATCH 01/11] import feature flag list feature added with validation and import endpoint --- .../api/controllers/FeatureFlagController.ts | 109 ++++++++- .../validators/FeatureFlagImportValidator.ts | 2 +- .../validators/FeatureFlagValidator.ts | 19 +- .../src/api/services/FeatureFlagService.ts | 207 +++++++++++++++++- .../feature-flags.data.service.ts | 18 +- .../app/core/segments/store/segments.model.ts | 14 +- .../import-feature-flag-modal.component.html | 50 ++++- .../import-feature-flag-modal.component.ts | 41 +++- ...lag-exclusions-section-card.component.html | 3 + ...-flag-exclusions-section-card.component.ts | 20 +- ...lag-inclusions-section-card.component.html | 3 + ...-flag-inclusions-section-card.component.ts | 20 +- .../shared/services/common-dialog.service.ts | 38 ++++ .../projects/upgrade/src/assets/i18n/en.json | 3 + .../src/environments/environment-types.ts | 2 + .../environment.beanstalk.prod.ts | 2 + .../src/environments/environment.bsnl.ts | 2 + .../src/environments/environment.demo.prod.ts | 2 + .../src/environments/environment.prod.ts | 2 + .../src/environments/environment.qa.ts | 2 + .../src/environments/environment.staging.ts | 2 + .../upgrade/src/environments/environment.ts | 2 + types/src/index.ts | 1 + 23 files changed, 527 insertions(+), 37 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts index dccdc679a8..ac3d59a760 100644 --- a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts +++ b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts @@ -24,7 +24,12 @@ import { import { FeatureFlagFilterModeUpdateValidator } from './validators/FeatureFlagFilterModeUpdateValidator'; import { AppRequest, PaginationResponse } from '../../types'; import { IImportError, FEATURE_FLAG_LIST_FILTER_MODE, SERVER_ERROR } from 'upgrade_types'; -import { FeatureFlagImportValidation, FeatureFlagValidation, IdValidator } from './validators/FeatureFlagValidator'; +import { + FeatureFlagImportValidation, + FeatureFlagListImportValidation, + FeatureFlagValidation, + IdValidator, +} from './validators/FeatureFlagValidator'; import { ExperimentUserService } from '../services/ExperimentUserService'; import { FeatureFlagListValidator } from '../controllers/validators/FeatureFlagListValidator'; import { Segment } from 'src/api/models/Segment'; @@ -123,6 +128,19 @@ interface FeatureFlagsPaginationInfo extends PaginationResponse { * list: * type: object * $ref: '#/definitions/FeatureFlagInclusionExclusionList' + * FeatureFlagListImportObject: + * required: + * - files + * - listType + * - flagId + * properties: + * files: + * type: object + * listType: + * type: string + * enum: [featureFlagSegmentInclusion, featureFlagSegmentExclusion] + * flagId: + * type: string */ /** @@ -749,7 +767,7 @@ export class FeatureFlagsController { * @swagger * /flags/import: * post: - * description: Validating Feature Flag + * description: Importing Feature Flag * consumes: * - application/json * parameters: @@ -841,4 +859,91 @@ export class FeatureFlagsController { } return response.status(404).send('Feature Flag not found'); } + + /** + * @swagger + * /flags/lists/import/validation: + * post: + * description: Validating Feature Flag List + * consumes: + * - application/json + * parameters: + * - in: body + * name: lists + * description: Import FeatureFlag List Files + * required: true + * schema: + * type: object + * $ref: '#/definitions/FeatureFlagListImportObject' + * tags: + * - Feature Flags + * produces: + * - application/json + * responses: + * '200': + * description: Validations are completed + * schema: + * type: array + * items: + * type: object + * properties: + * fileName: + * type: string + * compatibilityType: + * type: string + * enum: [compatible, warning, incompatible] + * '401': + * description: AuthorizationRequiredError + * '500': + * description: Internal Server Error + */ + @Post('/lists/import/validation') + public async validateImportFeatureFlagList( + @Body({ validate: true }) lists: FeatureFlagListImportValidation, + @Req() request: AppRequest + ): Promise { + return await this.featureFlagService.validateImportFeatureFlagLists(lists.files, lists.flagId, request.logger); + } + + /** + * @swagger + * /flags/lists/import: + * post: + * description: Importing Feature Flag List + * consumes: + * - application/json + * parameters: + * - in: body + * name: lists + * description: Import FeatureFlag List Files + * required: true + * schema: + * type: object + * $ref: '#/definitions/FeatureFlagListImportObject' + * tags: + * - Feature Flag Lists + * produces: + * - application/json + * responses: + * '200': + * description: New Feature flag is imported + * '401': + * description: AuthorizationRequiredError + * '500': + * description: Internal Server Error + */ + @Post('/lists/import') + public async importFeatureFlagLists( + @Body({ validate: true }) lists: FeatureFlagListImportValidation, + @CurrentUser() currentUser: User, + @Req() request: AppRequest + ): Promise { + return await this.featureFlagService.importFeatureFlagLists( + lists.files, + lists.flagId, + lists.listType, + currentUser, + request.logger + ); + } } diff --git a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagImportValidator.ts b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagImportValidator.ts index ce5c382789..b0416e91c9 100644 --- a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagImportValidator.ts +++ b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagImportValidator.ts @@ -88,7 +88,7 @@ class SegmentImportValidator { public subSegments: SegmentValidator[]; } -class FeatureFlagListImportValidator { +export class FeatureFlagListImportValidator { @IsDefined() @IsBoolean() public enabled: boolean; diff --git a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagValidator.ts b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagValidator.ts index 15777325cf..ce7895a4e3 100644 --- a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagValidator.ts +++ b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagValidator.ts @@ -1,6 +1,5 @@ import { IsNotEmpty, IsDefined, IsString, IsArray, IsEnum, IsOptional, ValidateNested, IsUUID } from 'class-validator'; -import { FILTER_MODE } from 'upgrade_types'; -import { FEATURE_FLAG_STATUS } from 'upgrade_types'; +import { FEATURE_FLAG_PARTICIPANT_LIST_KEY, FILTER_MODE, FEATURE_FLAG_STATUS } from 'upgrade_types'; import { Type } from 'class-transformer'; import { FeatureFlagListValidator } from './FeatureFlagListValidator'; @@ -77,6 +76,22 @@ export class FeatureFlagImportValidation { public files: FeatureFlagFile[]; } +export class FeatureFlagListImportValidation { + @IsArray() + @ValidateNested({ each: true }) + @Type(() => FeatureFlagFile) + public files: FeatureFlagFile[]; + + @IsString() + @IsNotEmpty() + @IsEnum(FEATURE_FLAG_PARTICIPANT_LIST_KEY) + public listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY; + + @IsUUID() + @IsNotEmpty() + public flagId: string; +} + class FeatureFlagFile { @IsString() @IsNotEmpty() diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index 47905af5f6..d6d8731a41 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -10,6 +10,7 @@ import { FeatureFlagSegmentExclusionRepository } from '../repositories/FeatureFl import { EntityManager, In, DataSource } from 'typeorm'; import { InjectDataSource, InjectRepository } from '../../typeorm-typedi-extensions'; import { v4 as uuid } from 'uuid'; +import { env } from '../../env'; import { IFeatureFlagSearchParams, IFeatureFlagSortParams, @@ -33,6 +34,7 @@ import { FEATURE_FLAG_LIST_FILTER_MODE, FEATURE_FLAG_LIST_OPERATION, ListOperationsData, + FEATURE_FLAG_PARTICIPANT_LIST_KEY, } from 'upgrade_types'; import { UpgradeLogger } from '../../lib/logger/UpgradeLogger'; import { FeatureFlagValidation } from '../controllers/validators/FeatureFlagValidator'; @@ -43,7 +45,10 @@ import { ErrorWithType } from '../errors/ErrorWithType'; import { RequestedExperimentUser } from '../controllers/validators/ExperimentUserValidator'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; -import { FeatureFlagImportDataValidation } from '../controllers/validators/FeatureFlagImportValidator'; +import { + FeatureFlagImportDataValidation, + FeatureFlagListImportValidator, +} from '../controllers/validators/FeatureFlagImportValidator'; import { ExperimentAuditLogRepository } from '../repositories/ExperimentAuditLogRepository'; import { User } from '../models/User'; import { diffString } from 'json-diff'; @@ -1107,4 +1112,204 @@ export class FeatureFlagService { compatibilityType: compatibilityType, }; } + + public async importFeatureFlagLists( + featureFlagListFiles: IFeatureFlagFile[], + featureFlagId: string, + listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY, + currentUser: User, + logger: UpgradeLogger + ): Promise { + logger.info({ message: 'Import feature flags' }); + const validatedFlags = await this.validateImportFeatureFlagLists(featureFlagListFiles, featureFlagId, logger); + + const fileStatusArray = featureFlagListFiles.map((file) => { + const validation = validatedFlags.find((error) => error.fileName === file.fileName); + const isCompatible = validation && validation.compatibilityType !== FF_COMPATIBILITY_TYPE.INCOMPATIBLE; + + return { + fileName: file.fileName, + error: isCompatible ? validation.compatibilityType : FF_COMPATIBILITY_TYPE.INCOMPATIBLE, + }; + }); + + const validFiles: FeatureFlagListImportValidator[] = await Promise.all( + fileStatusArray + .filter((fileStatus) => fileStatus.error !== FF_COMPATIBILITY_TYPE.INCOMPATIBLE) + .map(async (fileStatus) => { + const featureFlagListFile = featureFlagListFiles.find((file) => file.fileName === fileStatus.fileName); + + return JSON.parse(featureFlagListFile.fileContent as string); + }) + ); + + const createdLists: (FeatureFlagSegmentInclusion | FeatureFlagSegmentExclusion)[] = + await this.dataSource.transaction(async (transactionalEntityManager) => { + const listDocs: FeatureFlagListValidator[] = []; + for (const list of validFiles) { + const { name, description, context, type } = list.segment; + + const userIds = list.segment.individualForSegment.map((individual) => + individual.userId ? individual.userId : null + ); + + const subSegmentIds = list.segment.subSegments.map((subSegment) => (subSegment.id ? subSegment.id : null)); + + const groups = list.segment.groupForSegment.map((group) => { + return group.type && group.groupId ? { type: group.type, groupId: group.groupId } : null; + }); + + const listDoc: FeatureFlagListValidator = { + ...list, + enabled: false, + flagId: featureFlagId, + segment: { id: uuid(), name, description, context, type, userIds, subSegmentIds, groups }, + }; + + listDocs.push(listDoc); + } + + if (listType === FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE) { + return await this.addList( + listDocs, + FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION, + currentUser, + logger, + transactionalEntityManager + ); + } else { + return await this.addList( + listDocs, + FEATURE_FLAG_LIST_FILTER_MODE.EXCLUSION, + currentUser, + logger, + transactionalEntityManager + ); + } + }); + + logger.info({ message: 'Imported feature flags', details: createdLists }); + + fileStatusArray.forEach((fileStatus) => { + if (fileStatus.error !== FF_COMPATIBILITY_TYPE.INCOMPATIBLE) { + fileStatus.error = null; + } + }); + return fileStatusArray; + } + + public async validateImportFeatureFlagLists( + featureFlagFiles: IFeatureFlagFile[], + featureFlagId: string, + logger: UpgradeLogger + ): Promise { + logger.info({ message: 'Validate feature flag lists' }); + + const parsedFeatureFlagLists = featureFlagFiles.map((featureFlagFile) => { + try { + return { + fileName: featureFlagFile.fileName, + content: JSON.parse(featureFlagFile.fileContent as string), + }; + } catch (parseError) { + logger.error({ message: 'Error in parsing feature flag file', details: parseError }); + return { + fileName: featureFlagFile.fileName, + content: null, + }; + } + }); + + const featureFlag = await this.findOne(featureFlagId, logger); + + const validationErrors = await Promise.allSettled( + parsedFeatureFlagLists.map(async (parsedFile) => { + if (!featureFlag) { + return { + fileName: parsedFile.fileName, + compatibilityType: FF_COMPATIBILITY_TYPE.INCOMPATIBLE, + }; + } + + if (!parsedFile.content) { + return { + fileName: parsedFile.fileName, + compatibilityType: FF_COMPATIBILITY_TYPE.INCOMPATIBLE, + }; + } + + const error = await this.validateImportFeatureFlagList(parsedFile.fileName, featureFlag, parsedFile.content); + return error; + }) + ); + + // Filter out the files that have no promise rejection errors + return validationErrors + .map((result) => { + if (result.status === 'fulfilled') { + return result.value; + } else { + const { fileName, compatibilityType } = result.reason; + return { fileName: fileName, compatibilityType: compatibilityType }; + } + }) + .filter((error) => error !== null); + } + + public async validateImportFeatureFlagList( + fileName: string, + flag: FeatureFlag, + list: FeatureFlagListImportValidator + ) { + let compatibilityType = FF_COMPATIBILITY_TYPE.COMPATIBLE; + + list = plainToClass(FeatureFlagListImportValidator, list); + await validate(list, { forbidUnknownValues: true, stopAtFirstError: true }).then((errors) => { + if (errors.length > 0) { + compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; + } + }); + + if (!(list instanceof FeatureFlagListImportValidator)) { + compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; + } + + if (list?.segment?.context !== flag.context[0]) { + compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; + } + + if (compatibilityType === FF_COMPATIBILITY_TYPE.COMPATIBLE) { + if (list.listType === 'Segment') { + const subSegmentIds = list.segment.subSegments.map((seg) => seg.id); + const segments = await this.segmentService.getSegmentByIds(subSegmentIds); + + if (!segments.length) { + compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; + } + + segments?.forEach((segment) => { + if (!segment || segment.context !== flag.context[0]) { + compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; + } + }); + } else if (list.listType !== 'Individual' && list.segment.groupForSegment.length) { + const contextMetaData = env.initialization.contextMetadata; + const groupTypes = contextMetaData[flag.context[0]].GROUP_TYPES; + if (!groupTypes.includes(list.listType)) { + compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; + } + + list.segment.groupForSegment.forEach((group) => { + if (group.type !== list.listType) { + compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; + } + }); + } + } + + return { + fileName: fileName, + compatibilityType: compatibilityType, + }; + } } diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts index bf06dadc19..5511f2f929 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts @@ -13,7 +13,7 @@ import { } from './store/feature-flags.model'; import { Observable, delay, of } from 'rxjs'; import { AddPrivateSegmentListRequest, EditPrivateSegmentListRequest } from '../segments/store/segments.model'; -import { IFeatureFlagFile } from 'upgrade_types'; +import { FEATURE_FLAG_PARTICIPANT_LIST_KEY, IFeatureFlagFile } from 'upgrade_types'; @Injectable() export class FeatureFlagsDataService { @@ -45,9 +45,15 @@ export class FeatureFlagsDataService { return this.http.put(url, flag); } - validateFeatureFlag(featureFlag: { files: IFeatureFlagFile[] }) { + validateFeatureFlag(featureFlags: { files: IFeatureFlagFile[] }) { const url = this.environment.api.validateFeatureFlag; - return this.http.post(url, featureFlag); + return this.http.post(url, featureFlags); + } + + validateFeatureFlagList(files: IFeatureFlagFile[], flagId: string, listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY) { + const lists = { files: files, listType: listType, flagId: flagId }; + const url = this.environment.api.validateFeatureFlagList; + return this.http.post(url, lists); } importFeatureFlag(featureFlag: { files: IFeatureFlagFile[] }) { @@ -55,6 +61,12 @@ export class FeatureFlagsDataService { return this.http.post(url, featureFlag); } + importFeatureFlagList(files: IFeatureFlagFile[], flagId: string, listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY) { + const lists = { files: files, listType: listType, flagId: flagId }; + const url = this.environment.api.importFeatureFlagList; + return this.http.post(url, lists); + } + updateFilterMode(params: UpdateFilterModeRequest): Observable { const url = this.environment.api.updateFilterMode; return this.http.patch(url, params); 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 65078e1dfc..7dbe1a1ed1 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 @@ -1,6 +1,13 @@ import { AppState } from '../../core.state'; import { EntityState } from '@ngrx/entity'; -import { SEGMENT_TYPE, SEGMENT_STATUS, SEGMENT_SEARCH_KEY, SORT_AS_DIRECTION, SEGMENT_SORT_KEY } from 'upgrade_types'; +import { + SEGMENT_TYPE, + SEGMENT_STATUS, + SEGMENT_SEARCH_KEY, + SORT_AS_DIRECTION, + SEGMENT_SORT_KEY, + FEATURE_FLAG_PARTICIPANT_LIST_KEY, +} from 'upgrade_types'; import { ParticipantListTableRow } from '../../feature-flags/store/feature-flags.model'; export { SEGMENT_STATUS }; @@ -165,6 +172,11 @@ export interface UpsertPrivateSegmentListParams { flagId: string; } +export interface ImportListParams { + listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY; + flagId: string; +} + export enum LIST_OPTION_TYPE { INDIVIDUAL = 'Individual', SEGMENT = 'Segment', diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.html index 7f1581c123..d710100920 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.html @@ -16,12 +16,24 @@ >

- {{ 'feature-flags.import-feature-flag.message.text' | translate }} - Learn more + + {{ 'feature-flags.import-feature-flag.message.text' | translate }} + + + {{ 'feature-flags.import-feature-flag-list.message.text' | translate }} + + Learn more

- +
@@ -32,22 +44,26 @@ [disabled]="element.compatibilityType === 'compatible'" (click)="toggleExpand()" > - {{ element.compatibilityType === 'compatible' || !isDescriptionExpanded ? 'chevron_right' : 'expand_more' }} + {{ + element.compatibilityType === 'compatible' || !isDescriptionExpanded ? 'chevron_right' : 'expand_more' + }} - - + + - + @@ -56,23 +72,33 @@ - +
File Name {{element.fileName}} File Name{{ element.fileName }} Compatibility Type Compatibility Type - +
- {{'feature-flags.import-flag-modal.compatibility-description.incompatible.text' | translate}} + + {{ 'feature-flags.import-flag-modal.compatibility-description.incompatible.text' | translate }} + + + {{ 'feature-flags.import-flag-list-modal.compatibility-description.incompatible.text' | translate }} +
- {{'feature-flags.import-flag-modal.compatibility-description.warning.text' | translate}} + + {{ 'feature-flags.import-flag-modal.compatibility-description.warning.text' | translate }} + + + {{ 'feature-flags.import-flag-list-modal.compatibility-description.warning.text' | translate }} +
- + diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.ts index 51413cc912..7f7b832931 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.ts @@ -13,7 +13,7 @@ import { CommonModalConfig } from '../../../../../shared-standalone-component-li import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service'; import { MatTableDataSource } from '@angular/material/table'; import { ValidateFeatureFlagError } from '../../../../../core/feature-flags/store/feature-flags.model'; -import { importError } from '../../../../../core/segments/store/segments.model'; +import { importError, ImportListParams } from '../../../../../core/segments/store/segments.model'; import { NotificationService } from '../../../../../core/notifications/notification.service'; import { IFeatureFlagFile } from 'upgrade_types'; @@ -47,7 +47,7 @@ export class ImportFeatureFlagModalComponent { ]).pipe(map(([uploadedCount, isLoading]) => isLoading || uploadedCount === 0)); constructor( - @Inject(MAT_DIALOG_DATA) public data: CommonModalConfig, + @Inject(MAT_DIALOG_DATA) public data: CommonModalConfig, public featureFlagsService: FeatureFlagsService, public featureFlagsDataService: FeatureFlagsDataService, public dialogRef: MatDialogRef, @@ -86,9 +86,22 @@ export class ImportFeatureFlagModalComponent { async checkValidation(files: IFeatureFlagFile[]) { try { - const validationErrors = (await firstValueFrom( - this.featureFlagsDataService.validateFeatureFlag({ files: files }) - )) as ValidateFeatureFlagError[]; + let validationErrors: ValidateFeatureFlagError[]; + + if (this.data.title === 'Import Feature Flag') { + validationErrors = (await firstValueFrom( + this.featureFlagsDataService.validateFeatureFlag({ files: files }) + )) as ValidateFeatureFlagError[]; + } else if (this.data.title === 'Import List') { + validationErrors = (await firstValueFrom( + this.featureFlagsDataService.validateFeatureFlagList( + files, + this.data.params.flagId, + this.data.params.listType + ) + )) as ValidateFeatureFlagError[]; + } + this.fileValidationErrors = validationErrors.filter((data) => data.compatibilityType != null) || []; this.fileValidationErrorDataSource.data = this.fileValidationErrors; this.featureFlagsService.setIsLoadingImportFeatureFlag(false); @@ -113,9 +126,21 @@ export class ImportFeatureFlagModalComponent { async importFiles() { try { this.isImportActionBtnDisabled.next(true); - const importResult = (await firstValueFrom( - this.featureFlagsDataService.importFeatureFlag({ files: this.fileData }) - )) as importError[]; + let importResult: importError[]; + + if (this.data.title === 'Import Feature Flag') { + importResult = (await firstValueFrom( + this.featureFlagsDataService.importFeatureFlag({ files: this.fileData }) + )) as importError[]; + } else if (this.data.title === 'Import List') { + importResult = (await firstValueFrom( + this.featureFlagsDataService.importFeatureFlagList( + this.fileData, + this.data.params.flagId, + this.data.params.listType + ) + )) as importError[]; + } this.showNotification(importResult); this.isImportActionBtnDisabled.next(false); 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.html 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.html index 048e42d373..cdaf8d328c 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.html +++ 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.html @@ -12,8 +12,11 @@ header-right [showPrimaryButton]="(permissions$ | async)?.featureFlags.update" [primaryButtonText]="'feature-flags.details.add-exclude-list.button.text' | translate" + [showMenuButton]="(permissions$ | async)?.featureFlags.update" + [menuButtonItems]="menuButtonItems" [isSectionCardExpanded]="isSectionCardExpanded" (primaryButtonClick)="onAddExcludeListClick(flag.context[0], flag.id)" + (menuButtonItemClick)="onMenuButtonItemClick($event, flag)" (sectionCardExpandChange)="onSectionCardExpandChange($event)" > 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 1d8226fe66..2fe421f1b3 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 @@ -11,6 +11,7 @@ import { FeatureFlagExclusionsTableComponent } from './feature-flag-exclusions-t import { FeatureFlagsService } from '../../../../../../../core/feature-flags/feature-flags.service'; import { DialogService } from '../../../../../../../shared/services/common-dialog.service'; import { + FeatureFlag, PARTICIPANT_LIST_ROW_ACTION, ParticipantListRowActionEvent, ParticipantListTableRow, @@ -51,8 +52,8 @@ export class FeatureFlagExclusionsSectionCardComponent { private authService: AuthService ) {} menuButtonItems: IMenuButtonItem[] = [ - // { name: 'Import Exclude List', disabled: false }, - // { name: 'Export All Exclude Lists', disabled: false }, + { name: 'Import Exclude List', disabled: false }, + { name: 'Export All Exclude Lists', disabled: true }, ]; ngOnInit() { @@ -63,8 +64,21 @@ export class FeatureFlagExclusionsSectionCardComponent { this.dialogService.openAddExcludeListModal(appContext, flagId); } - onMenuButtonItemClick(event) { + onMenuButtonItemClick(event, flag: FeatureFlag) { console.log('Menu Button Item Clicked:', event); + switch (event) { + case 'Import Include List': + this.dialogService + .openImportFeatureFlagExcludeListModal(flag.id) + .afterClosed() + .subscribe(() => this.featureFlagService.fetchFeatureFlagById(flag.id)); + break; + case 'Export All Include Lists': + // this.dialogService.openImportFeatureFlagListModal(); + break; + default: + console.log('Unknown action'); + } } onSectionCardExpandChange(isSectionCardExpanded: boolean) { 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.html 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.html index 9de8dcea24..9ca33e15bc 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.html +++ 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.html @@ -16,10 +16,13 @@ [slideToggleDisabled]="!(permissions$ | async)?.featureFlags.update" [showPrimaryButton]="(permissions$ | async)?.featureFlags.update" [primaryButtonText]="'feature-flags.details.add-include-list.button.text' | translate" + [showMenuButton]="(permissions$ | async)?.featureFlags.update" + [menuButtonItems]="menuButtonItems" [isSectionCardExpanded]="isSectionCardExpanded && flag.filterMode !== FILTER_MODE.INCLUDE_ALL" [primaryActionBtnDisabled]="flag.filterMode === FILTER_MODE.INCLUDE_ALL" [sectionCardExpandBtnDisabled]="flag.filterMode === FILTER_MODE.INCLUDE_ALL" (primaryButtonClick)="onAddIncludeListClick(flag.context[0], flag.id)" + (menuButtonItemClick)="onMenuButtonItemClick($event, flag)" (slideToggleChange)="onSlideToggleChange($event, flag.id)" (sectionCardExpandChange)="onSectionCardExpandChange($event)" > 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 32e0eb2059..d01b009660 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 @@ -65,8 +65,8 @@ export class FeatureFlagInclusionsSectionCardComponent { ) {} subscriptions = new Subscription(); menuButtonItems: IMenuButtonItem[] = [ - // { name: 'Import Include List', disabled: false }, - // { name: 'Export All Include Lists', disabled: false }, + { name: 'Import Include List', disabled: false }, + { name: 'Export All Include Lists', disabled: true }, ]; confirmIncludeAllChangeDialogRef: MatDialogRef; @@ -121,8 +121,20 @@ export class FeatureFlagInclusionsSectionCardComponent { this.isSectionCardExpanded = newFilterMode !== FILTER_MODE.INCLUDE_ALL; } - onMenuButtonItemClick(event) { - console.log('Menu Button Item Clicked:', event); + onMenuButtonItemClick(event, flag) { + switch (event) { + case 'Import Include List': + this.dialogService + .openImportFeatureFlagIncludeListModal(flag.id) + .afterClosed() + .subscribe(() => this.featureFlagService.fetchFeatureFlagById(flag.id)); + break; + case 'Export All Include Lists': + // this.dialogService.openDeleteFeatureFlagModal(); + break; + default: + console.log('Unknown action'); + } } onSectionCardExpandChange(isSectionCardExpanded: boolean) { 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 11a9c5fa0f..d6a39aac2d 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 @@ -6,11 +6,13 @@ import { ImportFeatureFlagModalComponent } from '../../features/dashboard/featur import { UpsertFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component'; import { UpsertPrivateSegmentListModalComponent } from '../../features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component'; import { + ImportListParams, UPSERT_PRIVATE_SEGMENT_LIST_ACTION, UpsertPrivateSegmentListParams, } from '../../core/segments/store/segments.model'; import { FEATURE_FLAG_DETAILS_PAGE_ACTIONS, + FEATURE_FLAG_PARTICIPANT_LIST_KEY, FeatureFlag, ParticipantListTableRow, UPSERT_FEATURE_FLAG_ACTION, @@ -357,7 +359,43 @@ export class DialogService { primaryActionBtnLabel: 'Import', primaryActionBtnColor: 'primary', cancelBtnLabel: 'Cancel', + params: { + listType: null, + flagId: null, + }, + }; + return this.openImportModal(commonModalConfig); + } + + openImportFeatureFlagIncludeListModal(flagId: string) { + const commonModalConfig: CommonModalConfig = { + title: 'Import List', + primaryActionBtnLabel: 'Import', + primaryActionBtnColor: 'primary', + cancelBtnLabel: 'Cancel', + params: { + listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE, + flagId: flagId, + }, }; + return this.openImportModal(commonModalConfig); + } + + openImportFeatureFlagExcludeListModal(flagId: string) { + const commonModalConfig: CommonModalConfig = { + title: 'Import List', + primaryActionBtnLabel: 'Import', + primaryActionBtnColor: 'primary', + cancelBtnLabel: 'Cancel', + params: { + listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE, + flagId: flagId, + }, + }; + return this.openImportModal(commonModalConfig); + } + + openImportModal(commonModalConfig: CommonModalConfig) { const config: MatDialogConfig = { data: commonModalConfig, width: ModalSize.STANDARD, diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index a87d7d5381..6bcc80205b 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -371,6 +371,8 @@ "feature-flags.delete-flag.input-placeholder.text": "Feature flag name", "feature-flags.import-flag-modal.compatibility-description.incompatible.text": "This JSON file cannot be imported because it lacks required properties or it incorrectly formatted for the feature flag. Please ensure it is the correct file.", "feature-flags.import-flag-modal.compatibility-description.warning.text": "This JSON file can be imported, but it may contain outdated or missing properties/features. Please review the feature flag details post-import.", + "feature-flags.import-flag-list-modal.compatibility-description.incompatible.text": "This JSON file cannot be imported because it lacks required properties or it incorrectly formatted for the feature flag list. Please ensure it is the correct file.", + "feature-flags.import-flag-list-modal.compatibility-description.warning.text": "This JSON file can be imported, but it may contain outdated or missing properties/features. Please review the feature flag list details post-import.", "feature-flags.upsert-flag-modal.name-hint.text": "The name for this feature flag.", "feature-flags.upsert-flag-modal.key-hint.text": "A unique key used to retrieve this feature flag from the client application.", "feature-flags.upsert-flag-modal.duplicate-key-error.text": "Feature flag with this key already exists for this app-context.", @@ -410,6 +412,7 @@ "feature-flags.add-feature-flag.text": "Add Feature Flag", "feature-flags.import-feature-flag.text": "Import Feature Flag", "feature-flags.import-feature-flag.message.text": "The Feature Flag JSON file should include the required properties for it to be imported", + "feature-flags.import-feature-flag-list.message.text": "The List JSON file should include the required properties for it to be imported", "feature-flags.export-all-feature-flags.text": "Export All Feature Flag Designs", "feature-flags.export-feature-flag-design.text": "Export Feature Flag Design", "feature-flags.export-feature-flags-data.text": "Email Feature Flag Data", diff --git a/frontend/projects/upgrade/src/environments/environment-types.ts b/frontend/projects/upgrade/src/environments/environment-types.ts index 877fe95aac..77877230bb 100644 --- a/frontend/projects/upgrade/src/environments/environment-types.ts +++ b/frontend/projects/upgrade/src/environments/environment-types.ts @@ -29,7 +29,9 @@ export interface APIEndpoints { allExperimentNames: string; featureFlag: string; validateFeatureFlag: string; + validateFeatureFlagList: string; importFeatureFlag: string; + importFeatureFlagList: string; updateFlagStatus: string; updateFilterMode: string; getPaginatedFlags: string; diff --git a/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts b/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts index a9731e8f28..f404e6ce16 100644 --- a/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts @@ -47,7 +47,9 @@ export const environment = { updateFilterMode: '/flags/filterMode', getPaginatedFlags: '/flags/paginated', validateFeatureFlag: '/flags/import/validation', + validateFeatureFlagList: '/flags/lists/import/validation', importFeatureFlag: '/flags/import', + importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', emailFlagData: '/flags/mail', addFlagInclusionList: '/flags/inclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.bsnl.ts b/frontend/projects/upgrade/src/environments/environment.bsnl.ts index 474310a541..a8a97d2adb 100644 --- a/frontend/projects/upgrade/src/environments/environment.bsnl.ts +++ b/frontend/projects/upgrade/src/environments/environment.bsnl.ts @@ -47,7 +47,9 @@ export const environment = { updateFilterMode: '/flags/filterMode', getPaginatedFlags: '/flags/paginated', validateFeatureFlag: '/flags/import/validation', + validateFeatureFlagList: '/flags/lists/import/validation', importFeatureFlag: '/flags/import', + importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts index 916596ce1f..1022c2f68e 100755 --- a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts @@ -47,7 +47,9 @@ export const environment = { updateFilterMode: '/flags/filterMode', getPaginatedFlags: '/flags/paginated', validateFeatureFlag: '/flags/import/validation', + validateFeatureFlagList: '/flags/lists/import/validation', importFeatureFlag: '/flags/import', + importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.prod.ts b/frontend/projects/upgrade/src/environments/environment.prod.ts index 1cee609963..e3d28c4f42 100755 --- a/frontend/projects/upgrade/src/environments/environment.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.prod.ts @@ -47,7 +47,9 @@ export const environment = { updateFilterMode: '/flags/filterMode', getPaginatedFlags: '/flags/paginated', validateFeatureFlag: '/flags/import/validation', + validateFeatureFlagList: '/flags/lists/import/validation', importFeatureFlag: '/flags/import', + importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.qa.ts b/frontend/projects/upgrade/src/environments/environment.qa.ts index 93bfba0e35..1baa222fd7 100644 --- a/frontend/projects/upgrade/src/environments/environment.qa.ts +++ b/frontend/projects/upgrade/src/environments/environment.qa.ts @@ -47,7 +47,9 @@ export const environment = { updateFilterMode: '/flags/filterMode', getPaginatedFlags: '/flags/paginated', validateFeatureFlag: '/flags/import/validation', + validateFeatureFlagList: '/flags/lists/import/validation', importFeatureFlag: '/flags/import', + importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', emailFlagData: '/flags/mail', addFlagInclusionList: '/flags/inclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.staging.ts b/frontend/projects/upgrade/src/environments/environment.staging.ts index 8f1ea164be..fe1af50bac 100644 --- a/frontend/projects/upgrade/src/environments/environment.staging.ts +++ b/frontend/projects/upgrade/src/environments/environment.staging.ts @@ -47,7 +47,9 @@ export const environment = { updateFilterMode: '/flags/filterMode', getPaginatedFlags: '/flags/paginated', validateFeatureFlag: '/flags/import/validation', + validateFeatureFlagList: '/flags/lists/import/validation', importFeatureFlag: '/flags/import', + importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.ts b/frontend/projects/upgrade/src/environments/environment.ts index 289a01c44b..14e4b86071 100755 --- a/frontend/projects/upgrade/src/environments/environment.ts +++ b/frontend/projects/upgrade/src/environments/environment.ts @@ -52,7 +52,9 @@ export const environment = { updateFilterMode: '/flags/filterMode', getPaginatedFlags: '/flags/paginated', validateFeatureFlag: '/flags/import/validation', + validateFeatureFlagList: '/flags/lists/import/validation', importFeatureFlag: '/flags/import', + importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', diff --git a/types/src/index.ts b/types/src/index.ts index 8a81095a7a..18d0f4bc7a 100644 --- a/types/src/index.ts +++ b/types/src/index.ts @@ -35,6 +35,7 @@ export { FLAG_SORT_KEY, FLAG_SEARCH_KEY, FEATURE_FLAG_STATUS, + FEATURE_FLAG_PARTICIPANT_LIST_KEY, STATUS_INDICATOR_CHIP_TYPE, FILE_TYPE, } from './Experiment/enums'; From 2c7ce14169a38bfe3a74ea4793b2dcb7b5efa90a Mon Sep 17 00:00:00 2001 From: Yagnik Date: Mon, 21 Oct 2024 21:07:08 +0530 Subject: [PATCH 02/11] remove console log --- .../feature-flag-exclusions-section-card.component.ts | 1 - 1 file changed, 1 deletion(-) 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 2fe421f1b3..6303802e76 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 @@ -65,7 +65,6 @@ export class FeatureFlagExclusionsSectionCardComponent { } onMenuButtonItemClick(event, flag: FeatureFlag) { - console.log('Menu Button Item Clicked:', event); switch (event) { case 'Import Include List': this.dialogService From 89491b1e62e00dd55b7d8dc3401b40f6d07faa8c Mon Sep 17 00:00:00 2001 From: Yagnik Date: Tue, 22 Oct 2024 19:01:58 +0530 Subject: [PATCH 03/11] fixed failing testcases introduced due to dev merge --- .../src/api/controllers/FeatureFlagController.ts | 2 +- .../Upgrade/src/api/services/FeatureFlagService.ts | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts index 8a7b35ab3d..6be1a0929e 100644 --- a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts +++ b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts @@ -935,7 +935,7 @@ export class FeatureFlagsController { @Post('/lists/import') public async importFeatureFlagLists( @Body({ validate: true }) lists: FeatureFlagListImportValidation, - @CurrentUser() currentUser: User, + @CurrentUser() currentUser: UserDTO, @Req() request: AppRequest ): Promise { return await this.featureFlagService.importFeatureFlagLists( diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index 26e2538f8d..d175fa4369 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -150,7 +150,11 @@ export class FeatureFlagService { return featureFlag; } - public async create(flagDTO: FeatureFlagValidation, currentUser: UserDTO, logger: UpgradeLogger): Promise { + public async create( + flagDTO: FeatureFlagValidation, + currentUser: UserDTO, + logger: UpgradeLogger + ): Promise { logger.info({ message: 'Create a new feature flag', details: flagDTO }); const result = await this.featureFlagRepository.validateUniqueKey(flagDTO); @@ -336,7 +340,11 @@ export class FeatureFlagService { return featureFlag; } - public async update(flagDTO: FeatureFlagValidation, currentUser: UserDTO, logger: UpgradeLogger): Promise { + public async update( + flagDTO: FeatureFlagValidation, + currentUser: UserDTO, + logger: UpgradeLogger + ): Promise { logger.info({ message: `Update a Feature Flag => ${flagDTO.toString()}` }); const result = await this.featureFlagRepository.validateUniqueKey(flagDTO); @@ -1117,7 +1125,7 @@ export class FeatureFlagService { featureFlagListFiles: IFeatureFlagFile[], featureFlagId: string, listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY, - currentUser: User, + currentUser: UserDTO, logger: UpgradeLogger ): Promise { logger.info({ message: 'Import feature flags' }); From 98392793e942eea9a6f02c0465b3ee3af8504448 Mon Sep 17 00:00:00 2001 From: Yagnik Date: Wed, 23 Oct 2024 15:08:49 +0530 Subject: [PATCH 04/11] addressed review comments --- .../src/api/services/FeatureFlagService.ts | 27 ++++++------- ...-flag-exclusions-section-card.component.ts | 4 +- .../shared/services/common-dialog.service.ts | 38 +++++-------------- .../projects/upgrade/src/assets/i18n/en.json | 4 +- 4 files changed, 24 insertions(+), 49 deletions(-) diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index d175fa4369..2c94690e05 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -1057,7 +1057,7 @@ export class FeatureFlagService { return validationErrors .map((result) => { if (result.status === 'fulfilled') { - return result.value; + return result.value ? result.value : null; } else { const { fileName, compatibilityType } = result.reason; return { fileName: fileName, compatibilityType: compatibilityType }; @@ -1141,15 +1141,12 @@ export class FeatureFlagService { }; }); - const validFiles: FeatureFlagListImportValidator[] = await Promise.all( - fileStatusArray - .filter((fileStatus) => fileStatus.error !== FF_COMPATIBILITY_TYPE.INCOMPATIBLE) - .map(async (fileStatus) => { - const featureFlagListFile = featureFlagListFiles.find((file) => file.fileName === fileStatus.fileName); - - return JSON.parse(featureFlagListFile.fileContent as string); - }) - ); + const validFiles: FeatureFlagListImportValidator[] = fileStatusArray + .filter((fileStatus) => fileStatus.error !== FF_COMPATIBILITY_TYPE.INCOMPATIBLE) + .map((fileStatus) => { + const featureFlagListFile = featureFlagListFiles.find((file) => file.fileName === fileStatus.fileName); + return JSON.parse(featureFlagListFile.fileContent as string); + }); const createdLists: (FeatureFlagSegmentInclusion | FeatureFlagSegmentExclusion)[] = await this.dataSource.transaction(async (transactionalEntityManager) => { @@ -1157,14 +1154,12 @@ export class FeatureFlagService { for (const list of validFiles) { const { name, description, context, type } = list.segment; - const userIds = list.segment.individualForSegment.map((individual) => - individual.userId ? individual.userId : null - ); + const userIds = list.segment.individualForSegment.map((individual) => individual.userId); - const subSegmentIds = list.segment.subSegments.map((subSegment) => (subSegment.id ? subSegment.id : null)); + const subSegmentIds = list.segment.subSegments.map((subSegment) => subSegment.id); const groups = list.segment.groupForSegment.map((group) => { - return group.type && group.groupId ? { type: group.type, groupId: group.groupId } : null; + return { type: group.type, groupId: group.groupId }; }); const listDoc: FeatureFlagListValidator = { @@ -1255,7 +1250,7 @@ export class FeatureFlagService { return validationErrors .map((result) => { if (result.status === 'fulfilled') { - return result.value; + return result.value ? result.value : null; } else { const { fileName, compatibilityType } = result.reason; return { fileName: fileName, compatibilityType: compatibilityType }; 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 6303802e76..04c986dbc9 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 @@ -66,13 +66,13 @@ export class FeatureFlagExclusionsSectionCardComponent { onMenuButtonItemClick(event, flag: FeatureFlag) { switch (event) { - case 'Import Include List': + case 'Import Exclude List': this.dialogService .openImportFeatureFlagExcludeListModal(flag.id) .afterClosed() .subscribe(() => this.featureFlagService.fetchFeatureFlagById(flag.id)); break; - case 'Export All Include Lists': + case 'Export All Exclude Lists': // this.dialogService.openImportFeatureFlagListModal(); break; default: 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 d6a39aac2d..af1ee827c0 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 @@ -354,48 +354,28 @@ export class DialogService { } openImportFeatureFlagModal() { - const commonModalConfig: CommonModalConfig = { - title: 'Import Feature Flag', - primaryActionBtnLabel: 'Import', - primaryActionBtnColor: 'primary', - cancelBtnLabel: 'Cancel', - params: { - listType: null, - flagId: null, - }, - }; - return this.openImportModal(commonModalConfig); + return this.openImportModal('Import Feature Flag', null, null); } openImportFeatureFlagIncludeListModal(flagId: string) { - const commonModalConfig: CommonModalConfig = { - title: 'Import List', - primaryActionBtnLabel: 'Import', - primaryActionBtnColor: 'primary', - cancelBtnLabel: 'Cancel', - params: { - listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE, - flagId: flagId, - }, - }; - return this.openImportModal(commonModalConfig); + return this.openImportModal('Import List', FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE, flagId); } openImportFeatureFlagExcludeListModal(flagId: string) { - const commonModalConfig: CommonModalConfig = { - title: 'Import List', + return this.openImportModal('Import List', FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE, flagId); + } + + openImportModal(title, listType, flagId) { + const commonModalConfig: CommonModalConfig = { + title: title, primaryActionBtnLabel: 'Import', primaryActionBtnColor: 'primary', cancelBtnLabel: 'Cancel', params: { - listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE, + listType: listType, flagId: flagId, }, }; - return this.openImportModal(commonModalConfig); - } - - openImportModal(commonModalConfig: CommonModalConfig) { const config: MatDialogConfig = { data: commonModalConfig, width: ModalSize.STANDARD, diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index 6bcc80205b..81f3b83830 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -411,8 +411,8 @@ "feature-flags.enable.text": "Enable", "feature-flags.add-feature-flag.text": "Add Feature Flag", "feature-flags.import-feature-flag.text": "Import Feature Flag", - "feature-flags.import-feature-flag.message.text": "The Feature Flag JSON file should include the required properties for it to be imported", - "feature-flags.import-feature-flag-list.message.text": "The List JSON file should include the required properties for it to be imported", + "feature-flags.import-feature-flag.message.text": "The Feature Flag JSON file should include the required properties for it to be imported.", + "feature-flags.import-feature-flag-list.message.text": "The List JSON file should include the required properties for it to be imported.", "feature-flags.export-all-feature-flags.text": "Export All Feature Flag Designs", "feature-flags.export-feature-flag-design.text": "Export Feature Flag Design", "feature-flags.export-feature-flags-data.text": "Email Feature Flag Data", From fe0683f0369cc730874dea4d6a581bc382a76258 Mon Sep 17 00:00:00 2001 From: Yagnik Date: Fri, 25 Oct 2024 16:08:39 +0530 Subject: [PATCH 05/11] updated the import format by removing unnecessary parameters --- .../validators/FeatureFlagImportValidator.ts | 10 +++++++ .../src/api/services/FeatureFlagService.ts | 29 ++++++------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagImportValidator.ts b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagImportValidator.ts index b0416e91c9..387be692f7 100644 --- a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagImportValidator.ts +++ b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagImportValidator.ts @@ -12,6 +12,7 @@ import { import { SEGMENT_TYPE } from 'upgrade_types'; import { Type } from 'class-transformer'; import { FeatureFlagCoreValidation } from './FeatureFlagValidator'; +import { SegmentInputValidator } from './SegmentInputValidator'; class IndividualValidator { @IsNotEmpty() @@ -101,6 +102,15 @@ export class FeatureFlagListImportValidator { public segment: SegmentImportValidator; } +export class ImportFeatureFlagListValidator { + @IsNotEmpty() + public listType: string; + + @ValidateNested() + @Type(() => SegmentInputValidator) + public segment: SegmentInputValidator; +} + export class FeatureFlagImportDataValidation extends FeatureFlagCoreValidation { @IsOptional() @IsArray() diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index 4e13cb6c9a..6eebbe53ad 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -47,7 +47,7 @@ import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; import { FeatureFlagImportDataValidation, - FeatureFlagListImportValidator, + ImportFeatureFlagListValidator, } from '../controllers/validators/FeatureFlagImportValidator'; import { ExperimentAuditLogRepository } from '../repositories/ExperimentAuditLogRepository'; import { UserDTO } from '../DTO/UserDTO'; @@ -1126,7 +1126,7 @@ export class FeatureFlagService { }; }); - const validFiles: FeatureFlagListImportValidator[] = fileStatusArray + const validFiles: ImportFeatureFlagListValidator[] = fileStatusArray .filter((fileStatus) => fileStatus.error !== FF_COMPATIBILITY_TYPE.INCOMPATIBLE) .map((fileStatus) => { const featureFlagListFile = featureFlagListFiles.find((file) => file.fileName === fileStatus.fileName); @@ -1137,21 +1137,11 @@ export class FeatureFlagService { await this.dataSource.transaction(async (transactionalEntityManager) => { const listDocs: FeatureFlagListValidator[] = []; for (const list of validFiles) { - const { name, description, context, type } = list.segment; - - const userIds = list.segment.individualForSegment.map((individual) => individual.userId); - - const subSegmentIds = list.segment.subSegments.map((subSegment) => subSegment.id); - - const groups = list.segment.groupForSegment.map((group) => { - return { type: group.type, groupId: group.groupId }; - }); - const listDoc: FeatureFlagListValidator = { ...list, enabled: false, flagId: featureFlagId, - segment: { id: uuid(), name, description, context, type, userIds, subSegmentIds, groups }, + segment: { ...list.segment, id: uuid() }, }; listDocs.push(listDoc); @@ -1247,18 +1237,18 @@ export class FeatureFlagService { public async validateImportFeatureFlagList( fileName: string, flag: FeatureFlag, - list: FeatureFlagListImportValidator + list: ImportFeatureFlagListValidator ) { let compatibilityType = FF_COMPATIBILITY_TYPE.COMPATIBLE; - list = plainToClass(FeatureFlagListImportValidator, list); + list = plainToClass(ImportFeatureFlagListValidator, list); await validate(list, { forbidUnknownValues: true, stopAtFirstError: true }).then((errors) => { if (errors.length > 0) { compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; } }); - if (!(list instanceof FeatureFlagListImportValidator)) { + if (!(list instanceof ImportFeatureFlagListValidator)) { compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; } @@ -1268,8 +1258,7 @@ export class FeatureFlagService { if (compatibilityType === FF_COMPATIBILITY_TYPE.COMPATIBLE) { if (list.listType === 'Segment') { - const subSegmentIds = list.segment.subSegments.map((seg) => seg.id); - const segments = await this.segmentService.getSegmentByIds(subSegmentIds); + const segments = await this.segmentService.getSegmentByIds(list.segment.subSegmentIds); if (!segments.length) { compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; @@ -1280,14 +1269,14 @@ export class FeatureFlagService { compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; } }); - } else if (list.listType !== 'Individual' && list.segment.groupForSegment.length) { + } else if (list.listType !== 'Individual' && list.segment.groups.length) { const contextMetaData = env.initialization.contextMetadata; const groupTypes = contextMetaData[flag.context[0]].GROUP_TYPES; if (!groupTypes.includes(list.listType)) { compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; } - list.segment.groupForSegment.forEach((group) => { + list.segment.groups.forEach((group) => { if (group.type !== list.listType) { compatibilityType = FF_COMPATIBILITY_TYPE.INCOMPATIBLE; } From ce0548e5a2c36e182b275029b3b17719b57a75ca Mon Sep 17 00:00:00 2001 From: Yagnik Date: Mon, 28 Oct 2024 15:02:30 +0530 Subject: [PATCH 06/11] export feature flag list modals, API endpoing and functionality --- .../api/controllers/FeatureFlagController.ts | 117 +++++++++++++++++- .../src/api/services/FeatureFlagService.ts | 39 ++++++ .../feature-flags.data.service.ts | 10 ++ .../feature-flags/feature-flags.service.ts | 8 ++ .../store/feature-flags.actions.ts | 26 ++++ .../store/feature-flags.effects.ts | 44 +++++++ .../store/feature-flags.model.ts | 5 - ...-flag-exclusions-section-card.component.ts | 21 +++- ...feature-flag-exclusions-table.component.ts | 6 +- ...-flag-inclusions-section-card.component.ts | 26 ++-- ...feature-flag-inclusions-table.component.ts | 6 +- ...overview-details-section-card.component.ts | 2 +- ...etails-participant-list-table.component.ts | 2 +- .../shared/services/common-dialog.service.ts | 8 +- .../services/common-export-helpers.service.ts | 3 +- .../projects/upgrade/src/assets/i18n/en.json | 3 + .../src/environments/environment-types.ts | 2 + .../environment.beanstalk.prod.ts | 2 + .../src/environments/environment.bsnl.ts | 2 + .../src/environments/environment.demo.prod.ts | 2 + .../src/environments/environment.prod.ts | 2 + .../src/environments/environment.qa.ts | 2 + .../src/environments/environment.staging.ts | 2 + .../upgrade/src/environments/environment.ts | 2 + 24 files changed, 309 insertions(+), 33 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts index 6be1a0929e..4b24d03a43 100644 --- a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts +++ b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts @@ -23,7 +23,12 @@ import { } from './validators/FeatureFlagsPaginatedParamsValidator'; import { FeatureFlagFilterModeUpdateValidator } from './validators/FeatureFlagFilterModeUpdateValidator'; import { AppRequest, PaginationResponse } from '../../types'; -import { IImportError, FEATURE_FLAG_LIST_FILTER_MODE, SERVER_ERROR } from 'upgrade_types'; +import { + IImportError, + FEATURE_FLAG_LIST_FILTER_MODE, + SERVER_ERROR, + FEATURE_FLAG_PARTICIPANT_LIST_KEY, +} from 'upgrade_types'; import { FeatureFlagImportValidation, FeatureFlagListImportValidation, @@ -35,6 +40,8 @@ import { FeatureFlagListValidator } from '../controllers/validators/FeatureFlagL import { Segment } from 'src/api/models/Segment'; import { Response } from 'express'; import { UserDTO } from '../DTO/UserDTO'; +import { ImportFeatureFlagListValidator } from './validators/FeatureFlagImportValidator'; +import { NotFoundException } from '@nestjs/common/exceptions'; interface FeatureFlagsPaginationInfo extends PaginationResponse { nodes: FeatureFlag[]; @@ -946,4 +953,112 @@ export class FeatureFlagsController { request.logger ); } + + /** + * @swagger + * /flags/export/lists/{id}: + * get: + * description: Export All Include lists of Feature Flag JSON + * tags: + * - Feature Flags + * produces: + * - application/json + * parameters: + * - in: path + * flagId: Id + * description: Feature Flag Id + * required: true + * schema: + * type: string + * responses: + * '200': + * description: Get Feature Flag''s All Include Lists JSON + * '401': + * description: Authorization Required Error + * '404': + * description: Feature Flag not found + * '400': + * description: id must be a UUID + * '500': + * description: Internal Server Error + */ + @Get('/export/includeLists/:id') + public async exportAllIncludeLists( + @Params({ validate: true }) { id }: IdValidator, + @Req() request: AppRequest, + @Res() response: Response + ): Promise { + const lists = await this.featureFlagService.exportAllLists( + id, + FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE, + request.logger + ); + if (lists !== null) { + // download JSON file with appropriate headers to response body; + if (lists.length === 1) { + response.setHeader('Content-Disposition', `attachment; filename="${lists[0].segment.name}.json"`); + } else { + response.setHeader('Content-Disposition', `attachment; filename="lists.zip"`); + } + response.setHeader('Content-Type', 'application/json'); + } else { + throw new NotFoundException('Experiment not found.'); + } + + return lists; + } + + /** + * @swagger + * /flags/export/lists/{id}: + * get: + * description: Export All Exclude lists of Feature Flag JSON + * tags: + * - Feature Flags + * produces: + * - application/json + * parameters: + * - in: path + * flagId: Id + * description: Feature Flag Id + * required: true + * schema: + * type: string + * responses: + * '200': + * description: Get Feature Flag''s All Include Lists JSON + * '401': + * description: Authorization Required Error + * '404': + * description: Feature Flag not found + * '400': + * description: id must be a UUID + * '500': + * description: Internal Server Error + */ + @Get('/export/excludeLists/:id') + public async exportAllExcludeLists( + @Params({ validate: true }) { id }: IdValidator, + @Req() request: AppRequest, + @Res() response: Response + ): Promise { + const lists = await this.featureFlagService.exportAllLists( + id, + FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE, + request.logger + ); + if (lists !== null) { + // download JSON file with appropriate headers to response body; + if (lists.length === 1) { + response.setHeader('Content-Disposition', `attachment; filename="${lists[0].segment.name}.json"`); + } else { + response.setHeader('Content-Disposition', `attachment; filename="lists.zip"`); + } + response.setHeader('Content-Type', 'application/json'); + } else { + throw new NotFoundException('Experiment not found.'); + } + + return lists; + } } diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index 6eebbe53ad..badc041125 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -1289,4 +1289,43 @@ export class FeatureFlagService { compatibilityType: compatibilityType, }; } + + public async exportAllLists( + id: string, + listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY, + logger: UpgradeLogger + ): Promise { + const featureFlag = await this.findOne(id, logger); + let listsArray: ImportFeatureFlagListValidator[] = []; + if (featureFlag) { + let lists: (FeatureFlagSegmentExclusion | FeatureFlagSegmentExclusion)[] = []; + if (listType === FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE) { + lists = featureFlag.featureFlagSegmentInclusion; + } else if (listType === FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE) { + lists = featureFlag.featureFlagSegmentExclusion; + } else { + return null; + } + + listsArray = lists.map((list) => { + const { name, description, context, type } = list.segment; + + const userIds = list.segment.individualForSegment.map((individual) => individual.userId); + + const subSegmentIds = list.segment.subSegments.map((subSegment) => subSegment.id); + + const groups = list.segment.groupForSegment.map((group) => { + return { type: group.type, groupId: group.groupId }; + }); + + const listDoc: ImportFeatureFlagListValidator = { + listType: list.listType, + segment: { name, description, context, type, userIds, subSegmentIds, groups }, + }; + return listDoc; + }); + } + + return listsArray; + } } diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts index 5511f2f929..798b7e1a57 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts @@ -89,6 +89,16 @@ export class FeatureFlagsDataService { return this.http.get(url); } + exportAllIncludeListsDesign(id: string) { + const url = `${this.environment.api.exportFFAllIncludeListsDesign}/${id}`; + return this.http.get(url); + } + + exportAllExcludeListsDesign(id: string) { + const url = `${this.environment.api.exportFFAllExcludeListsDesign}/${id}`; + return this.http.get(url); + } + deleteFeatureFlag(id: string) { const url = `${this.environment.api.featureFlag}/${id}`; return this.http.delete(url); diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts index d721bd47d3..2d85183a4b 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts @@ -164,6 +164,14 @@ export class FeatureFlagsService { this.store$.dispatch(FeatureFlagsActions.actionExportFeatureFlagDesign({ featureFlagId })); } + exportAllIncludeListsData(featureFlagId: string) { + this.store$.dispatch(FeatureFlagsActions.actionExportAllIncludeListsDesign({ featureFlagId })); + } + + exportAllExcludeListsData(featureFlagId: string) { + this.store$.dispatch(FeatureFlagsActions.actionExportAllExcludeListsDesign({ featureFlagId })); + } + setSearchKey(searchKey: FLAG_SEARCH_KEY) { this.localStorageService.setItem(FeatureFlagLocalStorageKeys.FEATURE_FLAG_SEARCH_KEY, searchKey); this.store$.dispatch(FeatureFlagsActions.actionSetSearchKey({ searchKey })); diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts index ab00140bbb..8eb5cc2bf2 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts @@ -94,6 +94,32 @@ export const actionExportFeatureFlagDesignSuccess = createAction('[Feature Flags export const actionExportFeatureFlagDesignFailure = createAction('[Feature Flags] Export Feature Flag Design Failure'); +export const actionExportAllIncludeListsDesign = createAction( + '[Feature Flags] Export All Include Lists Design', + props<{ featureFlagId: string }>() +); + +export const actionExportAllIncludeListsDesignSuccess = createAction( + '[Feature Flags] Export All Include Lists Design Success' +); + +export const actionExportAllIncludeListsDesignFailure = createAction( + '[Feature Flags] Export All Include Lists Design Failure' +); + +export const actionExportAllExcludeListsDesign = createAction( + '[Feature Flags] Export All Exclude Lists Design', + props<{ featureFlagId: string }>() +); + +export const actionExportAllExcludeListsDesignSuccess = createAction( + '[Feature Flags] Export All Exclude Lists Design Success' +); + +export const actionExportAllExcludeListsDesignFailure = createAction( + '[Feature Flags] Export All Exclude Lists Design Failure' +); + export const actionSetIsLoadingFeatureFlags = createAction( '[Feature Flags] Set Is Loading Flags', props<{ isLoadingFeatureFlags: boolean }>() diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts index a0894d9ee8..43aaf8b03c 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts @@ -336,5 +336,49 @@ export class FeatureFlagsEffects { ) ); + exportAllIncludeListsDesign$ = createEffect(() => + this.actions$.pipe( + ofType(FeatureFlagsActions.actionExportAllIncludeListsDesign), + map((action) => ({ featureFlagId: action.featureFlagId })), + switchMap(({ featureFlagId }) => + this.featureFlagsDataService.exportAllIncludeListsDesign(featureFlagId).pipe( + map((exportedAllListsDesign: any[]) => { + if (exportedAllListsDesign.length) { + this.commonExportHelpersService.convertDataToDownload(exportedAllListsDesign, 'Lists'); + this.notificationService.showSuccess('Feature Flag Design JSON downloaded!'); + } + return FeatureFlagsActions.actionExportAllIncludeListsDesignSuccess(); + }), + catchError((error) => { + this.notificationService.showError('Failed to export All include lists Design'); + return of(FeatureFlagsActions.actionExportAllIncludeListsDesignFailure()); + }) + ) + ) + ) + ); + + exportAllExcludeListsDesign$ = createEffect(() => + this.actions$.pipe( + ofType(FeatureFlagsActions.actionExportAllExcludeListsDesign), + map((action) => ({ featureFlagId: action.featureFlagId })), + switchMap(({ featureFlagId }) => + this.featureFlagsDataService.exportAllExcludeListsDesign(featureFlagId).pipe( + map((exportedAllListsDesign: any[]) => { + if (exportedAllListsDesign) { + this.commonExportHelpersService.convertDataToDownload(exportedAllListsDesign, 'Lists'); + this.notificationService.showSuccess('Feature Flag Design JSON downloaded!'); + } + return FeatureFlagsActions.actionExportAllExcludeListsDesignSuccess(); + }), + catchError((error) => { + this.notificationService.showError('Failed to export All exclude lists Design'); + return of(FeatureFlagsActions.actionExportAllExcludeListsDesignFailure()); + }) + ) + ) + ) + ); + private getSearchString$ = () => this.store$.pipe(select(selectSearchString)).pipe(first()); } 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 713fc93478..c04f82e479 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 @@ -159,11 +159,6 @@ export enum FEATURE_FLAG_DETAILS_PAGE_ACTIONS { EMAIL_DATA = 'Email Feature Flag Data', } -export enum FEATURE_FLAG_PARTICIPANT_LIST_KEY { - INCLUDE = 'featureFlagSegmentInclusion', - EXCLUDE = 'featureFlagSegmentExclusion', -} - export const FLAG_ROOT_COLUMN_NAMES = { NAME: 'name', STATUS: 'status', 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 04c986dbc9..1aa2262f72 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 @@ -46,15 +46,16 @@ export class FeatureFlagExclusionsSectionCardComponent { tableRowCount$ = this.featureFlagService.selectFeatureFlagExclusionsLength$; selectedFlag$ = this.featureFlagService.selectedFeatureFlag$; + menuButtonItems: IMenuButtonItem[] = [ + { name: 'Import Exclude List', disabled: false }, + { name: 'Export All Exclude Lists', disabled: false }, + ]; + constructor( private featureFlagService: FeatureFlagsService, private dialogService: DialogService, private authService: AuthService ) {} - menuButtonItems: IMenuButtonItem[] = [ - { name: 'Import Exclude List', disabled: false }, - { name: 'Export All Exclude Lists', disabled: true }, - ]; ngOnInit() { this.permissions$ = this.authService.userPermissions$; @@ -65,6 +66,7 @@ export class FeatureFlagExclusionsSectionCardComponent { } onMenuButtonItemClick(event, flag: FeatureFlag) { + const confirmMessage = 'feature-flags.export-all-exclude-lists-design.confirmation-text.text'; switch (event) { case 'Import Exclude List': this.dialogService @@ -73,7 +75,16 @@ export class FeatureFlagExclusionsSectionCardComponent { .subscribe(() => this.featureFlagService.fetchFeatureFlagById(flag.id)); break; case 'Export All Exclude Lists': - // this.dialogService.openImportFeatureFlagListModal(); + if (flag.featureFlagSegmentExclusion.length) { + this.dialogService + .openExportDesignModal('Export All Exclude Lists', confirmMessage) + .afterClosed() + .subscribe((isExportClicked: boolean) => { + if (isExportClicked) { + this.featureFlagService.exportAllExcludeListsData(flag.id); + } + }); + } break; default: console.log('Unknown 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-table/feature-flag-exclusions-table.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-table/feature-flag-exclusions-table.component.ts index c7982e5b84..281d545122 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-table/feature-flag-exclusions-table.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-table/feature-flag-exclusions-table.component.ts @@ -1,12 +1,10 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { FeatureFlagsService } from '../../../../../../../../core/feature-flags/feature-flags.service'; -import { - FEATURE_FLAG_PARTICIPANT_LIST_KEY, - ParticipantListRowActionEvent, -} from '../../../../../../../../core/feature-flags/store/feature-flags.model'; +import { ParticipantListRowActionEvent } from '../../../../../../../../core/feature-flags/store/feature-flags.model'; import { CommonDetailsParticipantListTableComponent } from '../../../../../../../../shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; +import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; @Component({ selector: 'app-feature-flag-exclusions-table', 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 d01b009660..6a3da78569 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 @@ -15,6 +15,7 @@ import { MatDialogRef } from '@angular/material/dialog'; import { CommonSimpleConfirmationModalComponent } from '../../../../../../../shared-standalone-component-lib/components/common-simple-confirmation-modal/common-simple-confirmation-modal.component'; import { Observable, Subscription, combineLatest, map } from 'rxjs'; import { + FeatureFlag, PARTICIPANT_LIST_ROW_ACTION, ParticipantListRowActionEvent, ParticipantListTableRow, @@ -48,6 +49,12 @@ export class FeatureFlagInclusionsSectionCardComponent { tableRowCount$ = this.featureFlagService.selectFeatureFlagInclusionsLength$; selectedFlag$ = this.featureFlagService.selectedFeatureFlag$; + subscriptions = new Subscription(); + menuButtonItems: IMenuButtonItem[] = [ + { name: 'Import Include List', disabled: false }, + { name: 'Export All Include Lists', disabled: false }, + ]; + rowCountWithInclude$: Observable = combineLatest([this.tableRowCount$, this.selectedFlag$]).pipe( map(([tableRowCount, selectedFeatureFlag]) => selectedFeatureFlag?.filterMode === FILTER_MODE.INCLUDE_ALL ? 0 : tableRowCount @@ -63,11 +70,6 @@ export class FeatureFlagInclusionsSectionCardComponent { private dialogService: DialogService, private authService: AuthService ) {} - subscriptions = new Subscription(); - menuButtonItems: IMenuButtonItem[] = [ - { name: 'Import Include List', disabled: false }, - { name: 'Export All Include Lists', disabled: true }, - ]; confirmIncludeAllChangeDialogRef: MatDialogRef; @@ -121,7 +123,8 @@ export class FeatureFlagInclusionsSectionCardComponent { this.isSectionCardExpanded = newFilterMode !== FILTER_MODE.INCLUDE_ALL; } - onMenuButtonItemClick(event, flag) { + onMenuButtonItemClick(event, flag: FeatureFlag) { + const confirmMessage = 'feature-flags.export-all-include-lists-design.confirmation-text.text'; switch (event) { case 'Import Include List': this.dialogService @@ -130,7 +133,16 @@ export class FeatureFlagInclusionsSectionCardComponent { .subscribe(() => this.featureFlagService.fetchFeatureFlagById(flag.id)); break; case 'Export All Include Lists': - // this.dialogService.openDeleteFeatureFlagModal(); + if (flag.featureFlagSegmentInclusion.length) { + this.dialogService + .openExportDesignModal('Export All Include Lists', confirmMessage) + .afterClosed() + .subscribe((isExportClicked: boolean) => { + if (isExportClicked) { + this.featureFlagService.exportAllIncludeListsData(flag.id); + } + }); + } break; default: console.log('Unknown 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-inclusions-section-card/feature-flag-inclusions-table/feature-flag-inclusions-table.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-table/feature-flag-inclusions-table.component.ts index 5f89142211..c60b01b44e 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-table/feature-flag-inclusions-table.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-table/feature-flag-inclusions-table.component.ts @@ -1,9 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { FeatureFlagsService } from '../../../../../../../../core/feature-flags/feature-flags.service'; -import { - FEATURE_FLAG_PARTICIPANT_LIST_KEY, - ParticipantListRowActionEvent, -} from '../../../../../../../../core/feature-flags/store/feature-flags.model'; +import { ParticipantListRowActionEvent } from '../../../../../../../../core/feature-flags/store/feature-flags.model'; +import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; import { CommonDetailsParticipantListTableComponent } from '../../../../../../../../shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-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-overview-details-section-card/feature-flag-overview-details-section-card.component.ts index b697c371be..212a8276eb 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-overview-details-section-card/feature-flag-overview-details-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-overview-details-section-card/feature-flag-overview-details-section-card.component.ts @@ -156,7 +156,7 @@ export class FeatureFlagOverviewDetailsSectionCardComponent implements OnInit, O openConfirmExportDesignModal(id: string) { const confirmMessage = 'feature-flags.export-feature-flag-design.confirmation-text.text'; this.dialogService - .openExportFeatureFlagDesignModal(confirmMessage) + .openExportDesignModal(FEATURE_FLAG_DETAILS_PAGE_ACTIONS.EXPORT_DESIGN, confirmMessage) .afterClosed() .subscribe((isExportClicked: boolean) => { if (isExportClicked) { diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts index 4289bb26df..0221aee96a 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts @@ -10,12 +10,12 @@ import { RouterModule } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { CommonStatusIndicatorChipComponent } from '../common-status-indicator-chip/common-status-indicator-chip.component'; import { - FEATURE_FLAG_PARTICIPANT_LIST_KEY, PARTICIPANT_LIST_ROW_ACTION, ParticipantListRowActionEvent, ParticipantListTableRow, } from '../../../core/feature-flags/store/feature-flags.model'; import { MemberTypes } from '../../../core/segments/store/segments.model'; +import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; /** * `CommonDetailsParticipantListTableComponent` is a reusable Angular component that displays a table with common details for participant lists. 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 af1ee827c0..ff319f8f5c 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 @@ -6,13 +6,11 @@ import { ImportFeatureFlagModalComponent } from '../../features/dashboard/featur import { UpsertFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component'; import { UpsertPrivateSegmentListModalComponent } from '../../features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component'; import { - ImportListParams, UPSERT_PRIVATE_SEGMENT_LIST_ACTION, UpsertPrivateSegmentListParams, } from '../../core/segments/store/segments.model'; import { FEATURE_FLAG_DETAILS_PAGE_ACTIONS, - FEATURE_FLAG_PARTICIPANT_LIST_KEY, FeatureFlag, ParticipantListTableRow, UPSERT_FEATURE_FLAG_ACTION, @@ -25,6 +23,7 @@ import { ModalSize, SimpleConfirmationModalParams, } from '../../shared-standalone-component-lib/components/common-modal/common-modal.types'; +import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; @Injectable({ providedIn: 'root', @@ -322,9 +321,9 @@ export class DialogService { return this.dialog.open(DeleteFeatureFlagModalComponent, config); } - openExportFeatureFlagDesignModal(warning: string): MatDialogRef { + openExportDesignModal(title, warning: string): MatDialogRef { const commonModalConfig: CommonModalConfig = { - title: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.EXPORT_DESIGN, + title: title, primaryActionBtnLabel: 'Export', primaryActionBtnColor: 'primary', cancelBtnLabel: 'Cancel', @@ -376,6 +375,7 @@ export class DialogService { flagId: flagId, }, }; + const config: MatDialogConfig = { data: commonModalConfig, width: ModalSize.STANDARD, diff --git a/frontend/projects/upgrade/src/app/shared/services/common-export-helpers.service.ts b/frontend/projects/upgrade/src/app/shared/services/common-export-helpers.service.ts index 16aefb6ba6..5584e0c486 100644 --- a/frontend/projects/upgrade/src/app/shared/services/common-export-helpers.service.ts +++ b/frontend/projects/upgrade/src/app/shared/services/common-export-helpers.service.ts @@ -21,7 +21,8 @@ export class CommonExportHelpersService { if (data.length > 1) { const zip = new JSZip(); data.forEach((element, index) => { - zip.file(element.name + ' (File ' + (index + 1) + ').json', JSON.stringify(element)); + const fileName = element.name ? element.name : element.segment.name; + zip.file(fileName + ' (file ' + (index + 1) + ').json', JSON.stringify(element)); }); zip.generateAsync({ type: 'base64' }).then((content) => { this.download(zipFileName + '.zip', content, true); diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index 81f3b83830..112822aae5 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -419,6 +419,8 @@ "feature-flags.export-all-feature-flags.confirmation-text.text": "Are you sure you want to export all feature flags design (JSON)?", "feature-flags.export-feature-flag-design.confirmation-text.text": "Are you sure you want to export the feature flags design (JSON)?", "feature-flags.export-feature-flags-data.confirmation-text.text": "Are you sure you want to email the feature flags data (CSV)?", + "feature-flags.export-all-include-lists-design.confirmation-text.text": "Are you sure you want to export all include lists (JSON)?", + "feature-flags.export-all-exclude-lists-design.confirmation-text.text": "Are you sure you want to export all exclude lists (JSON)?", "feature-flags.upsert-list-modal.segment-placeholder.text": "Select segment", "feature-flags.upsert-list-modal.segment.error.message.text": "Segment must be selected from the available options.", "feature-flags.upsert-list-modal.values-label.text": "Values", @@ -467,6 +469,7 @@ "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.export-all-lists-design.confirmation-text.text": "Are you sure you want to export all lists (JSON)?", "segments.segment-experiment-list-title.text": "Used by ({{ numberOfUses }}) ", "segments.segment-experiment-list-name.text": "Name", "segments.segment-experiment-list-state.text": "Status", diff --git a/frontend/projects/upgrade/src/environments/environment-types.ts b/frontend/projects/upgrade/src/environments/environment-types.ts index 77877230bb..65488672b3 100644 --- a/frontend/projects/upgrade/src/environments/environment-types.ts +++ b/frontend/projects/upgrade/src/environments/environment-types.ts @@ -36,6 +36,8 @@ export interface APIEndpoints { updateFilterMode: string; getPaginatedFlags: string; exportFlagsDesign: string; + exportFFAllIncludeListsDesign: string; + exportFFAllExcludeListsDesign: string; emailFlagData: string; addFlagInclusionList: string; addFlagExclusionList: string; diff --git a/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts b/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts index f404e6ce16..72f956e62b 100644 --- a/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.beanstalk.prod.ts @@ -51,6 +51,8 @@ export const environment = { importFeatureFlag: '/flags/import', importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', + exportFFAllIncludeListsDesign: '/flags/export/includeLists', + exportFFAllExcludeListsDesign: '/flags/export/excludeLists', emailFlagData: '/flags/mail', addFlagInclusionList: '/flags/inclusionList', addFlagExclusionList: '/flags/exclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.bsnl.ts b/frontend/projects/upgrade/src/environments/environment.bsnl.ts index a8a97d2adb..abee623819 100644 --- a/frontend/projects/upgrade/src/environments/environment.bsnl.ts +++ b/frontend/projects/upgrade/src/environments/environment.bsnl.ts @@ -51,6 +51,8 @@ export const environment = { importFeatureFlag: '/flags/import', importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', + exportFFAllIncludeListsDesign: '/flags/export/includeLists', + exportFFAllExcludeListsDesign: '/flags/export/excludeLists', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', addFlagExclusionList: '/flags/exclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts index 1022c2f68e..298607b665 100755 --- a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts @@ -51,6 +51,8 @@ export const environment = { importFeatureFlag: '/flags/import', importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', + exportFFAllIncludeListsDesign: '/flags/export/includeLists', + exportFFAllExcludeListsDesign: '/flags/export/excludeLists', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', addFlagExclusionList: '/flags/exclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.prod.ts b/frontend/projects/upgrade/src/environments/environment.prod.ts index e3d28c4f42..ce859f8076 100755 --- a/frontend/projects/upgrade/src/environments/environment.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.prod.ts @@ -51,6 +51,8 @@ export const environment = { importFeatureFlag: '/flags/import', importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', + exportFFAllIncludeListsDesign: '/flags/export/includeLists', + exportFFAllExcludeListsDesign: '/flags/export/excludeLists', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', addFlagExclusionList: '/flags/exclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.qa.ts b/frontend/projects/upgrade/src/environments/environment.qa.ts index 1baa222fd7..1f94489ca2 100644 --- a/frontend/projects/upgrade/src/environments/environment.qa.ts +++ b/frontend/projects/upgrade/src/environments/environment.qa.ts @@ -51,6 +51,8 @@ export const environment = { importFeatureFlag: '/flags/import', importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', + exportFFAllIncludeListsDesign: '/flags/export/includeLists', + exportFFAllExcludeListsDesign: '/flags/export/excludeLists', emailFlagData: '/flags/mail', addFlagInclusionList: '/flags/inclusionList', addFlagExclusionList: '/flags/exclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.staging.ts b/frontend/projects/upgrade/src/environments/environment.staging.ts index fe1af50bac..b2267cd631 100644 --- a/frontend/projects/upgrade/src/environments/environment.staging.ts +++ b/frontend/projects/upgrade/src/environments/environment.staging.ts @@ -51,6 +51,8 @@ export const environment = { importFeatureFlag: '/flags/import', importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', + exportFFAllIncludeListsDesign: '/flags/export/includeLists', + exportFFAllExcludeListsDesign: '/flags/export/excludeLists', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', addFlagExclusionList: '/flags/exclusionList', diff --git a/frontend/projects/upgrade/src/environments/environment.ts b/frontend/projects/upgrade/src/environments/environment.ts index 14e4b86071..16893e74d2 100755 --- a/frontend/projects/upgrade/src/environments/environment.ts +++ b/frontend/projects/upgrade/src/environments/environment.ts @@ -56,6 +56,8 @@ export const environment = { importFeatureFlag: '/flags/import', importFeatureFlagList: '/flags/lists/import', exportFlagsDesign: '/flags/export', + exportFFAllIncludeListsDesign: '/flags/export/includeLists', + exportFFAllExcludeListsDesign: '/flags/export/excludeLists', emailFlagData: '/flags/email', addFlagInclusionList: '/flags/inclusionList', addFlagExclusionList: '/flags/exclusionList', From 37ceb71570243906a3c2154a9f363b485365dedc Mon Sep 17 00:00:00 2001 From: Yagnik Date: Tue, 29 Oct 2024 13:14:10 +0530 Subject: [PATCH 07/11] addressed review comments --- .../src/api/controllers/FeatureFlagController.ts | 12 ++++++------ .../Upgrade/src/api/services/FeatureFlagService.ts | 5 +++++ .../shared/services/common-export-helpers.service.ts | 5 +++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts index 4b24d03a43..f97d9aed84 100644 --- a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts +++ b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts @@ -956,7 +956,7 @@ export class FeatureFlagsController { /** * @swagger - * /flags/export/lists/{id}: + * /flags/export/includeLists/{id}: * get: * description: Export All Include lists of Feature Flag JSON * tags: @@ -993,7 +993,7 @@ export class FeatureFlagsController { FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE, request.logger ); - if (lists !== null) { + if (!lists?.length) { // download JSON file with appropriate headers to response body; if (lists.length === 1) { response.setHeader('Content-Disposition', `attachment; filename="${lists[0].segment.name}.json"`); @@ -1002,7 +1002,7 @@ export class FeatureFlagsController { } response.setHeader('Content-Type', 'application/json'); } else { - throw new NotFoundException('Experiment not found.'); + throw new NotFoundException('Include lists not found.'); } return lists; @@ -1010,7 +1010,7 @@ export class FeatureFlagsController { /** * @swagger - * /flags/export/lists/{id}: + * /flags/export/excludeLists/{id}: * get: * description: Export All Exclude lists of Feature Flag JSON * tags: @@ -1047,7 +1047,7 @@ export class FeatureFlagsController { FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE, request.logger ); - if (lists !== null) { + if (!lists?.length) { // download JSON file with appropriate headers to response body; if (lists.length === 1) { response.setHeader('Content-Disposition', `attachment; filename="${lists[0].segment.name}.json"`); @@ -1056,7 +1056,7 @@ export class FeatureFlagsController { } response.setHeader('Content-Type', 'application/json'); } else { - throw new NotFoundException('Experiment not found.'); + throw new NotFoundException('Exclude lists not found.'); } return lists; diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index badc041125..f9a1b08cc2 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -54,6 +54,7 @@ import { UserDTO } from '../DTO/UserDTO'; import { diffString } from 'json-diff'; import { SegmentRepository } from '../repositories/SegmentRepository'; import { ExperimentAuditLog } from '../models/ExperimentAuditLog'; +import { NotFoundException } from '@nestjs/common/exceptions'; @Service() export class FeatureFlagService { @@ -1307,6 +1308,8 @@ export class FeatureFlagService { return null; } + if (!lists.length) return []; + listsArray = lists.map((list) => { const { name, description, context, type } = list.segment; @@ -1324,6 +1327,8 @@ export class FeatureFlagService { }; return listDoc; }); + } else { + throw new NotFoundException('Experiment not found.'); } return listsArray; diff --git a/frontend/projects/upgrade/src/app/shared/services/common-export-helpers.service.ts b/frontend/projects/upgrade/src/app/shared/services/common-export-helpers.service.ts index 5584e0c486..dad0227db7 100644 --- a/frontend/projects/upgrade/src/app/shared/services/common-export-helpers.service.ts +++ b/frontend/projects/upgrade/src/app/shared/services/common-export-helpers.service.ts @@ -21,14 +21,15 @@ export class CommonExportHelpersService { if (data.length > 1) { const zip = new JSZip(); data.forEach((element, index) => { - const fileName = element.name ? element.name : element.segment.name; + const fileName = element.name || element.segment.name; zip.file(fileName + ' (file ' + (index + 1) + ').json', JSON.stringify(element)); }); zip.generateAsync({ type: 'base64' }).then((content) => { this.download(zipFileName + '.zip', content, true); }); } else { - this.download(data[0].name + '.json', data[0], false); + const filename = data[0].name || data[0].segment.name; + this.download(filename + '.json', data[0], false); } } } From eee03c00aeaeab01f06a40bf17dd589b89f5a277 Mon Sep 17 00:00:00 2001 From: Yagnik Date: Tue, 29 Oct 2024 13:16:48 +0530 Subject: [PATCH 08/11] fixed 404 error issue --- .../Upgrade/src/api/controllers/FeatureFlagController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts index f97d9aed84..4c42d0caaf 100644 --- a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts +++ b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts @@ -993,7 +993,7 @@ export class FeatureFlagsController { FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE, request.logger ); - if (!lists?.length) { + if (lists?.length) { // download JSON file with appropriate headers to response body; if (lists.length === 1) { response.setHeader('Content-Disposition', `attachment; filename="${lists[0].segment.name}.json"`); @@ -1047,7 +1047,7 @@ export class FeatureFlagsController { FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE, request.logger ); - if (!lists?.length) { + if (lists?.length) { // download JSON file with appropriate headers to response body; if (lists.length === 1) { response.setHeader('Content-Disposition', `attachment; filename="${lists[0].segment.name}.json"`); From a841c0add5482300408362b116edd125a74994df Mon Sep 17 00:00:00 2001 From: Yagnik Date: Tue, 29 Oct 2024 13:49:36 +0530 Subject: [PATCH 09/11] addressed review comments --- .../validators/FeatureFlagValidator.ts | 6 ++--- .../src/api/services/FeatureFlagService.ts | 25 +++---------------- .../feature-flags.data.service.ts | 6 ++--- .../app/core/segments/store/segments.model.ts | 4 +-- .../shared/services/common-dialog.service.ts | 9 +++---- 5 files changed, 15 insertions(+), 35 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagValidator.ts b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagValidator.ts index ce7895a4e3..b882ad8891 100644 --- a/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagValidator.ts +++ b/backend/packages/Upgrade/src/api/controllers/validators/FeatureFlagValidator.ts @@ -1,5 +1,5 @@ import { IsNotEmpty, IsDefined, IsString, IsArray, IsEnum, IsOptional, ValidateNested, IsUUID } from 'class-validator'; -import { FEATURE_FLAG_PARTICIPANT_LIST_KEY, FILTER_MODE, FEATURE_FLAG_STATUS } from 'upgrade_types'; +import { FILTER_MODE, FEATURE_FLAG_STATUS, FEATURE_FLAG_LIST_FILTER_MODE } from 'upgrade_types'; import { Type } from 'class-transformer'; import { FeatureFlagListValidator } from './FeatureFlagListValidator'; @@ -84,8 +84,8 @@ export class FeatureFlagListImportValidation { @IsString() @IsNotEmpty() - @IsEnum(FEATURE_FLAG_PARTICIPANT_LIST_KEY) - public listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY; + @IsEnum(FEATURE_FLAG_LIST_FILTER_MODE) + public listType: FEATURE_FLAG_LIST_FILTER_MODE; @IsUUID() @IsNotEmpty() diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index 6eebbe53ad..d110feef92 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -34,7 +34,6 @@ import { FEATURE_FLAG_LIST_FILTER_MODE, FEATURE_FLAG_LIST_OPERATION, ListOperationsData, - FEATURE_FLAG_PARTICIPANT_LIST_KEY, } from 'upgrade_types'; import { UpgradeLogger } from '../../lib/logger/UpgradeLogger'; import { FeatureFlagValidation } from '../controllers/validators/FeatureFlagValidator'; @@ -1109,7 +1108,7 @@ export class FeatureFlagService { public async importFeatureFlagLists( featureFlagListFiles: IFeatureFlagFile[], featureFlagId: string, - listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY, + listType: FEATURE_FLAG_LIST_FILTER_MODE, currentUser: UserDTO, logger: UpgradeLogger ): Promise { @@ -1147,23 +1146,7 @@ export class FeatureFlagService { listDocs.push(listDoc); } - if (listType === FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE) { - return await this.addList( - listDocs, - FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION, - currentUser, - logger, - transactionalEntityManager - ); - } else { - return await this.addList( - listDocs, - FEATURE_FLAG_LIST_FILTER_MODE.EXCLUSION, - currentUser, - logger, - transactionalEntityManager - ); - } + return await this.addList(listDocs, listType, currentUser, logger, transactionalEntityManager); }); logger.info({ message: 'Imported feature flags', details: createdLists }); @@ -1215,9 +1198,7 @@ export class FeatureFlagService { compatibilityType: FF_COMPATIBILITY_TYPE.INCOMPATIBLE, }; } - - const error = await this.validateImportFeatureFlagList(parsedFile.fileName, featureFlag, parsedFile.content); - return error; + return this.validateImportFeatureFlagList(parsedFile.fileName, featureFlag, parsedFile.content); }) ); diff --git a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts index 5511f2f929..fbd8ccc411 100644 --- a/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts @@ -13,7 +13,7 @@ import { } from './store/feature-flags.model'; import { Observable, delay, of } from 'rxjs'; import { AddPrivateSegmentListRequest, EditPrivateSegmentListRequest } from '../segments/store/segments.model'; -import { FEATURE_FLAG_PARTICIPANT_LIST_KEY, IFeatureFlagFile } from 'upgrade_types'; +import { FEATURE_FLAG_LIST_FILTER_MODE, IFeatureFlagFile } from 'upgrade_types'; @Injectable() export class FeatureFlagsDataService { @@ -50,7 +50,7 @@ export class FeatureFlagsDataService { return this.http.post(url, featureFlags); } - validateFeatureFlagList(files: IFeatureFlagFile[], flagId: string, listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY) { + validateFeatureFlagList(files: IFeatureFlagFile[], flagId: string, listType: FEATURE_FLAG_LIST_FILTER_MODE) { const lists = { files: files, listType: listType, flagId: flagId }; const url = this.environment.api.validateFeatureFlagList; return this.http.post(url, lists); @@ -61,7 +61,7 @@ export class FeatureFlagsDataService { return this.http.post(url, featureFlag); } - importFeatureFlagList(files: IFeatureFlagFile[], flagId: string, listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY) { + importFeatureFlagList(files: IFeatureFlagFile[], flagId: string, listType: FEATURE_FLAG_LIST_FILTER_MODE) { const lists = { files: files, listType: listType, flagId: flagId }; const url = this.environment.api.importFeatureFlagList; return this.http.post(url, lists); 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 7dbe1a1ed1..8738e72137 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 @@ -6,7 +6,7 @@ import { SEGMENT_SEARCH_KEY, SORT_AS_DIRECTION, SEGMENT_SORT_KEY, - FEATURE_FLAG_PARTICIPANT_LIST_KEY, + FEATURE_FLAG_LIST_FILTER_MODE, } from 'upgrade_types'; import { ParticipantListTableRow } from '../../feature-flags/store/feature-flags.model'; export { SEGMENT_STATUS }; @@ -173,7 +173,7 @@ export interface UpsertPrivateSegmentListParams { } export interface ImportListParams { - listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY; + listType: FEATURE_FLAG_LIST_FILTER_MODE; flagId: string; } 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 af1ee827c0..0f1e2f9620 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 @@ -6,13 +6,11 @@ import { ImportFeatureFlagModalComponent } from '../../features/dashboard/featur import { UpsertFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/upsert-feature-flag-modal/upsert-feature-flag-modal.component'; import { UpsertPrivateSegmentListModalComponent } from '../../features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component'; import { - ImportListParams, UPSERT_PRIVATE_SEGMENT_LIST_ACTION, UpsertPrivateSegmentListParams, } from '../../core/segments/store/segments.model'; import { FEATURE_FLAG_DETAILS_PAGE_ACTIONS, - FEATURE_FLAG_PARTICIPANT_LIST_KEY, FeatureFlag, ParticipantListTableRow, UPSERT_FEATURE_FLAG_ACTION, @@ -25,6 +23,7 @@ import { ModalSize, SimpleConfirmationModalParams, } from '../../shared-standalone-component-lib/components/common-modal/common-modal.types'; +import { FEATURE_FLAG_LIST_FILTER_MODE } from 'upgrade_types'; @Injectable({ providedIn: 'root', @@ -358,14 +357,14 @@ export class DialogService { } openImportFeatureFlagIncludeListModal(flagId: string) { - return this.openImportModal('Import List', FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE, flagId); + return this.openImportModal('Import List', FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION, flagId); } openImportFeatureFlagExcludeListModal(flagId: string) { - return this.openImportModal('Import List', FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE, flagId); + return this.openImportModal('Import List', FEATURE_FLAG_LIST_FILTER_MODE.EXCLUSION, flagId); } - openImportModal(title, listType, flagId) { + openImportModal(title: string, listType: FEATURE_FLAG_LIST_FILTER_MODE, flagId: string) { const commonModalConfig: CommonModalConfig = { title: title, primaryActionBtnLabel: 'Import', From 84616316e0ec4e4af2a61144a190448fe8020855 Mon Sep 17 00:00:00 2001 From: Yagnik Date: Tue, 29 Oct 2024 13:51:08 +0530 Subject: [PATCH 10/11] addressed missed review comments --- .../Upgrade/src/api/services/FeatureFlagService.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index d110feef92..09a0713d94 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -1185,19 +1185,13 @@ export class FeatureFlagService { const validationErrors = await Promise.allSettled( parsedFeatureFlagLists.map(async (parsedFile) => { - if (!featureFlag) { + if (!featureFlag || !parsedFile.content) { return { fileName: parsedFile.fileName, compatibilityType: FF_COMPATIBILITY_TYPE.INCOMPATIBLE, }; } - if (!parsedFile.content) { - return { - fileName: parsedFile.fileName, - compatibilityType: FF_COMPATIBILITY_TYPE.INCOMPATIBLE, - }; - } return this.validateImportFeatureFlagList(parsedFile.fileName, featureFlag, parsedFile.content); }) ); From a5194611e92a026d637efa3517b873899668f126 Mon Sep 17 00:00:00 2001 From: Yagnik Date: Tue, 29 Oct 2024 14:05:48 +0530 Subject: [PATCH 11/11] addressed failing build due to changes in import list PR --- .../src/api/controllers/FeatureFlagController.ts | 11 +++-------- .../Upgrade/src/api/services/FeatureFlagService.ts | 6 +++--- .../feature-flag-exclusions-table.component.ts | 4 ++-- .../feature-flag-inclusions-table.component.ts | 4 ++-- ...common-details-participant-list-table.component.ts | 6 +++--- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts index 4c42d0caaf..d566c0695a 100644 --- a/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts +++ b/backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts @@ -23,12 +23,7 @@ import { } from './validators/FeatureFlagsPaginatedParamsValidator'; import { FeatureFlagFilterModeUpdateValidator } from './validators/FeatureFlagFilterModeUpdateValidator'; import { AppRequest, PaginationResponse } from '../../types'; -import { - IImportError, - FEATURE_FLAG_LIST_FILTER_MODE, - SERVER_ERROR, - FEATURE_FLAG_PARTICIPANT_LIST_KEY, -} from 'upgrade_types'; +import { IImportError, FEATURE_FLAG_LIST_FILTER_MODE, SERVER_ERROR } from 'upgrade_types'; import { FeatureFlagImportValidation, FeatureFlagListImportValidation, @@ -990,7 +985,7 @@ export class FeatureFlagsController { ): Promise { const lists = await this.featureFlagService.exportAllLists( id, - FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE, + FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION, request.logger ); if (lists?.length) { @@ -1044,7 +1039,7 @@ export class FeatureFlagsController { ): Promise { const lists = await this.featureFlagService.exportAllLists( id, - FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE, + FEATURE_FLAG_LIST_FILTER_MODE.EXCLUSION, request.logger ); if (lists?.length) { diff --git a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts index 25f043fff2..361211397a 100644 --- a/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts +++ b/backend/packages/Upgrade/src/api/services/FeatureFlagService.ts @@ -1268,16 +1268,16 @@ export class FeatureFlagService { public async exportAllLists( id: string, - listType: FEATURE_FLAG_PARTICIPANT_LIST_KEY, + listType: FEATURE_FLAG_LIST_FILTER_MODE, logger: UpgradeLogger ): Promise { const featureFlag = await this.findOne(id, logger); let listsArray: ImportFeatureFlagListValidator[] = []; if (featureFlag) { let lists: (FeatureFlagSegmentExclusion | FeatureFlagSegmentExclusion)[] = []; - if (listType === FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE) { + if (listType === FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION) { lists = featureFlag.featureFlagSegmentInclusion; - } else if (listType === FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE) { + } else if (listType === FEATURE_FLAG_LIST_FILTER_MODE.EXCLUSION) { lists = featureFlag.featureFlagSegmentExclusion; } else { return null; 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-table/feature-flag-exclusions-table.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-table/feature-flag-exclusions-table.component.ts index 281d545122..7e98727a0c 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-table/feature-flag-exclusions-table.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-table/feature-flag-exclusions-table.component.ts @@ -4,7 +4,7 @@ import { ParticipantListRowActionEvent } from '../../../../../../../../core/feat import { CommonDetailsParticipantListTableComponent } from '../../../../../../../../shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; -import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; +import { FEATURE_FLAG_LIST_FILTER_MODE } from 'upgrade_types'; @Component({ selector: 'app-feature-flag-exclusions-table', @@ -16,7 +16,7 @@ import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; }) export class FeatureFlagExclusionsTableComponent { @Input() actionsDisabled?: boolean = false; - tableType = FEATURE_FLAG_PARTICIPANT_LIST_KEY.EXCLUDE; + tableType = FEATURE_FLAG_LIST_FILTER_MODE.EXCLUSION; dataSource$ = this.featureFlagService.selectFeatureFlagExclusions$; isLoading$ = this.featureFlagService.isLoadingSelectedFeatureFlag$; @Output() rowAction = new EventEmitter(); 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-table/feature-flag-inclusions-table.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-table/feature-flag-inclusions-table.component.ts index c60b01b44e..003cef6a95 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-table/feature-flag-inclusions-table.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-table/feature-flag-inclusions-table.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { FeatureFlagsService } from '../../../../../../../../core/feature-flags/feature-flags.service'; import { ParticipantListRowActionEvent } from '../../../../../../../../core/feature-flags/store/feature-flags.model'; -import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; +import { FEATURE_FLAG_LIST_FILTER_MODE } from 'upgrade_types'; import { CommonDetailsParticipantListTableComponent } from '../../../../../../../../shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; @@ -17,7 +17,7 @@ import { TranslateModule } from '@ngx-translate/core'; export class FeatureFlagInclusionsTableComponent { @Input() slideToggleDisabled?: boolean = false; @Input() actionsDisabled?: boolean = false; - tableType = FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE; + tableType = FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION; dataSource$ = this.featureFlagService.selectFeatureFlagInclusions$; isLoading$ = this.featureFlagService.isLoadingSelectedFeatureFlag$; @Output() rowAction = new EventEmitter(); diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts index 0221aee96a..e296e1afef 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts @@ -15,7 +15,7 @@ import { ParticipantListTableRow, } from '../../../core/feature-flags/store/feature-flags.model'; import { MemberTypes } from '../../../core/segments/store/segments.model'; -import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; +import { FEATURE_FLAG_LIST_FILTER_MODE } from 'upgrade_types'; /** * `CommonDetailsParticipantListTableComponent` is a reusable Angular component that displays a table with common details for participant lists. @@ -54,7 +54,7 @@ import { FEATURE_FLAG_PARTICIPANT_LIST_KEY } from 'upgrade_types'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class CommonDetailsParticipantListTableComponent { - @Input() tableType: FEATURE_FLAG_PARTICIPANT_LIST_KEY; + @Input() tableType: FEATURE_FLAG_LIST_FILTER_MODE; @Input() dataSource: any[]; @Input() noDataRowText: string; @Input() slideToggleDisabled?: boolean = false; @@ -83,7 +83,7 @@ export class CommonDetailsParticipantListTableComponent { ngOnInit() { this.displayedColumns = - this.tableType === FEATURE_FLAG_PARTICIPANT_LIST_KEY.INCLUDE + this.tableType === FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION ? ['type', 'values', 'name', 'enable', 'actions'] : ['type', 'values', 'name', 'actions']; }