Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import feature flag list feature #2071

Merged
merged 19 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
44305cd
import feature flag list feature added with validation and import end…
Yagnik56 Oct 21, 2024
2c7ce14
remove console log
Yagnik56 Oct 21, 2024
1e40601
Merge branch 'dev' into feature/import-feature-flag-list
Yagnik56 Oct 22, 2024
89491b1
fixed failing testcases introduced due to dev merge
Yagnik56 Oct 22, 2024
9839279
addressed review comments
Yagnik56 Oct 23, 2024
3198031
Merge branch 'dev' into feature/import-feature-flag-list
Yagnik56 Oct 23, 2024
fe0683f
updated the import format by removing unnecessary parameters
Yagnik56 Oct 25, 2024
05de828
Merge branch 'dev' into feature/import-feature-flag-list
Yagnik56 Oct 25, 2024
ce0548e
export feature flag list modals, API endpoing and functionality
Yagnik56 Oct 28, 2024
37ceb71
addressed review comments
Yagnik56 Oct 29, 2024
eee03c0
fixed 404 error issue
Yagnik56 Oct 29, 2024
a841c0a
addressed review comments
Yagnik56 Oct 29, 2024
8461631
addressed missed review comments
Yagnik56 Oct 29, 2024
c8c2abd
Merge branch 'feature/import-feature-flag-list' into feature/export-f…
Yagnik56 Oct 29, 2024
a519461
addressed failing build due to changes in import list PR
Yagnik56 Oct 29, 2024
9fb3293
Merge pull request #2079 from CarnegieLearningWeb/feature/export-feat…
Yagnik56 Oct 30, 2024
6224287
Merge branch 'dev' into feature/import-feature-flag-list
Yagnik56 Oct 30, 2024
7b80dfe
Merge branch 'dev' into feature/import-feature-flag-list
Yagnik56 Nov 4, 2024
6557f70
Merge branch 'dev' into feature/import-feature-flag-list
Yagnik56 Nov 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 217 additions & 2 deletions backend/packages/Upgrade/src/api/controllers/FeatureFlagController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@ 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';
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[];
Expand Down Expand Up @@ -123,6 +130,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
*/

/**
Expand Down Expand Up @@ -741,7 +761,7 @@ export class FeatureFlagsController {
* @swagger
* /flags/import:
* post:
* description: Validating Feature Flag
* description: Importing Feature Flag
* consumes:
* - application/json
* parameters:
Expand Down Expand Up @@ -833,4 +853,199 @@ 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<ValidatedFeatureFlagsError[]> {
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: UserDTO,
@Req() request: AppRequest
): Promise<IImportError[]> {
return await this.featureFlagService.importFeatureFlagLists(
lists.files,
lists.flagId,
lists.listType,
currentUser,
request.logger
);
}

/**
* @swagger
* /flags/export/includeLists/{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<ImportFeatureFlagListValidator[]> {
const lists = await this.featureFlagService.exportAllLists(
id,
FEATURE_FLAG_LIST_FILTER_MODE.INCLUSION,
request.logger
);
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"`);
} else {
response.setHeader('Content-Disposition', `attachment; filename="lists.zip"`);
}
response.setHeader('Content-Type', 'application/json');
} else {
throw new NotFoundException('Include lists not found.');
}

return lists;
}

/**
* @swagger
* /flags/export/excludeLists/{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<ImportFeatureFlagListValidator[]> {
const lists = await this.featureFlagService.exportAllLists(
id,
FEATURE_FLAG_LIST_FILTER_MODE.EXCLUSION,
request.logger
);
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"`);
} else {
response.setHeader('Content-Disposition', `attachment; filename="lists.zip"`);
}
response.setHeader('Content-Type', 'application/json');
} else {
throw new NotFoundException('Exclude lists not found.');
}

return lists;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -88,7 +89,7 @@ class SegmentImportValidator {
public subSegments: SegmentValidator[];
}

class FeatureFlagListImportValidator {
export class FeatureFlagListImportValidator {
@IsDefined()
@IsBoolean()
public enabled: boolean;
Expand All @@ -101,6 +102,15 @@ 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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { FILTER_MODE, FEATURE_FLAG_STATUS, FEATURE_FLAG_LIST_FILTER_MODE } from 'upgrade_types';
import { Type } from 'class-transformer';
import { FeatureFlagListValidator } from './FeatureFlagListValidator';

Expand Down Expand Up @@ -67,6 +66,22 @@ export class FeatureFlagImportValidation {
public files: FeatureFlagFile[];
}

export class FeatureFlagListImportValidation {
@IsArray()
@ValidateNested({ each: true })
@Type(() => FeatureFlagFile)
public files: FeatureFlagFile[];

@IsString()
@IsNotEmpty()
@IsEnum(FEATURE_FLAG_LIST_FILTER_MODE)
public listType: FEATURE_FLAG_LIST_FILTER_MODE;

@IsUUID()
@IsNotEmpty()
public flagId: string;
}

class FeatureFlagFile {
@IsString()
@IsNotEmpty()
Expand Down
Loading
Loading