mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-01 03:52:14 +00:00
fix: Improve Actions V2 Texts and reenable in settings (#9814)
# Which Problems Are Solved This pr includes improved texts to make the usage of Actions V2 more easy. Since the removal of the Actions V2 Feature Flag we removed the code that checks if it's enabled in the settings sidenav. # How the Problems Are Solved Added new texts to translations. Removed sidenav logic that checks for Actions V2 Feature Flag # Additional Context - Part of #7248 - Part of #9688 --------- Co-authored-by: Max Peintner <[email protected]> Co-authored-by: Max Peintner <[email protected]>
This commit is contained in:
co-authored by
Max Peintner
Max Peintner
parent
ce823c9176
commit
d930a09cb0
+2
-2
@@ -56,9 +56,9 @@ export class ActionsTwoActionsTableComponent {
|
||||
|
||||
return executions.map((execution) => {
|
||||
const mappedTargets = execution.targets.map((target) => {
|
||||
const targetType = targetsMap.get(target.type.value);
|
||||
const targetType = targetsMap.get(target);
|
||||
if (!targetType) {
|
||||
throw new Error(`Target with id ${target.type.value} not found`);
|
||||
throw new Error(`Target with id ${target} not found`);
|
||||
}
|
||||
return targetType;
|
||||
});
|
||||
|
||||
+3
@@ -1,4 +1,7 @@
|
||||
<h2>{{ 'ACTIONSTWO.EXECUTION.TITLE' | translate }}</h2>
|
||||
<cnsl-info-section [type]="InfoSectionType.ALERT">
|
||||
{{ 'ACTIONSTWO.BETA_NOTE' | translate }}
|
||||
</cnsl-info-section>
|
||||
<p class="cnsl-secondary-text">{{ 'ACTIONSTWO.EXECUTION.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<cnsl-actions-two-actions-table
|
||||
|
||||
+3
@@ -15,6 +15,7 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { MessageInitShape } from '@bufbuild/protobuf';
|
||||
import { SetExecutionRequestSchema } from '@zitadel/proto/zitadel/action/v2beta/action_service_pb';
|
||||
import { Target } from '@zitadel/proto/zitadel/action/v2beta/target_pb';
|
||||
import { InfoSectionType } from '../../info-section/info-section.component';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-actions-two-actions',
|
||||
@@ -110,4 +111,6 @@ export class ActionsTwoActionsComponent {
|
||||
this.toast.showError(error);
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly InfoSectionType = InfoSectionType;
|
||||
}
|
||||
|
||||
+4
-15
@@ -10,12 +10,7 @@ import {
|
||||
} from './actions-two-add-action-condition/actions-two-add-action-condition.component';
|
||||
import { ActionsTwoAddActionTargetComponent } from './actions-two-add-action-target/actions-two-add-action-target.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
Condition,
|
||||
Execution,
|
||||
ExecutionTargetType,
|
||||
ExecutionTargetTypeSchema,
|
||||
} from '@zitadel/proto/zitadel/action/v2beta/execution_pb';
|
||||
import { Condition, Execution } from '@zitadel/proto/zitadel/action/v2beta/execution_pb';
|
||||
import { Subject } from 'rxjs';
|
||||
import { SetExecutionRequestSchema } from '@zitadel/proto/zitadel/action/v2beta/action_service_pb';
|
||||
|
||||
@@ -27,11 +22,8 @@ enum Page {
|
||||
|
||||
export type CorrectlyTypedCondition = Condition & { conditionType: Extract<Condition['conditionType'], { case: string }> };
|
||||
|
||||
type CorrectlyTypedTargets = { type: Extract<ExecutionTargetType['type'], { case: 'target' }> };
|
||||
|
||||
export type CorrectlyTypedExecution = Omit<Execution, 'targets' | 'condition'> & {
|
||||
export type CorrectlyTypedExecution = Omit<Execution, 'condition'> & {
|
||||
condition: CorrectlyTypedCondition;
|
||||
targets: CorrectlyTypedTargets[];
|
||||
};
|
||||
|
||||
export const correctlyTypeExecution = (execution: Execution): CorrectlyTypedExecution => {
|
||||
@@ -48,9 +40,6 @@ export const correctlyTypeExecution = (execution: Execution): CorrectlyTypedExec
|
||||
return {
|
||||
...execution,
|
||||
condition,
|
||||
targets: execution.targets
|
||||
.map(({ type }) => ({ type }))
|
||||
.filter((target): target is CorrectlyTypedTargets => target.type.case === 'target'),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -81,7 +70,7 @@ export class ActionTwoAddActionDialogComponent {
|
||||
|
||||
protected readonly typeSignal = signal<ConditionType>('request');
|
||||
protected readonly conditionSignal = signal<MessageInitShape<typeof SetExecutionRequestSchema>['condition']>(undefined);
|
||||
protected readonly targetsSignal = signal<MessageInitShape<typeof ExecutionTargetTypeSchema>[]>([]);
|
||||
protected readonly targetsSignal = signal<string[]>([]);
|
||||
|
||||
protected readonly continueSubject = new Subject<void>();
|
||||
|
||||
@@ -112,7 +101,7 @@ export class ActionTwoAddActionDialogComponent {
|
||||
this.targetsSignal.set(data.execution.targets);
|
||||
this.typeSignal.set(data.execution.condition.conditionType.case);
|
||||
this.conditionSignal.set(data.execution.condition);
|
||||
this.preselectedTargetIds = data.execution.targets.map((target) => target.type.value);
|
||||
this.preselectedTargetIds = data.execution.targets;
|
||||
|
||||
this.page.set(Page.Target); // Set the initial page based on the provided execution data
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
<form class="form-grid" [formGroup]="form()" (ngSubmit)="submit()">
|
||||
<form *ngIf="form$ | async as form" class="form-grid" [formGroup]="form" (ngSubmit)="submit()">
|
||||
<p class="target-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.TARGET.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<cnsl-form-field class="full-width">
|
||||
@@ -8,9 +8,9 @@
|
||||
#trigger="matAutocompleteTrigger"
|
||||
#input
|
||||
type="text"
|
||||
[formControl]="form().controls.autocomplete"
|
||||
[formControl]="form.controls.autocomplete"
|
||||
[matAutocomplete]="autoservice"
|
||||
(keydown.enter)="handleEnter($event); input.blur(); trigger.closePanel()"
|
||||
(keydown.enter)="handleEnter($event, form); input.blur(); trigger.closePanel()"
|
||||
/>
|
||||
<mat-autocomplete #autoservice="matAutocomplete">
|
||||
<mat-option *ngIf="targets().state === 'loading'" class="is-loading">
|
||||
@@ -19,7 +19,7 @@
|
||||
<mat-option
|
||||
*ngFor="let target of selectableTargets(); trackBy: trackTarget"
|
||||
#option
|
||||
(click)="addTarget(target); option.deselect()"
|
||||
(click)="addTarget(target, form); option.deselect()"
|
||||
[value]="target.name"
|
||||
>
|
||||
{{ target.name }}
|
||||
@@ -27,7 +27,7 @@
|
||||
</mat-autocomplete>
|
||||
</cnsl-form-field>
|
||||
|
||||
<table mat-table cdkDropList (cdkDropListDropped)="drop($event)" [dataSource]="dataSource" [trackBy]="trackTarget">
|
||||
<table mat-table cdkDropList (cdkDropListDropped)="drop($event, form)" [dataSource]="dataSource" [trackBy]="trackTarget">
|
||||
<ng-container matColumnDef="order">
|
||||
<th mat-header-cell *matHeaderCellDef>Reorder</th>
|
||||
<td mat-cell *cnslCellDef="let row; let i = index; dataSource: dataSource">
|
||||
@@ -48,7 +48,7 @@
|
||||
actions
|
||||
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
|
||||
color="warn"
|
||||
(click)="removeTarget(i)"
|
||||
(click)="removeTarget(i, form)"
|
||||
mat-icon-button
|
||||
>
|
||||
<i class="las la-trash"></i>
|
||||
@@ -65,7 +65,7 @@
|
||||
{{ 'ACTIONS.BACK' | translate }}
|
||||
</button>
|
||||
<span class="fill-space"></span>
|
||||
<button color="primary" [disabled]="form().invalid" mat-raised-button type="submit">
|
||||
<button color="primary" [disabled]="form.invalid" mat-raised-button type="submit">
|
||||
{{ 'ACTIONS.CONTINUE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+48
-44
@@ -14,7 +14,7 @@ import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { FormBuilder, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ReplaySubject, switchMap } from 'rxjs';
|
||||
import { ObservedValueOf, ReplaySubject, shareReplay, switchMap } from 'rxjs';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
import { ActionService } from 'src/app/services/action.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
@@ -23,14 +23,13 @@ import { InputModule } from 'src/app/modules/input/input.module';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MessageInitShape } from '@bufbuild/protobuf';
|
||||
import { Target } from '@zitadel/proto/zitadel/action/v2beta/target_pb';
|
||||
import { ExecutionTargetTypeSchema } from '@zitadel/proto/zitadel/action/v2beta/execution_pb';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { ActionConditionPipeModule } from 'src/app/pipes/action-condition-pipe/action-condition-pipe.module';
|
||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||
import { startWith } from 'rxjs/operators';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { TypeSafeCellDefModule } from 'src/app/directives/type-safe-cell-def/type-safe-cell-def.module';
|
||||
import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { minArrayLengthValidator } from '../../../form-field/validators/validators';
|
||||
import { ProjectRoleChipModule } from '../../../project-role-chip/project-role-chip.module';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
@@ -72,11 +71,12 @@ export class ActionsTwoAddActionTargetComponent {
|
||||
}
|
||||
|
||||
@Output() public readonly back = new EventEmitter<void>();
|
||||
@Output() public readonly continue = new EventEmitter<MessageInitShape<typeof ExecutionTargetTypeSchema>[]>();
|
||||
@Output() public readonly continue = new EventEmitter<string[]>();
|
||||
|
||||
private readonly preselectedTargetIds$ = new ReplaySubject<string[]>(1);
|
||||
|
||||
protected readonly form: ReturnType<typeof this.buildForm>;
|
||||
protected readonly form$: ReturnType<typeof this.buildForm>;
|
||||
|
||||
protected readonly targets: ReturnType<typeof this.listTargets>;
|
||||
private readonly selectedTargetIds: Signal<string[]>;
|
||||
protected readonly selectableTargets: Signal<Target[]>;
|
||||
@@ -87,26 +87,27 @@ export class ActionsTwoAddActionTargetComponent {
|
||||
private readonly actionService: ActionService,
|
||||
private readonly toast: ToastService,
|
||||
) {
|
||||
this.form = this.buildForm();
|
||||
this.form$ = this.buildForm().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||
this.targets = this.listTargets();
|
||||
|
||||
this.selectedTargetIds = this.getSelectedTargetIds(this.form);
|
||||
this.selectableTargets = this.getSelectableTargets(this.targets, this.selectedTargetIds);
|
||||
this.selectedTargetIds = this.getSelectedTargetIds(this.form$);
|
||||
this.selectableTargets = this.getSelectableTargets(this.targets, this.selectedTargetIds, this.form$);
|
||||
this.dataSource = this.getDataSource(this.targets, this.selectedTargetIds);
|
||||
}
|
||||
|
||||
private buildForm() {
|
||||
const preselectedTargetIds = toSignal(this.preselectedTargetIds$, { initialValue: [] as string[] });
|
||||
|
||||
return computed(() => {
|
||||
return this.fb.group({
|
||||
autocomplete: new FormControl('', { nonNullable: true }),
|
||||
selectedTargetIds: new FormControl(preselectedTargetIds(), {
|
||||
nonNullable: true,
|
||||
validators: [minArrayLengthValidator(1)],
|
||||
}),
|
||||
});
|
||||
});
|
||||
return this.preselectedTargetIds$.pipe(
|
||||
startWith([] as string[]),
|
||||
map((preselectedTargetIds) => {
|
||||
return this.fb.group({
|
||||
autocomplete: new FormControl('', { nonNullable: true }),
|
||||
selectedTargetIds: new FormControl(preselectedTargetIds, {
|
||||
nonNullable: true,
|
||||
validators: [minArrayLengthValidator(1)],
|
||||
}),
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private listTargets() {
|
||||
@@ -129,25 +130,35 @@ export class ActionsTwoAddActionTargetComponent {
|
||||
return computed(targetsSignal);
|
||||
}
|
||||
|
||||
private getSelectedTargetIds(form: typeof this.form) {
|
||||
const selectedTargetIds$ = toObservable(form).pipe(
|
||||
startWith(form()),
|
||||
switchMap((form) => {
|
||||
const { selectedTargetIds } = form.controls;
|
||||
private getSelectedTargetIds(form$: typeof this.form$) {
|
||||
const selectedTargetIds$ = form$.pipe(
|
||||
switchMap(({ controls: { selectedTargetIds } }) => {
|
||||
return selectedTargetIds.valueChanges.pipe(startWith(selectedTargetIds.value));
|
||||
}),
|
||||
);
|
||||
return toSignal(selectedTargetIds$, { requireSync: true });
|
||||
}
|
||||
|
||||
private getSelectableTargets(targets: typeof this.targets, selectedTargetIds: Signal<string[]>) {
|
||||
return computed(() => {
|
||||
private getSelectableTargets(targets: typeof this.targets, selectedTargetIds: Signal<string[]>, form$: typeof this.form$) {
|
||||
const autocomplete$ = form$.pipe(
|
||||
switchMap(({ controls: { autocomplete } }) => {
|
||||
return autocomplete.valueChanges.pipe(startWith(autocomplete.value));
|
||||
}),
|
||||
);
|
||||
const autocompleteSignal = toSignal(autocomplete$, { requireSync: true });
|
||||
|
||||
const unselectedTargets = computed(() => {
|
||||
const targetsCopy = new Map(targets().targets);
|
||||
for (const selectedTargetId of selectedTargetIds()) {
|
||||
targetsCopy.delete(selectedTargetId);
|
||||
}
|
||||
return Array.from(targetsCopy.values());
|
||||
});
|
||||
|
||||
return computed(() => {
|
||||
const autocomplete = autocompleteSignal().toLowerCase();
|
||||
return unselectedTargets().filter(({ name }) => name.toLowerCase().includes(autocomplete));
|
||||
});
|
||||
}
|
||||
|
||||
private getDataSource(targetsSignal: typeof this.targets, selectedTargetIdsSignal: Signal<string[]>) {
|
||||
@@ -178,46 +189,39 @@ export class ActionsTwoAddActionTargetComponent {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
protected addTarget(target: Target) {
|
||||
const { selectedTargetIds } = this.form().controls;
|
||||
protected addTarget(target: Target, form: ObservedValueOf<typeof this.form$>) {
|
||||
const { selectedTargetIds } = form.controls;
|
||||
selectedTargetIds.setValue([target.id, ...selectedTargetIds.value]);
|
||||
this.form().controls.autocomplete.setValue('');
|
||||
form.controls.autocomplete.setValue('');
|
||||
}
|
||||
|
||||
protected removeTarget(index: number) {
|
||||
const { selectedTargetIds } = this.form().controls;
|
||||
protected removeTarget(index: number, form: ObservedValueOf<typeof this.form$>) {
|
||||
const { selectedTargetIds } = form.controls;
|
||||
const data = [...selectedTargetIds.value];
|
||||
data.splice(index, 1);
|
||||
selectedTargetIds.setValue(data);
|
||||
}
|
||||
|
||||
protected drop(event: CdkDragDrop<undefined>) {
|
||||
const { selectedTargetIds } = this.form().controls;
|
||||
protected drop(event: CdkDragDrop<undefined>, form: ObservedValueOf<typeof this.form$>) {
|
||||
const { selectedTargetIds } = form.controls;
|
||||
|
||||
const data = [...selectedTargetIds.value];
|
||||
moveItemInArray(data, event.previousIndex, event.currentIndex);
|
||||
selectedTargetIds.setValue(data);
|
||||
}
|
||||
|
||||
protected handleEnter(event: Event) {
|
||||
protected handleEnter(event: Event, form: ObservedValueOf<typeof this.form$>) {
|
||||
const selectableTargets = this.selectableTargets();
|
||||
if (selectableTargets.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
this.addTarget(selectableTargets[0]);
|
||||
this.addTarget(selectableTargets[0], form);
|
||||
}
|
||||
|
||||
protected submit() {
|
||||
const selectedTargets = this.selectedTargetIds().map((value) => ({
|
||||
type: {
|
||||
case: 'target' as const,
|
||||
value,
|
||||
},
|
||||
}));
|
||||
|
||||
this.continue.emit(selectedTargets);
|
||||
this.continue.emit(this.selectedTargetIds());
|
||||
}
|
||||
|
||||
protected trackTarget(_: number, target: Target) {
|
||||
|
||||
+3
@@ -26,6 +26,9 @@
|
||||
{{ 'ACTIONSTWO.TARGET.CREATE.TYPES.' + type | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<span class="name-hint cnsl-secondary-text types-description">
|
||||
{{ 'ACTIONSTWO.TARGET.CREATE.TYPES_DESCRIPTION' | translate }}
|
||||
</span>
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="full-width">
|
||||
|
||||
+4
@@ -23,3 +23,7 @@
|
||||
.name-hint {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.types-description {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
+3
@@ -1,4 +1,7 @@
|
||||
<h2>{{ 'ACTIONSTWO.TARGET.TITLE' | translate }}</h2>
|
||||
<cnsl-info-section [type]="InfoSectionType.ALERT">
|
||||
{{ 'ACTIONSTWO.BETA_NOTE' | translate }}
|
||||
</cnsl-info-section>
|
||||
<p class="cnsl-secondary-text">{{ 'ACTIONSTWO.TARGET.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<cnsl-actions-two-targets-table
|
||||
|
||||
+5
-1
@@ -12,6 +12,7 @@ import {
|
||||
CreateTargetRequestSchema,
|
||||
UpdateTargetRequestSchema,
|
||||
} from '@zitadel/proto/zitadel/action/v2beta/action_service_pb';
|
||||
import { InfoSectionType } from '../../info-section/info-section.component';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-actions-two-targets',
|
||||
@@ -76,7 +77,8 @@ export class ActionsTwoTargetsComponent {
|
||||
if ('id' in request) {
|
||||
await this.actionService.updateTarget(request);
|
||||
} else {
|
||||
await this.actionService.createTarget(request);
|
||||
const resp = await this.actionService.createTarget(request);
|
||||
console.log(`Your singing key: ${resp.signingKey}`);
|
||||
}
|
||||
|
||||
await new Promise((res) => setTimeout(res, 1000));
|
||||
@@ -86,4 +88,6 @@ export class ActionsTwoTargetsComponent {
|
||||
this.toast.showError(error);
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly InfoSectionType = InfoSectionType;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { ProjectRoleChipModule } from '../project-role-chip/project-role-chip.mo
|
||||
import { ActionConditionPipeModule } from 'src/app/pipes/action-condition-pipe/action-condition-pipe.module';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { InfoSectionModule } from '../info-section/info-section.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -47,6 +48,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
TypeSafeCellDefModule,
|
||||
ProjectRoleChipModule,
|
||||
ActionConditionPipeModule,
|
||||
InfoSectionModule,
|
||||
],
|
||||
exports: [ActionsTwoActionsComponent, ActionsTwoTargetsComponent, ActionsTwoTargetsTableComponent],
|
||||
})
|
||||
|
||||
@@ -231,6 +231,7 @@ export const ACTIONS: SidenavSetting = {
|
||||
// todo: figure out roles
|
||||
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
||||
},
|
||||
beta: true,
|
||||
};
|
||||
|
||||
export const ACTIONS_TARGETS: SidenavSetting = {
|
||||
@@ -241,4 +242,5 @@ export const ACTIONS_TARGETS: SidenavSetting = {
|
||||
// todo: figure out roles
|
||||
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
||||
},
|
||||
beta: true,
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
[attr.data-e2e]="'sidenav-element-' + setting.id"
|
||||
>
|
||||
<span>{{ setting.i18nKey | translate }}</span>
|
||||
<span class="state" *ngIf="setting?.beta">{{ 'SETTINGS.BETA' | translate }}</span>
|
||||
<mat-icon *ngIf="setting.showWarn" class="warn-icon" svgIcon="mdi_shield_alert"></mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.state {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
span {
|
||||
opacity: 1;
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface SidenavSetting {
|
||||
[PolicyComponentServiceType.ADMIN]?: string[];
|
||||
};
|
||||
showWarn?: boolean;
|
||||
beta?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
Reference in New Issue
Block a user