Skip to content

Commit

Permalink
Merge branch 'dev' into bugfix/enrollmentDate-issue-2099
Browse files Browse the repository at this point in the history
  • Loading branch information
ppratikcr7 authored Dec 4, 2024
2 parents 1d66793 + 8428490 commit 009e05e
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 44 deletions.
6 changes: 6 additions & 0 deletions backend/packages/Upgrade/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/packages/Upgrade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"chalk": "^5.3.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"compare-versions": "^6.1.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"csv-stringify": "^6.4.5",
Expand Down
38 changes: 30 additions & 8 deletions backend/packages/Upgrade/src/api/DTO/ExperimentDTO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export class PartitionValidator {
public excludeIfReached: boolean;
}

class ConditionPayloadValidator {
abstract class BaseConditionPayloadValidator {
@IsNotEmpty()
@IsString()
public id: string;
Expand All @@ -191,7 +191,9 @@ class ConditionPayloadValidator {
@ValidateNested()
@Type(() => PayloadValidator)
public payload: PayloadValidator;
}

export class ConditionPayloadValidator extends BaseConditionPayloadValidator {
@IsNotEmpty()
@IsString()
public parentCondition: string;
Expand All @@ -201,6 +203,16 @@ class ConditionPayloadValidator {
public decisionPoint?: string;
}

class OldConditionPayloadValidator extends BaseConditionPayloadValidator {
@IsNotEmpty()
@IsString()
public parentCondition: ConditionValidator;

@IsOptional()
@IsString()
public decisionPoint?: PartitionValidator;
}

class MetricValidator {
@IsNotEmpty()
@IsString()
Expand Down Expand Up @@ -322,7 +334,7 @@ class StratificationFactor {
public stratificationFactorName: string;
}

export class ExperimentDTO {
abstract class BaseExperimentWithoutPayload {
@IsString()
@IsOptional()
public id?: string;
Expand Down Expand Up @@ -418,12 +430,6 @@ export class ExperimentDTO {
@Type(() => PartitionValidator)
public partitions: PartitionValidator[];

@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => ConditionPayloadValidator)
public conditionPayloads?: ConditionPayloadValidator[];

@IsOptional()
@IsArray()
@ValidateNested({ each: true })
Expand Down Expand Up @@ -455,6 +461,22 @@ export class ExperimentDTO {
public type: EXPERIMENT_TYPE;
}

export class ExperimentDTO extends BaseExperimentWithoutPayload {
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => ConditionPayloadValidator)
public conditionPayloads?: ConditionPayloadValidator[];
}

export class OldExperimentDTO extends BaseExperimentWithoutPayload {
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => OldConditionPayloadValidator)
public conditionPayloads?: OldConditionPayloadValidator[];
}

export class ExperimentIdValidator {
@IsNotEmpty()
@IsUUID()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { ExperimentRepository } from '../repositories/ExperimentRepository';
import { IndividualExclusion } from '../models/IndividualExclusion';
import { GroupExclusion } from '../models/GroupExclusion';
import { Experiment } from '../models/Experiment';
import { ScheduledJobService } from './ScheduledJobService';
import { ExperimentCondition } from '../models/ExperimentCondition';
import { v4 as uuid } from 'uuid';
import { PreviewUserService } from './PreviewUserService';
Expand Down Expand Up @@ -107,7 +106,6 @@ export class ExperimentAssignmentService {

public previewUserService: PreviewUserService,
public experimentUserService: ExperimentUserService,
public scheduledJobService: ScheduledJobService,
public errorService: ErrorService,
public settingService: SettingService,
public segmentService: SegmentService,
Expand Down
27 changes: 25 additions & 2 deletions backend/packages/Upgrade/src/api/services/ExperimentService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-var */
import { GroupExclusion } from './../models/GroupExclusion';
import { ErrorWithType } from './../errors/ErrorWithType';
import { Service } from 'typedi';
import { Inject, Service } from 'typedi';
import { InjectDataSource, InjectRepository } from '../../typeorm-typedi-extensions';
import { ExperimentRepository } from '../repositories/ExperimentRepository';
import {
Expand Down Expand Up @@ -73,6 +73,7 @@ import {
ParticipantsValidator,
ExperimentFile,
ValidatedExperimentError,
OldExperimentDTO,
} from '../DTO/ExperimentDTO';
import { ConditionPayloadDTO } from '../DTO/ConditionPayloadDTO';
import { FactorDTO } from '../DTO/FactorDTO';
Expand All @@ -85,6 +86,7 @@ import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { StratificationFactorRepository } from '../repositories/StratificationFactorRepository';
import { ExperimentDetailsForCSVData } from '../repositories/AnalyticsRepository';
import { compare } from 'compare-versions';

const errorRemovePart = 'instance of ExperimentDTO has failed the validation:\n - ';
const stratificationErrorMessage =
Expand Down Expand Up @@ -121,6 +123,7 @@ export class ExperimentService {
@InjectDataSource() private dataSource: DataSource,
public previewUserService: PreviewUserService,
public segmentService: SegmentService,
@Inject(() => ScheduledJobService)
public scheduledJobService: ScheduledJobService,
public errorService: ErrorService,
public cacheService: CacheService,
Expand Down Expand Up @@ -1548,7 +1551,14 @@ export class ExperimentService {
} catch (error) {
return { fileName: experimentFile.fileName, error: 'Invalid JSON' };
}
const newExperiment = plainToClass(ExperimentDTO, experiment);

let newExperiment: ExperimentDTO;
if (compare(experiment.backendVersion, '5.3.0', '>=')) {
newExperiment = plainToClass(ExperimentDTO, experiment);
} else {
newExperiment = plainToClass(ExperimentDTO, this.experimentPayloadConverter(experiment));
}

if (!(newExperiment instanceof ExperimentDTO)) {
return { fileName: experimentFile.fileName, error: 'Invalid JSON' };
}
Expand Down Expand Up @@ -1592,6 +1602,19 @@ export class ExperimentService {
.filter((error) => error !== null);
}

private experimentPayloadConverter(experiment: OldExperimentDTO): ExperimentDTO {
const updatedExperimentPayload = experiment.conditionPayloads.map((conditionPayload) => {
return {
...conditionPayload,
parentCondition: conditionPayload.parentCondition.id,
decisionPoint: conditionPayload.decisionPoint.id,
};
});

const newExperiment: ExperimentDTO = { ...experiment, conditionPayloads: updatedExperimentPayload };
return newExperiment;
}

private async validateExperimentJSON(experiment: ExperimentDTO): Promise<string> {
let errorString = '';
await validate(experiment).then((errors) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class ScheduledJobService {
if (scheduledJob && scheduledJob.experiment) {
const experimentRepository = transactionalEntityManager.getRepository(Experiment);
const experiment = await experimentRepository.findOneBy({ id: scheduledJob.experiment.id });
if (scheduledJob && experiment) {
if (experiment) {
const systemUser = await transactionalEntityManager
.getRepository(User)
.findOneBy({ email: systemUserDoc.email });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { ExperimentAssignmentService } from '../../../src/api/services/Experimen
import { ExperimentService } from '../../../src/api/services/ExperimentService';
import { ExperimentUserService } from '../../../src/api/services/ExperimentUserService';
import { PreviewUserService } from '../../../src/api/services/PreviewUserService';
import { ScheduledJobService } from '../../../src/api/services/ScheduledJobService';
import { SegmentService } from '../../../src/api/services/SegmentService';
import { SettingService } from '../../../src/api/services/SettingService';
import {
Expand Down Expand Up @@ -54,7 +53,6 @@ describe('Experiment Assignment Service Test', () => {
const userStratificationFactorRepository = sinon.createStubInstance(UserStratificationFactorRepository);
const previewUserServiceMock = sinon.createStubInstance(PreviewUserService);
const experimentUserServiceMock = sinon.createStubInstance(ExperimentUserService);
const scheduledJobServiceMock = sinon.createStubInstance(ScheduledJobService);
const errorServiceMock = sinon.createStubInstance(ErrorService);
const settingServiceMock = sinon.createStubInstance(SettingService);
const segmentServiceMock = sinon.createStubInstance(SegmentService);
Expand Down Expand Up @@ -87,7 +85,6 @@ describe('Experiment Assignment Service Test', () => {
userStratificationFactorRepository,
previewUserServiceMock,
experimentUserServiceMock,
scheduledJobServiceMock,
errorServiceMock,
settingServiceMock,
segmentServiceMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<form [formGroup]="featureFlagForm" class="form-standard dense-3">
<mat-form-field appearance="outline">
<mat-label class="ft-14-400">Name</mat-label>
<input matInput formControlName="name" placeholder="e.g., My feature flag" class="ft-14-400"/>
<input matInput formControlName="name" placeholder="e.g., My feature flag" class="ft-14-400" />
<mat-hint class="form-hint ft-12-400">
{{ 'feature-flags.upsert-flag-modal.name-hint.text' | translate }}
</mat-hint>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,23 @@ export class UpsertFeatureFlagModalComponent {
this.listenForPrimaryButtonDisabled();
this.listenForDuplicateKey();
this.listenOnContext();

if (this.isDisabled()) {
this.disableRestrictedFields();
}
}

isDisabled() {
return (
this.config.params.action === UPSERT_FEATURE_FLAG_ACTION.EDIT &&
this.config.params.sourceFlag?.status === FEATURE_FLAG_STATUS.ENABLED
);
}

disableRestrictedFields(): void {
this.featureFlagForm.get('name')?.disable();
this.featureFlagForm.get('key')?.disable();
this.featureFlagForm.get('appContext')?.disable();
}

createFeatureFlagForm(): void {
Expand Down Expand Up @@ -234,10 +251,13 @@ export class UpsertFeatureFlagModalComponent {
this.featureFlagsService.addFeatureFlag(flagRequest);
}

createEditRequest(
{ name, key, description, appContext, tags }: FeatureFlagFormData,
{ id, status, filterMode }: FeatureFlag
): void {
createEditRequest({ name, key, description, appContext, tags }: FeatureFlagFormData, sourceFlag: FeatureFlag): void {
const { id, status, filterMode } = sourceFlag;
if (sourceFlag.status === 'enabled') {
name = sourceFlag.name;
key = sourceFlag.key;
appContext = sourceFlag.context[0];
}
const flagRequest: UpdateFeatureFlagRequest = {
id,
name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
[slideToggleChecked]="flag.status === FEATURE_FLAG_STATUS.ENABLED"
[slideToggleText]="'feature-flags.enable.text' | translate"
[showMenuButton]="true"
[menuButtonItems]="menuButtonItems"
[menuButtonItems]="menuButtonItems$ | async"
[isSectionCardExpanded]="isSectionCardExpanded"
(slideToggleChange)="onSlideToggleChange($event, flag)"
(menuButtonItemClick)="onMenuButtonItemClick($event, flag)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
FEATURE_FLAG_DETAILS_PAGE_ACTIONS,
FeatureFlag,
} from '../../../../../../../core/feature-flags/store/feature-flags.model';
import { Observable, Subscription } from 'rxjs';
import { combineLatest, map, Observable, Subscription } from 'rxjs';
import { MatDialogRef } from '@angular/material/dialog';
import { CommonSimpleConfirmationModalComponent } from '../../../../../../../shared-standalone-component-lib/components/common-simple-confirmation-modal/common-simple-confirmation-modal.component';
import { Router } from '@angular/router';
Expand All @@ -39,16 +39,21 @@ import { AuthService } from '../../../../../../../core/auth/auth.service';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FeatureFlagOverviewDetailsSectionCardComponent implements OnInit, OnDestroy {
permissions$: Observable<UserPermission>;
isSectionCardExpanded = true;
@Output() sectionCardExpandChange = new EventEmitter<boolean>();
emailId = '';
permissions$: Observable<UserPermission> = this.authService.userPermissions$;
featureFlag$ = this.featureFlagService.selectedFeatureFlag$;
flagAndPermissions$: Observable<{ flag: FeatureFlag; permissions: UserPermission }> = combineLatest([
this.featureFlag$,
this.permissions$,
]).pipe(map(([flag, permissions]) => ({ flag, permissions })));

subscriptions = new Subscription();
flagOverviewDetails$ = this.featureFlagService.selectedFlagOverviewDetails;
shouldShowWarning$ = this.featureFlagService.shouldShowWarningForSelectedFlag$;
subscriptions = new Subscription();
confirmStatusChangeDialogRef: MatDialogRef<CommonSimpleConfirmationModalComponent>;
menuButtonItems: IMenuButtonItem[];
menuButtonItems$: Observable<IMenuButtonItem[]>;
isSectionCardExpanded = true;
emailId = '';

constructor(
private dialogService: DialogService,
Expand All @@ -58,13 +63,20 @@ export class FeatureFlagOverviewDetailsSectionCardComponent implements OnInit, O
) {}

ngOnInit(): void {
this.permissions$ = this.authService.userPermissions$;
this.subscriptions.add(this.featureFlagService.currentUserEmailAddress$.subscribe((id) => (this.emailId = id)));

this.subscriptions.add(
this.permissions$.subscribe((permissions) => {
this.updateMenuItems(permissions);
})
this.menuButtonItems$ = this.flagAndPermissions$.pipe(
map(({ flag, permissions }) => [
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.EDIT, disabled: !permissions.featureFlags.update },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.DUPLICATE, disabled: !permissions.featureFlags.create },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.EXPORT_DESIGN, disabled: false },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.EMAIL_DATA, disabled: true },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.ARCHIVE, disabled: true },
{
name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.DELETE,
disabled: !permissions?.featureFlags.delete || flag?.status === 'enabled',
},
])
);
}

Expand All @@ -76,17 +88,6 @@ export class FeatureFlagOverviewDetailsSectionCardComponent implements OnInit, O
return FILTER_MODE;
}

private updateMenuItems(permissions: UserPermission): void {
this.menuButtonItems = [
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.EDIT, disabled: !permissions?.featureFlags.update },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.DUPLICATE, disabled: !permissions?.featureFlags.create },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.EXPORT_DESIGN, disabled: false },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.EMAIL_DATA, disabled: true },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.ARCHIVE, disabled: true },
{ name: FEATURE_FLAG_DETAILS_PAGE_ACTIONS.DELETE, disabled: !permissions?.featureFlags.delete },
];
}

onSlideToggleChange(event: MatSlideToggleChange, flag: FeatureFlag) {
const slideToggleEvent = event.source;
const newStatus = slideToggleEvent.checked ? FEATURE_FLAG_STATUS.ENABLED : FEATURE_FLAG_STATUS.DISABLED;
Expand Down

0 comments on commit 009e05e

Please sign in to comment.