feat(execution): refine tree and subflow layouts
This commit is contained in:
parent
14f78b79ce
commit
4d92cbc51c
|
|
@ -16,13 +16,58 @@
|
|||
}
|
||||
|
||||
.tasks-executor-rail-list {
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tasks-executor-rail-tree {
|
||||
.tasks-executor-tree-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tasks-executor-tree-panel-collapsed {
|
||||
flex: 0 0 auto;
|
||||
max-height: 45%;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.tasks-executor-tree-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #dbe2ea;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
color: #334155;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.tasks-executor-tree-toggle-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
transform: rotate(180deg);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.tasks-executor-tree-toggle-icon-open {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.tasks-executor-rail-tree {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.interactive-subflow-switcher {
|
||||
|
|
|
|||
|
|
@ -10,12 +10,25 @@
|
|||
</app-tasks-executions-list>
|
||||
|
||||
@if (selectedExecution(); as run) {
|
||||
<app-execution-tree
|
||||
class="tasks-executor-rail-tree"
|
||||
[rootExecution]="run"
|
||||
[selectedExecutionId]="displayedExecution()?.id ?? null"
|
||||
(executionSelected)="selectTreeExecution($event)">
|
||||
</app-execution-tree>
|
||||
<section class="tasks-executor-tree-panel" [class.tasks-executor-tree-panel-collapsed]="!executionTreeOpen()">
|
||||
<button
|
||||
type="button"
|
||||
class="tasks-executor-tree-toggle"
|
||||
[attr.aria-expanded]="executionTreeOpen()"
|
||||
(click)="toggleExecutionTree()">
|
||||
<span>Execution tree</span>
|
||||
<span class="tasks-executor-tree-toggle-icon" [class.tasks-executor-tree-toggle-icon-open]="executionTreeOpen()">⌃</span>
|
||||
</button>
|
||||
|
||||
@if (executionTreeOpen()) {
|
||||
<app-execution-tree
|
||||
class="tasks-executor-rail-tree"
|
||||
[rootExecution]="run"
|
||||
[selectedExecutionId]="displayedExecution()?.id ?? null"
|
||||
(executionSelected)="selectTreeExecution($event)">
|
||||
</app-execution-tree>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,16 @@ describe('TasksExecutor', () => {
|
|||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('toggles the execution tree panel', () => {
|
||||
expect(component.executionTreeOpen()).toBe(true);
|
||||
|
||||
component.toggleExecutionTree();
|
||||
expect(component.executionTreeOpen()).toBe(false);
|
||||
|
||||
component.toggleExecutionTree();
|
||||
expect(component.executionTreeOpen()).toBe(true);
|
||||
});
|
||||
|
||||
it('follows only container steps waiting for a child subflow', () => {
|
||||
const execution = {
|
||||
id: 'parent-1',
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ export class TasksExecutor {
|
|||
|
||||
readonly selectedExecutionId = signal<string | null>(null);
|
||||
readonly requestedExecutionId = signal<string | null>(null);
|
||||
readonly executionTreeOpen = signal(true);
|
||||
|
||||
readonly selectedExecution = computed<TaskExecution | null>(() => {
|
||||
const selectedId = this.selectedExecutionId();
|
||||
|
|
@ -240,6 +241,10 @@ export class TasksExecutor {
|
|||
this.manualTreeSelectionId.set(selection.executionId);
|
||||
}
|
||||
|
||||
toggleExecutionTree() {
|
||||
this.executionTreeOpen.update((open) => !open);
|
||||
}
|
||||
|
||||
private resolveExecutionById(executionId: string | null): TaskExecution | null {
|
||||
if (!executionId) return null;
|
||||
return this.executionDetails().find((execution) => execution.id === executionId)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
:host {
|
||||
display: block;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
:host(.execution-tree-empty) {
|
||||
|
|
@ -8,7 +10,9 @@
|
|||
|
||||
.execution-tree-shell {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
|
|
@ -16,16 +20,6 @@
|
|||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.execution-tree-title {
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #64748b;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.execution-tree-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@if (rootExecution(); as root) {
|
||||
@if (hasContainerContent()) {
|
||||
<div class="execution-tree-shell">
|
||||
<div class="execution-tree-title">Execution tree</div>
|
||||
<div class="execution-tree-body">
|
||||
<ng-container *ngTemplateOutlet="execNode; context: { $implicit: root, depth: 0 }"></ng-container>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -460,16 +460,17 @@ button.llm-node-bias-summary:focus-visible {
|
|||
|
||||
.llm-pill-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.llm-pill-context {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
font-size: 9px;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
|
|
|
|||
|
|
@ -150,17 +150,22 @@
|
|||
}
|
||||
|
||||
.execution-aside-tab {
|
||||
flex: 0 0 auto;
|
||||
border: 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
padding: 9px 9px;
|
||||
padding: 9px 6px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.execution-aside-tabs {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.execution-aside-tab-active {
|
||||
border-bottom-color: #2563eb;
|
||||
color: #1d4ed8;
|
||||
|
|
|
|||
|
|
@ -160,8 +160,8 @@
|
|||
</button>
|
||||
|
||||
@if (contextAsideOpen()) {
|
||||
<aside class="w-90 border border-slate-200 rounded-md bg-white min-h-0 overflow-hidden flex flex-col">
|
||||
<div class="flex border-b border-slate-200 bg-slate-50 px-2 pt-2">
|
||||
<aside class="w-[22.5rem] shrink-0 border border-slate-200 rounded-md bg-white min-h-0 overflow-hidden flex flex-col">
|
||||
<div class="execution-aside-tabs flex border-b border-slate-200 bg-slate-50 px-2 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
class="execution-aside-tab"
|
||||
|
|
@ -196,7 +196,7 @@
|
|||
class="execution-aside-tab"
|
||||
[class.execution-aside-tab-active]="activeAsideTab() === 'bias-reports'"
|
||||
(click)="selectAsideTab('bias-reports')">
|
||||
Bias impact reports
|
||||
Bias impact
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -105,11 +105,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if (flow()) {
|
||||
@if (flow() && !editingSubflow()) {
|
||||
<div class="title-toolbar-meta">
|
||||
@if (editingSubflow()) {
|
||||
<span><span class="title-toolbar-meta-label">Main flow:</span> {{ flow()!.name }}</span>
|
||||
}
|
||||
<span><span class="title-toolbar-meta-label">Author:</span> {{ flow()!.author }}</span>
|
||||
<span><span class="title-toolbar-meta-label">Created:</span> {{ flow()!.createdAt | date:'dd/MM/yyyy HH:mm' }}</span>
|
||||
<span><span class="title-toolbar-meta-label">Updated:</span> {{ flow()!.updatedAt | date:'dd/MM/yyyy HH:mm' }}</span>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,50 @@ describe('TitleToolbar', () => {
|
|||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hides parent metadata and global inputs while editing a subflow', async () => {
|
||||
editorState.currentFlow.set({
|
||||
...makeFlow(),
|
||||
description: 'Main flow description',
|
||||
data: {
|
||||
blocks: [],
|
||||
containers: [{
|
||||
id: 'container-1',
|
||||
name: 'Container',
|
||||
typeName: 'LoopContainer',
|
||||
nodeFamily: 'container',
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
specificConfiguration: {
|
||||
subFlow: {
|
||||
blocks: [],
|
||||
containers: [],
|
||||
connections: [],
|
||||
dependencies: [],
|
||||
globalInputs: [{ name: 'topic', type: 'TEXT', multiple: false }],
|
||||
lanes: []
|
||||
}
|
||||
}
|
||||
}],
|
||||
connections: [],
|
||||
dependencies: [],
|
||||
globalInputs: [{ name: 'rootInput', type: 'TEXT', multiple: false }],
|
||||
lanes: []
|
||||
}
|
||||
});
|
||||
editorState.openSubflow([{ containerId: 'container-1', configurationPath: 'subFlow' }]);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const host = fixture.nativeElement as HTMLElement;
|
||||
expect(host.textContent).not.toContain('Description:');
|
||||
expect(host.textContent).not.toContain('Global Inputs');
|
||||
expect(host.textContent).not.toContain('Author:');
|
||||
expect(host.textContent).not.toContain('Created:');
|
||||
expect(host.textContent).not.toContain('Updated:');
|
||||
expect(host.textContent).not.toContain('Main flow:');
|
||||
});
|
||||
|
||||
describe('lanes', () => {
|
||||
beforeEach(() => {
|
||||
editorState.currentFlow.set(makeFlow());
|
||||
|
|
|
|||
Loading…
Reference in New Issue