ng-configcat-publicapi-ui 5.6.2 → 5.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OVERLAY_DEFAULT_CONFIG, Overlay } from '@angular/cdk/overlay';
|
|
2
2
|
import { DatePipe, NgClass, NgTemplateOutlet, SlicePipe, KeyValuePipe } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, Injectable, ChangeDetectionStrategy, Component, viewChild, DestroyRef, DOCUMENT, InjectionToken, HostListener, Directive, input, ElementRef, model, effect, output, forwardRef, signal, viewChildren, linkedSignal,
|
|
4
|
+
import { inject, Injectable, ChangeDetectionStrategy, Component, viewChild, DestroyRef, DOCUMENT, InjectionToken, HostListener, Directive, input, ElementRef, model, effect, output, forwardRef, signal, computed, viewChildren, linkedSignal, ViewEncapsulation, ChangeDetectorRef } from '@angular/core';
|
|
5
5
|
import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY, MatAutocompleteTrigger, MatAutocomplete } from '@angular/material/autocomplete';
|
|
6
6
|
import { MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS, MatButtonToggleGroup, MatButtonToggle } from '@angular/material/button-toggle';
|
|
7
7
|
import { MAT_DATEPICKER_SCROLL_STRATEGY, MatDatepicker, MatDatepickerToggle, MatDatepickerInput } from '@angular/material/datepicker';
|
|
@@ -35,7 +35,7 @@ import { config, edit } from 'ace-builds/src-min-noconflict/ace';
|
|
|
35
35
|
import 'ace-builds/src-noconflict/mode-json';
|
|
36
36
|
import 'ace-builds/src-noconflict/theme-tomorrow';
|
|
37
37
|
import 'ace-builds/src-noconflict/theme-github_dark';
|
|
38
|
-
import {
|
|
38
|
+
import { CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
|
|
39
39
|
import { MatDivider } from '@angular/material/divider';
|
|
40
40
|
import { MatCheckbox } from '@angular/material/checkbox';
|
|
41
41
|
import { CdkCopyToClipboard, Clipboard } from '@angular/cdk/clipboard';
|
|
@@ -2326,6 +2326,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
|
|
|
2326
2326
|
}]
|
|
2327
2327
|
}], propDecorators: { selectedItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "appDeselect", required: false }] }] } });
|
|
2328
2328
|
|
|
2329
|
+
// When moving items in a formArray, we should not do it via the recommended way on the angular dragndrop documentations.
|
|
2330
|
+
// It recommends a simple moveItemInArray(formArray.controls). But doing that will not fire change events, etc.
|
|
2331
|
+
// This item is working on the FormArray's recommended way to push, remove items - do it directly on the FormArray.
|
|
2332
|
+
// It is not an array, so we cannot call moveItemInArray on it, but it has methods to modify the array.
|
|
2333
|
+
function moveItemInFormArray(formArray, previousIndex, currentIndex) {
|
|
2334
|
+
if (previousIndex === currentIndex)
|
|
2335
|
+
return;
|
|
2336
|
+
const draggedItem = formArray.at(previousIndex);
|
|
2337
|
+
formArray.removeAt(previousIndex);
|
|
2338
|
+
// Insert at the drop index. CDK's currentIndex is the intended insertion position
|
|
2339
|
+
// after removal, inserting at event.currentIndex places the control in the
|
|
2340
|
+
// position equivalent to the CDK moveItemInArray behavior.
|
|
2341
|
+
formArray.insert(currentIndex, draggedItem);
|
|
2342
|
+
formArray.markAsDirty();
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2329
2345
|
class RolloutRulesComponent {
|
|
2330
2346
|
constructor() {
|
|
2331
2347
|
this.dialog = inject(MatDialog);
|
|
@@ -2375,9 +2391,7 @@ class RolloutRulesComponent {
|
|
|
2375
2391
|
this.formGroup().controls.rolloutRules.markAsDirty();
|
|
2376
2392
|
}
|
|
2377
2393
|
onDrop(event) {
|
|
2378
|
-
|
|
2379
|
-
moveItemInArray(rolloutRules, event.previousIndex, event.currentIndex);
|
|
2380
|
-
this.formGroup().markAsDirty();
|
|
2394
|
+
moveItemInFormArray(this.formGroup().controls.rolloutRules, event.previousIndex, event.currentIndex);
|
|
2381
2395
|
}
|
|
2382
2396
|
attributeChanged(eventTarget, comparisonAttribute) {
|
|
2383
2397
|
const event = eventTarget.value;
|
|
@@ -2697,6 +2711,14 @@ class SettingListComponent {
|
|
|
2697
2711
|
this.deleteSettingRequested = output();
|
|
2698
2712
|
this.formValuesChanged = output();
|
|
2699
2713
|
this.saveFailed = output();
|
|
2714
|
+
// Track actual value changes, not just form interaction. If a value is modified then reverted to its original state,
|
|
2715
|
+
// it's not considered dirty. This ensures save/publish buttons activate only for genuine changes, avoiding unnecessary backend operations.
|
|
2716
|
+
this.initialValueJson = signal("", /* @ts-ignore */
|
|
2717
|
+
...(ngDevMode ? [{ debugName: "initialValueJson" }] : /* istanbul ignore next */ []));
|
|
2718
|
+
this.settingValueFormGroupChanged = signal("", /* @ts-ignore */
|
|
2719
|
+
...(ngDevMode ? [{ debugName: "settingValueFormGroupChanged" }] : /* istanbul ignore next */ []));
|
|
2720
|
+
this.formGroupDirty = computed(() => this.settingValueFormGroupChanged() !== this.initialValueJson(), /* @ts-ignore */
|
|
2721
|
+
...(ngDevMode ? [{ debugName: "formGroupDirty" }] : /* istanbul ignore next */ []));
|
|
2700
2722
|
this.loading = true;
|
|
2701
2723
|
this.submitting = false;
|
|
2702
2724
|
this.showFeedBackMessage = false;
|
|
@@ -2744,8 +2766,12 @@ class SettingListComponent {
|
|
|
2744
2766
|
}
|
|
2745
2767
|
createFormGroup() {
|
|
2746
2768
|
this.formGroup = this.formHelper.createSettingValuesFormGroup(this.settingValues);
|
|
2769
|
+
const initialValueRaw = JSON.stringify(this.formGroup.getRawValue());
|
|
2770
|
+
this.initialValueJson.set(initialValueRaw);
|
|
2771
|
+
this.settingValueFormGroupChanged.set(initialValueRaw);
|
|
2747
2772
|
this.formValuesChangedSubscription = this.formGroup.valueChanges.subscribe(() => {
|
|
2748
2773
|
this.formValuesChanged.emit();
|
|
2774
|
+
this.settingValueFormGroupChanged.set(JSON.stringify(this.formGroup.getRawValue()));
|
|
2749
2775
|
});
|
|
2750
2776
|
}
|
|
2751
2777
|
settingValuesFormArray() {
|
|
@@ -2877,11 +2903,11 @@ class SettingListComponent {
|
|
|
2877
2903
|
});
|
|
2878
2904
|
}
|
|
2879
2905
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SettingListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2880
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SettingListComponent, isStandalone: true, selector: "app-setting-list", inputs: { productId: { classPropertyName: "productId", publicName: "productId", isSignal: true, isRequired: true, transformFunction: null }, environmentId: { classPropertyName: "environmentId", publicName: "environmentId", isSignal: true, isRequired: true, transformFunction: null }, configId: { classPropertyName: "configId", publicName: "configId", isSignal: true, isRequired: true, transformFunction: null }, dashboardBasePath: { classPropertyName: "dashboardBasePath", publicName: "dashboardBasePath", isSignal: true, isRequired: false, transformFunction: null }, canDeleteSetting: { classPropertyName: "canDeleteSetting", publicName: "canDeleteSetting", isSignal: true, isRequired: false, transformFunction: null }, onSave: { classPropertyName: "onSave", publicName: "onSave", isSignal: true, isRequired: true, transformFunction: null }, loadSettingValues: { classPropertyName: "loadSettingValues", publicName: "loadSettingValues", isSignal: true, isRequired: true, transformFunction: null }, loadSegments: { classPropertyName: "loadSegments", publicName: "loadSegments", isSignal: true, isRequired: true, transformFunction: null }, deleteSettingText: { classPropertyName: "deleteSettingText", publicName: "deleteSettingText", isSignal: true, isRequired: false, transformFunction: null }, showEnvironmentName: { classPropertyName: "showEnvironmentName", publicName: "showEnvironmentName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadSucceeded: "loadSucceeded", deleteSettingRequested: "deleteSettingRequested", formValuesChanged: "formValuesChanged", saveFailed: "saveFailed" }, usesOnChanges: true, ngImport: i0, template: "@if (!loading) {\n <form [formGroup]=\"formGroup\" (ngSubmit)=\"submit()\">\n @for (settingValueFromGroup of formGroup!.controls.settingValues.controls; track $index; let i = $index) {\n <div class=\"wrapper\">\n <div class=\"size-obs\" [style.display]=\"'block'\">\n <div>\n <app-setting\n [formGroup]=\"settingValueFromGroup\"\n [readOnly]=\"readOnly\"\n [canDeleteSetting]=\"canDeleteSetting()\"\n [deleteSettingText]=\"deleteSettingText()\"\n [showEnvironmentName]=\"showEnvironmentName()\"\n [model]=\"settingValues!.settingValues[i]\"\n [segments]=\"segments\"\n [productId]=\"productId()\"\n [configId]=\"configId()\"\n [environmentId]=\"environmentId()\"\n [dashboardBasePath]=\"dashboardBasePath()\"\n [featureFlagLimitations]=\"settingValues!.featureFlagLimitations\"\n (subscriptionLimitReached)=\"onSubscriptionLimitReached()\"\n (deleteSettingRequested)=\"onDeleteSettingRequested($event)\" />\n </div>\n </div>\n </div>\n }\n @if (!readOnly &&
|
|
2906
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SettingListComponent, isStandalone: true, selector: "app-setting-list", inputs: { productId: { classPropertyName: "productId", publicName: "productId", isSignal: true, isRequired: true, transformFunction: null }, environmentId: { classPropertyName: "environmentId", publicName: "environmentId", isSignal: true, isRequired: true, transformFunction: null }, configId: { classPropertyName: "configId", publicName: "configId", isSignal: true, isRequired: true, transformFunction: null }, dashboardBasePath: { classPropertyName: "dashboardBasePath", publicName: "dashboardBasePath", isSignal: true, isRequired: false, transformFunction: null }, canDeleteSetting: { classPropertyName: "canDeleteSetting", publicName: "canDeleteSetting", isSignal: true, isRequired: false, transformFunction: null }, onSave: { classPropertyName: "onSave", publicName: "onSave", isSignal: true, isRequired: true, transformFunction: null }, loadSettingValues: { classPropertyName: "loadSettingValues", publicName: "loadSettingValues", isSignal: true, isRequired: true, transformFunction: null }, loadSegments: { classPropertyName: "loadSegments", publicName: "loadSegments", isSignal: true, isRequired: true, transformFunction: null }, deleteSettingText: { classPropertyName: "deleteSettingText", publicName: "deleteSettingText", isSignal: true, isRequired: false, transformFunction: null }, showEnvironmentName: { classPropertyName: "showEnvironmentName", publicName: "showEnvironmentName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadSucceeded: "loadSucceeded", deleteSettingRequested: "deleteSettingRequested", formValuesChanged: "formValuesChanged", saveFailed: "saveFailed" }, usesOnChanges: true, ngImport: i0, template: "@if (!loading) {\n <form [formGroup]=\"formGroup\" (ngSubmit)=\"submit()\">\n @for (settingValueFromGroup of formGroup!.controls.settingValues.controls; track $index; let i = $index) {\n <div class=\"wrapper\">\n <div class=\"size-obs\" [style.display]=\"'block'\">\n <div>\n <app-setting\n [formGroup]=\"settingValueFromGroup\"\n [readOnly]=\"readOnly\"\n [canDeleteSetting]=\"canDeleteSetting()\"\n [deleteSettingText]=\"deleteSettingText()\"\n [showEnvironmentName]=\"showEnvironmentName()\"\n [model]=\"settingValues!.settingValues[i]\"\n [segments]=\"segments\"\n [productId]=\"productId()\"\n [configId]=\"configId()\"\n [environmentId]=\"environmentId()\"\n [dashboardBasePath]=\"dashboardBasePath()\"\n [featureFlagLimitations]=\"settingValues!.featureFlagLimitations\"\n (subscriptionLimitReached)=\"onSubscriptionLimitReached()\"\n (deleteSettingRequested)=\"onDeleteSettingRequested($event)\" />\n </div>\n </div>\n </div>\n }\n @if (!readOnly && formGroupDirty()) {\n <div class=\"save\">\n <button class=\"save-button\" mat-flat-button color=\"warn\" type=\"submit\" [disabled]=\"submitting\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish changes</span>\n </button>\n <button type=\"button\" class=\"revert-button\" mat-stroked-button [disabled]=\"submitting\" (click)=\"revert()\">\n <span>Revert</span>\n </button>\n @if (formGroup.touched && formGroup.invalid && showFeedBackMessage) {\n <div class=\"feedback\">\n @if (formGroup.errors && formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>\n Something went wrong and we could not save your settings. You can try again or\n <a href=\"https://configcat.com/support\" target=\"_blank\" rel=\"noopener noreferrer\">contact us.</a>\n </span>\n </div>\n }\n @if (!formGroup.errors || !formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>Oh no! A feature flag has invalid or missing data.</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </form>\n}\n", styles: [".wrapper{margin:1rem 0;padding:0 .5rem}.size-obs .hidden{display:none}.size-obs .visible{display:block}.show-hide a{cursor:pointer;font-size:.8rem}.save{position:sticky;bottom:0;min-height:38px;padding:.5rem 0 .5rem 12px;background-color:var(--page-background-color);display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;z-index:999}.save .save-button{animation:shadow-pulse 1s 3}.save .save-button:disabled{animation:none}.save>*{margin:.3em}.save .revert{margin:0 16px;font-size:14px}.save .yes{margin:0 5px}.error{color:var(--flag-validation-error);padding:5px;font-size:.9em}.error a{color:var(--flag-validation-error);text-decoration:underline}@media(max-width:700px){.save{padding:.5rem 0}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: RxReactiveFormsModule }, { kind: "directive", type: i2.RxwebFormDirective, selector: "[formGroup],[rxwebForm]", inputs: ["formGroup", "rxwebForm"] }, { kind: "component", type: SettingComponent, selector: "app-setting", inputs: ["dashboardBasePath", "productId", "environmentId", "configId", "featureFlagLimitations", "formGroup", "readOnly", "canDeleteSetting", "deleteSettingText", "showEnvironmentName", "segments", "model"], outputs: ["deleteSettingRequested", "subscriptionLimitReached"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
|
|
2881
2907
|
}
|
|
2882
2908
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SettingListComponent, decorators: [{
|
|
2883
2909
|
type: Component,
|
|
2884
|
-
args: [{ selector: "app-setting-list", changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, ReactiveFormsModule, RxReactiveFormsModule, SettingComponent, MatButton, MatIcon], template: "@if (!loading) {\n <form [formGroup]=\"formGroup\" (ngSubmit)=\"submit()\">\n @for (settingValueFromGroup of formGroup!.controls.settingValues.controls; track $index; let i = $index) {\n <div class=\"wrapper\">\n <div class=\"size-obs\" [style.display]=\"'block'\">\n <div>\n <app-setting\n [formGroup]=\"settingValueFromGroup\"\n [readOnly]=\"readOnly\"\n [canDeleteSetting]=\"canDeleteSetting()\"\n [deleteSettingText]=\"deleteSettingText()\"\n [showEnvironmentName]=\"showEnvironmentName()\"\n [model]=\"settingValues!.settingValues[i]\"\n [segments]=\"segments\"\n [productId]=\"productId()\"\n [configId]=\"configId()\"\n [environmentId]=\"environmentId()\"\n [dashboardBasePath]=\"dashboardBasePath()\"\n [featureFlagLimitations]=\"settingValues!.featureFlagLimitations\"\n (subscriptionLimitReached)=\"onSubscriptionLimitReached()\"\n (deleteSettingRequested)=\"onDeleteSettingRequested($event)\" />\n </div>\n </div>\n </div>\n }\n @if (!readOnly &&
|
|
2910
|
+
args: [{ selector: "app-setting-list", changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, ReactiveFormsModule, RxReactiveFormsModule, SettingComponent, MatButton, MatIcon], template: "@if (!loading) {\n <form [formGroup]=\"formGroup\" (ngSubmit)=\"submit()\">\n @for (settingValueFromGroup of formGroup!.controls.settingValues.controls; track $index; let i = $index) {\n <div class=\"wrapper\">\n <div class=\"size-obs\" [style.display]=\"'block'\">\n <div>\n <app-setting\n [formGroup]=\"settingValueFromGroup\"\n [readOnly]=\"readOnly\"\n [canDeleteSetting]=\"canDeleteSetting()\"\n [deleteSettingText]=\"deleteSettingText()\"\n [showEnvironmentName]=\"showEnvironmentName()\"\n [model]=\"settingValues!.settingValues[i]\"\n [segments]=\"segments\"\n [productId]=\"productId()\"\n [configId]=\"configId()\"\n [environmentId]=\"environmentId()\"\n [dashboardBasePath]=\"dashboardBasePath()\"\n [featureFlagLimitations]=\"settingValues!.featureFlagLimitations\"\n (subscriptionLimitReached)=\"onSubscriptionLimitReached()\"\n (deleteSettingRequested)=\"onDeleteSettingRequested($event)\" />\n </div>\n </div>\n </div>\n }\n @if (!readOnly && formGroupDirty()) {\n <div class=\"save\">\n <button class=\"save-button\" mat-flat-button color=\"warn\" type=\"submit\" [disabled]=\"submitting\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish changes</span>\n </button>\n <button type=\"button\" class=\"revert-button\" mat-stroked-button [disabled]=\"submitting\" (click)=\"revert()\">\n <span>Revert</span>\n </button>\n @if (formGroup.touched && formGroup.invalid && showFeedBackMessage) {\n <div class=\"feedback\">\n @if (formGroup.errors && formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>\n Something went wrong and we could not save your settings. You can try again or\n <a href=\"https://configcat.com/support\" target=\"_blank\" rel=\"noopener noreferrer\">contact us.</a>\n </span>\n </div>\n }\n @if (!formGroup.errors || !formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>Oh no! A feature flag has invalid or missing data.</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </form>\n}\n", styles: [".wrapper{margin:1rem 0;padding:0 .5rem}.size-obs .hidden{display:none}.size-obs .visible{display:block}.show-hide a{cursor:pointer;font-size:.8rem}.save{position:sticky;bottom:0;min-height:38px;padding:.5rem 0 .5rem 12px;background-color:var(--page-background-color);display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;z-index:999}.save .save-button{animation:shadow-pulse 1s 3}.save .save-button:disabled{animation:none}.save>*{margin:.3em}.save .revert{margin:0 16px;font-size:14px}.save .yes{margin:0 5px}.error{color:var(--flag-validation-error);padding:5px;font-size:.9em}.error a{color:var(--flag-validation-error);text-decoration:underline}@media(max-width:700px){.save{padding:.5rem 0}}\n"] }]
|
|
2885
2911
|
}], propDecorators: { productId: [{ type: i0.Input, args: [{ isSignal: true, alias: "productId", required: true }] }], environmentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "environmentId", required: true }] }], configId: [{ type: i0.Input, args: [{ isSignal: true, alias: "configId", required: true }] }], dashboardBasePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardBasePath", required: false }] }], canDeleteSetting: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDeleteSetting", required: false }] }], onSave: [{ type: i0.Input, args: [{ isSignal: true, alias: "onSave", required: true }] }], loadSettingValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadSettingValues", required: true }] }], loadSegments: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadSegments", required: true }] }], deleteSettingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "deleteSettingText", required: false }] }], showEnvironmentName: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEnvironmentName", required: false }] }], loadSucceeded: [{ type: i0.Output, args: ["loadSucceeded"] }], deleteSettingRequested: [{ type: i0.Output, args: ["deleteSettingRequested"] }], formValuesChanged: [{ type: i0.Output, args: ["formValuesChanged"] }], saveFailed: [{ type: i0.Output, args: ["saveFailed"] }] } });
|
|
2886
2912
|
|
|
2887
2913
|
class SettingItemComponent extends BaseComponent {
|
|
@@ -3751,6 +3777,22 @@ class CreateChangeRequestDialogComponent {
|
|
|
3751
3777
|
error: (error) => {
|
|
3752
3778
|
this.saving.set(false);
|
|
3753
3779
|
FormHelper.handleErrors(this.formGroup, error);
|
|
3780
|
+
// Concurrent update and concurrent delete common handling
|
|
3781
|
+
if (error instanceof HttpErrorResponse && (error.status === 409 || error.status === 404)) {
|
|
3782
|
+
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
|
3783
|
+
width: "450px",
|
|
3784
|
+
data: {
|
|
3785
|
+
title: "There was a conflict while trying to create your change request",
|
|
3786
|
+
body: "Seems like some of the feature flags or settings were deleted or updated by someone else while you were editing them. Reload the config to see its latest state, apply your changes and try the save again.",
|
|
3787
|
+
yesButtonLabel: "Reload",
|
|
3788
|
+
hideCancelButton: true,
|
|
3789
|
+
},
|
|
3790
|
+
});
|
|
3791
|
+
dialogRef.afterClosed().subscribe(() => {
|
|
3792
|
+
this.dialogRef.close({ shouldReloadSettingValues: true });
|
|
3793
|
+
});
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3754
3796
|
if (error instanceof HttpErrorResponse
|
|
3755
3797
|
&& error.status === 400
|
|
3756
3798
|
&& FormHelper.getHttpErrorResponseMessage(error, "SegmentCondition") === "Segment not found.") {
|
|
@@ -3841,7 +3883,7 @@ class CreateChangeRequestDialogComponent {
|
|
|
3841
3883
|
};
|
|
3842
3884
|
}
|
|
3843
3885
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: CreateChangeRequestDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3844
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: CreateChangeRequestDialogComponent, isStandalone: true, selector: "app-create-change-request-dialog", viewQueries: [{ propertyName: "reasonInput", first: true, predicate: ["reason"], descendants: true, isSignal: true }], ngImport: i0, template: "<div mat-dialog-title cdkDrag cdkDragRootElement=\".cdk-overlay-pane\" cdkDragHandle class=\"draggable title\">\n @if (data.isSchedule) {\n <div>Schedule change request</div>\n } @else {\n <div>Create change request</div>\n }\n</div>\n\n<div mat-dialog-content>\n <form class=\"form\" [formGroup]=\"formGroup\">\n <mat-form-field appearance=\"outline\" class=\"field-name\">\n <mat-label>Title</mat-label>\n <mat-hint>A short name your team will see on the ConfigCat Dashboard.</mat-hint>\n <input matInput formControlName=\"title\" placeholder=\"e.g. Enable dark mode for beta users\" maxlength=\"255\" />\n @if (formGroup.controls.title.invalid) {\n <mat-error>\n A short name your team will see on the ConfigCat Dashboard.\n {{ FormHelper.getErrorMessage(formGroup.controls.title) }}\n </mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"field-reason\" subscriptSizing=\"dynamic\">\n <mat-label>\n Notes\n @if (!data.reasonRequired) {\n <span class=\"optional\">(optional)</span>\n }\n </mat-label>\n <mat-hint>\n This will appear in the Audit Log (in the Notes section when you expand the corresponding entry) upon applying\n the change request.\n </mat-hint>\n <textarea\n #reason\n matInput\n formControlName=\"reason\"\n rows=\"2\"\n maxlength=\"1000\"\n placeholder=\"Why is this change needed?\"></textarea>\n @if (formGroup.controls.reason.invalid) {\n <mat-error>\n Notes are mandatory for the target environment. {{ FormHelper.getErrorMessage(formGroup.controls.reason) }}\n </mat-error>\n }\n </mat-form-field>\n\n @if (data.isSchedule) {\n <div class=\"section\">\n <h3>When should the proposed changes be applied?</h3>\n <div class=\"scheduled\">\n <div class=\"date-selector\">\n <app-date-time-picker [formControl]=\"formGroup.controls.applyAt\" />\n @if (formGroup.controls.applyAt.invalid) {\n @let scheduleErrorMessage = FormHelper.getErrorMessage(formGroup.controls.applyAt);\n @if (scheduleErrorMessage) {\n <mat-error class=\"error\">\n {{ scheduleErrorMessage }}\n </mat-error>\n }\n }\n </div>\n @if (data.environmentApproveRequired && data.canBypassApproval) {\n <div>\n <mat-checkbox\n matInput\n color=\"primary\"\n class=\"bypass-approval-checkbox\"\n formControlName=\"bypassApproval\"\n matTooltip=\"Enabling this option will bypass the approval requirement of the target environment and \n lets the change request be applied on schedule even if it's not approved.\"\n matTooltipPosition=\"below\">\n Bypass approval requirement for scheduled changes\n </mat-checkbox>\n </div>\n }\n <app-box type=\"info\">\n The proposed changes will be applied at the scheduled time.\n <br />\n If conflicting changes occur before the scheduled time, they will prevent applying the scheduled changes.\n <br />\n You can always cancel the scheduled operation before it is executed and apply the changes manually.\n </app-box>\n </div>\n </div>\n }\n </form>\n</div>\n\n<div mat-dialog-actions>\n <button class=\"submit-button\" mat-flat-button type=\"submit\" color=\"success\" [disabled]=\"saving()\" (click)=\"submit()\">\n @if (data.isSchedule) {\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n } @else {\n <mat-icon>merge_type</mat-icon>\n }\n <span>{{ submitLabel }}</span>\n </button>\n <button type=\"button\" mat-stroked-button [mat-dialog-close]>Cancel</button>\n</div>\n", styles: [".title{display:flex;justify-content:space-between;align-items:center;gap:16px}.title .sub-title{font-size:13px;font-weight:400;color:var(--panel-description-color)}.form{display:flex;flex-direction:column;gap:10px;margin-top:10px;margin-bottom:10px}.form .field-name,.form .field-reason,.form .field-apply-at{width:100%}.form .optional{font-size:12px;font-weight:400;color:var(--panel-description-color)}h3{font-size:14px;font-weight:500;color:var(--text-color)}.changes-title{display:flex;justify-content:space-between;align-items:center}.error{font-size:10px}.section{margin-top:.8rem}.section .scheduled{display:flex;flex-direction:column;flex-wrap:wrap;gap:12px}.section .scheduled .bypass-approval-checkbox{--mat-checkbox-label-text-size: 12px;--mat-checkbox-label-text-color: var(--panel-description-color);margin-left:-2px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: BoxComponent, selector: "app-box", inputs: ["type", "title"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: DateTimePickerComponent, selector: "app-date-time-picker", inputs: ["dateTooltip", "timeTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3886
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: CreateChangeRequestDialogComponent, isStandalone: true, selector: "app-create-change-request-dialog", viewQueries: [{ propertyName: "reasonInput", first: true, predicate: ["reason"], descendants: true, isSignal: true }], ngImport: i0, template: "<div mat-dialog-title cdkDrag cdkDragRootElement=\".cdk-overlay-pane\" cdkDragHandle class=\"draggable title\">\n @if (data.isSchedule) {\n <div>Schedule change request</div>\n } @else {\n <div>Create change request</div>\n }\n</div>\n\n<div mat-dialog-content>\n <form class=\"form\" [formGroup]=\"formGroup\">\n <mat-form-field appearance=\"outline\" class=\"field-name\">\n <mat-label>Title</mat-label>\n <mat-hint>A short name your team will see on the ConfigCat Dashboard.</mat-hint>\n <input matInput formControlName=\"title\" placeholder=\"e.g. Enable dark mode for beta users\" maxlength=\"255\" />\n @if (formGroup.controls.title.invalid) {\n <mat-error>\n A short name your team will see on the ConfigCat Dashboard.\n {{ FormHelper.getErrorMessage(formGroup.controls.title) }}\n </mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"field-reason\" subscriptSizing=\"dynamic\">\n <mat-label>\n Notes\n @if (!data.reasonRequired) {\n <span class=\"optional\">(optional)</span>\n }\n </mat-label>\n <mat-hint>\n This will appear in the Audit Log (in the Notes section when you expand the corresponding entry) upon applying\n the change request.\n </mat-hint>\n <textarea\n #reason\n matInput\n formControlName=\"reason\"\n rows=\"2\"\n maxlength=\"1000\"\n placeholder=\"Why is this change needed?\"></textarea>\n @if (formGroup.controls.reason.invalid) {\n <mat-error>\n Notes are mandatory for the target environment. {{ FormHelper.getErrorMessage(formGroup.controls.reason) }}\n </mat-error>\n }\n </mat-form-field>\n\n @if (data.isSchedule) {\n <div class=\"section\">\n <h3>When should the proposed changes be applied?</h3>\n <div class=\"scheduled\">\n <div class=\"date-selector\">\n <app-date-time-picker [formControl]=\"formGroup.controls.applyAt\" />\n @if (formGroup.controls.applyAt.invalid) {\n @let scheduleErrorMessage = FormHelper.getErrorMessage(formGroup.controls.applyAt);\n @if (scheduleErrorMessage) {\n <mat-error class=\"error\">\n {{ scheduleErrorMessage }}\n </mat-error>\n }\n }\n </div>\n @if (data.environmentApproveRequired && data.canBypassApproval) {\n <div>\n <mat-checkbox\n matInput\n color=\"primary\"\n class=\"bypass-approval-checkbox\"\n formControlName=\"bypassApproval\"\n matTooltip=\"Enabling this option will bypass the approval requirement of the target environment and \n lets the change request be applied on schedule even if it's not approved.\"\n matTooltipPosition=\"below\">\n Bypass approval requirement for scheduled changes\n </mat-checkbox>\n </div>\n }\n <app-box type=\"info\">\n The proposed changes will be applied at the scheduled time.\n <br />\n If conflicting changes occur before the scheduled time, they will prevent applying the scheduled changes.\n <br />\n You can always cancel the scheduled operation before it is executed and apply the changes manually.\n </app-box>\n </div>\n </div>\n }\n @if (formGroup.errors && formGroup.errors[\"serverSide\"]) {\n <mat-error>\n <span>{{ formGroup.errors[\"serverSide\"] }}</span>\n </mat-error>\n }\n </form>\n</div>\n\n<div mat-dialog-actions>\n <button class=\"submit-button\" mat-flat-button type=\"submit\" color=\"success\" [disabled]=\"saving()\" (click)=\"submit()\">\n @if (data.isSchedule) {\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n } @else {\n <mat-icon>merge_type</mat-icon>\n }\n <span>{{ submitLabel }}</span>\n </button>\n <button type=\"button\" mat-stroked-button [mat-dialog-close]>Cancel</button>\n</div>\n", styles: [".title{display:flex;justify-content:space-between;align-items:center;gap:16px}.title .sub-title{font-size:13px;font-weight:400;color:var(--panel-description-color)}.form{display:flex;flex-direction:column;gap:10px;margin-top:10px;margin-bottom:10px}.form .field-name,.form .field-reason,.form .field-apply-at{width:100%}.form .optional{font-size:12px;font-weight:400;color:var(--panel-description-color)}h3{font-size:14px;font-weight:500;color:var(--text-color)}.changes-title{display:flex;justify-content:space-between;align-items:center}.error{font-size:10px;color:var(--flag-validation-error)}.section{margin-top:.8rem}.section .scheduled{display:flex;flex-direction:column;flex-wrap:wrap;gap:12px}.section .scheduled .bypass-approval-checkbox{--mat-checkbox-label-text-size: 12px;--mat-checkbox-label-text-color: var(--panel-description-color);margin-left:-2px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: BoxComponent, selector: "app-box", inputs: ["type", "title"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: DateTimePickerComponent, selector: "app-date-time-picker", inputs: ["dateTooltip", "timeTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3845
3887
|
}
|
|
3846
3888
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: CreateChangeRequestDialogComponent, decorators: [{
|
|
3847
3889
|
type: Component,
|
|
@@ -3865,7 +3907,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
|
|
|
3865
3907
|
CdkDrag,
|
|
3866
3908
|
CdkDragHandle,
|
|
3867
3909
|
DateTimePickerComponent,
|
|
3868
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div mat-dialog-title cdkDrag cdkDragRootElement=\".cdk-overlay-pane\" cdkDragHandle class=\"draggable title\">\n @if (data.isSchedule) {\n <div>Schedule change request</div>\n } @else {\n <div>Create change request</div>\n }\n</div>\n\n<div mat-dialog-content>\n <form class=\"form\" [formGroup]=\"formGroup\">\n <mat-form-field appearance=\"outline\" class=\"field-name\">\n <mat-label>Title</mat-label>\n <mat-hint>A short name your team will see on the ConfigCat Dashboard.</mat-hint>\n <input matInput formControlName=\"title\" placeholder=\"e.g. Enable dark mode for beta users\" maxlength=\"255\" />\n @if (formGroup.controls.title.invalid) {\n <mat-error>\n A short name your team will see on the ConfigCat Dashboard.\n {{ FormHelper.getErrorMessage(formGroup.controls.title) }}\n </mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"field-reason\" subscriptSizing=\"dynamic\">\n <mat-label>\n Notes\n @if (!data.reasonRequired) {\n <span class=\"optional\">(optional)</span>\n }\n </mat-label>\n <mat-hint>\n This will appear in the Audit Log (in the Notes section when you expand the corresponding entry) upon applying\n the change request.\n </mat-hint>\n <textarea\n #reason\n matInput\n formControlName=\"reason\"\n rows=\"2\"\n maxlength=\"1000\"\n placeholder=\"Why is this change needed?\"></textarea>\n @if (formGroup.controls.reason.invalid) {\n <mat-error>\n Notes are mandatory for the target environment. {{ FormHelper.getErrorMessage(formGroup.controls.reason) }}\n </mat-error>\n }\n </mat-form-field>\n\n @if (data.isSchedule) {\n <div class=\"section\">\n <h3>When should the proposed changes be applied?</h3>\n <div class=\"scheduled\">\n <div class=\"date-selector\">\n <app-date-time-picker [formControl]=\"formGroup.controls.applyAt\" />\n @if (formGroup.controls.applyAt.invalid) {\n @let scheduleErrorMessage = FormHelper.getErrorMessage(formGroup.controls.applyAt);\n @if (scheduleErrorMessage) {\n <mat-error class=\"error\">\n {{ scheduleErrorMessage }}\n </mat-error>\n }\n }\n </div>\n @if (data.environmentApproveRequired && data.canBypassApproval) {\n <div>\n <mat-checkbox\n matInput\n color=\"primary\"\n class=\"bypass-approval-checkbox\"\n formControlName=\"bypassApproval\"\n matTooltip=\"Enabling this option will bypass the approval requirement of the target environment and \n lets the change request be applied on schedule even if it's not approved.\"\n matTooltipPosition=\"below\">\n Bypass approval requirement for scheduled changes\n </mat-checkbox>\n </div>\n }\n <app-box type=\"info\">\n The proposed changes will be applied at the scheduled time.\n <br />\n If conflicting changes occur before the scheduled time, they will prevent applying the scheduled changes.\n <br />\n You can always cancel the scheduled operation before it is executed and apply the changes manually.\n </app-box>\n </div>\n </div>\n }\n </form>\n</div>\n\n<div mat-dialog-actions>\n <button class=\"submit-button\" mat-flat-button type=\"submit\" color=\"success\" [disabled]=\"saving()\" (click)=\"submit()\">\n @if (data.isSchedule) {\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n } @else {\n <mat-icon>merge_type</mat-icon>\n }\n <span>{{ submitLabel }}</span>\n </button>\n <button type=\"button\" mat-stroked-button [mat-dialog-close]>Cancel</button>\n</div>\n", styles: [".title{display:flex;justify-content:space-between;align-items:center;gap:16px}.title .sub-title{font-size:13px;font-weight:400;color:var(--panel-description-color)}.form{display:flex;flex-direction:column;gap:10px;margin-top:10px;margin-bottom:10px}.form .field-name,.form .field-reason,.form .field-apply-at{width:100%}.form .optional{font-size:12px;font-weight:400;color:var(--panel-description-color)}h3{font-size:14px;font-weight:500;color:var(--text-color)}.changes-title{display:flex;justify-content:space-between;align-items:center}.error{font-size:10px}.section{margin-top:.8rem}.section .scheduled{display:flex;flex-direction:column;flex-wrap:wrap;gap:12px}.section .scheduled .bypass-approval-checkbox{--mat-checkbox-label-text-size: 12px;--mat-checkbox-label-text-color: var(--panel-description-color);margin-left:-2px}\n"] }]
|
|
3910
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div mat-dialog-title cdkDrag cdkDragRootElement=\".cdk-overlay-pane\" cdkDragHandle class=\"draggable title\">\n @if (data.isSchedule) {\n <div>Schedule change request</div>\n } @else {\n <div>Create change request</div>\n }\n</div>\n\n<div mat-dialog-content>\n <form class=\"form\" [formGroup]=\"formGroup\">\n <mat-form-field appearance=\"outline\" class=\"field-name\">\n <mat-label>Title</mat-label>\n <mat-hint>A short name your team will see on the ConfigCat Dashboard.</mat-hint>\n <input matInput formControlName=\"title\" placeholder=\"e.g. Enable dark mode for beta users\" maxlength=\"255\" />\n @if (formGroup.controls.title.invalid) {\n <mat-error>\n A short name your team will see on the ConfigCat Dashboard.\n {{ FormHelper.getErrorMessage(formGroup.controls.title) }}\n </mat-error>\n }\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"field-reason\" subscriptSizing=\"dynamic\">\n <mat-label>\n Notes\n @if (!data.reasonRequired) {\n <span class=\"optional\">(optional)</span>\n }\n </mat-label>\n <mat-hint>\n This will appear in the Audit Log (in the Notes section when you expand the corresponding entry) upon applying\n the change request.\n </mat-hint>\n <textarea\n #reason\n matInput\n formControlName=\"reason\"\n rows=\"2\"\n maxlength=\"1000\"\n placeholder=\"Why is this change needed?\"></textarea>\n @if (formGroup.controls.reason.invalid) {\n <mat-error>\n Notes are mandatory for the target environment. {{ FormHelper.getErrorMessage(formGroup.controls.reason) }}\n </mat-error>\n }\n </mat-form-field>\n\n @if (data.isSchedule) {\n <div class=\"section\">\n <h3>When should the proposed changes be applied?</h3>\n <div class=\"scheduled\">\n <div class=\"date-selector\">\n <app-date-time-picker [formControl]=\"formGroup.controls.applyAt\" />\n @if (formGroup.controls.applyAt.invalid) {\n @let scheduleErrorMessage = FormHelper.getErrorMessage(formGroup.controls.applyAt);\n @if (scheduleErrorMessage) {\n <mat-error class=\"error\">\n {{ scheduleErrorMessage }}\n </mat-error>\n }\n }\n </div>\n @if (data.environmentApproveRequired && data.canBypassApproval) {\n <div>\n <mat-checkbox\n matInput\n color=\"primary\"\n class=\"bypass-approval-checkbox\"\n formControlName=\"bypassApproval\"\n matTooltip=\"Enabling this option will bypass the approval requirement of the target environment and \n lets the change request be applied on schedule even if it's not approved.\"\n matTooltipPosition=\"below\">\n Bypass approval requirement for scheduled changes\n </mat-checkbox>\n </div>\n }\n <app-box type=\"info\">\n The proposed changes will be applied at the scheduled time.\n <br />\n If conflicting changes occur before the scheduled time, they will prevent applying the scheduled changes.\n <br />\n You can always cancel the scheduled operation before it is executed and apply the changes manually.\n </app-box>\n </div>\n </div>\n }\n @if (formGroup.errors && formGroup.errors[\"serverSide\"]) {\n <mat-error>\n <span>{{ formGroup.errors[\"serverSide\"] }}</span>\n </mat-error>\n }\n </form>\n</div>\n\n<div mat-dialog-actions>\n <button class=\"submit-button\" mat-flat-button type=\"submit\" color=\"success\" [disabled]=\"saving()\" (click)=\"submit()\">\n @if (data.isSchedule) {\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n } @else {\n <mat-icon>merge_type</mat-icon>\n }\n <span>{{ submitLabel }}</span>\n </button>\n <button type=\"button\" mat-stroked-button [mat-dialog-close]>Cancel</button>\n</div>\n", styles: [".title{display:flex;justify-content:space-between;align-items:center;gap:16px}.title .sub-title{font-size:13px;font-weight:400;color:var(--panel-description-color)}.form{display:flex;flex-direction:column;gap:10px;margin-top:10px;margin-bottom:10px}.form .field-name,.form .field-reason,.form .field-apply-at{width:100%}.form .optional{font-size:12px;font-weight:400;color:var(--panel-description-color)}h3{font-size:14px;font-weight:500;color:var(--text-color)}.changes-title{display:flex;justify-content:space-between;align-items:center}.error{font-size:10px;color:var(--flag-validation-error)}.section{margin-top:.8rem}.section .scheduled{display:flex;flex-direction:column;flex-wrap:wrap;gap:12px}.section .scheduled .bypass-approval-checkbox{--mat-checkbox-label-text-size: 12px;--mat-checkbox-label-text-color: var(--panel-description-color);margin-left:-2px}\n"] }]
|
|
3869
3911
|
}], propDecorators: { reasonInput: [{ type: i0.ViewChild, args: ["reason", { isSignal: true }] }] } });
|
|
3870
3912
|
|
|
3871
3913
|
class CopyableValueComponent {
|
|
@@ -4538,8 +4580,7 @@ class VariationsDialogComponent {
|
|
|
4538
4580
|
});
|
|
4539
4581
|
}
|
|
4540
4582
|
dropTable(event) {
|
|
4541
|
-
|
|
4542
|
-
this.formGroup.controls.predefinedVariations.markAsDirty();
|
|
4583
|
+
moveItemInFormArray(this.formGroup.controls.predefinedVariations, event.previousIndex, event.currentIndex);
|
|
4543
4584
|
this.setDataSource();
|
|
4544
4585
|
}
|
|
4545
4586
|
getColorIndex(row, index) {
|
|
@@ -6568,9 +6609,7 @@ class EvaluationFormulaComponent {
|
|
|
6568
6609
|
return true;
|
|
6569
6610
|
}
|
|
6570
6611
|
onDrop(event) {
|
|
6571
|
-
|
|
6572
|
-
moveItemInArray(targetingRules, event.previousIndex, event.currentIndex);
|
|
6573
|
-
this.formGroup().markAsDirty();
|
|
6612
|
+
moveItemInFormArray(this.formGroup().controls.targetingRules, event.previousIndex, event.currentIndex);
|
|
6574
6613
|
this.recalcAddButtons.emit();
|
|
6575
6614
|
}
|
|
6576
6615
|
isDragDisabled(targetingRule) {
|
|
@@ -6985,6 +7024,14 @@ class FeatureFlagItemComponent extends BaseComponent {
|
|
|
6985
7024
|
this.showFeedBackMessage = false;
|
|
6986
7025
|
this.hasSettingValues = false;
|
|
6987
7026
|
this.hasOtherEnvironment = false;
|
|
7027
|
+
// Track actual value changes, not just form interaction. If a value is modified then reverted to its original state,
|
|
7028
|
+
// it's not considered dirty. This ensures save/publish buttons activate only for genuine changes, avoiding unnecessary backend operations.
|
|
7029
|
+
this.initialValueJson = signal("", /* @ts-ignore */
|
|
7030
|
+
...(ngDevMode ? [{ debugName: "initialValueJson" }] : /* istanbul ignore next */ []));
|
|
7031
|
+
this.settingValueFormGroupChanged = signal("", /* @ts-ignore */
|
|
7032
|
+
...(ngDevMode ? [{ debugName: "settingValueFormGroupChanged" }] : /* istanbul ignore next */ []));
|
|
7033
|
+
this.formGroupDirty = computed(() => this.settingValueFormGroupChanged() !== this.initialValueJson(), /* @ts-ignore */
|
|
7034
|
+
...(ngDevMode ? [{ debugName: "formGroupDirty" }] : /* istanbul ignore next */ []));
|
|
6988
7035
|
this.comparisonAttributes = computed(() => {
|
|
6989
7036
|
const settingFormula = this.settingFormula();
|
|
6990
7037
|
if (this.loading || !settingFormula)
|
|
@@ -7054,8 +7101,12 @@ class FeatureFlagItemComponent extends BaseComponent {
|
|
|
7054
7101
|
};
|
|
7055
7102
|
this.unsubscribeFormValueChangesSubscription();
|
|
7056
7103
|
this.createFormGroupFromState(model);
|
|
7104
|
+
const initialValueRaw = JSON.stringify(this.formGroup.getRawValue());
|
|
7105
|
+
this.initialValueJson.set(initialValueRaw);
|
|
7106
|
+
this.settingValueFormGroupChanged.set(initialValueRaw);
|
|
7057
7107
|
this.formValueChangesSubscription = this.formGroup.valueChanges.subscribe(() => {
|
|
7058
7108
|
this.formValuesChanged.emit();
|
|
7109
|
+
this.settingValueFormGroupChanged.set(JSON.stringify(this.formGroup.getRawValue()));
|
|
7059
7110
|
});
|
|
7060
7111
|
this.recalcAddButtons();
|
|
7061
7112
|
this.recalcPrerequisites();
|
|
@@ -7214,6 +7265,9 @@ class FeatureFlagItemComponent extends BaseComponent {
|
|
|
7214
7265
|
this.messagingService.showSuccessSnack("Change request created successfully.", 5000);
|
|
7215
7266
|
this.reloadSettingValues();
|
|
7216
7267
|
}
|
|
7268
|
+
else if (result?.shouldReloadSettingValues) {
|
|
7269
|
+
this.reloadSettingValues();
|
|
7270
|
+
}
|
|
7217
7271
|
});
|
|
7218
7272
|
}
|
|
7219
7273
|
updateSettingValues(updateModel, bypassApproval, reason, latestVersionId) {
|
|
@@ -7414,7 +7468,7 @@ class FeatureFlagItemComponent extends BaseComponent {
|
|
|
7414
7468
|
return dialogRef.afterClosed();
|
|
7415
7469
|
}
|
|
7416
7470
|
canDeactivate() {
|
|
7417
|
-
return !this.
|
|
7471
|
+
return !this.formGroupDirty();
|
|
7418
7472
|
}
|
|
7419
7473
|
viewVariations(event) {
|
|
7420
7474
|
if (!this.canDeactivate()) {
|
|
@@ -7524,7 +7578,7 @@ class FeatureFlagItemComponent extends BaseComponent {
|
|
|
7524
7578
|
});
|
|
7525
7579
|
}
|
|
7526
7580
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: FeatureFlagItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
7527
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: FeatureFlagItemComponent, isStandalone: true, selector: "app-feature-flag-item", inputs: { productId: { classPropertyName: "productId", publicName: "productId", isSignal: true, isRequired: true, transformFunction: null }, environmentId: { classPropertyName: "environmentId", publicName: "environmentId", isSignal: true, isRequired: true, transformFunction: null }, configId: { classPropertyName: "configId", publicName: "configId", isSignal: true, isRequired: true, transformFunction: null }, settingId: { classPropertyName: "settingId", publicName: "settingId", isSignal: true, isRequired: true, transformFunction: null }, canDeleteSetting: { classPropertyName: "canDeleteSetting", publicName: "canDeleteSetting", isSignal: true, isRequired: false, transformFunction: null }, customize: { classPropertyName: "customize", publicName: "customize", isSignal: true, isRequired: false, transformFunction: null }, showEnvironmentName: { classPropertyName: "showEnvironmentName", publicName: "showEnvironmentName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { deleteSettingRequested: "deleteSettingRequested", loadSucceeded: "loadSucceeded", loadFailed: "loadFailed", saveSucceeded: "saveSucceeded", componentError: "componentError", expandedStateChanged: "expandedStateChanged", formValuesChanged: "formValuesChanged" }, usesInheritance: true, ngImport: i0, template: "@let settingFormulaValue = settingFormula();\n<div class=\"container\">\n @if (!loading && settingFormulaValue) {\n <form id=\"feature-flag-form\" [formGroup]=\"formGroup\" (ngSubmit)=\"submit()\">\n <div [ngClass]=\"{ 'invalid-flag': formGroup.invalid }\">\n <app-feature-flag\n [formGroup]=\"formGroup\"\n [flagState]=\"flagState\"\n [segments]=\"segments\"\n [comparisonAttributes]=\"comparisonAttributes()\"\n [featureFlagLimitations]=\"settingFormulaValue.featureFlagLimitations\"\n [hasOtherEnvironment]=\"false\"\n [showEnvironmentName]=\"showEnvironmentName()\"\n [environmentName]=\"settingFormulaValue.environment.name\"\n [canDeleteSetting]=\"canDeleteSetting()\"\n [predefinedVariationsEnabled]=\"true\"\n [dashboardBasePath]=\"dashboardBasePath\"\n [customize]=\"customize()\"\n (deleteSettingRequested)=\"onDeleteSettingRequested($event)\"\n (subscriptionLimitReached)=\"subscriptionLimitReachedCalled()\"\n (addSegmentConditionRequestedWithoutSegments)=\"addSegmentConditionRequestedWithoutSegments()\"\n (prerequisiteConditionChanged)=\"recalcPrerequisites()\"\n (recalcAddButtons)=\"recalcAddButtons()\"\n (expandedChanged)=\"expandedChanged($event)\"\n (viewVariationsRequested)=\"viewVariations($event)\" />\n </div>\n @if (!settingFormulaValue.readOnly && formGroup!.dirty) {\n <div class=\"save\">\n <div class=\"save-button-group\">\n @if (!settingFormulaValue.approveRequired) {\n <button\n class=\"save-button right-side-flat\"\n mat-flat-button\n color=\"warn\"\n type=\"submit\"\n [disabled]=\"submitting\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish changes</span>\n </button>\n <button\n class=\"save-button-dropdown\"\n mat-flat-button\n color=\"warn\"\n type=\"button\"\n [disabled]=\"submitting\"\n [matMenuTriggerFor]=\"proposeMenu\">\n <mat-icon>arrow_drop_down</mat-icon>\n </button>\n <mat-menu #proposeMenu=\"matMenu\" xPosition=\"before\">\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(false)\">\n <mat-icon>call_merge</mat-icon>\n <span>Propose changes</span>\n </button>\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(true)\">\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n <span>Schedule changes</span>\n </button>\n </mat-menu>\n } @else {\n <button\n class=\"save-button right-side-flat\"\n mat-flat-button\n type=\"button\"\n color=\"success\"\n [disabled]=\"submitting\"\n (click)=\"createChangeRequest(false)\">\n <mat-icon>call_merge</mat-icon>\n <span>Propose changes</span>\n </button>\n <button\n class=\"save-button-dropdown\"\n mat-flat-button\n color=\"success\"\n type=\"button\"\n [disabled]=\"submitting\"\n [matMenuTriggerFor]=\"proposeMenu\">\n <mat-icon>arrow_drop_down</mat-icon>\n </button>\n <mat-menu #proposeMenu=\"matMenu\" xPosition=\"before\" class=\"full-width-menu\">\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(true)\">\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n <span>Schedule changes</span>\n </button>\n @if (settingFormulaValue.canBypassApproval) {\n <button type=\"button\" mat-menu-item (click)=\"submit(true)\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish without approval</span>\n </button>\n }\n </mat-menu>\n }\n </div>\n <button\n type=\"button\"\n class=\"revert-button\"\n mat-stroked-button\n [disabled]=\"submitting\"\n [track]=\"['revert click', 'feature flags and settings']\"\n (click)=\"revert()\">\n <span>Revert</span>\n </button>\n @if (formGroup.touched && formGroup.invalid && showFeedBackMessage) {\n <div class=\"feedback\">\n @if (formGroup.errors && formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>\n Something went wrong and we could not save your settings. You can try again or\n <a href=\"https://configcat.com/support\" target=\"_blank\" rel=\"noopener noreferrer\">contact us.</a>\n </span>\n </div>\n }\n @if (!formGroup.errors || !formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>Oh no! A feature flag has invalid or missing data. </span>\n </div>\n }\n </div>\n }\n </div>\n }\n </form>\n }\n</div>\n", styles: [".container{padding:0 .5rem 1rem}.container .container-content{padding-top:1rem}.container .size-obs{min-width:40px;min-height:120px}.container .size-obs .hidden{display:none}.container .size-obs .visible{display:block}.container .controls{display:flex;flex-wrap:wrap;justify-content:space-between;padding-bottom:.5rem;padding-top:.2rem;gap:1rem}.container .controls .controls-left{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.container .controls .controls-left .add-feature-flag-wrapper{display:flex;flex-wrap:nowrap}.container .controls .controls-left .add-feature-flag-wrapper .add-feature-flag{border-bottom-right-radius:0%;border-top-right-radius:0%}.container .controls .controls-left .add-feature-flag-wrapper .add-other-setting{min-width:0;border-bottom-left-radius:0%;border-top-left-radius:0%;border-left:0px;padding:0 12px}.container .controls .controls-left .add-feature-flag-wrapper .add-other-setting .mat-icon{margin-right:0;margin-left:0}.container .controls .controls-left .tag-filter{min-width:0}.container .controls .controls-left .tag-filter mat-form-field{max-width:600px;min-width:246px}.container .controls .controls-left .tag-filter mat-form-field input{max-width:100px}.container .controls .controls-left .tag-filter .tag{font-size:.9em;max-width:120px;min-width:0}.container .controls .more-container{display:flex;gap:.5rem}.container .controls .more-container .more-button{padding:0 8px;min-width:unset}.container .controls .more-container .more-button mat-icon{margin-left:unset;margin-right:unset}.container .controls .option-icon{opacity:.7}.container .controls .option-name{font-size:14px}.container .controls .option-name mat-icon{font-size:22px;width:22px;height:22px}.container .not-found{text-align:center;padding-top:2rem}.container .not-found img{width:300px}.container .history-link{display:flex;align-items:center;margin-left:.2em}.container .history-link mat-icon{margin-right:5px;font-size:20px;width:20px;height:20px}.container .jump{cursor:pointer}.container .feature-flags-container{margin-bottom:.3rem}.container .save{position:sticky;bottom:0;background-color:var(--page-background-color);display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;z-index:999;padding-top:1rem;padding-bottom:1rem;gap:.5rem}.container .save .save-button{height:38px;font-size:13px!important}.container .save .save-button:disabled{animation:none}.container .save .save-button-group{display:flex;align-items:stretch}.container .save .save-button-group .save-button.right-side-flat{border-top-right-radius:0;border-bottom-right-radius:0}.container .save .save-button-group .save-button-dropdown{border-top-left-radius:0;border-bottom-left-radius:0;min-width:unset;height:38px;padding:0 12px;border-left:1px var(--save-separator-color) solid}.container .save .save-button-group .save-button-dropdown .mat-icon{margin-right:0;margin-left:0}.container .save .save-button-group .save-button-dropdown:disabled{border-left-color:var(--save-separator-disabled-color)}.container .error{color:var(--flag-validation-error);padding:5px;font-size:.9em}.container .error a{color:var(--flag-validation-error);text-decoration:underline}.container .steps{display:flex;align-items:center;margin:1rem 0;justify-content:space-between;flex-wrap:wrap}.container .steps .steps-left{display:flex;flex-wrap:nowrap;align-items:center;justify-content:flex-start}.container .steps .steps-left>h2{cursor:pointer}.container .steps .connected{display:flex;align-items:center}.container .divider{margin-top:1.3rem}.add-menu-item{display:flex;align-items:center;text-wrap:nowrap}.add-menu-item .add-menu-img{width:24px;height:24px;margin-right:8px}.top-menu-selector .strong{font-weight:600}.top-menu-selector .feature-flag-selector-item{display:flex;flex-wrap:nowrap}.top-menu-selector .feature-flag-selector-item .feature-flag-selector-img{width:24px;height:24px;margin-right:8px;align-self:center}.top-menu-selector .feature-flag-selector-item span{overflow:hidden;text-overflow:ellipsis}.top-menu-selector .filter{display:flex;margin:0 8px 8px;justify-content:space-around}.top-menu-selector .filter mat-form-field{width:100%}.top-menu-selector .filter-not-found{margin-left:12px}.connect-title{font-size:17px;font-weight:700;margin:16px 0;cursor:pointer}.search-key-icon{display:inline-flex;justify-content:center;align-items:center;font-size:11px;width:1.8em;height:1.8em;margin-right:1px;background-color:var(--flag-search-bar-icon-background);border-radius:3px;border:1px solid var(--flag-search-bar-icon-border)}@media(max-width:750px){.container{padding:0 .5rem 1rem}.expand-collapse{flex-wrap:wrap!important}.expand-collapse .btn{margin-bottom:2px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: RxReactiveFormsModule }, { kind: "directive", type: i2.RxwebFormDirective, selector: "[formGroup],[rxwebForm]", inputs: ["formGroup", "rxwebForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FeatureFlagComponent, selector: "app-feature-flag", inputs: ["formGroup", "flagState", "segments", "featureFlagLimitations", "hasOtherEnvironment", "environmentName", "showEnvironmentName", "canDeleteSetting", "predefinedVariationsEnabled", "dashboardBasePath", "comparisonAttributes", "customize"], outputs: ["addSegmentConditionRequestedWithoutSegments", "expandedChanged", "deleteSettingRequested", "subscriptionLimitReached", "prerequisiteConditionChanged", "recalcAddButtons", "viewVariationsRequested"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: TrackingDirective, selector: "[track]", inputs: ["track"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
|
|
7581
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: FeatureFlagItemComponent, isStandalone: true, selector: "app-feature-flag-item", inputs: { productId: { classPropertyName: "productId", publicName: "productId", isSignal: true, isRequired: true, transformFunction: null }, environmentId: { classPropertyName: "environmentId", publicName: "environmentId", isSignal: true, isRequired: true, transformFunction: null }, configId: { classPropertyName: "configId", publicName: "configId", isSignal: true, isRequired: true, transformFunction: null }, settingId: { classPropertyName: "settingId", publicName: "settingId", isSignal: true, isRequired: true, transformFunction: null }, canDeleteSetting: { classPropertyName: "canDeleteSetting", publicName: "canDeleteSetting", isSignal: true, isRequired: false, transformFunction: null }, customize: { classPropertyName: "customize", publicName: "customize", isSignal: true, isRequired: false, transformFunction: null }, showEnvironmentName: { classPropertyName: "showEnvironmentName", publicName: "showEnvironmentName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { deleteSettingRequested: "deleteSettingRequested", loadSucceeded: "loadSucceeded", loadFailed: "loadFailed", saveSucceeded: "saveSucceeded", componentError: "componentError", expandedStateChanged: "expandedStateChanged", formValuesChanged: "formValuesChanged" }, usesInheritance: true, ngImport: i0, template: "@let settingFormulaValue = settingFormula();\n<div class=\"container\">\n @if (!loading && settingFormulaValue) {\n <form id=\"feature-flag-form\" [formGroup]=\"formGroup\" (ngSubmit)=\"submit()\">\n <div [ngClass]=\"{ 'invalid-flag': formGroup.invalid }\">\n <app-feature-flag\n [formGroup]=\"formGroup\"\n [flagState]=\"flagState\"\n [segments]=\"segments\"\n [comparisonAttributes]=\"comparisonAttributes()\"\n [featureFlagLimitations]=\"settingFormulaValue.featureFlagLimitations\"\n [hasOtherEnvironment]=\"false\"\n [showEnvironmentName]=\"showEnvironmentName()\"\n [environmentName]=\"settingFormulaValue.environment.name\"\n [canDeleteSetting]=\"canDeleteSetting()\"\n [predefinedVariationsEnabled]=\"true\"\n [dashboardBasePath]=\"dashboardBasePath\"\n [customize]=\"customize()\"\n (deleteSettingRequested)=\"onDeleteSettingRequested($event)\"\n (subscriptionLimitReached)=\"subscriptionLimitReachedCalled()\"\n (addSegmentConditionRequestedWithoutSegments)=\"addSegmentConditionRequestedWithoutSegments()\"\n (prerequisiteConditionChanged)=\"recalcPrerequisites()\"\n (recalcAddButtons)=\"recalcAddButtons()\"\n (expandedChanged)=\"expandedChanged($event)\"\n (viewVariationsRequested)=\"viewVariations($event)\" />\n </div>\n @if (!settingFormulaValue.readOnly && formGroupDirty()) {\n <div class=\"save\">\n <div class=\"save-button-group\">\n @if (!settingFormulaValue.approveRequired) {\n <button\n class=\"save-button right-side-flat\"\n mat-flat-button\n color=\"warn\"\n type=\"submit\"\n [disabled]=\"submitting\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish changes</span>\n </button>\n <button\n class=\"save-button-dropdown\"\n mat-flat-button\n color=\"warn\"\n type=\"button\"\n [disabled]=\"submitting\"\n [matMenuTriggerFor]=\"proposeMenu\">\n <mat-icon>arrow_drop_down</mat-icon>\n </button>\n <mat-menu #proposeMenu=\"matMenu\" xPosition=\"before\">\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(false)\">\n <mat-icon>call_merge</mat-icon>\n <span>Propose changes</span>\n </button>\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(true)\">\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n <span>Schedule changes</span>\n </button>\n </mat-menu>\n } @else {\n <button\n class=\"save-button right-side-flat\"\n mat-flat-button\n type=\"button\"\n color=\"success\"\n [disabled]=\"submitting\"\n (click)=\"createChangeRequest(false)\">\n <mat-icon>call_merge</mat-icon>\n <span>Propose changes</span>\n </button>\n <button\n class=\"save-button-dropdown\"\n mat-flat-button\n color=\"success\"\n type=\"button\"\n [disabled]=\"submitting\"\n [matMenuTriggerFor]=\"proposeMenu\">\n <mat-icon>arrow_drop_down</mat-icon>\n </button>\n <mat-menu #proposeMenu=\"matMenu\" xPosition=\"before\" class=\"full-width-menu\">\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(true)\">\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n <span>Schedule changes</span>\n </button>\n @if (settingFormulaValue.canBypassApproval) {\n <button type=\"button\" mat-menu-item (click)=\"submit(true)\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish without approval</span>\n </button>\n }\n </mat-menu>\n }\n </div>\n <button\n type=\"button\"\n class=\"revert-button\"\n mat-stroked-button\n [disabled]=\"submitting\"\n [track]=\"['revert click', 'feature flags and settings']\"\n (click)=\"revert()\">\n <span>Revert</span>\n </button>\n @if (formGroup.touched && formGroup.invalid && showFeedBackMessage) {\n <div class=\"feedback\">\n @if (formGroup.errors && formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>\n Something went wrong and we could not save your settings. You can try again or\n <a href=\"https://configcat.com/support\" target=\"_blank\" rel=\"noopener noreferrer\">contact us.</a>\n </span>\n </div>\n }\n @if (!formGroup.errors || !formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>Oh no! A feature flag has invalid or missing data. </span>\n </div>\n }\n </div>\n }\n </div>\n }\n </form>\n }\n</div>\n", styles: [".container{padding:0 .5rem 1rem}.container .container-content{padding-top:1rem}.container .size-obs{min-width:40px;min-height:120px}.container .size-obs .hidden{display:none}.container .size-obs .visible{display:block}.container .controls{display:flex;flex-wrap:wrap;justify-content:space-between;padding-bottom:.5rem;padding-top:.2rem;gap:1rem}.container .controls .controls-left{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.container .controls .controls-left .add-feature-flag-wrapper{display:flex;flex-wrap:nowrap}.container .controls .controls-left .add-feature-flag-wrapper .add-feature-flag{border-bottom-right-radius:0%;border-top-right-radius:0%}.container .controls .controls-left .add-feature-flag-wrapper .add-other-setting{min-width:0;border-bottom-left-radius:0%;border-top-left-radius:0%;border-left:0px;padding:0 12px}.container .controls .controls-left .add-feature-flag-wrapper .add-other-setting .mat-icon{margin-right:0;margin-left:0}.container .controls .controls-left .tag-filter{min-width:0}.container .controls .controls-left .tag-filter mat-form-field{max-width:600px;min-width:246px}.container .controls .controls-left .tag-filter mat-form-field input{max-width:100px}.container .controls .controls-left .tag-filter .tag{font-size:.9em;max-width:120px;min-width:0}.container .controls .more-container{display:flex;gap:.5rem}.container .controls .more-container .more-button{padding:0 8px;min-width:unset}.container .controls .more-container .more-button mat-icon{margin-left:unset;margin-right:unset}.container .controls .option-icon{opacity:.7}.container .controls .option-name{font-size:14px}.container .controls .option-name mat-icon{font-size:22px;width:22px;height:22px}.container .not-found{text-align:center;padding-top:2rem}.container .not-found img{width:300px}.container .history-link{display:flex;align-items:center;margin-left:.2em}.container .history-link mat-icon{margin-right:5px;font-size:20px;width:20px;height:20px}.container .jump{cursor:pointer}.container .feature-flags-container{margin-bottom:.3rem}.container .save{position:sticky;bottom:0;background-color:var(--page-background-color);display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;z-index:999;padding-top:1rem;padding-bottom:1rem;gap:.5rem}.container .save .save-button{height:38px;font-size:13px!important}.container .save .save-button:disabled{animation:none}.container .save .save-button-group{display:flex;align-items:stretch}.container .save .save-button-group .save-button.right-side-flat{border-top-right-radius:0;border-bottom-right-radius:0}.container .save .save-button-group .save-button-dropdown{border-top-left-radius:0;border-bottom-left-radius:0;min-width:unset;height:38px;padding:0 12px;border-left:1px var(--save-separator-color) solid}.container .save .save-button-group .save-button-dropdown .mat-icon{margin-right:0;margin-left:0}.container .save .save-button-group .save-button-dropdown:disabled{border-left-color:var(--save-separator-disabled-color)}.container .error{color:var(--flag-validation-error);padding:5px;font-size:.9em}.container .error a{color:var(--flag-validation-error);text-decoration:underline}.container .steps{display:flex;align-items:center;margin:1rem 0;justify-content:space-between;flex-wrap:wrap}.container .steps .steps-left{display:flex;flex-wrap:nowrap;align-items:center;justify-content:flex-start}.container .steps .steps-left>h2{cursor:pointer}.container .steps .connected{display:flex;align-items:center}.container .divider{margin-top:1.3rem}.add-menu-item{display:flex;align-items:center;text-wrap:nowrap}.add-menu-item .add-menu-img{width:24px;height:24px;margin-right:8px}.top-menu-selector .strong{font-weight:600}.top-menu-selector .feature-flag-selector-item{display:flex;flex-wrap:nowrap}.top-menu-selector .feature-flag-selector-item .feature-flag-selector-img{width:24px;height:24px;margin-right:8px;align-self:center}.top-menu-selector .feature-flag-selector-item span{overflow:hidden;text-overflow:ellipsis}.top-menu-selector .filter{display:flex;margin:0 8px 8px;justify-content:space-around}.top-menu-selector .filter mat-form-field{width:100%}.top-menu-selector .filter-not-found{margin-left:12px}.connect-title{font-size:17px;font-weight:700;margin:16px 0;cursor:pointer}.search-key-icon{display:inline-flex;justify-content:center;align-items:center;font-size:11px;width:1.8em;height:1.8em;margin-right:1px;background-color:var(--flag-search-bar-icon-background);border-radius:3px;border:1px solid var(--flag-search-bar-icon-border)}@media(max-width:750px){.container{padding:0 .5rem 1rem}.expand-collapse{flex-wrap:wrap!important}.expand-collapse .btn{margin-bottom:2px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: RxReactiveFormsModule }, { kind: "directive", type: i2.RxwebFormDirective, selector: "[formGroup],[rxwebForm]", inputs: ["formGroup", "rxwebForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FeatureFlagComponent, selector: "app-feature-flag", inputs: ["formGroup", "flagState", "segments", "featureFlagLimitations", "hasOtherEnvironment", "environmentName", "showEnvironmentName", "canDeleteSetting", "predefinedVariationsEnabled", "dashboardBasePath", "comparisonAttributes", "customize"], outputs: ["addSegmentConditionRequestedWithoutSegments", "expandedChanged", "deleteSettingRequested", "subscriptionLimitReached", "prerequisiteConditionChanged", "recalcAddButtons", "viewVariationsRequested"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: TrackingDirective, selector: "[track]", inputs: ["track"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
|
|
7528
7582
|
}
|
|
7529
7583
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: FeatureFlagItemComponent, decorators: [{
|
|
7530
7584
|
type: Component,
|
|
@@ -7540,7 +7594,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
|
|
|
7540
7594
|
MatMenu,
|
|
7541
7595
|
MatMenuItem,
|
|
7542
7596
|
MatMenuTrigger,
|
|
7543
|
-
], template: "@let settingFormulaValue = settingFormula();\n<div class=\"container\">\n @if (!loading && settingFormulaValue) {\n <form id=\"feature-flag-form\" [formGroup]=\"formGroup\" (ngSubmit)=\"submit()\">\n <div [ngClass]=\"{ 'invalid-flag': formGroup.invalid }\">\n <app-feature-flag\n [formGroup]=\"formGroup\"\n [flagState]=\"flagState\"\n [segments]=\"segments\"\n [comparisonAttributes]=\"comparisonAttributes()\"\n [featureFlagLimitations]=\"settingFormulaValue.featureFlagLimitations\"\n [hasOtherEnvironment]=\"false\"\n [showEnvironmentName]=\"showEnvironmentName()\"\n [environmentName]=\"settingFormulaValue.environment.name\"\n [canDeleteSetting]=\"canDeleteSetting()\"\n [predefinedVariationsEnabled]=\"true\"\n [dashboardBasePath]=\"dashboardBasePath\"\n [customize]=\"customize()\"\n (deleteSettingRequested)=\"onDeleteSettingRequested($event)\"\n (subscriptionLimitReached)=\"subscriptionLimitReachedCalled()\"\n (addSegmentConditionRequestedWithoutSegments)=\"addSegmentConditionRequestedWithoutSegments()\"\n (prerequisiteConditionChanged)=\"recalcPrerequisites()\"\n (recalcAddButtons)=\"recalcAddButtons()\"\n (expandedChanged)=\"expandedChanged($event)\"\n (viewVariationsRequested)=\"viewVariations($event)\" />\n </div>\n @if (!settingFormulaValue.readOnly && formGroup!.dirty) {\n <div class=\"save\">\n <div class=\"save-button-group\">\n @if (!settingFormulaValue.approveRequired) {\n <button\n class=\"save-button right-side-flat\"\n mat-flat-button\n color=\"warn\"\n type=\"submit\"\n [disabled]=\"submitting\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish changes</span>\n </button>\n <button\n class=\"save-button-dropdown\"\n mat-flat-button\n color=\"warn\"\n type=\"button\"\n [disabled]=\"submitting\"\n [matMenuTriggerFor]=\"proposeMenu\">\n <mat-icon>arrow_drop_down</mat-icon>\n </button>\n <mat-menu #proposeMenu=\"matMenu\" xPosition=\"before\">\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(false)\">\n <mat-icon>call_merge</mat-icon>\n <span>Propose changes</span>\n </button>\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(true)\">\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n <span>Schedule changes</span>\n </button>\n </mat-menu>\n } @else {\n <button\n class=\"save-button right-side-flat\"\n mat-flat-button\n type=\"button\"\n color=\"success\"\n [disabled]=\"submitting\"\n (click)=\"createChangeRequest(false)\">\n <mat-icon>call_merge</mat-icon>\n <span>Propose changes</span>\n </button>\n <button\n class=\"save-button-dropdown\"\n mat-flat-button\n color=\"success\"\n type=\"button\"\n [disabled]=\"submitting\"\n [matMenuTriggerFor]=\"proposeMenu\">\n <mat-icon>arrow_drop_down</mat-icon>\n </button>\n <mat-menu #proposeMenu=\"matMenu\" xPosition=\"before\" class=\"full-width-menu\">\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(true)\">\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n <span>Schedule changes</span>\n </button>\n @if (settingFormulaValue.canBypassApproval) {\n <button type=\"button\" mat-menu-item (click)=\"submit(true)\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish without approval</span>\n </button>\n }\n </mat-menu>\n }\n </div>\n <button\n type=\"button\"\n class=\"revert-button\"\n mat-stroked-button\n [disabled]=\"submitting\"\n [track]=\"['revert click', 'feature flags and settings']\"\n (click)=\"revert()\">\n <span>Revert</span>\n </button>\n @if (formGroup.touched && formGroup.invalid && showFeedBackMessage) {\n <div class=\"feedback\">\n @if (formGroup.errors && formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>\n Something went wrong and we could not save your settings. You can try again or\n <a href=\"https://configcat.com/support\" target=\"_blank\" rel=\"noopener noreferrer\">contact us.</a>\n </span>\n </div>\n }\n @if (!formGroup.errors || !formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>Oh no! A feature flag has invalid or missing data. </span>\n </div>\n }\n </div>\n }\n </div>\n }\n </form>\n }\n</div>\n", styles: [".container{padding:0 .5rem 1rem}.container .container-content{padding-top:1rem}.container .size-obs{min-width:40px;min-height:120px}.container .size-obs .hidden{display:none}.container .size-obs .visible{display:block}.container .controls{display:flex;flex-wrap:wrap;justify-content:space-between;padding-bottom:.5rem;padding-top:.2rem;gap:1rem}.container .controls .controls-left{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.container .controls .controls-left .add-feature-flag-wrapper{display:flex;flex-wrap:nowrap}.container .controls .controls-left .add-feature-flag-wrapper .add-feature-flag{border-bottom-right-radius:0%;border-top-right-radius:0%}.container .controls .controls-left .add-feature-flag-wrapper .add-other-setting{min-width:0;border-bottom-left-radius:0%;border-top-left-radius:0%;border-left:0px;padding:0 12px}.container .controls .controls-left .add-feature-flag-wrapper .add-other-setting .mat-icon{margin-right:0;margin-left:0}.container .controls .controls-left .tag-filter{min-width:0}.container .controls .controls-left .tag-filter mat-form-field{max-width:600px;min-width:246px}.container .controls .controls-left .tag-filter mat-form-field input{max-width:100px}.container .controls .controls-left .tag-filter .tag{font-size:.9em;max-width:120px;min-width:0}.container .controls .more-container{display:flex;gap:.5rem}.container .controls .more-container .more-button{padding:0 8px;min-width:unset}.container .controls .more-container .more-button mat-icon{margin-left:unset;margin-right:unset}.container .controls .option-icon{opacity:.7}.container .controls .option-name{font-size:14px}.container .controls .option-name mat-icon{font-size:22px;width:22px;height:22px}.container .not-found{text-align:center;padding-top:2rem}.container .not-found img{width:300px}.container .history-link{display:flex;align-items:center;margin-left:.2em}.container .history-link mat-icon{margin-right:5px;font-size:20px;width:20px;height:20px}.container .jump{cursor:pointer}.container .feature-flags-container{margin-bottom:.3rem}.container .save{position:sticky;bottom:0;background-color:var(--page-background-color);display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;z-index:999;padding-top:1rem;padding-bottom:1rem;gap:.5rem}.container .save .save-button{height:38px;font-size:13px!important}.container .save .save-button:disabled{animation:none}.container .save .save-button-group{display:flex;align-items:stretch}.container .save .save-button-group .save-button.right-side-flat{border-top-right-radius:0;border-bottom-right-radius:0}.container .save .save-button-group .save-button-dropdown{border-top-left-radius:0;border-bottom-left-radius:0;min-width:unset;height:38px;padding:0 12px;border-left:1px var(--save-separator-color) solid}.container .save .save-button-group .save-button-dropdown .mat-icon{margin-right:0;margin-left:0}.container .save .save-button-group .save-button-dropdown:disabled{border-left-color:var(--save-separator-disabled-color)}.container .error{color:var(--flag-validation-error);padding:5px;font-size:.9em}.container .error a{color:var(--flag-validation-error);text-decoration:underline}.container .steps{display:flex;align-items:center;margin:1rem 0;justify-content:space-between;flex-wrap:wrap}.container .steps .steps-left{display:flex;flex-wrap:nowrap;align-items:center;justify-content:flex-start}.container .steps .steps-left>h2{cursor:pointer}.container .steps .connected{display:flex;align-items:center}.container .divider{margin-top:1.3rem}.add-menu-item{display:flex;align-items:center;text-wrap:nowrap}.add-menu-item .add-menu-img{width:24px;height:24px;margin-right:8px}.top-menu-selector .strong{font-weight:600}.top-menu-selector .feature-flag-selector-item{display:flex;flex-wrap:nowrap}.top-menu-selector .feature-flag-selector-item .feature-flag-selector-img{width:24px;height:24px;margin-right:8px;align-self:center}.top-menu-selector .feature-flag-selector-item span{overflow:hidden;text-overflow:ellipsis}.top-menu-selector .filter{display:flex;margin:0 8px 8px;justify-content:space-around}.top-menu-selector .filter mat-form-field{width:100%}.top-menu-selector .filter-not-found{margin-left:12px}.connect-title{font-size:17px;font-weight:700;margin:16px 0;cursor:pointer}.search-key-icon{display:inline-flex;justify-content:center;align-items:center;font-size:11px;width:1.8em;height:1.8em;margin-right:1px;background-color:var(--flag-search-bar-icon-background);border-radius:3px;border:1px solid var(--flag-search-bar-icon-border)}@media(max-width:750px){.container{padding:0 .5rem 1rem}.expand-collapse{flex-wrap:wrap!important}.expand-collapse .btn{margin-bottom:2px}}\n"] }]
|
|
7597
|
+
], template: "@let settingFormulaValue = settingFormula();\n<div class=\"container\">\n @if (!loading && settingFormulaValue) {\n <form id=\"feature-flag-form\" [formGroup]=\"formGroup\" (ngSubmit)=\"submit()\">\n <div [ngClass]=\"{ 'invalid-flag': formGroup.invalid }\">\n <app-feature-flag\n [formGroup]=\"formGroup\"\n [flagState]=\"flagState\"\n [segments]=\"segments\"\n [comparisonAttributes]=\"comparisonAttributes()\"\n [featureFlagLimitations]=\"settingFormulaValue.featureFlagLimitations\"\n [hasOtherEnvironment]=\"false\"\n [showEnvironmentName]=\"showEnvironmentName()\"\n [environmentName]=\"settingFormulaValue.environment.name\"\n [canDeleteSetting]=\"canDeleteSetting()\"\n [predefinedVariationsEnabled]=\"true\"\n [dashboardBasePath]=\"dashboardBasePath\"\n [customize]=\"customize()\"\n (deleteSettingRequested)=\"onDeleteSettingRequested($event)\"\n (subscriptionLimitReached)=\"subscriptionLimitReachedCalled()\"\n (addSegmentConditionRequestedWithoutSegments)=\"addSegmentConditionRequestedWithoutSegments()\"\n (prerequisiteConditionChanged)=\"recalcPrerequisites()\"\n (recalcAddButtons)=\"recalcAddButtons()\"\n (expandedChanged)=\"expandedChanged($event)\"\n (viewVariationsRequested)=\"viewVariations($event)\" />\n </div>\n @if (!settingFormulaValue.readOnly && formGroupDirty()) {\n <div class=\"save\">\n <div class=\"save-button-group\">\n @if (!settingFormulaValue.approveRequired) {\n <button\n class=\"save-button right-side-flat\"\n mat-flat-button\n color=\"warn\"\n type=\"submit\"\n [disabled]=\"submitting\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish changes</span>\n </button>\n <button\n class=\"save-button-dropdown\"\n mat-flat-button\n color=\"warn\"\n type=\"button\"\n [disabled]=\"submitting\"\n [matMenuTriggerFor]=\"proposeMenu\">\n <mat-icon>arrow_drop_down</mat-icon>\n </button>\n <mat-menu #proposeMenu=\"matMenu\" xPosition=\"before\">\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(false)\">\n <mat-icon>call_merge</mat-icon>\n <span>Propose changes</span>\n </button>\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(true)\">\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n <span>Schedule changes</span>\n </button>\n </mat-menu>\n } @else {\n <button\n class=\"save-button right-side-flat\"\n mat-flat-button\n type=\"button\"\n color=\"success\"\n [disabled]=\"submitting\"\n (click)=\"createChangeRequest(false)\">\n <mat-icon>call_merge</mat-icon>\n <span>Propose changes</span>\n </button>\n <button\n class=\"save-button-dropdown\"\n mat-flat-button\n color=\"success\"\n type=\"button\"\n [disabled]=\"submitting\"\n [matMenuTriggerFor]=\"proposeMenu\">\n <mat-icon>arrow_drop_down</mat-icon>\n </button>\n <mat-menu #proposeMenu=\"matMenu\" xPosition=\"before\" class=\"full-width-menu\">\n <button type=\"button\" mat-menu-item (click)=\"createChangeRequest(true)\">\n <mat-icon class=\"icon-transform-rotate\">schedule</mat-icon>\n <span>Schedule changes</span>\n </button>\n @if (settingFormulaValue.canBypassApproval) {\n <button type=\"button\" mat-menu-item (click)=\"submit(true)\">\n <mat-icon>backup</mat-icon>\n <span>Save & publish without approval</span>\n </button>\n }\n </mat-menu>\n }\n </div>\n <button\n type=\"button\"\n class=\"revert-button\"\n mat-stroked-button\n [disabled]=\"submitting\"\n [track]=\"['revert click', 'feature flags and settings']\"\n (click)=\"revert()\">\n <span>Revert</span>\n </button>\n @if (formGroup.touched && formGroup.invalid && showFeedBackMessage) {\n <div class=\"feedback\">\n @if (formGroup.errors && formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>\n Something went wrong and we could not save your settings. You can try again or\n <a href=\"https://configcat.com/support\" target=\"_blank\" rel=\"noopener noreferrer\">contact us.</a>\n </span>\n </div>\n }\n @if (!formGroup.errors || !formGroup.errors[\"internalServerError\"]) {\n <div class=\"error\">\n <span>Oh no! A feature flag has invalid or missing data. </span>\n </div>\n }\n </div>\n }\n </div>\n }\n </form>\n }\n</div>\n", styles: [".container{padding:0 .5rem 1rem}.container .container-content{padding-top:1rem}.container .size-obs{min-width:40px;min-height:120px}.container .size-obs .hidden{display:none}.container .size-obs .visible{display:block}.container .controls{display:flex;flex-wrap:wrap;justify-content:space-between;padding-bottom:.5rem;padding-top:.2rem;gap:1rem}.container .controls .controls-left{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.container .controls .controls-left .add-feature-flag-wrapper{display:flex;flex-wrap:nowrap}.container .controls .controls-left .add-feature-flag-wrapper .add-feature-flag{border-bottom-right-radius:0%;border-top-right-radius:0%}.container .controls .controls-left .add-feature-flag-wrapper .add-other-setting{min-width:0;border-bottom-left-radius:0%;border-top-left-radius:0%;border-left:0px;padding:0 12px}.container .controls .controls-left .add-feature-flag-wrapper .add-other-setting .mat-icon{margin-right:0;margin-left:0}.container .controls .controls-left .tag-filter{min-width:0}.container .controls .controls-left .tag-filter mat-form-field{max-width:600px;min-width:246px}.container .controls .controls-left .tag-filter mat-form-field input{max-width:100px}.container .controls .controls-left .tag-filter .tag{font-size:.9em;max-width:120px;min-width:0}.container .controls .more-container{display:flex;gap:.5rem}.container .controls .more-container .more-button{padding:0 8px;min-width:unset}.container .controls .more-container .more-button mat-icon{margin-left:unset;margin-right:unset}.container .controls .option-icon{opacity:.7}.container .controls .option-name{font-size:14px}.container .controls .option-name mat-icon{font-size:22px;width:22px;height:22px}.container .not-found{text-align:center;padding-top:2rem}.container .not-found img{width:300px}.container .history-link{display:flex;align-items:center;margin-left:.2em}.container .history-link mat-icon{margin-right:5px;font-size:20px;width:20px;height:20px}.container .jump{cursor:pointer}.container .feature-flags-container{margin-bottom:.3rem}.container .save{position:sticky;bottom:0;background-color:var(--page-background-color);display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;z-index:999;padding-top:1rem;padding-bottom:1rem;gap:.5rem}.container .save .save-button{height:38px;font-size:13px!important}.container .save .save-button:disabled{animation:none}.container .save .save-button-group{display:flex;align-items:stretch}.container .save .save-button-group .save-button.right-side-flat{border-top-right-radius:0;border-bottom-right-radius:0}.container .save .save-button-group .save-button-dropdown{border-top-left-radius:0;border-bottom-left-radius:0;min-width:unset;height:38px;padding:0 12px;border-left:1px var(--save-separator-color) solid}.container .save .save-button-group .save-button-dropdown .mat-icon{margin-right:0;margin-left:0}.container .save .save-button-group .save-button-dropdown:disabled{border-left-color:var(--save-separator-disabled-color)}.container .error{color:var(--flag-validation-error);padding:5px;font-size:.9em}.container .error a{color:var(--flag-validation-error);text-decoration:underline}.container .steps{display:flex;align-items:center;margin:1rem 0;justify-content:space-between;flex-wrap:wrap}.container .steps .steps-left{display:flex;flex-wrap:nowrap;align-items:center;justify-content:flex-start}.container .steps .steps-left>h2{cursor:pointer}.container .steps .connected{display:flex;align-items:center}.container .divider{margin-top:1.3rem}.add-menu-item{display:flex;align-items:center;text-wrap:nowrap}.add-menu-item .add-menu-img{width:24px;height:24px;margin-right:8px}.top-menu-selector .strong{font-weight:600}.top-menu-selector .feature-flag-selector-item{display:flex;flex-wrap:nowrap}.top-menu-selector .feature-flag-selector-item .feature-flag-selector-img{width:24px;height:24px;margin-right:8px;align-self:center}.top-menu-selector .feature-flag-selector-item span{overflow:hidden;text-overflow:ellipsis}.top-menu-selector .filter{display:flex;margin:0 8px 8px;justify-content:space-around}.top-menu-selector .filter mat-form-field{width:100%}.top-menu-selector .filter-not-found{margin-left:12px}.connect-title{font-size:17px;font-weight:700;margin:16px 0;cursor:pointer}.search-key-icon{display:inline-flex;justify-content:center;align-items:center;font-size:11px;width:1.8em;height:1.8em;margin-right:1px;background-color:var(--flag-search-bar-icon-background);border-radius:3px;border:1px solid var(--flag-search-bar-icon-border)}@media(max-width:750px){.container{padding:0 .5rem 1rem}.expand-collapse{flex-wrap:wrap!important}.expand-collapse .btn{margin-bottom:2px}}\n"] }]
|
|
7544
7598
|
}], propDecorators: { productId: [{ type: i0.Input, args: [{ isSignal: true, alias: "productId", required: true }] }], environmentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "environmentId", required: true }] }], configId: [{ type: i0.Input, args: [{ isSignal: true, alias: "configId", required: true }] }], settingId: [{ type: i0.Input, args: [{ isSignal: true, alias: "settingId", required: true }] }], canDeleteSetting: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDeleteSetting", required: false }] }], customize: [{ type: i0.Input, args: [{ isSignal: true, alias: "customize", required: false }] }], showEnvironmentName: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEnvironmentName", required: false }] }], deleteSettingRequested: [{ type: i0.Output, args: ["deleteSettingRequested"] }], loadSucceeded: [{ type: i0.Output, args: ["loadSucceeded"] }], loadFailed: [{ type: i0.Output, args: ["loadFailed"] }], saveSucceeded: [{ type: i0.Output, args: ["saveSucceeded"] }], componentError: [{ type: i0.Output, args: ["componentError"] }], expandedStateChanged: [{ type: i0.Output, args: ["expandedStateChanged"] }], formValuesChanged: [{ type: i0.Output, args: ["formValuesChanged"] }] } });
|
|
7545
7599
|
|
|
7546
7600
|
const DEFAULT_CUSTOMIZE_LINK_FEATURE_FLAG = {
|