From 158bffdb61bcd0f5067311703ae784fff1ecec69 Mon Sep 17 00:00:00 2001 From: Yagnik Date: Tue, 18 Jun 2024 13:25:47 +0530 Subject: [PATCH 01/24] added consition to include all segments as member in global segment --- .../segment-members/segment-members.component.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segment-members/segment-members.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segment-members/segment-members.component.ts index 795710491d..b0985a80d3 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segment-members/segment-members.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segment-members/segment-members.component.ts @@ -164,12 +164,13 @@ export class SegmentMembersComponent implements OnInit, OnChanges { ) { this.subSegmentIds.push(segment.name); this.segmentNameId.set(segment.name, segment.id); - } - } else { - if (segment.type !== SEGMENT_TYPE.GLOBAL_EXCLUDE && segment.context === this.currentContext) { + } else if (segment.type !== SEGMENT_TYPE.GLOBAL_EXCLUDE && segment.id !== this.segmentInfo.id && this.currentContext==="ALL") { this.subSegmentIds.push(segment.name); this.segmentNameId.set(segment.name, segment.id); } + } else if (segment.type !== SEGMENT_TYPE.GLOBAL_EXCLUDE && segment.context === this.currentContext) { + this.subSegmentIds.push(segment.name); + this.segmentNameId.set(segment.name, segment.id); } }); } From 9bbc8508ed0498f3bfbeea7103feb1bc3f973187 Mon Sep 17 00:00:00 2001 From: Yagnik Date: Tue, 25 Jun 2024 12:30:10 +0530 Subject: [PATCH 02/24] refactored the code and resolved the review comment --- .../segment-members.component.ts | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segment-members/segment-members.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segment-members/segment-members.component.ts index b0985a80d3..64d30e417d 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segment-members/segment-members.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segment-members/segment-members.component.ts @@ -154,26 +154,22 @@ export class SegmentMembersComponent implements OnInit, OnChanges { } selectSubSegments(): void { - if (this.allSegments) { - this.allSegments.forEach((segment) => { - if (this.segmentInfo) { - if ( - segment.type !== SEGMENT_TYPE.GLOBAL_EXCLUDE && - segment.id !== this.segmentInfo.id && - segment.context === this.currentContext - ) { - this.subSegmentIds.push(segment.name); - this.segmentNameId.set(segment.name, segment.id); - } else if (segment.type !== SEGMENT_TYPE.GLOBAL_EXCLUDE && segment.id !== this.segmentInfo.id && this.currentContext==="ALL") { - this.subSegmentIds.push(segment.name); - this.segmentNameId.set(segment.name, segment.id); - } - } else if (segment.type !== SEGMENT_TYPE.GLOBAL_EXCLUDE && segment.context === this.currentContext) { + if (!this.allSegments) { + return; + } + + const isContextAll = this.currentContext === "ALL"; + + this.allSegments + .filter((segment) => + segment.type !== SEGMENT_TYPE.GLOBAL_EXCLUDE && + (isContextAll || segment.context === this.currentContext) && + (!this.segmentInfo || segment.id !== this.segmentInfo.id) + ) + .forEach(segment => { this.subSegmentIds.push(segment.name); this.segmentNameId.set(segment.name, segment.id); - } }); - } } onFileSelected(event: any): void { From 3dcb843bbbb90408ea13aadf615eee07fc4725ef Mon Sep 17 00:00:00 2001 From: Khanjan Dalwadi <17khanjan@gmail.com> Date: Wed, 26 Jun 2024 10:49:20 +0530 Subject: [PATCH 03/24] Resolved edit tags issue --- .../common-tag-input/common-tag-input.component.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts index 9eee4a7096..c2750245f2 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts @@ -61,15 +61,11 @@ export class CommonTagsInputComponent implements ControlValueAccessor { removeChip(tag: string) { const currentTags = this.tags.value || []; - const index = currentTags.indexOf(tag); + const newTags = currentTags.filter((t) => t !== tag); - if (index >= 0) { - currentTags.splice(index, 1); - this.tags.setValue(currentTags); - this.tags.updateValueAndValidity(); - } + this.tags.setValue(newTags); + this.tags.updateValueAndValidity(); } - // Implement ControlValueAccessor methods writeValue(value: string[]) { this.tags.setValue(value || []); From 85fb2daca401fb3ece918040c796668945306580 Mon Sep 17 00:00:00 2001 From: Yagnik Hingrajiya <50392803+Yagnik56@users.noreply.github.com> Date: Wed, 26 Jun 2024 20:30:58 +0530 Subject: [PATCH 04/24] feature of autofill for key from name (#1693) Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../app/core/feature-flags/feature-flags.service.ts | 6 ++++++ .../add-feature-flag-modal.component.ts | 10 ++++++++++ .../edit-feature-flag-modal.component.ts | 10 ++++++++++ 3 files changed, 26 insertions(+) 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 0d94dc47c9..abed8ae865 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 @@ -86,6 +86,12 @@ export class FeatureFlagsService { map((exclusions) => exclusions.length) ); + convertNameStringToKey(name:string):string { + let upperCaseString = name.trim().toUpperCase(); + let key = upperCaseString.replace(/ /g, '_'); + return key; +} + fetchFeatureFlags(fromStarting?: boolean) { this.store$.dispatch(FeatureFlagsActions.actionFetchFeatureFlags({ fromStarting })); } diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts index bc1d9f9f06..e7ab7c26b4 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts @@ -77,6 +77,7 @@ export class AddFeatureFlagModalComponent { this.experimentService.fetchContextMetaData(); this.buildForm(); this.listenForFeatureFlagListLengthChanges(); + this.listenOnNameChangesToUpdateKey(); } buildForm(): void { @@ -94,6 +95,15 @@ export class AddFeatureFlagModalComponent { this.subscriptions = this.featureFlagsListLengthChange$.subscribe(() => this.closeModal()); } + listenOnNameChangesToUpdateKey(): void { + this.featureFlagForm.get('name')?.valueChanges.subscribe((name) => { + const keyControl = this.featureFlagForm.get('key'); + if (keyControl && !keyControl.dirty) { + keyControl.setValue(this.featureFlagsService.convertNameStringToKey(name)); + } + }); + } + onPrimaryActionBtnClicked(): void { if (this.featureFlagForm.valid) { // Handle extra form validation logic here? diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts index e5ffbf2150..9c7289af0e 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts @@ -80,6 +80,7 @@ export class EditFeatureFlagModalComponent { this.buildForm(); this.initializeFormValues(); this.listenForFeatureFlagGetUpdated(); + this.listenOnNameChangesToUpdateKey(); } buildForm(): void { @@ -109,6 +110,15 @@ export class EditFeatureFlagModalComponent { ); } + listenOnNameChangesToUpdateKey(): void { + this.featureFlagForm.get('name')?.valueChanges.subscribe((name) => { + const keyControl = this.featureFlagForm.get('key'); + if (keyControl && !keyControl.dirty) { + keyControl.setValue(this.featureFlagsService.convertNameStringToKey(name)); + } + }); + } + // Close the modal once the feature flag list length changes, as that indicates actual success listenForFeatureFlagGetUpdated(): void { this.subscriptions = this.isSelectedFeatureFlagUpdated$.subscribe(() => this.closeModal()); From 4b1f359ca0e6f65da58682095f592d1482068ac6 Mon Sep 17 00:00:00 2001 From: Yagnik Hingrajiya <50392803+Yagnik56@users.noreply.github.com> Date: Thu, 27 Jun 2024 22:34:22 +0530 Subject: [PATCH 05/24] added header and content component for exposures (#1697) Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- ...lag-exclusions-section-card.component.html | 4 +--- ...-flag-exclusions-section-card.component.ts | 3 +-- ...feature-flag-exposures-data.component.html | 5 +++++ ...feature-flag-exposures-data.component.scss | 1 + .../feature-flag-exposures-data.component.ts | 15 ++++++++++++++ ...flag-exposures-section-card.component.html | 20 ++++++++++++++++--- ...flag-exposures-section-card.component.scss | 3 +++ ...e-flag-exposures-section-card.component.ts | 12 +++++++++-- ...lag-inclusions-section-card.component.html | 2 +- ...-tabbed-section-card-footer.component.scss | 3 +++ .../projects/upgrade/src/assets/i18n/en.json | 2 ++ 11 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.html create mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.scss create mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.ts 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 33996ac262..226fc69ad3 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 @@ -22,7 +22,5 @@ - + 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 1ff9f5f3b3..82ef722230 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 @@ -27,6 +27,7 @@ import { FeatureFlagsService } from '../../../../../../../core/feature-flags/fea }) export class FeatureFlagExclusionsSectionCardComponent { tableRowCount$ = this.featureFlagService.selectFeatureFlagExclusionsLength$; + isSectionCardExpanded = true; constructor(private featureFlagService: FeatureFlagsService) {} @@ -35,8 +36,6 @@ export class FeatureFlagExclusionsSectionCardComponent { { name: 'Delete', disabled: false }, ]; - isSectionCardExpanded = true; - addExcludeListClicked() { console.log('add Exclude List Clicked'); } diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.html new file mode 100644 index 0000000000..3c57c982d1 --- /dev/null +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.html @@ -0,0 +1,5 @@ +
+ + Add Exposures data here! + +
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.scss @@ -0,0 +1 @@ + diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.ts new file mode 100644 index 0000000000..866aa5d595 --- /dev/null +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-data/feature-flag-exposures-data.component.ts @@ -0,0 +1,15 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { FeatureFlagsService } from '../../../../../../../../core/feature-flags/feature-flags.service'; +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'; + +@Component({ + selector: 'app-feature-flag-exposures-data', + standalone: true, + templateUrl: './feature-flag-exposures-data.component.html', + styleUrl: './feature-flag-exposures-data.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonDetailsParticipantListTableComponent, CommonModule, TranslateModule], +}) +export class FeatureFlagExposuresDataComponent {} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-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-exposures-section-card/feature-flag-exposures-section-card.component.html index 791f6df23d..033462451e 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-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-exposures-section-card/feature-flag-exposures-section-card.component.html @@ -1,5 +1,19 @@ -
header-left
-
header-right
-
content: Exposures
+ + + + + + + + +
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-section-card.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-section-card.component.scss index e69de29bb2..54079260c1 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-section-card.component.scss +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-section-card.component.scss @@ -0,0 +1,3 @@ +.full-width { + width: 100%; +} \ No newline at end of file diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-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-exposures-section-card/feature-flag-exposures-section-card.component.ts index 43f0b955e7..0bc6512dce 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-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-exposures-section-card/feature-flag-exposures-section-card.component.ts @@ -1,15 +1,23 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { CommonSectionCardComponent } from '../../../../../../../shared-standalone-component-lib/components'; +import { CommonSectionCardActionButtonsComponent, CommonSectionCardComponent, CommonSectionCardTitleHeaderComponent } from '../../../../../../../shared-standalone-component-lib/components'; import { FeatureFlag } from '../../../../../../../core/feature-flags/store/feature-flags.model'; +import { TranslateModule } from '@ngx-translate/core'; +import { FeatureFlagExposuresDataComponent } from './feature-flag-exposures-data/feature-flag-exposures-data.component'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'app-feature-flag-exposures-section-card', standalone: true, - imports: [CommonSectionCardComponent], + imports: [CommonSectionCardComponent, CommonSectionCardTitleHeaderComponent, CommonSectionCardActionButtonsComponent, CommonModule, TranslateModule, FeatureFlagExposuresDataComponent], templateUrl: './feature-flag-exposures-section-card.component.html', styleUrl: './feature-flag-exposures-section-card.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class FeatureFlagExposuresSectionCardComponent { @Input() data: FeatureFlag; + isSectionCardExpanded = true; + + onSectionCardExpandChange(isSectionCardExpanded: boolean) { + this.isSectionCardExpanded = isSectionCardExpanded; + } } 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 c50c7a3593..923164b3c5 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 @@ -22,5 +22,5 @@ - + diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tabbed-section-card-footer/common-tabbed-section-card-footer.component.scss b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tabbed-section-card-footer/common-tabbed-section-card-footer.component.scss index e69de29bb2..5e794b6c31 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tabbed-section-card-footer/common-tabbed-section-card-footer.component.scss +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tabbed-section-card-footer/common-tabbed-section-card-footer.component.scss @@ -0,0 +1,3 @@ +::ng-deep .mat-mdc-tab-group .mat-mdc-tab-header .mat-mdc-tab-label-container .mat-mdc-tab-labels .mat-mdc-tab:first-of-type { + margin-left: 0; +} \ No newline at end of file diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index 5e57f98da1..0f7c5f3418 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -364,6 +364,8 @@ "feature-flags.details.exclusions.card.no-data-row.text": "No Exclude Lists defined.", "feature-flags.details.exclusions.card.title.text": "Exclude Lists", "feature-flags.details.exclusions.card.subtitle.text": "Define Exclude lists for this feature flag", + "feature-flags.details.exposures.card.title.text": "Exposures", + "feature-flags.details.exposures.card.subtitle.text": "View total exposures for the feature flag during the specific period.", "feature-flags.details.add-exclude-list.button.text": "Add Exclude List", "feature-flags.table-variation-type.text": "Variation type", "feature-flags.overview.flag-variation-type.text": "Flag Variation Type", From f2e7df9413d1ddf45cf06d1181542b3510bdcb6c Mon Sep 17 00:00:00 2001 From: Pratik Prajapati <33730817+ppratikcr7@users.noreply.github.com> Date: Fri, 28 Jun 2024 00:28:58 +0530 Subject: [PATCH 06/24] import modal texts updated to plural for multiple file support (#1701) Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../import-experiment/import-experiment.component.html | 2 +- .../modal/import-segment/import-segment.component.html | 2 +- .../components/segments-list/segments-list.component.html | 2 +- frontend/projects/upgrade/src/assets/i18n/en.json | 7 ++++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/components/modal/import-experiment/import-experiment.component.html b/frontend/projects/upgrade/src/app/features/dashboard/home/components/modal/import-experiment/import-experiment.component.html index 1367cc07ad..d080d9959d 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/home/components/modal/import-experiment/import-experiment.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/home/components/modal/import-experiment/import-experiment.component.html @@ -1,5 +1,5 @@
- {{ 'home.experiment.import-experiment.text' | translate | titlecase }} + {{ 'home.experiment.import-experiments.text' | translate | titlecase }}

diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/import-segment/import-segment.component.html b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/import-segment/import-segment.component.html index 5d4c58aaaf..b788170744 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/import-segment/import-segment.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/import-segment/import-segment.component.html @@ -1,5 +1,5 @@

- {{ 'segments.import-segment.text' | translate | titlecase }} + {{ 'segments.import-segments.text' | translate | titlecase }}

diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.html b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.html index 18401ceea5..236f546764 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.html @@ -43,7 +43,7 @@ *ngIf="(permissions$ | async)?.segments.create" > add - {{ 'segments.import-segments.text' | translate }} + {{ 'segments.import-segment.text' | translate }} - -

diff --git a/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.scss deleted file mode 100644 index 9f569caaee..0000000000 --- a/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.scss +++ /dev/null @@ -1,21 +0,0 @@ -.metric-delete-dialog { - .name { - width: 100%; - line-height: 18px; - margin-top: -10px; - padding-bottom: 20px; - - &-input { - line-height: 18px; - } - } - - &-actions { - justify-content: flex-end; - margin: 0; - - .delete-btn { - background-color: var(--red) !important; - } - } -} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.spec.ts b/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.spec.ts deleted file mode 100644 index 4d3b786045..0000000000 --- a/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DeleteMetricsComponent } from './delete-metrics.component'; -import { TestingModule } from '../../../../../../../testing/testing.module'; -import { AnalysisService } from '../../../../../../core/analysis/analysis.service'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; - -xdescribe('DeleteMetricsComponent', () => { - let component: DeleteMetricsComponent; - let fixture: ComponentFixture; - - const modalData = { - key: ['key1', 'key2'], - }; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [DeleteMetricsComponent], - imports: [TestingModule], - providers: [ - AnalysisService, - { provide: MatDialogRef, useValue: {} }, - { - provide: MAT_DIALOG_DATA, - useValue: modalData, - }, - ], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(DeleteMetricsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.ts deleted file mode 100644 index 567f7e64c2..0000000000 --- a/frontend/projects/upgrade/src/app/features/dashboard/profile/components/modals/delete-metrics/delete-metrics.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component, ChangeDetectionStrategy, Inject } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { AnalysisService } from '../../../../../../core/analysis/analysis.service'; -import { METRICS_JOIN_TEXT } from '../../../../../../core/analysis/store/analysis.models'; - -@Component({ - selector: 'app-delete-metrics', - templateUrl: './delete-metrics.component.html', - styleUrls: ['./delete-metrics.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class DeleteMetricsComponent { - metricName: string; - constructor( - private analysisService: AnalysisService, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: any - ) {} - - onCancelClick(): void { - this.dialogRef.close(); - } - - deleteMetric() { - this.analysisService.deleteMetric(this.data.key.join(METRICS_JOIN_TEXT)); - this.onCancelClick(); - } -} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/profile/profile.module.ts b/frontend/projects/upgrade/src/app/features/dashboard/profile/profile.module.ts index 8ca0f431ff..0228a4fe28 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/profile/profile.module.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/profile/profile.module.ts @@ -8,7 +8,6 @@ import { NewUserComponent } from './components/modals/new-user/new-user.componen import { ProfileInfoComponent } from './components/profile-info/profile-info.component'; import { MetricsComponent } from './components/metrics/metrics.component'; import { AddMetricsComponent } from './components/modals/add-metrics/add-metrics.component'; -import { DeleteMetricsComponent } from './components/modals/delete-metrics/delete-metrics.component'; import { NgJsonEditorModule } from 'ang-jsoneditor'; @NgModule({ @@ -17,7 +16,6 @@ import { NgJsonEditorModule } from 'ang-jsoneditor'; NewUserComponent, ProfileInfoComponent, MetricsComponent, - DeleteMetricsComponent, AddMetricsComponent, ], imports: [CommonModule, ProfileRoutingModule, SharedModule, NgJsonEditorModule], diff --git a/frontend/projects/upgrade/src/app/shared/components/delete/delete.component.html b/frontend/projects/upgrade/src/app/shared/components/delete/delete.component.html index d16fcc022f..d356efeb25 100644 --- a/frontend/projects/upgrade/src/app/shared/components/delete/delete.component.html +++ b/frontend/projects/upgrade/src/app/shared/components/delete/delete.component.html @@ -1,5 +1,5 @@
-

+

{{ 'global.delete-confirmation.message.text' | translate }}

, @Inject(MAT_DIALOG_DATA) public data: any) {} onCancelClick(): void { - this.message = false; - this.dialogRef.close(this.message); + this.isDeleteButtonClicked = false; + this.dialogRef.close(this.isDeleteButtonClicked); } delete(): void { - this.message = true; - this.dialogRef.close(this.message); + this.isDeleteButtonClicked = true; + this.dialogRef.close(this.isDeleteButtonClicked); } } From 551cccd43431986508bbedb8f6cebf33567dbaaa Mon Sep 17 00:00:00 2001 From: danoswaltCL Date: Thu, 27 Jun 2024 17:14:02 -0400 Subject: [PATCH 08/24] correct a funky observable input --- .../add-feature-flag-modal.component.html | 2 +- .../delete-feature-flag-modal.component.html | 2 +- .../delete-feature-flag-modal.component.ts | 4 +--- .../edit-feature-flag-modal.component.html | 2 +- .../edit-feature-flag-modal.component.ts | 3 +-- .../components/common-modal/common-modal.component.html | 2 +- .../components/common-modal/common-modal.component.ts | 2 +- 7 files changed, 7 insertions(+), 10 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html index d83d38d2c0..d743ce3824 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html @@ -4,7 +4,7 @@ [cancelBtnLabel]="config.cancelBtnLabel" [primaryActionBtnLabel]="config.primaryActionBtnLabel" [primaryActionBtnColor]="config.primaryActionBtnColor" - [primaryActionBtnDisabled$]="isLoadingUpsertFeatureFlag$" + [primaryActionBtnDisabled]="isLoadingUpsertFeatureFlag$ | async" (primaryActionBtnClicked)="onPrimaryActionBtnClicked()" >
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html index 91d8574177..21b360fb11 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html @@ -5,7 +5,7 @@ [cancelBtnLabel]="data.cancelBtnLabel" [primaryActionBtnLabel]="data.primaryActionBtnLabel" [primaryActionBtnColor]="data.primaryActionBtnColor" - [primaryActionBtnDisabled$]="isDeleteActionBtnDisabled$" + [primaryActionBtnDisabled$]="isDeleteActionBtnDisabled$ | async" (primaryActionBtnClicked)="onPrimaryActionBtnClicked(flag.id)" >
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.ts index c3c7883781..d54f0b7646 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.ts @@ -44,9 +44,7 @@ export class DeleteFeatureFlagModalComponent { private inputSubject: BehaviorSubject = new BehaviorSubject(''); // Observable that emits true if inputValue is 'delete', false otherwise - isDeleteNotTyped$: Observable = this.inputSubject - .asObservable() - .pipe(map((value) => value.toLowerCase() !== 'delete')); + isDeleteNotTyped$: Observable = this.inputSubject.pipe(map((value) => value.toLowerCase() !== 'delete')); isDeleteActionBtnDisabled$: Observable = combineLatest([ this.isDeleteNotTyped$, diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.html index c8944ecde9..a9096fff44 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.html @@ -4,7 +4,7 @@ [cancelBtnLabel]="config.cancelBtnLabel" [primaryActionBtnLabel]="config.primaryActionBtnLabel" [primaryActionBtnColor]="config.primaryActionBtnColor" - [primaryActionBtnDisabled$]="isLoadingUpsertFeatureFlag$" + [primaryActionBtnDisabled]="isLoadingUpsertFeatureFlag$ | async" (primaryActionBtnClicked)="onPrimaryActionBtnClicked()" > diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts index 9c7289af0e..4487b22bb8 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component.ts @@ -23,8 +23,7 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service'; import { CommonFormHelpersService } from '../../../../../shared/services/common-form-helpers.service'; -import { FEATURE_FLAG_STATUS, SEGMENT_TYPE, FILTER_MODE } from '../../../../../../../../../../types/src'; -import { AddFeatureFlagRequest, FeatureFlag, FeatureFlagFormData } from '../../../../../core/feature-flags/store/feature-flags.model'; +import { FeatureFlag, FeatureFlagFormData } from '../../../../../core/feature-flags/store/feature-flags.model'; import { Subscription } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { ExperimentService } from '../../../../../core/experiments/experiments.service'; diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.html b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.html index f9ef3b8b2b..c4cc2e3f90 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.html +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.html @@ -21,7 +21,7 @@

{{ title }}

class="dialog-action-btn primary-btn" mat-flat-button [color]="primaryActionBtnColor" - [disabled]="primaryActionBtnDisabled$ | async" + [disabled]="primaryActionBtnDisabled" (click)="onPrimaryActionBtnClicked()" > {{ primaryActionBtnLabel }} diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.ts index 8a791cd184..94e8feaac3 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.ts +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.ts @@ -35,7 +35,7 @@ export class CommonModalComponent { @Input() primaryActionBtnLabel = 'Submit'; @Input() primaryActionBtnColor = 'primary'; @Input() hideFooter = false; - @Input() primaryActionBtnDisabled$: Observable; + @Input() primaryActionBtnDisabled = false; @Output() primaryActionBtnClicked = new EventEmitter(); onPrimaryActionBtnClicked() { From 037e36c269700d538880fe5b9fd9770b9f18a3e4 Mon Sep 17 00:00:00 2001 From: danoswaltCL Date: Fri, 28 Jun 2024 09:32:33 -0400 Subject: [PATCH 09/24] correct misspelling --- .../delete-feature-flag-modal.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html index 21b360fb11..8c8e9e9f76 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.html @@ -5,7 +5,7 @@ [cancelBtnLabel]="data.cancelBtnLabel" [primaryActionBtnLabel]="data.primaryActionBtnLabel" [primaryActionBtnColor]="data.primaryActionBtnColor" - [primaryActionBtnDisabled$]="isDeleteActionBtnDisabled$ | async" + [primaryActionBtnDisabled]="isDeleteActionBtnDisabled$ | async" (primaryActionBtnClicked)="onPrimaryActionBtnClicked(flag.id)" >
From dcf0b1de4907994d4ff994e893a309349af7dc60 Mon Sep 17 00:00:00 2001 From: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:52:16 -0400 Subject: [PATCH 10/24] bringing in the metrics display toggle from the release branch (#1705) Co-authored-by: Ben Blanchard --- .../projects/upgrade/src/app/app.module.ts | 2 ++ .../core/analysis/analysis.service.spec.ts | 31 +++++++++++++++++-- .../src/app/core/analysis/analysis.service.ts | 14 +++++++-- .../view-experiment.component.html | 13 +++++++- .../view-experiment.component.scss | 11 +++++++ .../view-experiment.component.ts | 10 ++++-- .../projects/upgrade/src/assets/i18n/en.json | 1 + .../src/environments/environment-types.ts | 4 ++- .../src/environments/environment.bsnl.ts | 1 + .../src/environments/environment.demo.prod.ts | 1 + .../src/environments/environment.prod.ts | 1 + .../src/environments/environment.staging.ts | 1 + .../upgrade/src/environments/environment.ts | 1 + 13 files changed, 81 insertions(+), 10 deletions(-) diff --git a/frontend/projects/upgrade/src/app/app.module.ts b/frontend/projects/upgrade/src/app/app.module.ts index dcd48f9052..8290384486 100755 --- a/frontend/projects/upgrade/src/app/app.module.ts +++ b/frontend/projects/upgrade/src/app/app.module.ts @@ -33,6 +33,8 @@ export const getEnvironmentConfig = (http: HttpClient, env: Environment) => { env.withinSubjectExperimentSupportToggle = config.withinSubjectExperimentSupportToggle ?? env.withinSubjectExperimentSupportToggle ?? false; env.errorLogsToggle = config.errorLogsToggle ?? env.errorLogsToggle ?? false; + env.metricAnalyticsExperimentDisplayToggle = + config.metricAnalyticsExperimentDisplayToggle ?? env.metricAnalyticsExperimentDisplayToggle ?? false; }) .catch((error) => { console.log({ error }); diff --git a/frontend/projects/upgrade/src/app/core/analysis/analysis.service.spec.ts b/frontend/projects/upgrade/src/app/core/analysis/analysis.service.spec.ts index 3d6b0e85e3..69d5740828 100644 --- a/frontend/projects/upgrade/src/app/core/analysis/analysis.service.spec.ts +++ b/frontend/projects/upgrade/src/app/core/analysis/analysis.service.spec.ts @@ -8,6 +8,7 @@ import { actionUpsertMetrics, } from './store/analysis.actions'; import { UpsertMetrics } from './store/analysis.models'; +import { Environment } from '../../../environments/environment-types'; const mockStateStore$ = new BehaviorSubject({}); (mockStateStore$ as any).dispatch = jest.fn(); @@ -22,10 +23,12 @@ jest.mock('./store/analysis.selectors', () => ({ describe('AnalysisService', () => { const mockStore: any = mockStateStore$; + let mockEnvironment: Environment = { metricAnalyticsExperimentDisplayToggle: true } as Environment; let service: AnalysisService; beforeEach(() => { - service = new AnalysisService(mockStore); + service = new AnalysisService(mockStore, mockEnvironment); + jest.resetAllMocks(); }); describe('#queryResultById$', () => { @@ -78,12 +81,34 @@ describe('AnalysisService', () => { }); describe('#executeQuery', () => { - it('should dispatch executeQuery with the supplied string input array', () => { + let originalEnvironment; + + beforeEach(() => { + // Save the original environment to restore it after tests + originalEnvironment = { ...mockEnvironment }; + }); + + afterEach(() => { + // Restore the original environment after each test + mockEnvironment = { ...originalEnvironment }; + }); + + it('should dispatch executeQuery with the supplied string input array when metricAnalyticsExperimentDisplayToggle is true', () => { + mockEnvironment = { metricAnalyticsExperimentDisplayToggle: true } as Environment; const mockQueryIds = ['test', 'test2']; service.executeQuery(mockQueryIds); - expect(mockStore.dispatch).toHaveBeenLastCalledWith(actionExecuteQuery({ queryIds: mockQueryIds })); + expect(mockStore.dispatch).toHaveBeenCalledWith(actionExecuteQuery({ queryIds: mockQueryIds })); + }); + + it('should not dispatch executeQuery and log a warning when metricAnalyticsExperimentDisplayToggle is false', () => { + mockEnvironment = { metricAnalyticsExperimentDisplayToggle: false } as Environment; + const mockQueryIds = ['test3', 'test4']; + + service.executeQuery(mockQueryIds); + + expect(mockStore.dispatch).not.toHaveBeenCalledWith(mockQueryIds); }); }); diff --git a/frontend/projects/upgrade/src/app/core/analysis/analysis.service.ts b/frontend/projects/upgrade/src/app/core/analysis/analysis.service.ts index b527b4d521..4522bc1aca 100644 --- a/frontend/projects/upgrade/src/app/core/analysis/analysis.service.ts +++ b/frontend/projects/upgrade/src/app/core/analysis/analysis.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { AppState } from '../core.module'; import { Store, select } from '@ngrx/store'; import { @@ -11,10 +11,11 @@ import { import * as AnalysisActions from './store/analysis.actions'; import { UpsertMetrics } from './store/analysis.models'; import { selectExperimentQueries } from '../experiments/store/experiments.selectors'; +import { ENV, Environment } from '../../../environments/environment-types'; @Injectable() export class AnalysisService { - constructor(private store$: Store) {} + constructor(private store$: Store, @Inject(ENV) private environment: Environment) {} isMetricsLoading$ = this.store$.pipe(select(selectIsMetricsLoading)); isQueryExecuting$ = this.store$.pipe(select(selectIsQueryExecuting)); @@ -35,7 +36,14 @@ export class AnalysisService { } executeQuery(queryIds: string[]) { - this.store$.dispatch(AnalysisActions.actionExecuteQuery({ queryIds })); + if (this.environment.metricAnalyticsExperimentDisplayToggle) { + this.store$.dispatch(AnalysisActions.actionExecuteQuery({ queryIds })); + } else { + console.warn( + 'executeQuery is currently disabled via metricAnalyticsExperimentDisplayToggle:', + this.environment.metricAnalyticsExperimentDisplayToggle + ); + } } experimentQueryResult$(experimentId: string) { diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.html b/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.html index 3900306e47..de8864bb3d 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.html @@ -952,7 +952,18 @@
- + + + + +
+ warning +

+ {{ 'home.view-experiment.metrics-analysis-unavailable.text' | translate }} +

+
+
+
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.scss index 47113edc80..0c08e02c00 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.scss +++ b/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.scss @@ -389,6 +389,17 @@ $font-size-small: 15px; margin-bottom: 24px; padding: 10px; } + + .disabled-analysis-view-messsage-container { + padding-top: 50px; + padding-bottom: 20px; + display: flex; + justify-content: center; + + .mat-icon { + margin-right: 20px; + } + } } ::ng-deep .owl-dt-calendar-table .owl-dt-calendar-cell-selected { diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.ts index 0ca78e4a2c..e39c389ebe 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/home/pages/view-experiment/view-experiment.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewEncapsulation, Inject } from '@angular/core'; import { ExperimentService } from '../../../../../core/experiments/experiments.service'; import { MatDialog } from '@angular/material/dialog'; import { ExperimentStatusComponent } from '../../components/modal/experiment-status/experiment-status.component'; @@ -32,6 +32,7 @@ import { FactorialConditionTableDataFromConditionPayload, SimpleExperimentPayloadTableRowData, } from '../../../../../core/experiment-design-stepper/store/experiment-design-stepper.model'; +import { ENV, Environment } from '../../../../../../environments/environment-types'; // Used in view-experiment component only enum DialogType { CHANGE_STATUS = 'Change status', @@ -101,7 +102,8 @@ export class ViewExperimentComponent implements OnInit, OnDestroy { private dialog: MatDialog, private authService: AuthService, private router: Router, - private _Activatedroute: ActivatedRoute + private _Activatedroute: ActivatedRoute, + @Inject(ENV) private environment: Environment ) {} get DialogType() { @@ -124,6 +126,10 @@ export class ViewExperimentComponent implements OnInit, OnDestroy { return this.experiment.state === EXPERIMENT_STATE.CANCELLED; } + get showMetricAnalysisDisplay() { + return this.environment.metricAnalyticsExperimentDisplayToggle; + } + ngOnInit() { this.isLoadingExperimentDetailStats$ = this.experimentService.isLoadingExperimentDetailStats$; this.isPollingExperimentDetailStats$ = this.experimentService.isPollingExperimentDetailStats$; diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index 3b206fbed9..c646543e96 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -247,6 +247,7 @@ "home.view-experiment.experiment-payloads.column-label.target": "TARGET", "home.view-experiment.experiment-payloads.column-label.condition": "CONDITION", "home.view-experiment.experiment-payloads.column-label.payload": "PAYLOAD", + "home.view-experiment.metrics-analysis-unavailable.text": "Experiment metrics analysis has been temporarily disabled in this view. This information is still available when exporting the experiment data.", "home.view-experiment.graph-type.text": "Type", "home.view-experiment.graph-conditions.text": "Conditions", "home.view-experiment.graph-decision-point.text": "Sites", diff --git a/frontend/projects/upgrade/src/environments/environment-types.ts b/frontend/projects/upgrade/src/environments/environment-types.ts index aa189af8bf..3580d10186 100644 --- a/frontend/projects/upgrade/src/environments/environment-types.ts +++ b/frontend/projects/upgrade/src/environments/environment-types.ts @@ -59,8 +59,9 @@ export interface Environment { pollingLimit: number; api: APIEndpoints; featureFlagNavToggle: boolean; - withinSubjectExperimentSupportToggle: boolean; errorLogsToggle: boolean; + withinSubjectExperimentSupportToggle: boolean; + metricAnalyticsExperimentDisplayToggle: boolean; } export interface RuntimeEnvironmentConfig { @@ -71,4 +72,5 @@ export interface RuntimeEnvironmentConfig { featureFlagNavToggle?: boolean; withinSubjectExperimentSupportToggle?: boolean; errorLogsToggle?: boolean; + metricAnalyticsExperimentDisplayToggle?: boolean; } diff --git a/frontend/projects/upgrade/src/environments/environment.bsnl.ts b/frontend/projects/upgrade/src/environments/environment.bsnl.ts index 2efeddb526..78d11f14ff 100644 --- a/frontend/projects/upgrade/src/environments/environment.bsnl.ts +++ b/frontend/projects/upgrade/src/environments/environment.bsnl.ts @@ -13,6 +13,7 @@ export const environment = { featureFlagNavToggle: false, withinSubjectExperimentSupportToggle: false, errorLogsToggle: false, + metricAnalyticsExperimentDisplayToggle: true, api: { getAllExperiments: '/experiments/paginated', createNewExperiments: '/experiments', diff --git a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts index b41b0bb018..075a02037f 100755 --- a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts @@ -13,6 +13,7 @@ export const environment = { featureFlagNavToggle: false, withinSubjectExperimentSupportToggle: false, errorLogsToggle: false, + metricAnalyticsExperimentDisplayToggle: false, api: { getAllExperiments: '/experiments/paginated', createNewExperiments: '/experiments', diff --git a/frontend/projects/upgrade/src/environments/environment.prod.ts b/frontend/projects/upgrade/src/environments/environment.prod.ts index 962b584476..fb7aad0702 100755 --- a/frontend/projects/upgrade/src/environments/environment.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.prod.ts @@ -13,6 +13,7 @@ export const environment = { featureFlagNavToggle: false, withinSubjectExperimentSupportToggle: false, errorLogsToggle: false, + metricAnalyticsExperimentDisplayToggle: false, api: { getAllExperiments: '/experiments/paginated', createNewExperiments: '/experiments', diff --git a/frontend/projects/upgrade/src/environments/environment.staging.ts b/frontend/projects/upgrade/src/environments/environment.staging.ts index 51c001752b..144bb38cdf 100644 --- a/frontend/projects/upgrade/src/environments/environment.staging.ts +++ b/frontend/projects/upgrade/src/environments/environment.staging.ts @@ -13,6 +13,7 @@ export const environment = { featureFlagNavToggle: false, withinSubjectExperimentSupportToggle: false, errorLogsToggle: false, + metricAnalyticsExperimentDisplayToggle: false, api: { getAllExperiments: '/experiments/paginated', createNewExperiments: '/experiments', diff --git a/frontend/projects/upgrade/src/environments/environment.ts b/frontend/projects/upgrade/src/environments/environment.ts index 336a8e1cc0..774d1998ab 100755 --- a/frontend/projects/upgrade/src/environments/environment.ts +++ b/frontend/projects/upgrade/src/environments/environment.ts @@ -18,6 +18,7 @@ export const environment = { featureFlagNavToggle: true, withinSubjectExperimentSupportToggle: false, errorLogsToggle: false, + metricAnalyticsExperimentDisplayToggle: false, api: { getAllExperiments: '/experiments/paginated', createNewExperiments: '/experiments', From 4f127e9f8d0e49e1e1848d32d475e59aa54b9151 Mon Sep 17 00:00:00 2001 From: Yagnik Hingrajiya <50392803+Yagnik56@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:27:32 +0530 Subject: [PATCH 11/24] redirect to new feature flag detail page when added new ff (#1709) Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../app/core/feature-flags/store/feature-flags.effects.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 74842d3d3a..7152cd6a44 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 @@ -91,8 +91,9 @@ export class FeatureFlagsEffects { ofType(FeatureFlagsActions.actionAddFeatureFlag), switchMap((action) => { return this.featureFlagsDataService.addFeatureFlag(action.addFeatureFlagRequest).pipe( - map((response) => { - return FeatureFlagsActions.actionAddFeatureFlagSuccess({ response }); + map((response) => FeatureFlagsActions.actionAddFeatureFlagSuccess({ response })), + tap(({ response }) => { + this.router.navigate(['/featureflags', 'detail', response.id]); }), catchError(() => [FeatureFlagsActions.actionAddFeatureFlagFailure()]) ); From 84cc429883b638e535342376f61d44164eab849a Mon Sep 17 00:00:00 2001 From: Yagnik Hingrajiya <50392803+Yagnik56@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:49:55 +0530 Subject: [PATCH 12/24] Not able to create feature flag without adding tags (#1710) * fixed issue of tags sending null instead of array * resolve the review cmt --------- Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../add-feature-flag-modal.component.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts index e7ab7c26b4..6ad0cd4d42 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts @@ -24,7 +24,7 @@ import { MatSelectModule } from '@angular/material/select'; import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service'; import { CommonFormHelpersService } from '../../../../../shared/services/common-form-helpers.service'; import { FEATURE_FLAG_STATUS, SEGMENT_TYPE, FILTER_MODE } from '../../../../../../../../../../types/src'; -import { AddFeatureFlagRequest } from '../../../../../core/feature-flags/store/feature-flags.model'; +import { AddFeatureFlagRequest, FeatureFlagFormData } from '../../../../../core/feature-flags/store/feature-flags.model'; import { Subscription } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { ExperimentService } from '../../../../../core/experiments/experiments.service'; @@ -86,7 +86,7 @@ export class AddFeatureFlagModalComponent { key: ['', Validators.required], description: [''], appContext: ['', Validators.required], - tags: [], + tags: [[]], }); } @@ -116,8 +116,7 @@ export class AddFeatureFlagModalComponent { createAddFeatureFlagRequest(): void { // temporarily use any until tags feature is added - // const { name, key, description, appContext, tags }: FeatureFlagFormData = this.featureFlagForm.value; - const { name, key, description, appContext, tags }: any = this.featureFlagForm.value; + const { name, key, description, appContext, tags }: FeatureFlagFormData = this.featureFlagForm.value; const addFeatureFlagRequest: AddFeatureFlagRequest = { name, From 3d642f7659d63836aaee39e3ee39cc11c5c0e88a Mon Sep 17 00:00:00 2001 From: Khanjan Dalwadi <80506682+KD1712@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:58:49 +0530 Subject: [PATCH 13/24] Import Feature flag modal implemented (#1673) * Import Feature flag modal implemented * Enabled menu button and linked up the modal with it * Made state variables as BehaviorSubjects and removed duplicate code --------- Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../api/repositories/ExperimentRepository.ts | 68 +++++++++------ .../import-feature-flag-modal.component.html | 39 +++++++++ .../import-feature-flag-modal.component.scss | 60 +++++++++++++ .../import-feature-flag-modal.component.ts | 86 +++++++++++++++++++ ...ture-flag-root-section-card.component.html | 1 + ...eature-flag-root-section-card.component.ts | 6 +- .../shared/services/common-dialog.service.ts | 19 ++++ .../projects/upgrade/src/assets/i18n/en.json | 1 + 8 files changed, 251 insertions(+), 29 deletions(-) create mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.html create mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.scss create mode 100644 frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.ts diff --git a/backend/packages/Upgrade/src/api/repositories/ExperimentRepository.ts b/backend/packages/Upgrade/src/api/repositories/ExperimentRepository.ts index 832a7230aa..a7370c5f42 100644 --- a/backend/packages/Upgrade/src/api/repositories/ExperimentRepository.ts +++ b/backend/packages/Upgrade/src/api/repositories/ExperimentRepository.ts @@ -462,12 +462,12 @@ export class ExperimentRepository extends Repository { public async findOneExperiment(id: string): Promise { const experiment = await this.createBaseQueryBuilder() - .addOrderBy('conditions.order', 'ASC') - .addOrderBy('partitions.order', 'ASC') - .addOrderBy('factors.order', 'ASC') - .addOrderBy('levels.order', 'ASC') - .where({ id }) - .getOne(); + .addOrderBy('conditions.order', 'ASC') + .addOrderBy('partitions.order', 'ASC') + .addOrderBy('factors.order', 'ASC') + .addOrderBy('levels.order', 'ASC') + .where({ id }) + .getOne(); return experiment; } @@ -475,31 +475,43 @@ export class ExperimentRepository extends Repository { // Get the experiment details const experimentQuery = await this.createBaseQueryBuilder() .select([ - 'experiment.id as "experimentId"', - 'experiment.name as "experimentName"', - 'experiment.context as "context"', - 'experiment.assignmentUnit as "assignmentUnit"', - 'experiment.group as "group"', - 'experiment.consistencyRule as "consistencyRule"', - 'experiment.type as "designType"', - 'experiment.assignmentAlgorithm as "algorithmType"', - 'experiment.stratificationFactorStratificationFactorName as "stratification"', - 'experiment.postExperimentRule as "postRule"', - 'experimentRevertCondition.conditionCode as "revertTo"', - '"enrollingStateTimeLog"."timeLog" as "enrollmentStartDate"', - '"enrollmentCompleteStateTimeLog"."timeLog" as "enrollmentCompleteDate"', - '"conditionPayloadMain"."payloadValue" as "payload"', - '"decisionPointData"."excludeIfReached" as "excludeIfReached"', - '"decisionPointData"."id" as "expDecisionPointId"', - 'experimentCondition.id as "expConditionId"', - 'experimentCondition.conditionCode as "conditionName"', + 'experiment.id as "experimentId"', + 'experiment.name as "experimentName"', + 'experiment.context as "context"', + 'experiment.assignmentUnit as "assignmentUnit"', + 'experiment.group as "group"', + 'experiment.consistencyRule as "consistencyRule"', + 'experiment.type as "designType"', + 'experiment.assignmentAlgorithm as "algorithmType"', + 'experiment.stratificationFactorStratificationFactorName as "stratification"', + 'experiment.postExperimentRule as "postRule"', + 'experimentRevertCondition.conditionCode as "revertTo"', + '"enrollingStateTimeLog"."timeLog" as "enrollmentStartDate"', + '"enrollmentCompleteStateTimeLog"."timeLog" as "enrollmentCompleteDate"', + '"conditionPayloadMain"."payloadValue" as "payload"', + '"decisionPointData"."excludeIfReached" as "excludeIfReached"', + '"decisionPointData"."id" as "expDecisionPointId"', + 'experimentCondition.id as "expConditionId"', + 'experimentCondition.conditionCode as "conditionName"', ]) .leftJoin(ExperimentCondition, 'experimentCondition', 'experimentCondition.experimentId = experiment.id') .leftJoin(ExperimentCondition, 'experimentRevertCondition', 'experimentRevertCondition.id = experiment.revertTo') .leftJoin(DecisionPoint, 'decisionPointData', 'decisionPointData.experimentId = experiment.id') - .leftJoin(ConditionPayload, 'conditionPayloadMain', 'conditionPayloadMain.parentConditionId = experimentCondition.id AND conditionPayloadMain.decisionPointId = decisionPointData.id') - .leftJoin(StateTimeLog, 'enrollingStateTimeLog', 'enrollingStateTimeLog.experimentId = experiment.id AND enrollingStateTimeLog.toState = \'enrolling\'') - .leftJoin(StateTimeLog, 'enrollmentCompleteStateTimeLog', 'enrollmentCompleteStateTimeLog.experimentId = experiment.id AND enrollmentCompleteStateTimeLog.toState = \'enrollmentComplete\'') + .leftJoin( + ConditionPayload, + 'conditionPayloadMain', + 'conditionPayloadMain.parentConditionId = experimentCondition.id AND conditionPayloadMain.decisionPointId = decisionPointData.id' + ) + .leftJoin( + StateTimeLog, + 'enrollingStateTimeLog', + "enrollingStateTimeLog.experimentId = experiment.id AND enrollingStateTimeLog.toState = 'enrolling'" + ) + .leftJoin( + StateTimeLog, + 'enrollmentCompleteStateTimeLog', + "enrollmentCompleteStateTimeLog.experimentId = experiment.id AND enrollmentCompleteStateTimeLog.toState = 'enrollmentComplete'" + ) .groupBy('experiment.id') .addGroupBy('experimentCondition.id') .addGroupBy('experimentRevertCondition.conditionCode') @@ -509,7 +521,7 @@ export class ExperimentRepository extends Repository { .addGroupBy('enrollmentCompleteStateTimeLog.timeLog') .where('experiment.id = :experimentId', { experimentId }) .getRawMany(); - + return experimentQuery; } } 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 new file mode 100644 index 0000000000..259e49ebe8 --- /dev/null +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.html @@ -0,0 +1,39 @@ + diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.scss new file mode 100644 index 0000000000..8ee757f43a --- /dev/null +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.scss @@ -0,0 +1,60 @@ +.drag-drop-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + color: grey; + + mat-icon { + height: 70px; + width: 70px; + font-size: 70px; + color: grey; + } +} + +.input-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 1px dashed grey; + border-radius: 4px; + height: 206px; + width: 592px; + position: relative; + + button { + font-size: 14px; + } +} + +.input-container-header { + position: absolute; + top: 5px; + right: 5px; + + mat-icon { + height: 24px; + width: 24px; + font-size: 24px; + } +} + +.input-container-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +p { + color: grey; + font-size: 12px; +} + +a { + text-decoration: none; +} 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 new file mode 100644 index 0000000000..e9cb49b01a --- /dev/null +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.ts @@ -0,0 +1,86 @@ +import { ChangeDetectionStrategy, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; +import { CommonModalComponent } from '../../../../../shared-standalone-component-lib/components'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { CommonModalConfig } from '../../../../../shared-standalone-component-lib/components/common-modal/common-modal-config'; +import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service'; +import { BehaviorSubject } from 'rxjs'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '../../../../../shared/shared.module'; + +@Component({ + selector: 'app-import-feature-flag-modal', + standalone: true, + imports: [CommonModalComponent, CommonModule, SharedModule], + templateUrl: './import-feature-flag-modal.component.html', + styleUrls: ['./import-feature-flag-modal.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ImportFeatureFlagModalComponent { + @ViewChild('fileInput') fileInput: ElementRef; + + isImportActionBtnDisabled = new BehaviorSubject(true); + isDragOver = new BehaviorSubject(false); + fileName = new BehaviorSubject(null); + + constructor( + @Inject(MAT_DIALOG_DATA) + public data: CommonModalConfig, + public dialog: MatDialog, + public dialogRef: MatDialogRef + ) {} + + onDragOver(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + this.isDragOver.next(true); + } + + onDragLeave(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + this.isDragOver.next(false); + } + + onDrop(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + this.isDragOver.next(false); + + const files = event.dataTransfer?.files; + if (files && files.length > 0) { + this.processFile(files[0]); + } + } + + onFileSelected(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + this.processFile(input.files[0]); + } + } + + processFile(file: File) { + if (file.type === 'application/json') { + this.fileName.next(file.name); + this.isImportActionBtnDisabled.next(false); + this.handleFileInput(file); + } else { + alert('Please upload a valid JSON file.'); + this.fileName.next(null); + this.isImportActionBtnDisabled.next(true); + } + } + + handleFileInput(file: File) { + const reader = new FileReader(); + reader.onload = (e: any) => { + const jsonContent = e.target.result; + console.log(JSON.parse(jsonContent)); + }; + reader.readAsText(file); + } + + closeModal() { + this.dialogRef.close(); + } +} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.html index c7c574f264..36dc297154 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.html @@ -14,6 +14,7 @@ [showPrimaryButton]="true" [primaryButtonText]="'feature-flags.add-feature-flag.text' | translate" [menuButtonItems]="menuButtonItems" + [showMenuButton]="true" [isSectionCardExpanded]="isSectionCardExpanded" (primaryButtonClick)="onAddFeatureFlagButtonClick()" (menuButtonItemClick)="onMenuButtonItemClick($event)" diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.ts index e8525b1306..ae07c422d8 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card.component.ts @@ -98,7 +98,11 @@ export class FeatureFlagRootSectionCardComponent { } onMenuButtonItemClick(menuButtonItemName: string) { - console.log('onMenuButtonItemClick:', menuButtonItemName); + if (menuButtonItemName === 'Import Feature Flag') { + this.dialogService.openImportFeatureFlagModal(); + } else if (menuButtonItemName === 'Export All Feature Flags') { + console.log('onMenuButtonItemClick:', menuButtonItemName); + } } 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 eedd21cfa2..51ea502f5f 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 @@ -4,6 +4,8 @@ import { MatConfirmDialogComponent } from '../components/mat-confirm-dialog/mat- import { AddFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component'; import { CommonModalConfig } from '../../shared-standalone-component-lib/components/common-modal/common-modal-config'; import { DeleteFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component'; + +import { ImportFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component'; import { UpdateFlagStatusConfirmationModalComponent } from '../../features/dashboard/feature-flags/modals/update-flag-status-confirmation-modal/update-flag-status-confirmation-modal.component'; import { EditFeatureFlagModalComponent } from '../../features/dashboard/feature-flags/modals/edit-feature-flag-modal/edit-feature-flag-modal.component'; @@ -101,4 +103,21 @@ export class DialogService { }; return this.dialog.open(DeleteFeatureFlagModalComponent, config); } + + openImportFeatureFlagModal() { + const commonModalConfig: CommonModalConfig = { + title: 'Import Feature Flag', + primaryActionBtnLabel: 'Import', + primaryActionBtnColor: 'primary', + cancelBtnLabel: 'Cancel', + }; + const config: MatDialogConfig = { + data: commonModalConfig, + width: '670px', + height: '450px', + autoFocus: 'input', + disableClose: true, + }; + return this.dialog.open(ImportFeatureFlagModalComponent, config); + } } diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index c646543e96..7c1bde17b1 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -388,6 +388,7 @@ "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.export-all-feature-flags.text": "Export All Feature Flags", "segments.title.text": "Segments", "segments.subtitle.text": "Define new segments to include or exclude from any experiment", From fdaf358e3f0408843f591c94a57b5ccd6d516e4a Mon Sep 17 00:00:00 2001 From: Zack Lee <90279765+zackcl@users.noreply.github.com> Date: Thu, 4 Jul 2024 02:53:34 +0900 Subject: [PATCH 14/24] Update .env.example to avoid confusion (#1731) Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- backend/packages/Upgrade/.env.docker.local.example | 4 ++-- backend/packages/Upgrade/.env.example | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/packages/Upgrade/.env.docker.local.example b/backend/packages/Upgrade/.env.docker.local.example index 903356e001..19f401f9ee 100644 --- a/backend/packages/Upgrade/.env.docker.local.example +++ b/backend/packages/Upgrade/.env.docker.local.example @@ -73,9 +73,9 @@ EMAIL_BUCKET="s3_bucket" # # Initialization # -ADMIN_USERS=user@email:role/\user2@email:role +ADMIN_USERS=user@email:admin/\user2@email:admin CLIENT_API_SECRET=secret CLIENT_API_KEY=key -CONTEXT_METADATA={"context_identifier_1":{"CONDITIONS":["potential-condition-1","potential-condition-1"],"GROUP_TYPES":["client_group_identifier_1","client_group_identifier_2","client_group_identifier_3"],"EXP_IDS":["decision_point_target_identifier_1","decision_point_target_identifier_2"],"EXP_POINTS":["decision_point_site_identifier_1","decision_point_site_identifier_2"]}} +CONTEXT_METADATA={"context_identifier_1":{"CONDITIONS":["potential-condition-1","potential-condition-2"],"GROUP_TYPES":["client_group_identifier_1","client_group_identifier_2","client_group_identifier_3"],"EXP_IDS":["decision_point_target_identifier_1","decision_point_target_identifier_2"],"EXP_POINTS":["decision_point_site_identifier_1","decision_point_site_identifier_2"]}} METRICS=[{"metric":"totalTimeSeconds","datatype":"continuous"},{"groupClass":"masteryWorkspace","allowedKeys":["calculating_area_various_figures","Compare_functions_diff_reps_quadratic"],"attributes":[{"metric":"timeSeconds","datatype":"continuous"}]}] \ No newline at end of file diff --git a/backend/packages/Upgrade/.env.example b/backend/packages/Upgrade/.env.example index 3b7b441d07..315c9e82ed 100644 --- a/backend/packages/Upgrade/.env.example +++ b/backend/packages/Upgrade/.env.example @@ -73,9 +73,9 @@ EMAIL_BUCKET="s3_bucket" # # Initialization # -ADMIN_USERS=user@email:role/\user2@email:role +ADMIN_USERS=user@email:admin/\user2@email:admin CLIENT_API_SECRET=secret CLIENT_API_KEY=key -CONTEXT_METADATA={"context_identifier_1":{"CONDITIONS":["potential-condition-1","potential-condition-1"],"GROUP_TYPES":["client_group_identifier_1","client_group_identifier_2","client_group_identifier_3"],"EXP_IDS":["decision_point_target_identifier_1","decision_point_target_identifier_2"],"EXP_POINTS":["decision_point_site_identifier_1","decision_point_site_identifier_2"]}} +CONTEXT_METADATA={"context_identifier_1":{"CONDITIONS":["potential-condition-1","potential-condition-2"],"GROUP_TYPES":["client_group_identifier_1","client_group_identifier_2","client_group_identifier_3"],"EXP_IDS":["decision_point_target_identifier_1","decision_point_target_identifier_2"],"EXP_POINTS":["decision_point_site_identifier_1","decision_point_site_identifier_2"]}} METRICS=[{"metrics":[{"metric":"totalTimeSeconds","datatype":"continuous"},{"groupClass":"masteryWorkspace","allowedKeys":["calculating_area_various_figures","Compare_functions_diff_reps_quadratic"],"attributes":[{"metric":"timeSeconds","datatype":"continuous"}]}],"contexts":["context_identifier_1"]}] From 611b5a3dcef8c499ad1fa98156012a6f5659eb48 Mon Sep 17 00:00:00 2001 From: Yagnik Hingrajiya <50392803+Yagnik56@users.noreply.github.com> Date: Thu, 4 Jul 2024 01:25:10 +0530 Subject: [PATCH 15/24] sort functionality for feature flag root table (#1711) * sort functionality for feature flag root table * fix error while loading root page of feature flag --------- Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../feature-flags/feature-flags.service.ts | 4 +++ .../store/feature-flags.model.ts | 12 ++++---- ...lag-root-section-card-table.component.html | 28 +++++++++++++----- ...-flag-root-section-card-table.component.ts | 29 +++++++++++++++---- types/src/Experiment/enums.ts | 1 - 5 files changed, 53 insertions(+), 21 deletions(-) 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 abed8ae865..deaa35e600 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 @@ -19,6 +19,8 @@ import { selectFeatureFlagInclusions, selectFeatureFlagExclusions, selectIsLoadingSelectedFeatureFlag, + selectSortKey, + selectSortAs, } from './store/feature-flags.selectors'; import * as FeatureFlagsActions from './store/feature-flags.actions'; import { actionFetchContextMetaData } from '../experiments/store/experiments.actions'; @@ -39,6 +41,8 @@ export class FeatureFlagsService { isAllFlagsFetched$ = this.store$.pipe(select(selectIsAllFlagsFetched)); searchString$ = this.store$.pipe(select(selectSearchString)); searchKey$ = this.store$.pipe(select(selectSearchKey)); + sortKey$ = this.store$.pipe(select(selectSortKey)); + sortAs$ = this.store$.pipe(select(selectSortAs)); isLoadingUpsertFeatureFlag$ = this.store$.pipe(select(selectIsLoadingUpsertFeatureFlag)); IsLoadingFeatureFlagDelete$ = this.store$.pipe(select(selectIsLoadingFeatureFlagDelete)); 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 4429f7f9c2..69a24197f0 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 @@ -104,12 +104,12 @@ export enum FLAG_SEARCH_KEY { } export const FLAG_ROOT_COLUMN_NAMES = { - NAME: 'Name', - STATUS: 'Status', - UPDATED_AT: 'Updated at', - APP_CONTEXT: 'App Context', - TAGS: 'Tags', - EXPOSURES: 'Exposures', + NAME: 'name', + STATUS: 'status', + UPDATED_AT: 'updatedAt', + APP_CONTEXT: 'appContext', + TAGS: 'tags', + EXPOSURES: 'exposures', }; export const FLAG_TRANSLATION_KEYS = { diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html index 6e80bef9df..b8686c982d 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.html @@ -1,7 +1,15 @@
- +
@@ -19,7 +27,7 @@ {{ flag.name | truncate: 30 }} @@ -39,7 +47,9 @@ - {{ FLAG_TRANSLATION_KEYS.STATUS | translate | uppercase }} + + {{ FLAG_TRANSLATION_KEYS.STATUS | translate | uppercase }} + @@ -58,7 +68,7 @@ - + {{ FLAG_TRANSLATION_KEYS.APP_CONTEXT | translate | uppercase }} @@ -69,8 +79,10 @@ - - {{ FLAG_TRANSLATION_KEYS.TAGS | translate | uppercase }} + + + {{ FLAG_TRANSLATION_KEYS.TAGS | translate | uppercase }} + {{ tag }} @@ -78,7 +90,7 @@ - + {{ FLAG_TRANSLATION_KEYS.EXPOSURES | translate | uppercase }} @@ -97,4 +109,4 @@
-
\ No newline at end of file +
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.ts index c0d0a70b70..297474a56e 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, ViewChild } from '@angular/core'; import { Observable } from 'rxjs'; import { FLAG_ROOT_COLUMN_NAMES, @@ -7,12 +7,14 @@ import { FeatureFlag, } from '../../../../../../../../core/feature-flags/store/feature-flags.model'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; -import { AsyncPipe, NgIf, NgFor, UpperCasePipe, DatePipe } from '@angular/common'; +import { AsyncPipe, NgIf, NgFor, UpperCasePipe, DatePipe, CommonModule } from '@angular/common'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslateModule } from '@ngx-translate/core'; import { MatChipsModule } from '@angular/material/chips'; import { RouterModule } from '@angular/router'; +import { MatSort, MatSortModule } from '@angular/material/sort'; import { CommonStatusIndicatorChipComponent } from '../../../../../../../../shared-standalone-component-lib/components'; +import { FeatureFlagsService } from '../../../../../../../../core/feature-flags/feature-flags.service'; @Component({ selector: 'app-feature-flag-root-section-card-table', @@ -22,7 +24,9 @@ import { CommonStatusIndicatorChipComponent } from '../../../../../../../../shar AsyncPipe, NgIf, NgFor, + MatSortModule, MatTooltipModule, + CommonModule, TranslateModule, UpperCasePipe, MatChipsModule, @@ -34,9 +38,21 @@ import { CommonStatusIndicatorChipComponent } from '../../../../../../../../shar styleUrl: './feature-flag-root-section-card-table.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class FeatureFlagRootSectionCardTableComponent { - @Input() dataSource$: MatTableDataSource; +export class FeatureFlagRootSectionCardTableComponent implements OnInit { + @Input() dataSource$: MatTableDataSource; @Input() isLoading$: Observable; + flagSortKey$ = this.featureFlagsService.sortKey$; + flagSortAs$ = this.featureFlagsService.sortAs$; + + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor(private featureFlagsService: FeatureFlagsService) {} + + ngOnInit() { + if (this.dataSource$?.data) { + this.dataSource$.sort = this.sort; + } + } get displayedColumns(): string[] { return FLAG_ROOT_DISPLAYED_COLUMNS; @@ -54,7 +70,8 @@ export class FeatureFlagRootSectionCardTableComponent { console.log('fetchFlagsOnScroll'); } - changeSorting($event) { - console.log('onSearch:', $event); + changeSorting(event) { + this.featureFlagsService.setSortingType(event.direction ? event.direction.toUpperCase() : null); + this.featureFlagsService.setSortKey(event.direction ? event.active : null); } } diff --git a/types/src/Experiment/enums.ts b/types/src/Experiment/enums.ts index ad451bbeac..2e026925b1 100644 --- a/types/src/Experiment/enums.ts +++ b/types/src/Experiment/enums.ts @@ -212,7 +212,6 @@ export enum METRIC_SEARCH_KEY { export enum FLAG_SORT_KEY { NAME = 'name', - KEY = 'key', STATUS = 'status', UPDATED_AT = 'updatedAt', } From 7f59375f76f040e741095fcdad83a6f7a3d58e16 Mon Sep 17 00:00:00 2001 From: Pratik Prajapati <33730817+ppratikcr7@users.noreply.github.com> Date: Tue, 9 Jul 2024 18:24:50 +0530 Subject: [PATCH 16/24] adding segmentcontroller unit test cases and resolved other small issues in unit tests (#1729) Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../api/controllers/ExperimentController.ts | 40 ++++++ .../controllers/AnalyticsController.test.ts | 2 +- .../controllers/ExperimentController.test.ts | 6 +- .../controllers/SegmentController.test.ts | 115 ++++++++++++++++++ .../controllers/mocks/SegmentServiceMock.ts | 52 ++++++++ 5 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts create mode 100644 backend/packages/Upgrade/test/unit/controllers/mocks/SegmentServiceMock.ts diff --git a/backend/packages/Upgrade/src/api/controllers/ExperimentController.ts b/backend/packages/Upgrade/src/api/controllers/ExperimentController.ts index 75ddfa8a48..4f81feccc7 100644 --- a/backend/packages/Upgrade/src/api/controllers/ExperimentController.ts +++ b/backend/packages/Upgrade/src/api/controllers/ExperimentController.ts @@ -1232,6 +1232,46 @@ export class ExperimentController { return this.experimentService.importExperiment(experiments, currentUser, request.logger); } + /** + * @swagger + * /experiments/{export}: + * get: + * description: Export Experiment JSON + * parameters: + * - in: body + * name: experiments + * required: true + * schema: + * type: array + * items: + * type: object + * properties: + * fileName: + * type: string + * fileContent: + * type: string + * description: Experiment Files + * tags: + * - Experiments + * produces: + * - application/json + * responses: + * '200': + * description: Experiment is exported + * schema: + * type: array + * items: + * type: object + * properties: + * fileName: + * type: string + * error: + * type: string + * '401': + * description: AuthorizationRequiredError + * '500': + * description: Internal Server Error + */ @Get('/export') public exportExperiment( @QueryParams() diff --git a/backend/packages/Upgrade/test/unit/controllers/AnalyticsController.test.ts b/backend/packages/Upgrade/test/unit/controllers/AnalyticsController.test.ts index 87e6c22fe1..ae0bf9b98a 100644 --- a/backend/packages/Upgrade/test/unit/controllers/AnalyticsController.test.ts +++ b/backend/packages/Upgrade/test/unit/controllers/AnalyticsController.test.ts @@ -56,7 +56,7 @@ describe('Analytics Controller Testing', () => { .expect(200); }); - test('Post request for /api/stats/csv', () => { + test('Get request for /api/stats/csv', () => { return request(app) .get('/api/stats/csv') .query({ diff --git a/backend/packages/Upgrade/test/unit/controllers/ExperimentController.test.ts b/backend/packages/Upgrade/test/unit/controllers/ExperimentController.test.ts index dd5ba9bac2..68a2e4c2df 100644 --- a/backend/packages/Upgrade/test/unit/controllers/ExperimentController.test.ts +++ b/backend/packages/Upgrade/test/unit/controllers/ExperimentController.test.ts @@ -198,8 +198,10 @@ describe('Experiment Controller Testing', () => { test('Get request for /api/experiments/export', () => { return request(app) - .post('/api/experiments/import') - .send([experimentData.id]) + .get('/api/experiments/export') + .query({ + ids: [uuid()], + }) .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200); diff --git a/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts b/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts new file mode 100644 index 0000000000..ca948d95b8 --- /dev/null +++ b/backend/packages/Upgrade/test/unit/controllers/SegmentController.test.ts @@ -0,0 +1,115 @@ +import app from '../../utils/expressApp'; +import request from 'supertest'; +import { configureLogger } from '../../utils/logger'; +import { useContainer as routingUseContainer } from 'routing-controllers'; +import { Container } from 'typedi'; +import { v4 as uuid } from 'uuid'; +import SegmentServiceMock from './mocks/SegmentServiceMock'; +import { SegmentService } from '../../../src/api/services/SegmentService'; + +import { useContainer as classValidatorUseContainer } from 'class-validator'; +import { useContainer as ormUseContainer } from 'typeorm'; + +describe('Segment Controller Testing', () => { + beforeAll(() => { + configureLogger(); + routingUseContainer(Container); + ormUseContainer(Container); + classValidatorUseContainer(Container); + + // set mock container + Container.set(SegmentService, new SegmentServiceMock()); + }); + + afterAll(() => { + Container.reset(); + }); + + const segmentData = { + id: uuid(), + name: "segment1", + description: "desc", + context: "home", + type: "public", + status: "Unused", + userIds: ["user1"], + groups: { + groupId : "group1", + type: "school" + }, + subSegmentIds: ["seg2"] + }; + + test('Get request for /api/segments', () => { + return request(app) + .get('/api/segments') + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + + test('Get request for /api/segments/:segmentId', () => { + return request(app) + .get(`/api/segments/${uuid()}`) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + + test('Get request for /api/segments/status/:segmentId', () => { + return request(app) + .get(`/api/segments/status/${uuid()}`) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + + test('Post request for /api/segments', () => { + return request(app) + .post('/api/segments') + .send([segmentData]) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + + test('Delete request for /api/segments/:segmentId', () => { + return request(app).delete(`/api/segments/${uuid()}`).expect('Content-Type', /json/).expect(200); + }); + + test('Post request for /api/segments/import', () => { + return request(app) + .post('/api/segments/import') + .send(segmentData) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + + test('Post request for /api/segments/validation', () => { + return request(app).post('/api/segments/validation').send(segmentData).expect('Content-Type', /json/).expect(200); + }); + + test('Get request for /api/segments/export/json', () => { + return request(app) + .get('/api/segments/export/json') + .query({ + ids: [uuid()], + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + + test('Get request for /api/segments/export/csv', () => { + return request(app) + .get('/api/segments/export/csv') + .query({ + ids: [uuid()], + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200); + }); + +}); diff --git a/backend/packages/Upgrade/test/unit/controllers/mocks/SegmentServiceMock.ts b/backend/packages/Upgrade/test/unit/controllers/mocks/SegmentServiceMock.ts new file mode 100644 index 0000000000..f4ee792779 --- /dev/null +++ b/backend/packages/Upgrade/test/unit/controllers/mocks/SegmentServiceMock.ts @@ -0,0 +1,52 @@ +import { Service } from 'typedi'; + +@Service() +export default class ExcludeServiceMock { + public getAllSegments(): Promise<[]> { + return Promise.resolve([]); + } + + public getAllSegmentWithStatus(): Promise<[]> { + return Promise.resolve([]); + } + + public getSegmentById(id: string): Promise<[]> { + return Promise.resolve([]); + } + + public getSegmentWithStatusById(id: string): Promise<[]> { + return Promise.resolve([]); + } + + public getSingleSegmentWithStatus(id: string): Promise<[]> { + return Promise.resolve([]); + } + + public upsertSegment(): Promise<[]> { + return Promise.resolve([]); + } + + public importSegments(): Promise<[]> { + return Promise.resolve([]); + } + + public deleteSegment(id: string): Promise<[]> { + return Promise.resolve([]); + } + + public validateSegments(): Promise<[]> { + return Promise.resolve([]); + } + + public exportSegments(id: string): Promise<[]> { + return Promise.resolve([]); + } + + public exportSegment(id: string): Promise<[]> { + return Promise.resolve([]); + } + + public exportSegmentCSV(id: string): Promise<[]> { + return Promise.resolve([]); + } +} From 3c746a270e2d8f63c87f1e183234828c700b6225 Mon Sep 17 00:00:00 2001 From: Yagnik Hingrajiya <50392803+Yagnik56@users.noreply.github.com> Date: Wed, 10 Jul 2024 02:31:23 +0530 Subject: [PATCH 17/24] fixed issue due to which tooltip on description not working (#1739) Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- ...ture-flag-root-section-card-table.component.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.ts index 297474a56e..a14bc2456d 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-root-page/feature-flag-root-page-content/feature-flag-root-section-card/feature-flag-root-section-card-table/feature-flag-root-section-card-table.component.ts @@ -7,14 +7,12 @@ import { FeatureFlag, } from '../../../../../../../../core/feature-flags/store/feature-flags.model'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; -import { AsyncPipe, NgIf, NgFor, UpperCasePipe, DatePipe, CommonModule } from '@angular/common'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { TranslateModule } from '@ngx-translate/core'; -import { MatChipsModule } from '@angular/material/chips'; +import { AsyncPipe, NgIf, NgFor, UpperCasePipe } from '@angular/common'; import { RouterModule } from '@angular/router'; -import { MatSort, MatSortModule } from '@angular/material/sort'; +import { MatSort } from '@angular/material/sort'; import { CommonStatusIndicatorChipComponent } from '../../../../../../../../shared-standalone-component-lib/components'; import { FeatureFlagsService } from '../../../../../../../../core/feature-flags/feature-flags.service'; +import { SharedModule } from '../../../../../../../../shared/shared.module'; @Component({ selector: 'app-feature-flag-root-section-card-table', @@ -24,14 +22,9 @@ import { FeatureFlagsService } from '../../../../../../../../core/feature-flags/ AsyncPipe, NgIf, NgFor, - MatSortModule, - MatTooltipModule, - CommonModule, - TranslateModule, + SharedModule, UpperCasePipe, - MatChipsModule, RouterModule, - DatePipe, CommonStatusIndicatorChipComponent, ], templateUrl: './feature-flag-root-section-card-table.component.html', From e9c70298a5c425b4e1c9833a78db9161d5c6089c Mon Sep 17 00:00:00 2001 From: Yagnik Hingrajiya <50392803+Yagnik56@users.noreply.github.com> Date: Wed, 10 Jul 2024 02:40:50 +0530 Subject: [PATCH 18/24] common import container (#1734) * common import container * added comment for example use * resolved review cmts --------- Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../import-feature-flag-modal.component.html | 31 ++------ .../import-feature-flag-modal.component.scss | 46 +---------- .../import-feature-flag-modal.component.ts | 53 +++---------- .../common-import-container.component.html | 14 ++++ .../common-import-container.component.scss | 29 +++++++ .../common-import-container.component.ts | 79 +++++++++++++++++++ .../common-modal.component.spec.ts | 22 ------ .../common-modal/common-modal.component.ts | 1 - .../shared/services/common-dialog.service.ts | 2 +- types/src/Experiment/enums.ts | 5 ++ types/src/index.ts | 1 + 11 files changed, 151 insertions(+), 132 deletions(-) create mode 100644 frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.html create mode 100644 frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.scss create mode 100644 frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.ts delete mode 100644 frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.spec.ts 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 259e49ebe8..6632f185b8 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 @@ -5,32 +5,17 @@ [primaryActionBtnLabel]="data.primaryActionBtnLabel" [primaryActionBtnColor]="data.primaryActionBtnColor" [primaryActionBtnDisabled]="isImportActionBtnDisabled | async" + (primaryActionBtnClicked)="importFiles()" >
-
-
- close -
-
- insert_drive_file -

{{ fileName }}

-
- - file_upload -

Drag & drop or

- - -
-
-

+ +

{{ 'feature-flags.import-feature-flag.message.text' | translate }} Learn More

diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.scss index 8ee757f43a..0a13fbb9a7 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.scss +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/import-feature-flag-modal/import-feature-flag-modal.component.scss @@ -7,52 +7,14 @@ height: 100%; color: grey; - mat-icon { - height: 70px; - width: 70px; - font-size: 70px; - color: grey; + .full-width { + width: 100%; } } -.input-container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - border: 1px dashed grey; - border-radius: 4px; - height: 206px; - width: 592px; - position: relative; - - button { - font-size: 14px; - } -} - -.input-container-header { - position: absolute; - top: 5px; - right: 5px; - - mat-icon { - height: 24px; - width: 24px; - font-size: 24px; - } -} - -.input-container-content { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -p { +.import-message { + margin-top: 5px; color: grey; - font-size: 12px; } a { 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 e9cb49b01a..fced0d1298 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 @@ -2,25 +2,22 @@ import { ChangeDetectionStrategy, Component, ElementRef, Inject, OnInit, ViewChi import { CommonModalComponent } from '../../../../../shared-standalone-component-lib/components'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { CommonModalConfig } from '../../../../../shared-standalone-component-lib/components/common-modal/common-modal-config'; -import { FeatureFlagsService } from '../../../../../core/feature-flags/feature-flags.service'; import { BehaviorSubject } from 'rxjs'; import { CommonModule } from '@angular/common'; import { SharedModule } from '../../../../../shared/shared.module'; +import { CommonImportContainerComponent } from '../../../../../shared-standalone-component-lib/components/common-import-container/common-import-container.component'; @Component({ selector: 'app-import-feature-flag-modal', standalone: true, - imports: [CommonModalComponent, CommonModule, SharedModule], + imports: [CommonModalComponent, CommonModule, SharedModule, CommonImportContainerComponent], templateUrl: './import-feature-flag-modal.component.html', styleUrls: ['./import-feature-flag-modal.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ImportFeatureFlagModalComponent { - @ViewChild('fileInput') fileInput: ElementRef; isImportActionBtnDisabled = new BehaviorSubject(true); - isDragOver = new BehaviorSubject(false); - fileName = new BehaviorSubject(null); constructor( @Inject(MAT_DIALOG_DATA) @@ -29,46 +26,12 @@ export class ImportFeatureFlagModalComponent { public dialogRef: MatDialogRef ) {} - onDragOver(event: DragEvent) { - event.preventDefault(); - event.stopPropagation(); - this.isDragOver.next(true); - } - - onDragLeave(event: DragEvent) { - event.preventDefault(); - event.stopPropagation(); - this.isDragOver.next(false); - } - - onDrop(event: DragEvent) { - event.preventDefault(); - event.stopPropagation(); - this.isDragOver.next(false); - - const files = event.dataTransfer?.files; - if (files && files.length > 0) { - this.processFile(files[0]); - } - } - - onFileSelected(event: Event) { - const input = event.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - this.processFile(input.files[0]); - } - } - - processFile(file: File) { - if (file.type === 'application/json') { - this.fileName.next(file.name); + handleFilesSelected(files: File[]) { + if(files.length>0) { this.isImportActionBtnDisabled.next(false); - this.handleFileInput(file); - } else { - alert('Please upload a valid JSON file.'); - this.fileName.next(null); - this.isImportActionBtnDisabled.next(true); } + console.log('Selected files:', files); + //Send files to validation endpoint to receive data for table } handleFileInput(file: File) { @@ -80,6 +43,10 @@ export class ImportFeatureFlagModalComponent { reader.readAsText(file); } + importFiles() { + console.log('Import feature flags'); + } + closeModal() { this.dialogRef.close(); } diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.html b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.html new file mode 100644 index 0000000000..c23e8623c5 --- /dev/null +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.html @@ -0,0 +1,14 @@ +
+ file_upload +

Drag & drop or

+ + +
\ No newline at end of file diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.scss b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.scss new file mode 100644 index 0000000000..744ffe9850 --- /dev/null +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.scss @@ -0,0 +1,29 @@ +.input-container { + /* Add your styles here */ + height: 210px; + width: 592px; + border: 2px dashed #ccc; + padding: 20px; + text-align: center; + border-radius: 10px; + transition: background-color 0.3s; + + &.drag-over { + background-color: #f0f0f0; + } + + .drag-text { + margin-bottom: 0.7rem; + } + + mat-icon { + height: 70px; + width: 70px; + font-size: 70px; + color: grey; + } + + button { + font-size: 14px; + } +} \ No newline at end of file diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.ts new file mode 100644 index 0000000000..250566b484 --- /dev/null +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-import-container/common-import-container.component.ts @@ -0,0 +1,79 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '../../../shared/shared.module'; +import { CommonModalComponent } from '../common-modal/common-modal.component'; +import { BehaviorSubject } from 'rxjs'; +import { FILE_TYPE } from 'upgrade_types'; + +/** + * A reusable component for drag-and-drop file import functionality. + * This component allows users to drag and drop files or select them via a file input. + * It supports specifying a file type and emits the selected files to the parent component. + * + * The component accepts the following inputs: + * - `fileType`: A string representing the accepted file type (e.g., '.json'). Only files with this extension can be selected or dropped. + * - `buttonLabel`: A string representing the label text of the button. Defaults to 'Upload File'. + * + * The component emits the following outputs: + * - `filesSelected`: An event that emits the selected files as an array of `File` objects. + * + * Example usage: + * + * ``` + * + * ``` + */ +@Component({ + selector: 'app-common-import-container', + standalone: true, + imports: [CommonModalComponent, CommonModule, SharedModule], + templateUrl: './common-import-container.component.html', + styleUrls: ['./common-import-container.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CommonImportContainerComponent { + @Input() fileType!: FILE_TYPE; + @Input() buttonLabel!: string; + @Output() filesSelected = new EventEmitter(); + + isDragOver = new BehaviorSubject(false); + + onDragOver(event: DragEvent) { + this.handleDragState(event, true); + } + + onDragLeave(event: DragEvent) { + this.handleDragState(event, false); + } + + onDrop(event: DragEvent) { + this.handleDragState(event, false); + this.handleFileSelection(event.dataTransfer?.files); + } + + private handleDragState(event: DragEvent, isOver: boolean) { + event.preventDefault(); + event.stopPropagation(); + this.isDragOver.next(isOver); + } + + onFileSelected(event: Event) { + const input = event.target as HTMLInputElement; + this.handleFileSelection(input.files); + } + + private handleFileSelection(files: FileList | null) { + if (files && files.length > 0) { + const validFiles = Array.from(files).filter(file => file.name.endsWith(this.fileType)); + if (validFiles.length > 0) { + this.filesSelected.emit(validFiles); + } else { + console.error('Invalid file types...'); + } + } + } +} \ No newline at end of file diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.spec.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.spec.ts deleted file mode 100644 index 7323254d65..0000000000 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CommonModalComponent } from './common-modal.component'; - -xdescribe('CommonDialogComponent', () => { - let component: CommonModalComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [CommonModalComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(CommonModalComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.ts index 94e8feaac3..1fded529d5 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.ts +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-modal/common-modal.component.ts @@ -7,7 +7,6 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDialogActions, MatDialogClose, MatDialogContent, MatDialogTitle } from '@angular/material/dialog'; import { CommonModule, NgTemplateOutlet } from '@angular/common'; import { MatIcon } from '@angular/material/icon'; -import { Observable } from 'rxjs'; @Component({ selector: 'app-common-dialog', 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 51ea502f5f..9d3b1ea15c 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 @@ -114,7 +114,7 @@ export class DialogService { const config: MatDialogConfig = { data: commonModalConfig, width: '670px', - height: '450px', + height: '460px', autoFocus: 'input', disableClose: true, }; diff --git a/types/src/Experiment/enums.ts b/types/src/Experiment/enums.ts index 2e026925b1..d666c63f4c 100644 --- a/types/src/Experiment/enums.ts +++ b/types/src/Experiment/enums.ts @@ -270,3 +270,8 @@ export enum FEATURE_FLAG_PARTICIPANT_LIST_KEY { INCLUDE = 'featureFlagSegmentInclusion', EXCLUDE = 'featureFlagSegmentExclusion', } + +export enum FILE_TYPE { + JSON = '.json', + CSV = '.csv', +} diff --git a/types/src/index.ts b/types/src/index.ts index 56f95714d2..2d92cf032e 100644 --- a/types/src/index.ts +++ b/types/src/index.ts @@ -34,6 +34,7 @@ export { FLAG_SEARCH_KEY, FEATURE_FLAG_STATUS, STATUS_INDICATOR_CHIP_TYPE, + FILE_TYPE, } from './Experiment/enums'; export { IEnrollmentCompleteCondition, From 80167508879512709a6511e9cef0dafc198261d8 Mon Sep 17 00:00:00 2001 From: Khanjan Dalwadi <80506682+KD1712@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:52:20 +0530 Subject: [PATCH 19/24] Added collapsible functionality to toggle all section cards (#1741) Co-authored-by: Yagnik Hingrajiya <50392803+Yagnik56@users.noreply.github.com> --- ...ure-flag-details-page-content.component.html | 10 +++++++++- ...ature-flag-details-page-content.component.ts | 6 ++++++ ...re-flag-exclusions-section-card.component.ts | 2 +- ...ure-flag-exposures-section-card.component.ts | 17 ++++++++++++++--- ...re-flag-inclusions-section-card.component.ts | 3 +-- ...g-overview-details-section-card.component.ts | 11 +++++------ 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-details-page-content.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-details-page-content.component.html index fb374ba420..77db00743a 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-details-page-content.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-details-page-content.component.html @@ -3,21 +3,29 @@ inclusions-card exclusions-card - exposures-card diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-details-page-content.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-details-page-content.component.ts index b352c439df..d387cd2f5b 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-details-page-content.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-details-page-content.component.ts @@ -28,6 +28,7 @@ import { SharedModule } from '../../../../../../shared/shared.module'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class FeatureFlagDetailsPageContentComponent implements OnInit, OnDestroy { + isSectionCardExpanded = true; activeTabIndex$ = this.featureFlagsService.activeDetailsTabIndex$; featureFlag$: Observable; @@ -42,6 +43,11 @@ export class FeatureFlagDetailsPageContentComponent implements OnInit, OnDestroy this.featureFlag$ = this.featureFlagsService.selectedFeatureFlag$; } + + onSectionCardExpandChange(expanded: boolean) { + this.isSectionCardExpanded = expanded; + } + ngOnDestroy() { this.featureFlagIdSub.unsubscribe(); } 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 82ef722230..80aac588e1 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 @@ -26,8 +26,8 @@ import { FeatureFlagsService } from '../../../../../../../core/feature-flags/fea changeDetection: ChangeDetectionStrategy.OnPush, }) export class FeatureFlagExclusionsSectionCardComponent { + @Input() isSectionCardExpanded; tableRowCount$ = this.featureFlagService.selectFeatureFlagExclusionsLength$; - isSectionCardExpanded = true; constructor(private featureFlagService: FeatureFlagsService) {} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-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-exposures-section-card/feature-flag-exposures-section-card.component.ts index 0bc6512dce..5ad928ccda 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-exposures-section-card/feature-flag-exposures-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-exposures-section-card/feature-flag-exposures-section-card.component.ts @@ -1,5 +1,9 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { CommonSectionCardActionButtonsComponent, CommonSectionCardComponent, CommonSectionCardTitleHeaderComponent } from '../../../../../../../shared-standalone-component-lib/components'; +import { + CommonSectionCardActionButtonsComponent, + CommonSectionCardComponent, + CommonSectionCardTitleHeaderComponent, +} from '../../../../../../../shared-standalone-component-lib/components'; import { FeatureFlag } from '../../../../../../../core/feature-flags/store/feature-flags.model'; import { TranslateModule } from '@ngx-translate/core'; import { FeatureFlagExposuresDataComponent } from './feature-flag-exposures-data/feature-flag-exposures-data.component'; @@ -8,14 +12,21 @@ import { CommonModule } from '@angular/common'; @Component({ selector: 'app-feature-flag-exposures-section-card', standalone: true, - imports: [CommonSectionCardComponent, CommonSectionCardTitleHeaderComponent, CommonSectionCardActionButtonsComponent, CommonModule, TranslateModule, FeatureFlagExposuresDataComponent], + imports: [ + CommonSectionCardComponent, + CommonSectionCardTitleHeaderComponent, + CommonSectionCardActionButtonsComponent, + CommonModule, + TranslateModule, + FeatureFlagExposuresDataComponent, + ], templateUrl: './feature-flag-exposures-section-card.component.html', styleUrl: './feature-flag-exposures-section-card.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class FeatureFlagExposuresSectionCardComponent { @Input() data: FeatureFlag; - isSectionCardExpanded = true; + @Input() isSectionCardExpanded; onSectionCardExpandChange(isSectionCardExpanded: boolean) { this.isSectionCardExpanded = isSectionCardExpanded; 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 81951af67e..7dfdd8e124 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 @@ -26,6 +26,7 @@ import { FeatureFlagsService } from '../../../../../../../core/feature-flags/fea changeDetection: ChangeDetectionStrategy.OnPush, }) export class FeatureFlagInclusionsSectionCardComponent { + @Input() isSectionCardExpanded; tableRowCount$ = this.featureFlagService.selectFeatureFlagInclusionsLength$; constructor(private featureFlagService: FeatureFlagsService) {} @@ -35,8 +36,6 @@ export class FeatureFlagInclusionsSectionCardComponent { { name: 'Delete', disabled: false }, ]; - isSectionCardExpanded = true; - addIncludeListClicked() { console.log('add Include List Clicked'); } 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 f78f82a1ff..01d235c9f6 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 @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; import { CommonSectionCardActionButtonsComponent, CommonSectionCardComponent, @@ -28,6 +28,8 @@ import { DialogService } from '../../../../../../../shared/services/common-dialo changeDetection: ChangeDetectionStrategy.OnPush, }) export class FeatureFlagOverviewDetailsSectionCardComponent { + isSectionCardExpanded = true; + @Output() sectionCardExpandChange = new EventEmitter(); featureFlag$ = this.featureFlagService.selectedFeatureFlag$; flagOverviewDetails$ = this.featureFlagService.selectedFlagOverviewDetails; @@ -35,12 +37,8 @@ export class FeatureFlagOverviewDetailsSectionCardComponent { { name: 'Edit', disabled: false }, { name: 'Delete', disabled: false }, ]; - isSectionCardExpanded = true; - constructor( - private dialogService: DialogService, - private featureFlagService: FeatureFlagsService, - ) {} + constructor(private dialogService: DialogService, private featureFlagService: FeatureFlagsService) {} get FEATURE_FLAG_STATUS() { return FEATURE_FLAG_STATUS; @@ -82,5 +80,6 @@ export class FeatureFlagOverviewDetailsSectionCardComponent { onSectionCardExpandChange(isSectionCardExpanded: boolean) { this.isSectionCardExpanded = isSectionCardExpanded; + this.sectionCardExpandChange.emit(this.isSectionCardExpanded); } } From 6711c79df3637c6bfc3ff8043bcdbda1be392fca Mon Sep 17 00:00:00 2001 From: Pratik Prajapati <33730817+ppratikcr7@users.noreply.github.com> Date: Thu, 11 Jul 2024 01:30:34 +0530 Subject: [PATCH 20/24] (ExperimentClient Controller) Error code swagger updates and 401 auth error for invalid JWT token (#1740) * error code swagger for exp client controller and 401 auth error for invalid jwt (cherry picked from commit 8e72871c9549c62482becd3bdcc9f36fde07de61) * swagger updates to all client versions --------- Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../controllers/ExperimentClientController.ts | 105 +++++++++++++++--- .../ExperimentClientController.v1.ts | 100 +++++++++++++++-- .../ExperimentClientController.v4.ts | 92 +++++++++++++-- .../ExperimentClientController.v5.ts | 78 +++++++++++-- .../api/middlewares/ClientLibMiddleware.ts | 17 ++- ...0515933833-addTokenValidationFailedEnum.ts | 31 ++++++ types/src/Experiment/enums.ts | 1 + 7 files changed, 378 insertions(+), 46 deletions(-) create mode 100644 backend/packages/Upgrade/src/database/migrations/1720515933833-addTokenValidationFailedEnum.ts diff --git a/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.ts b/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.ts index 9a2215f379..5713161d03 100644 --- a/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.ts +++ b/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.ts @@ -91,7 +91,7 @@ interface IExperimentAssignment { /** * @swagger * tags: - * - name: Client Side SDK + * - name: Client API calls * description: CRUD operations related to experiments points */ @@ -155,7 +155,7 @@ export class ExperimentClientController { * example: instructor1 * description: ExperimentUser * tags: - * - Client Side SDK + * - Client API calls * produces: * - application/json * responses: @@ -163,6 +163,15 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error + * */ @Post('init') public async init( @@ -247,8 +256,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * description: Internal Server Error */ @Post('groupmembership') public async setGroupMemberShip( @@ -310,8 +325,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * description: Internal Server Error */ @Post('workinggroup') public async setWorkingGroup( @@ -413,8 +434,14 @@ export class ExperimentClientController { * - enrollmentCode * - userId * - condition + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: User not defined + * description: Internal Server Error */ @Post('mark') public async markExperimentPoint( @@ -529,10 +556,14 @@ export class ExperimentClientController { * - conditionCode * - assignmentWeight * - order - * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '404': - * description: Experiment user not defined + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('assign') public async getAllExperimentConditions( @@ -631,8 +662,14 @@ export class ExperimentClientController { * responses: * '200': * description: Log data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('log') public async log( @@ -670,9 +707,15 @@ export class ExperimentClientController { * - application/json * responses: * '200': - * description: Log data + * description: Log Caliper data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('log/caliper') public async caliperLog( @@ -727,6 +770,14 @@ export class ExperimentClientController { * responses: * '200': * description: Log blob data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('bloblog') public async blobLog(@Req() request: express.Request): Promise { @@ -796,8 +847,14 @@ export class ExperimentClientController { * responses: * '200': * description: Client side reported error + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('failed') public async failedExperimentPoint( @@ -852,6 +909,14 @@ export class ExperimentClientController { * responses: * '200': * description: Feature flags list + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('featureflag') public async getAllFlags( @@ -886,6 +951,10 @@ export class ExperimentClientController { * responses: * '200': * description: Filtered Metrics + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '500': * description: Insert error in database */ @@ -962,8 +1031,14 @@ export class ExperimentClientController { * originalUser: * type: string * minLength: 1 + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('useraliases') public async setUserAliases( @@ -1008,6 +1083,10 @@ export class ExperimentClientController { * responses: * '200': * description: Database cleared + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '500': * description: DEMO mode is disabled */ diff --git a/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v1.ts b/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v1.ts index 91db90bbb5..d36fa28c8e 100644 --- a/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v1.ts +++ b/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v1.ts @@ -168,6 +168,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('init') public async init( @@ -251,8 +259,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * description: Internal Server Error */ @Patch('groupmembership') public async setGroupMemberShip( @@ -319,8 +333,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * description: Internal Server Error */ @Patch('workinggroup') public async setWorkingGroup( @@ -414,8 +434,14 @@ export class ExperimentClientController { * - enrollmentCode * - userId * - condition + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: User not defined + * description: Internal Server Error */ @Post('mark') public async markExperimentPoint( @@ -493,10 +519,14 @@ export class ExperimentClientController { * condition: * type: string * minLength: 1 - * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '404': - * description: Experiment user not defined + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('assign') public async getAllExperimentConditions( @@ -594,8 +624,14 @@ export class ExperimentClientController { * responses: * '200': * description: Log data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('log') public async log( @@ -636,9 +672,15 @@ export class ExperimentClientController { * - application/json * responses: * '200': - * description: Log data + * description: Log Caliper data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('log/caliper') public async caliperLog( @@ -693,6 +735,14 @@ export class ExperimentClientController { * responses: * '200': * description: Log blob data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('bloblog') public async blobLog(@Req() request: express.Request): Promise { @@ -762,8 +812,14 @@ export class ExperimentClientController { * responses: * '200': * description: Client side reported error + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('failed') public async failedExperimentPoint( @@ -818,6 +874,14 @@ export class ExperimentClientController { * responses: * '200': * description: Feature flags list + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('featureflag') public async getAllFlags( @@ -852,6 +916,10 @@ export class ExperimentClientController { * responses: * '200': * description: Filtered Metrics + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '500': * description: Insert error in database */ @@ -911,8 +979,14 @@ export class ExperimentClientController { * required: * - userId * - userAliases + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Patch('useraliases') public async setUserAliases( @@ -942,6 +1016,10 @@ export class ExperimentClientController { * responses: * '200': * description: Database cleared + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '500': * description: DEMO mode is disabled */ diff --git a/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v4.ts b/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v4.ts index 9b4f513583..b4ba343665 100644 --- a/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v4.ts +++ b/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v4.ts @@ -167,6 +167,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('init') public async init( @@ -250,8 +258,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * description: Internal Server Error */ @Patch('groupmembership') public async setGroupMemberShip( @@ -318,8 +332,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * description: Internal Server Error */ @Patch('workinggroup') public async setWorkingGroup( @@ -413,8 +433,14 @@ export class ExperimentClientController { * - enrollmentCode * - userId * - condition + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: User not defined + * description: Internal Server Error */ @Post('mark') public async markExperimentPoint( @@ -493,10 +519,14 @@ export class ExperimentClientController { * condition: * type: string * minLength: 1 - * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '404': - * description: Experiment user not defined + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('assign') public async getAllExperimentConditions( @@ -614,8 +644,14 @@ export class ExperimentClientController { * responses: * '200': * description: Log data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('log') public async log( @@ -656,9 +692,15 @@ export class ExperimentClientController { * - application/json * responses: * '200': - * description: Log data + * description: Log Caliper data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('log/caliper') public async caliperLog( @@ -713,6 +755,14 @@ export class ExperimentClientController { * responses: * '200': * description: Log blob data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('bloblog') public async blobLog(@Req() request: express.Request): Promise { @@ -780,6 +830,14 @@ export class ExperimentClientController { * responses: * '200': * description: Feature flags list + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('featureflag') public async getAllFlags( @@ -814,6 +872,10 @@ export class ExperimentClientController { * responses: * '200': * description: Filtered Metrics + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '500': * description: Insert error in database */ @@ -873,8 +935,14 @@ export class ExperimentClientController { * required: * - userId * - userAliases + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Patch('useraliases') public async setUserAliases( @@ -904,6 +972,10 @@ export class ExperimentClientController { * responses: * '200': * description: Database cleared + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '500': * description: DEMO mode is disabled */ diff --git a/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v5.ts b/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v5.ts index ae0b6d4875..58f747b1e6 100644 --- a/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v5.ts +++ b/backend/packages/Upgrade/src/api/controllers/ExperimentClientController.v5.ts @@ -163,6 +163,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('init') public async init( @@ -246,8 +254,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * description: Internal Server Error */ @Patch('groupmembership') public async setGroupMemberShip( @@ -314,8 +328,14 @@ export class ExperimentClientController { * description: Set Group Membership * schema: * $ref: '#/definitions/initResponse' + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * description: Internal Server Error */ @Patch('workinggroup') public async setWorkingGroup( @@ -409,8 +429,14 @@ export class ExperimentClientController { * - enrollmentCode * - userId * - condition + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: User not defined + * description: Internal Server Error */ @Post('mark') public async markExperimentPoint( @@ -490,10 +516,14 @@ export class ExperimentClientController { * condition: * type: string * minLength: 1 - * '500': - * description: null value in column "id" of relation "experiment_user" violates not-null constraint + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '404': - * description: Experiment user not defined + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('assign') public async getAllExperimentConditions( @@ -616,8 +646,14 @@ export class ExperimentClientController { * responses: * '200': * description: Log data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Post('log') public async log( @@ -668,6 +704,14 @@ export class ExperimentClientController { * responses: * '200': * description: Log blob data + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('bloblog') public async blobLog(@Req() request: express.Request): Promise { @@ -735,6 +779,14 @@ export class ExperimentClientController { * responses: * '200': * description: Feature flags list + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined + * '500': + * description: Internal Server Error */ @Post('featureflag') public async getAllFlags( @@ -792,8 +844,14 @@ export class ExperimentClientController { * required: * - userId * - userAliases + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError + * '404': + * description: Experiment User not defined * '500': - * description: null value in column "id\" of relation \"experiment_user\" violates not-null constraint + * description: Internal Server Error */ @Patch('useraliases') public async setUserAliases( @@ -823,6 +881,10 @@ export class ExperimentClientController { * responses: * '200': * description: Database cleared + * '400': + * description: BadRequestError - InvalidParameterValue + * '401': + * description: AuthorizationRequiredError * '500': * description: DEMO mode is disabled */ diff --git a/backend/packages/Upgrade/src/api/middlewares/ClientLibMiddleware.ts b/backend/packages/Upgrade/src/api/middlewares/ClientLibMiddleware.ts index af6aab6c58..2e51dc9923 100644 --- a/backend/packages/Upgrade/src/api/middlewares/ClientLibMiddleware.ts +++ b/backend/packages/Upgrade/src/api/middlewares/ClientLibMiddleware.ts @@ -38,11 +38,20 @@ export class ClientLibMiddleware implements ExpressMiddlewareInterface { req.logger.warn({ message: 'Token is not present in request header' }); const error = new Error('Token is not present in request header from client'); (error as any).type = SERVER_ERROR.TOKEN_NOT_PRESENT; - (error as any).httpCode = '401' + (error as any).httpCode = 401; throw error; } const { secret, key } = env.clientApi; - const decodeToken: any = jwt.verify(token, secret); + let decodeToken: any; + try { + decodeToken = jwt.verify(token, secret); + } catch (err) { + const error = err as ErrorWithType; + (error as any).type = SERVER_ERROR.TOKEN_VALIDATION_FAILED; + (error as any).httpCode = 401; + req.logger.error(error); + throw error; + } delete decodeToken.iat; delete decodeToken.exp; @@ -51,7 +60,7 @@ export class ClientLibMiddleware implements ExpressMiddlewareInterface { } else { const error = new Error('Provided token is invalid'); (error as any).type = SERVER_ERROR.INVALID_TOKEN; - (error as any).httpCode = '401' + (error as any).httpCode = 401; req.logger.error(error); throw error; } @@ -62,7 +71,7 @@ export class ClientLibMiddleware implements ExpressMiddlewareInterface { const err = error as ErrorWithType; if (err.message === 'jwt expired' || err.message === 'invalid signature') { err.type = SERVER_ERROR.INVALID_TOKEN; - (error as any).httpCode = '401' + (error as any).httpCode = 401; req.logger.error(err); throw err; } else { diff --git a/backend/packages/Upgrade/src/database/migrations/1720515933833-addTokenValidationFailedEnum.ts b/backend/packages/Upgrade/src/database/migrations/1720515933833-addTokenValidationFailedEnum.ts new file mode 100644 index 0000000000..fac72f8971 --- /dev/null +++ b/backend/packages/Upgrade/src/database/migrations/1720515933833-addTokenValidationFailedEnum.ts @@ -0,0 +1,31 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class addTokenValidationFailedEnum1720515933833 implements MigrationInterface { + name = 'addTokenValidationFailedEnum1720515933833'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TYPE "public"."experiment_error_type_enum" RENAME TO "experiment_error_type_enum_old"` + ); + await queryRunner.query( + `CREATE TYPE "public"."experiment_error_type_enum" AS ENUM('Database not reachable', 'Database auth fail', 'Error in the assignment algorithm', 'Parameter missing in the client request', 'Parameter not in the correct format', 'User ID not found', 'Query Failed', 'Error reported from client', 'Experiment user not defined', 'Experiment user group not defined', 'Working group is not a subset of user group', 'Invalid token', 'Token is not present in request', 'JWT Token validation failed', 'Error in migration', 'Email send error', 'Condition not found', 'Experiment ID not provided for shared Decision Point', 'Experiment ID provided is invalid for shared Decision Point', 'Caliper profile or event not supported')` + ); + await queryRunner.query( + `ALTER TABLE "experiment_error" ALTER COLUMN "type" TYPE "public"."experiment_error_type_enum" USING "type"::"text"::"public"."experiment_error_type_enum"` + ); + await queryRunner.query(`DROP TYPE "public"."experiment_error_type_enum_old"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."experiment_error_type_enum_old" AS ENUM('Caliper profile or event not supported', 'Condition not found', 'Database auth fail', 'Database not reachable', 'Email send error', 'Error in migration', 'Error in the assignment algorithm', 'Error reported from client', 'Experiment ID not provided for shared Decision Point', 'Experiment ID provided is invalid for shared Decision Point', 'Experiment user group not defined', 'Experiment user not defined', 'Invalid token', 'Parameter missing in the client request', 'Parameter not in the correct format', 'Query Failed', 'Token is not present in request', 'User ID not found', 'Working group is not a subset of user group')` + ); + await queryRunner.query( + `ALTER TABLE "experiment_error" ALTER COLUMN "type" TYPE "public"."experiment_error_type_enum_old" USING "type"::"text"::"public"."experiment_error_type_enum_old"` + ); + await queryRunner.query(`DROP TYPE "public"."experiment_error_type_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."experiment_error_type_enum_old" RENAME TO "experiment_error_type_enum"` + ); + } +} diff --git a/types/src/Experiment/enums.ts b/types/src/Experiment/enums.ts index d666c63f4c..d7a4228b5c 100644 --- a/types/src/Experiment/enums.ts +++ b/types/src/Experiment/enums.ts @@ -58,6 +58,7 @@ export enum SERVER_ERROR { WORKING_GROUP_NOT_SUBSET_OF_GROUP = 'Working group is not a subset of user group', INVALID_TOKEN = 'Invalid token', TOKEN_NOT_PRESENT = 'Token is not present in request', + TOKEN_VALIDATION_FAILED = 'JWT Token validation failed', MIGRATION_ERROR = 'Error in migration', EMAIL_SEND_ERROR = 'Email send error', CONDITION_NOT_FOUND = 'Condition not found', From 766ff9193e2b6fee4349930f276f43576c74e98d Mon Sep 17 00:00:00 2001 From: Zack Lee <90279765+zackcl@users.noreply.github.com> Date: Fri, 12 Jul 2024 19:26:44 +0900 Subject: [PATCH 21/24] Update demo-app.js for the demo app's tour to work with UpGrade v5.2.0 (#1748) --- .../upgrade/src/assets/js/demo-app.js | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/frontend/projects/upgrade/src/assets/js/demo-app.js b/frontend/projects/upgrade/src/assets/js/demo-app.js index 3366f0540e..6afc20172a 100644 --- a/frontend/projects/upgrade/src/assets/js/demo-app.js +++ b/frontend/projects/upgrade/src/assets/js/demo-app.js @@ -5,80 +5,88 @@ if (window.self !== window.top) { const getElementById = (id) => { switch (id) { // Home - case 'login-with-google-button': - return document.querySelector('div.login-container button.google-sign-in-btn'); + case 'upgrade-logo-link': + return document.querySelector('div.logo a.logo-link'); case 'experiments-tab': return document.querySelectorAll('div.list-item-container a.nav-item')[0]; case 'signout-button': - return document.querySelector('span.mat-list-item-content a.logout-link'); + return document.querySelector('mat-list.user-list a.logout-link'); case 'add-experiment-button': - return document.querySelectorAll('mat-card.mat-card button.mat-flat-button')[1]; + return document.querySelectorAll('mat-card.mat-mdc-card button.mat-mdc-unelevated-button')[1]; // Experiment Stepper - Overview Step case 'experiment-stepper-overview-name': - return document.querySelectorAll('form.experiment-overview input.mat-input-element')[0]; + return document.querySelectorAll('form.experiment-overview input.mat-mdc-input-element')[0]; case 'experiment-stepper-overview-description': - return document.querySelectorAll('form.experiment-overview input.mat-input-element')[1]; + return document.querySelectorAll('form.experiment-overview input.mat-mdc-input-element')[1]; case 'experiment-stepper-overview-app-context': - return document.querySelectorAll('form.experiment-overview div.mat-select-value')[0]; + return document.querySelectorAll('form.experiment-overview div.mat-mdc-select-value')[0]; case 'experiment-stepper-overview-unit-of-assignment': - return document.querySelectorAll('form.experiment-overview div.mat-select-value')[1]; + return document.querySelectorAll('form.experiment-overview div.mat-mdc-select-value')[1]; case 'experiment-stepper-overview-consistency-rule': - return document.querySelectorAll('form.experiment-overview div.mat-select-value')[2]; + return document.querySelectorAll('form.experiment-overview div.mat-mdc-select-value')[2]; case 'experiment-stepper-overview-next-button': - return document.querySelectorAll('div.new-experiment-modal button.mat-raised-button')[1]; + return document.querySelectorAll('div.new-experiment-modal button.mat-mdc-raised-button')[1]; // Experiment Stepper - Design Step case 'experiment-stepper-design-add-decision-point-button': return document.querySelector('form.experiment-design button.add-decision-point'); case 'experiment-stepper-design-decision-points-row1-site': - return document.querySelectorAll('mat-table.decision-point-table input.mat-input-element')[0]; + return document.querySelectorAll('mat-table.decision-point-table input.mat-mdc-input-element')[0]; case 'experiment-stepper-design-decision-points-row1-target': - return document.querySelectorAll('mat-table.decision-point-table input.mat-input-element')[1]; + return document.querySelectorAll('mat-table.decision-point-table input.mat-mdc-input-element')[1]; case 'experiment-stepper-design-decision-points-row1-exclude-if-reached': - return document.querySelector('mat-table.decision-point-table input.mat-checkbox-input'); + return document.querySelector('mat-table.decision-point-table input.mdc-checkbox__native-control'); + case 'experiment-stepper-design-decision-points-row1-confirm-button': + return document.querySelectorAll('mat-table.decision-point-table button.row-action-btn')[0]; case 'experiment-stepper-design-add-condition-button': return document.querySelector('form.experiment-design button.add-condition'); case 'experiment-stepper-design-conditions-row1-condition': - return document.querySelectorAll('mat-table.condition-table input.mat-input-element')[0]; + return document.querySelectorAll('mat-table.condition-table input.mat-mdc-input-element')[0]; + case 'experiment-stepper-design-conditions-row1-confirm': + return document.querySelectorAll('mat-table.condition-table button.row-action-btn')[0]; case 'experiment-stepper-design-conditions-row2-condition': - return document.querySelectorAll('mat-table.condition-table input.mat-input-element')[3]; + return document.querySelectorAll('mat-table.condition-table input.mat-mdc-input-element')[0]; + case 'experiment-stepper-design-conditions-row2-confirm': + return document.querySelectorAll('mat-table.condition-table button.row-action-btn')[2]; case 'experiment-stepper-design-next-button': - return document.querySelectorAll('div.new-experiment-modal button.mat-raised-button')[4]; + return document.querySelectorAll('div.new-experiment-modal button.mat-mdc-raised-button')[4]; // Experiment Stepper - Participants Step - case 'experiment-stepper-participants-inclusion-criteria': - return document.querySelector('form.experiment-participants div.mat-select-value'); + case 'experiment-stepper-participants-add-member-button': + return document.querySelector('form.experiment-participants button.add-member'); + case 'experiment-stepper-participants-include-row1-type': + return document.querySelectorAll('mat-table.member-table div.mat-mdc-select-value')[0]; case 'experiment-stepper-participants-next-button': - return document.querySelectorAll('div.new-experiment-modal button.mat-raised-button')[7]; + return document.querySelectorAll('div.new-experiment-modal button.mat-mdc-raised-button')[7]; // Experiment Stepper - Metrics Step case 'experiment-stepper-metrics-add-metric-button': return document.querySelector('form.metric-design button.add-metric'); case 'experiment-stepper-metrics-metrics-row1-metric': - return document.querySelectorAll('mat-table.metric-table input.mat-input-element')[0]; + return document.querySelectorAll('mat-table.metric-table input.mat-mdc-input-element')[0]; case 'experiment-stepper-metrics-metrics-row1-statistic': - return document.querySelectorAll('mat-table.metric-table div.mat-select-value')[0]; + return document.querySelectorAll('mat-table.metric-table div.mat-mdc-select-value')[0]; case 'experiment-stepper-metrics-metrics-row1-display-name': - return document.querySelectorAll('mat-table.metric-table input.mat-input-element')[1]; + return document.querySelectorAll('mat-table.metric-table input.mat-mdc-input-element')[1]; case 'experiment-stepper-metrics-metrics-row2-metric': - return document.querySelectorAll('mat-table.metric-table input.mat-input-element')[2]; + return document.querySelectorAll('mat-table.metric-table input.mat-mdc-input-element')[2]; case 'experiment-stepper-metrics-metrics-row2-statistic': - return document.querySelectorAll('mat-table.metric-table div.mat-select-value')[1]; + return document.querySelectorAll('mat-table.metric-table div.mat-mdc-select-value')[1]; case 'experiment-stepper-metrics-metrics-row2-display-name': - return document.querySelectorAll('mat-table.metric-table input.mat-input-element')[3]; + return document.querySelectorAll('mat-table.metric-table input.mat-mdc-input-element')[3]; case 'experiment-stepper-metrics-next-button': - return document.querySelectorAll('div.new-experiment-modal button.mat-raised-button')[10]; + return document.querySelectorAll('div.new-experiment-modal button.mat-mdc-raised-button')[10]; // Experiment Stepper - Schedule Step case 'experiment-stepper-schedule-next-button': - return document.querySelectorAll('div.new-experiment-modal button.mat-raised-button')[13]; + return document.querySelectorAll('div.new-experiment-modal button.mat-mdc-raised-button')[13]; // Experiment Stepper - Post Rule Step case 'experiment-stepper-post-rule-post-rule': - return document.querySelectorAll('form.post-experiment-rule-form div.mat-select-value')[0]; + return document.querySelectorAll('form.post-experiment-rule-form div.mat-mdc-select-value')[0]; case 'experiment-stepper-post-rule-create-button': - return document.querySelectorAll('div.new-experiment-modal button.mat-raised-button')[16]; + return document.querySelectorAll('div.new-experiment-modal button.mat-mdc-raised-button')[16]; // Experiment Details - Overview Tab case 'experiment-details-overview-status': @@ -86,13 +94,13 @@ if (window.self !== window.top) { // Experiment Details - Data Tab case 'experiment-details-data-tab': - return document.querySelectorAll('div.mat-tab-list div.mat-tab-label')[4]; + return document.querySelectorAll('div.mat-mdc-tab-list div.mat-mdc-tab')[4]; // Change Experiment Status Modal case 'change-experiment-status-modal-new-status': - return document.querySelectorAll('form.experiment-status-form div.mat-select-value')[1]; + return document.querySelectorAll('form.experiment-status-form div.mat-mdc-select-value')[1]; case 'change-experiment-status-modal-save-button': - return document.querySelectorAll('div.button-container button.mat-raised-button')[1]; + return document.querySelectorAll('div.button-container button.mat-mdc-raised-button')[1]; } console.error(`Error: The element ID "${id}" is not valid.`); return null; @@ -127,7 +135,7 @@ if (window.self !== window.top) { }; const closeOpenedModals = () => { - const modalButtonsSelector = 'div.cdk-overlay-pane button.mat-raised-button'; + const modalButtonsSelector = 'div.cdk-overlay-pane button.mat-mdc-raised-button'; const modalCloseButton = Array.from(document.querySelectorAll(modalButtonsSelector)).find( (elem) => elem.innerText.toLowerCase() === 'close' ); @@ -152,16 +160,16 @@ if (window.self !== window.top) { const signOutButton = getElementById('signout-button'); if (signOutButton) { signOutButton.click(); - } else if (gapi.auth2.getAuthInstance().isSignedIn.get()) { - gapi.auth2.getAuthInstance().signOut(); } }, 'on-upgrade-tab-click': () => { - if (!gapi.auth2.getAuthInstance().isSignedIn.get()) { + if (!getElementById('upgrade-logo-link')) { return closeOpenedModals(); } const experimentsTab = getElementById('experiments-tab'); - experimentsTab.click(); + if (experimentsTab) { + experimentsTab.click(); + } }, 'remove-active-window-event': () => { removeActiveWindowEvent(); From 532e05b6f4b913e4445ff2501fd1a38bb51523f5 Mon Sep 17 00:00:00 2001 From: Khanjan Dalwadi <80506682+KD1712@users.noreply.github.com> Date: Tue, 16 Jul 2024 04:14:06 +0530 Subject: [PATCH 22/24] Import-Export buttons in common tag component (#1730) * Added Import Export buttons in Common Tags Input Component * Change in tsdoc * Toggle the action buttons w.r.t tags value * Changed the icons using google icons and added stylesheet request for it in index file --------- Co-authored-by: danoswaltCL <97542869+danoswaltCL@users.noreply.github.com> --- .../common-tag-input.component.html | 52 +++++++++++-------- .../common-tag-input.component.scss | 29 +++++++++++ .../common-tag-input.component.ts | 52 +++++++++++++++++-- frontend/projects/upgrade/src/index.html | 6 +++ 4 files changed, 114 insertions(+), 25 deletions(-) diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.html b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.html index db7101a866..2775d6146c 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.html +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.html @@ -1,25 +1,35 @@ - + {{ 'home.new-experiment.overview.tags.placeHolder' | translate }} - - - - {{ componentTag }} - - cancel - - - +
+ + + + {{ componentTag }} + + cancel + + + + + +
diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.scss b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.scss index c7acb4bf6e..654ab0ba87 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.scss +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.scss @@ -1,3 +1,32 @@ mat-form-field { width: 100%; } +.icon-buttons-present { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} +.container { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.icon-button { + background: none; + border: none; + box-shadow: none; + outline: none; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; +} + +.icon-button { + color: var(--grey-2); +} diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts index c2750245f2..761b793a9d 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts @@ -1,4 +1,4 @@ -import { Component, forwardRef } from '@angular/core'; +import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; import { MatChipInputEvent } from '@angular/material/chips'; import { ENTER, COMMA } from '@angular/cdk/keycodes'; @@ -15,9 +15,21 @@ import { TranslateModule } from '@ngx-translate/core'; // registerOnChange(fn: any): Registers a callback for when the value changes. // registerOnTouched(fn: any): Registers a callback for when the component is touched. -// Example Usage: +// Typical usage // +// To add Import/Export button while using component +// you can add the property 'actionButtons' which will check for +// tags value and will display Import/Export icon accordingly +// and bind it with 'actionButtonClicked' to implement action + +// Manage built-in optional action buttons +// + @Component({ selector: 'app-common-tags-input', templateUrl: './common-tag-input.component.html', @@ -32,14 +44,42 @@ import { TranslateModule } from '@ngx-translate/core'; ], imports: [CommonModule, MatChipsModule, MatFormFieldModule, MatIconModule, MatInputModule, TranslateModule], }) -export class CommonTagsInputComponent implements ControlValueAccessor { - isChipSelectable = true; +export class CommonTagsInputComponent implements ControlValueAccessor, OnInit { + showExportIcon = false; + showImportIcon = false; + @Input() actionButtons = false; + @Output() actionButtonClicked = new EventEmitter(); + + isChipSelectable = false; isChipRemovable = true; addChipOnBlur = true; readonly separatorKeysCodes: number[] = [ENTER, COMMA]; tags = new FormControl([]); + ngOnInit(): void { + this.checkTagValue(); + } + + checkTagValue(): void { + this.tags.valueChanges.subscribe((value) => { + if (this.actionButtons) { + // Update showExportIcon and showImportIcon based on tags value + if (value && value.length > 0) { + this.showExportIcon = true; + this.showImportIcon = false; + } else { + this.showImportIcon = true; + this.showExportIcon = false; + } + } + }); + } + + onActionButtonClick(): void { + this.actionButtonClicked.emit(); + } + addChip(event: MatChipInputEvent) { const input = event.chipInput; const value = (event.value || '').trim().toLowerCase(); @@ -57,6 +97,8 @@ export class CommonTagsInputComponent implements ControlValueAccessor { if (input) { input.clear(); } + + this.checkTagValue(); } removeChip(tag: string) { @@ -65,6 +107,8 @@ export class CommonTagsInputComponent implements ControlValueAccessor { this.tags.setValue(newTags); this.tags.updateValueAndValidity(); + + this.checkTagValue(); } // Implement ControlValueAccessor methods writeValue(value: string[]) { diff --git a/frontend/projects/upgrade/src/index.html b/frontend/projects/upgrade/src/index.html index 183c8a31a2..b8614f0f04 100755 --- a/frontend/projects/upgrade/src/index.html +++ b/frontend/projects/upgrade/src/index.html @@ -11,6 +11,12 @@ + + + From 0a45c617ab62556bc5d0d61be248d1c0a0c7bca6 Mon Sep 17 00:00:00 2001 From: pratik Date: Tue, 16 Jul 2024 15:48:00 +0530 Subject: [PATCH 23/24] remove groupId column from individualExclusion table --- .../Upgrade/src/api/models/IndividualExclusion.ts | 3 --- ...4249413-removeGroupIdFromIndividualExclusion.ts | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 backend/packages/Upgrade/src/database/migrations/1721124249413-removeGroupIdFromIndividualExclusion.ts diff --git a/backend/packages/Upgrade/src/api/models/IndividualExclusion.ts b/backend/packages/Upgrade/src/api/models/IndividualExclusion.ts index fa39ad97c6..f8ef8ac3f4 100644 --- a/backend/packages/Upgrade/src/api/models/IndividualExclusion.ts +++ b/backend/packages/Upgrade/src/api/models/IndividualExclusion.ts @@ -14,9 +14,6 @@ export class IndividualExclusion extends BaseModel { @ManyToOne(() => Experiment, { onDelete: 'CASCADE' }) public experiment: Experiment; - @Column({ nullable: true }) - public groupId?: string; - @IsNotEmpty() @Column({ type: 'enum', enum: EXCLUSION_CODE, nullable: true }) public exclusionCode: EXCLUSION_CODE; diff --git a/backend/packages/Upgrade/src/database/migrations/1721124249413-removeGroupIdFromIndividualExclusion.ts b/backend/packages/Upgrade/src/database/migrations/1721124249413-removeGroupIdFromIndividualExclusion.ts new file mode 100644 index 0000000000..712bcd7529 --- /dev/null +++ b/backend/packages/Upgrade/src/database/migrations/1721124249413-removeGroupIdFromIndividualExclusion.ts @@ -0,0 +1,14 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class removeGroupIdFromIndividualExclusion1721124249413 implements MigrationInterface { + name = 'removeGroupIdFromIndividualExclusion1721124249413' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "individual_exclusion" DROP COLUMN "groupId"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "individual_exclusion" ADD "groupId" character varying`); + } + +} From 2aa5ba238772324c5d7a81280a525db16939411d Mon Sep 17 00:00:00 2001 From: pratik Date: Tue, 16 Jul 2024 18:21:20 +0530 Subject: [PATCH 24/24] remove groupId from assignment service --- .../api/services/ExperimentAssignmentService.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/backend/packages/Upgrade/src/api/services/ExperimentAssignmentService.ts b/backend/packages/Upgrade/src/api/services/ExperimentAssignmentService.ts index e34a6773f1..c06909e2e3 100644 --- a/backend/packages/Upgrade/src/api/services/ExperimentAssignmentService.ts +++ b/backend/packages/Upgrade/src/api/services/ExperimentAssignmentService.ts @@ -1290,10 +1290,9 @@ export class ExperimentAssignmentService { } if (status === MARKED_DECISION_POINT_STATUS.CONDITION_FAILED_TO_APPLY) { - const excludeUserDoc: Pick = { + const excludeUserDoc: Pick = { user, experiment, - groupId: user?.workingGroup?.[experiment.group], exclusionCode: EXCLUSION_CODE.EXCLUDED_BY_CLIENT, }; await this.individualExclusionRepository.saveRawJson([excludeUserDoc]); @@ -1303,10 +1302,9 @@ export class ExperimentAssignmentService { // Don't mark the experiment if user or group are in exclusion list // TODO update this with segment implementation // Create the excludeUserDoc outside of the conditional statements to avoid repetition - const excludeUserDoc: Pick = { + const excludeUserDoc: Pick = { user, experiment, - groupId: user?.workingGroup?.[experiment.group], exclusionCode: EXCLUSION_CODE.PARTICIPANT_ON_EXCLUSION_LIST, }; if (globallyExcluded.user || globallyExcluded.group.length) { @@ -1394,18 +1392,16 @@ export class ExperimentAssignmentService { if (!individualEnrollment && !individualExclusion) { if (assignmentUnit === ASSIGNMENT_UNIT.GROUP && !groupEnrollment) { - const excludeUserDoc: Pick = { + const excludeUserDoc: Pick = { user, experiment, - groupId: user?.workingGroup?.[experiment.group], exclusionCode: EXCLUSION_CODE.REACHED_AFTER, }; promiseArray.push(this.individualExclusionRepository.saveRawJson([excludeUserDoc])); } else if (assignmentUnit !== ASSIGNMENT_UNIT.GROUP) { - const excludeUserDoc: Pick = { + const excludeUserDoc: Pick = { user, experiment, - groupId: user?.workingGroup?.[experiment.group], exclusionCode: EXCLUSION_CODE.REACHED_AFTER, }; promiseArray.push(this.individualExclusionRepository.saveRawJson([excludeUserDoc])); @@ -1451,7 +1447,6 @@ export class ExperimentAssignmentService { > = { experiment, user, - groupId: user?.workingGroup?.[experiment.group], exclusionCode: EXCLUSION_CODE.EXCLUDED_DUE_TO_GROUP_LOGIC, }; individualExclusion = individualExclusionDocument as IndividualExclusion; @@ -1484,7 +1479,6 @@ export class ExperimentAssignmentService { > = { experiment, user, - groupId: user?.workingGroup?.[experiment.group], exclusionCode: invalidGroup ? EXCLUSION_CODE.INVALID_GROUP_OR_WORKING_GROUP : EXCLUSION_CODE.NO_GROUP_SPECIFIED,