Files
zitadel/console/src/app/modules/actions-two/actions-two-actions/actions-two-actions-table/actions-two-actions-table.component.ts
Ramon 3348acdbab feat: Actions V2 improvements in console (#9759)
# Which Problems Are Solved
This PR allows one to edit the order of Actions V2 Targets in an
Execution. Editing of Targets was also added back again.

# How the Problems Are Solved
One of the changes is the addition of the CorrectlyTypedExecution which
restricts the Grpc types a bit more to make working with them easier.
Some fields may be optional in the Grpc Protobuf but in reality are
always set.
Typings were generally improved to make them more accurate and safer to
work with.

# Additional Changes
Removal of the Actions V2 Feature flag as it will be enabled by default
anyways.

# Additional Context
This pr used some advanced Angular Signals logic which is very
interesting for future PR's.
- Part of the tasks from #7248

---------

Co-authored-by: Max Peintner <peintnerm@gmail.com>
(cherry picked from commit 56e0df67d5)
2025-04-29 13:04:56 +02:00

108 lines
3.3 KiB
TypeScript

import { ChangeDetectionStrategy, Component, computed, effect, EventEmitter, Input, Output } from '@angular/core';
import { combineLatestWith, Observable, ReplaySubject } from 'rxjs';
import { filter, map, startWith } from 'rxjs/operators';
import { MatTableDataSource } from '@angular/material/table';
import { Target } from '@zitadel/proto/zitadel/action/v2beta/target_pb';
import { toSignal } from '@angular/core/rxjs-interop';
import { CorrectlyTypedExecution } from '../../actions-two-add-action/actions-two-add-action-dialog.component';
@Component({
selector: 'cnsl-actions-two-actions-table',
templateUrl: './actions-two-actions-table.component.html',
styleUrls: ['./actions-two-actions-table.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ActionsTwoActionsTableComponent {
@Output()
public readonly refresh = new EventEmitter<void>();
@Output()
public readonly selected = new EventEmitter<CorrectlyTypedExecution>();
@Output()
public readonly delete = new EventEmitter<CorrectlyTypedExecution>();
@Input({ required: true })
public set executions(executions: CorrectlyTypedExecution[] | null) {
this.executions$.next(executions);
}
@Input({ required: true })
public set targets(targets: Target[] | null) {
this.targets$.next(targets);
}
private readonly executions$ = new ReplaySubject<CorrectlyTypedExecution[] | null>(1);
private readonly targets$ = new ReplaySubject<Target[] | null>(1);
protected readonly dataSource = this.getDataSource();
protected readonly loading = this.getLoading();
private getDataSource() {
const executions$: Observable<CorrectlyTypedExecution[]> = this.executions$.pipe(filter(Boolean), startWith([]));
const executionsSignal = toSignal(executions$, { requireSync: true });
const targetsMapSignal = this.getTargetsMap();
const dataSignal = computed(() => {
const executions = executionsSignal();
const targetsMap = targetsMapSignal();
if (targetsMap.size === 0) {
return [];
}
return executions.map((execution) => {
const mappedTargets = execution.targets.map((target) => {
const targetType = targetsMap.get(target.type.value);
if (!targetType) {
throw new Error(`Target with id ${target.type.value} not found`);
}
return targetType;
});
return { execution, mappedTargets };
});
});
const dataSource = new MatTableDataSource(dataSignal());
effect(() => {
const data = dataSignal();
if (dataSource.data !== data) {
dataSource.data = data;
}
});
return dataSource;
}
private getTargetsMap() {
const targets$ = this.targets$.pipe(filter(Boolean), startWith([] as Target[]));
const targetsSignal = toSignal(targets$, { requireSync: true });
return computed(() => {
const map = new Map<string, Target>();
for (const target of targetsSignal()) {
map.set(target.id, target);
}
return map;
});
}
private getLoading() {
const loading$ = this.executions$.pipe(
combineLatestWith(this.targets$),
map(([executions, targets]) => executions === null || targets === null),
startWith(true),
);
return toSignal(loading$, { requireSync: true });
}
protected trackTarget(_: number, target: Target) {
return target.id;
}
}