feat(console): feature settings (#7713)

* feature settings

* feature component, i18n

* i18n, save settings

* i18n

* reset button, state if system inherited, i18n

* iam.restrictions.read

* refetch feature after reset

* use toggles instead of checkboxes

* i18n

* rm logs

* fix i18n

* show unspecified as inherited

---------

Co-authored-by: Elio Bischof <[email protected]>
This commit is contained in:
Max Peintner
2024-04-10 16:30:58 +00:00
committed by GitHub
co-authored by Elio Bischof
parent dcfa2f7955
commit 2061d1acdc
24 changed files with 1140 additions and 3 deletions
@@ -0,0 +1,347 @@
<div class="feature-settings-wrapper">
<div class="feature-title-row">
<h2>{{ 'DESCRIPTIONS.SETTINGS.FEATURES.TITLE' | translate }}</h2>
<a
mat-icon-button
href="https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service"
rel="noreferrer"
target="_blank"
>
<mat-icon class="icon">info_outline</mat-icon>
</a>
</div>
<p class="events-desc cnsl-secondary-text">{{ 'DESCRIPTIONS.SETTINGS.FEATURES.DESCRIPTION' | translate }}</p>
<ng-template cnslHasRole [hasRole]="['iam.restrictions.write']">
<button color="warn" (click)="resetSettings()" mat-stroked-button>
{{ 'SETTING.FEATURES.RESET' | translate }}
</button>
</ng-template>
<cnsl-card *ngIf="toggleStates && featureData">
<div class="features">
<div class="feature-row" *ngIf="toggleStates.loginDefaultOrg">
<span>{{ 'SETTING.FEATURES.LOGINDEFAULTORG' | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="toggleStates.loginDefaultOrg.state"
(change)="validateAndSave()"
name="displayview"
aria-label="Display View"
>
<mat-button-toggle [value]="ToggleState.INHERITED">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
<i
class="info-i las la-question-circle"
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
></i>
<div
*ngIf="
!!featureData.loginDefaultOrg?.enabled &&
(featureData.loginDefaultOrg?.source === Source.SOURCE_SYSTEM ||
featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
></div>
<div
*ngIf="
!featureData.loginDefaultOrg?.enabled &&
(featureData.loginDefaultOrg?.source === Source.SOURCE_SYSTEM ||
featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.DISABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.ENABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<cnsl-info-section class="feature-info">{{
'SETTING.FEATURES.LOGINDEFAULTORG_DESCRIPTION' | translate
}}</cnsl-info-section>
</div>
<div class="feature-row" *ngIf="toggleStates.oidcLegacyIntrospection">
<span>{{ 'SETTING.FEATURES.OIDCLEGACYINTROSPECTION' | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="toggleStates.oidcLegacyIntrospection.state"
(change)="validateAndSave()"
name="displayview"
aria-label="Display View"
>
<mat-button-toggle [value]="ToggleState.INHERITED">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
<i
class="info-i las la-question-circle"
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
></i>
<div
*ngIf="
!!featureData.oidcLegacyIntrospection?.enabled &&
(featureData.oidcLegacyIntrospection?.source === Source.SOURCE_SYSTEM ||
featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
></div>
<div
*ngIf="
!featureData.oidcLegacyIntrospection?.enabled &&
(featureData.oidcLegacyIntrospection?.source === Source.SOURCE_SYSTEM ||
featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.DISABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.ENABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<cnsl-info-section class="feature-info">{{
'SETTING.FEATURES.OIDCLEGACYINTROSPECTION_DESCRIPTION' | translate
}}</cnsl-info-section>
</div>
<div class="feature-row" *ngIf="toggleStates.oidcTokenExchange">
<span>{{ 'SETTING.FEATURES.OIDCTOKENEXCHANGE' | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="toggleStates.oidcTokenExchange.state"
(change)="validateAndSave()"
name="displayview"
aria-label="Display View"
>
<mat-button-toggle [value]="ToggleState.INHERITED">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
<i
class="info-i las la-question-circle"
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
></i>
<div
*ngIf="
!!featureData.oidcTokenExchange?.enabled &&
(featureData.oidcTokenExchange?.source === Source.SOURCE_SYSTEM ||
featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
></div>
<div
*ngIf="
!featureData.oidcTokenExchange?.enabled &&
(featureData.oidcTokenExchange?.source === Source.SOURCE_SYSTEM ||
featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.DISABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.ENABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<cnsl-info-section class="feature-info">{{
'SETTING.FEATURES.OIDCTOKENEXCHANGE_DESCRIPTION' | translate
}}</cnsl-info-section>
</div>
<div class="feature-row" *ngIf="toggleStates.oidcTriggerIntrospectionProjections">
<span>{{ 'SETTING.FEATURES.OIDCTRIGGERINTROSPECTIONPROJECTIONS' | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="toggleStates.oidcTriggerIntrospectionProjections.state"
(change)="validateAndSave()"
name="displayview"
aria-label="Display View"
>
<mat-button-toggle [value]="ToggleState.INHERITED">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
<i
class="info-i las la-question-circle"
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
></i>
<div
*ngIf="
!!featureData.oidcTriggerIntrospectionProjections?.enabled &&
(featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_SYSTEM ||
featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
></div>
<div
*ngIf="
!featureData.oidcTriggerIntrospectionProjections?.enabled &&
(featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_SYSTEM ||
featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.DISABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.ENABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<cnsl-info-section class="feature-info">{{
'SETTING.FEATURES.OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION' | translate
}}</cnsl-info-section>
</div>
<div class="feature-row" *ngIf="toggleStates.userSchema">
<span>{{ 'SETTING.FEATURES.USERSCHEMA' | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="toggleStates.userSchema.state"
(change)="validateAndSave()"
name="displayview"
aria-label="Display View"
>
<mat-button-toggle [value]="ToggleState.INHERITED">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
<i
class="info-i las la-question-circle"
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
></i>
<div
*ngIf="
!!featureData.userSchema?.enabled &&
(featureData.userSchema?.source === Source.SOURCE_SYSTEM ||
featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
></div>
<div
*ngIf="
!featureData.userSchema?.enabled &&
(featureData.userSchema?.source === Source.SOURCE_SYSTEM ||
featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.DISABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.ENABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<cnsl-info-section class="feature-info">{{
'SETTING.FEATURES.USERSCHEMA_DESCRIPTION' | translate
}}</cnsl-info-section>
</div>
<div class="feature-row" *ngIf="toggleStates.actions">
<span>{{ 'SETTING.FEATURES.ACTIONS' | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="toggleStates.actions.state"
(change)="validateAndSave()"
name="displayview"
aria-label="Display View"
>
<mat-button-toggle [value]="ToggleState.INHERITED">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
<i
class="info-i las la-question-circle"
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
></i>
<div
*ngIf="
!!featureData.actions?.enabled &&
(featureData.actions?.source === Source.SOURCE_SYSTEM ||
featureData.actions?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot enabled"
></div>
<div
*ngIf="
!featureData.actions?.enabled &&
(featureData.actions?.source === Source.SOURCE_SYSTEM ||
featureData.actions?.source === Source.SOURCE_UNSPECIFIED)
"
class="current-dot disabled"
></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.DISABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.ENABLED">
<div class="toggle-row">
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<cnsl-info-section class="feature-info">{{ 'SETTING.FEATURES.ACTIONS_DESCRIPTION' | translate }}</cnsl-info-section>
</div>
</div>
</cnsl-card>
</div>
<ng-template #sourceLabel let-source="source" let-last="last">
<span class="state" *ngIf="source === Source.SOURCE_SYSTEM">
{{ 'SETTING.FEATURES.SOURCE.' + source | translate }}
</span>
</ng-template>
@@ -0,0 +1,69 @@
.feature-settings-wrapper {
.feature-title-row {
display: flex;
align-items: center;
h1 {
margin: 0;
}
a .icon {
font-size: 1.2rem;
height: 1.2rem;
line-height: 1.2rem;
}
}
.features {
.feature-row {
display: flex;
flex-direction: column;
.row {
display: flex;
align-items: center;
justify-content: space-between;
.buttongroup {
margin-right: 0.5rem;
margin-top: 0.5rem;
.toggle-row {
display: flex;
align-items: center;
i {
margin-right: 0.5rem;
}
.info-i {
font-size: 1.2rem;
margin-left: 0.5rem;
margin-right: 0;
}
.current-dot {
height: 8px;
width: 8px;
border-radius: 50%;
// background-color: rgb(84, 142, 230);
margin-left: 0.5rem;
&.enabled {
background-color: var(--success);
}
&.disabled {
background-color: var(--warn);
}
}
}
}
}
.feature-info {
margin-bottom: 1rem;
}
}
}
}
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FeaturesComponent } from './features.component';
describe('FeaturesComponent', () => {
let component: FeaturesComponent;
let fixture: ComponentFixture<FeaturesComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [FeaturesComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FeaturesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,255 @@
import { CommonModule } from '@angular/common';
import { Component, OnDestroy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { CardModule } from 'src/app/modules/card/card.module';
import { DisplayJsonDialogComponent } from 'src/app/modules/display-json-dialog/display-json-dialog.component';
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { Event } from 'src/app/proto/generated/zitadel/event_pb';
import { Source } from 'src/app/proto/generated/zitadel/feature/v2beta/feature_pb';
import {
GetInstanceFeaturesResponse,
SetInstanceFeaturesRequest,
} from 'src/app/proto/generated/zitadel/feature/v2beta/instance_pb';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { FeatureService } from 'src/app/services/feature.service';
import { ToastService } from 'src/app/services/toast.service';
enum ToggleState {
ENABLED = 'ENABLED',
DISABLED = 'DISABLED',
INHERITED = 'INHERITED',
}
type FeatureState = { source: Source; state: ToggleState };
type ToggleStates = {
loginDefaultOrg?: FeatureState;
oidcTriggerIntrospectionProjections?: FeatureState;
oidcLegacyIntrospection?: FeatureState;
userSchema?: FeatureState;
oidcTokenExchange?: FeatureState;
actions?: FeatureState;
};
@Component({
imports: [
CommonModule,
FormsModule,
MatButtonToggleModule,
HasRolePipeModule,
MatIconModule,
CardModule,
TranslateModule,
MatButtonModule,
MatCheckboxModule,
InfoSectionModule,
MatTooltipModule,
HasRoleModule,
],
standalone: true,
selector: 'cnsl-features',
templateUrl: './features.component.html',
styleUrls: ['./features.component.scss'],
})
export class FeaturesComponent implements OnDestroy {
private destroy$: Subject<void> = new Subject();
public _loading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public featureData: GetInstanceFeaturesResponse.AsObject | undefined = undefined;
public toggleStates: ToggleStates | undefined = undefined;
public Source: any = Source;
public ToggleState: any = ToggleState;
constructor(
private featureService: FeatureService,
private breadcrumbService: BreadcrumbService,
private toast: ToastService,
private dialog: MatDialog,
) {
const breadcrumbs = [
new Breadcrumb({
type: BreadcrumbType.INSTANCE,
name: 'Instance',
routerLink: ['/instance'],
}),
];
this.breadcrumbService.setBreadcrumb(breadcrumbs);
this.getFeatures(true);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
public openDialog(event: Event): void {
this.dialog.open(DisplayJsonDialogComponent, {
data: {
event: event,
},
width: '450px',
});
}
public validateAndSave() {
this.featureService.resetInstanceFeatures().then(() => {
const req = new SetInstanceFeaturesRequest();
let changed = false;
console.log(this.toggleStates);
if (this.toggleStates?.loginDefaultOrg?.state !== ToggleState.INHERITED) {
req.setLoginDefaultOrg(this.toggleStates?.loginDefaultOrg?.state === ToggleState.ENABLED);
changed = true;
}
if (this.toggleStates?.oidcTriggerIntrospectionProjections?.state !== ToggleState.INHERITED) {
req.setOidcTriggerIntrospectionProjections(
this.toggleStates?.oidcTriggerIntrospectionProjections?.state === ToggleState.ENABLED,
);
changed = true;
}
if (this.toggleStates?.oidcLegacyIntrospection?.state !== ToggleState.INHERITED) {
req.setOidcLegacyIntrospection(this.toggleStates?.oidcLegacyIntrospection?.state === ToggleState.ENABLED);
changed = true;
}
if (this.toggleStates?.userSchema?.state !== ToggleState.INHERITED) {
req.setUserSchema(this.toggleStates?.userSchema?.state === ToggleState.ENABLED);
changed = true;
}
if (this.toggleStates?.oidcTokenExchange?.state !== ToggleState.INHERITED) {
req.setOidcTokenExchange(this.toggleStates?.oidcTokenExchange?.state === ToggleState.ENABLED);
changed = true;
}
if (this.toggleStates?.actions?.state !== ToggleState.INHERITED) {
req.setActions(this.toggleStates?.actions?.state === ToggleState.ENABLED);
changed = true;
}
if (changed) {
this.featureService
.setInstanceFeatures(req)
.then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
}
});
}
private getFeatures(inheritance: boolean) {
this.featureService.getInstanceFeatures(inheritance).then((instanceFeaturesResponse) => {
this.featureData = instanceFeaturesResponse.toObject();
console.log(this.featureData);
this.toggleStates = {
loginDefaultOrg: {
source: this.featureData.loginDefaultOrg?.source || Source.SOURCE_SYSTEM,
state:
this.featureData.loginDefaultOrg?.source === Source.SOURCE_SYSTEM ||
this.featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED
? ToggleState.INHERITED
: !!this.featureData.loginDefaultOrg?.enabled
? ToggleState.ENABLED
: ToggleState.DISABLED,
},
oidcTriggerIntrospectionProjections: {
source: this.featureData.oidcTriggerIntrospectionProjections?.source || Source.SOURCE_SYSTEM,
state:
this.featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_SYSTEM ||
this.featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED
? ToggleState.INHERITED
: !!this.featureData.oidcTriggerIntrospectionProjections?.enabled
? ToggleState.ENABLED
: ToggleState.DISABLED,
},
oidcLegacyIntrospection: {
source: this.featureData.oidcLegacyIntrospection?.source || Source.SOURCE_SYSTEM,
state:
this.featureData.oidcLegacyIntrospection?.source === Source.SOURCE_SYSTEM ||
this.featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED
? ToggleState.INHERITED
: !!this.featureData.oidcLegacyIntrospection?.enabled
? ToggleState.ENABLED
: ToggleState.DISABLED,
},
userSchema: {
source: this.featureData.userSchema?.source || Source.SOURCE_SYSTEM,
state:
this.featureData.userSchema?.source === Source.SOURCE_SYSTEM ||
this.featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED
? ToggleState.INHERITED
: !!this.featureData.userSchema?.enabled
? ToggleState.ENABLED
: ToggleState.DISABLED,
},
oidcTokenExchange: {
source: this.featureData.oidcTokenExchange?.source || Source.SOURCE_SYSTEM,
state:
this.featureData.oidcTokenExchange?.source === Source.SOURCE_SYSTEM ||
this.featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED
? ToggleState.INHERITED
: !!this.featureData.oidcTokenExchange?.enabled
? ToggleState.ENABLED
: ToggleState.DISABLED,
},
actions: {
source: Source.SOURCE_SYSTEM,
state:
this.featureData.actions?.source === Source.SOURCE_SYSTEM ||
this.featureData.actions?.source === Source.SOURCE_UNSPECIFIED
? ToggleState.INHERITED
: !!this.featureData.actions?.enabled
? ToggleState.ENABLED
: ToggleState.DISABLED,
},
};
});
}
public resetSettings(): void {
this.featureService
.resetInstanceFeatures()
.then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.getFeatures(true);
}, 1000);
})
.catch((error) => {
this.toast.showError(error);
});
}
public saveFeatures(): void {
if (this.featureData) {
const req = new SetInstanceFeaturesRequest();
req.setLoginDefaultOrg(!!this.featureData.loginDefaultOrg?.enabled);
req.setOidcLegacyIntrospection(!!this.featureData.oidcLegacyIntrospection?.enabled);
req.setOidcTokenExchange(!!this.featureData.oidcTokenExchange?.enabled);
req.setOidcTriggerIntrospectionProjections(!!this.featureData.oidcTriggerIntrospectionProjections?.enabled);
req.setUserSchema(!!this.featureData.userSchema?.enabled);
this.featureService
.setInstanceFeatures(req)
.then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
}
}
}