Skip to content

Commit b3b3727

Browse files
committed
Cleanup
1 parent 0c9779b commit b3b3727

81 files changed

Lines changed: 3173 additions & 3250 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/frontend/editor-ui/src/features/ai/instanceAi/__tests__/WorkflowSetupWizard.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ interface ContextOptions {
8585
function makeContext(isComplete: Ref<boolean>, options: ContextOptions = {}): WorkflowSetupContext {
8686
const sections = options.sections ?? [sectionA];
8787
const currentStepIndex = options.currentStepIndex ?? ref(0);
88-
const steps = computed<WorkflowSetupStep[]>(() => sections.map((section) => ({ section })));
88+
const steps = computed<WorkflowSetupStep[]>(() =>
89+
sections.map((section) => ({ kind: 'section', section })),
90+
);
8991

9092
const isStepHandled = (step: WorkflowSetupStep): boolean => {
91-
const sec = step.section;
92-
if (!sec) return false;
93+
if (step.kind !== 'section') return false;
9394
return isComplete.value || (options.isSkipped?.value ?? false);
9495
};
9596

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/__tests__/factories.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
22
import type { WorkflowSetupSection } from '../workflowSetup.types';
3+
import { buildSectionId } from '../workflowSetup.helpers';
34

45
export function makeSetupRequest(
56
overrides: Omit<Partial<InstanceAiWorkflowSetupNode>, 'node'> & {
@@ -50,7 +51,7 @@ export function makeWorkflowSetupSection(
5051
};
5152

5253
return {
53-
id: overrides.id ?? `${targetNodeName}:${credentialType ?? 'parameters'}`,
54+
id: overrides.id ?? buildSectionId(targetNodeName, credentialType),
5455
...(credentialType ? { credentialType } : {}),
5556
targetNodeName,
5657
node: finalNode,

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/components/WorkflowSetupCard.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ const renderComponent = createComponentRenderer(WorkflowSetupCard);
7575
function makeContext(section: WorkflowSetupSection): WorkflowSetupContext {
7676
return {
7777
sections: computed(() => [section]),
78-
steps: computed(() => [{ section }]),
78+
steps: computed(() => [{ kind: 'section', section }]),
7979
currentStepIndex: ref(0),
80-
activeStep: computed(() => ({ section })),
80+
activeStep: computed(() => ({ kind: 'section', section })),
8181
hasOtherUnhandledSteps: computed(() => false),
8282
canAdvanceToNextIncomplete: computed(() => false),
8383
credentialSelections: ref({}),

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/components/WorkflowSetupWizard.vue

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,16 @@ import WorkflowSetupCard from './WorkflowSetupCard.vue';
44
import WorkflowSetupGroupCard from './WorkflowSetupGroupCard.vue';
55
import WorkflowSetupWizardFooter from './WorkflowSetupWizardFooter.vue';
66
import { useWorkflowSetupContext } from '../composables/useWorkflowSetupContext';
7-
import { isWorkflowSetupGroupStep } from '../workflowSetup.types';
87
98
const ctx = useWorkflowSetupContext();
109
11-
const activeGroup = computed(() => {
12-
const step = ctx.activeStep.value;
13-
return step && isWorkflowSetupGroupStep(step) ? step.group : undefined;
14-
});
10+
const activeGroup = computed(() =>
11+
ctx.activeStep.value?.kind === 'group' ? ctx.activeStep.value.group : undefined,
12+
);
1513
16-
const activeSection = computed(() => {
17-
const step = ctx.activeStep.value;
18-
return step && !isWorkflowSetupGroupStep(step) ? step.section : undefined;
19-
});
14+
const activeSection = computed(() =>
15+
ctx.activeStep.value?.kind === 'section' ? ctx.activeStep.value.section : undefined,
16+
);
2017
2118
const groupKey = computed(() => {
2219
return activeGroup.value ? `group:${activeGroup.value.parentNode.name}` : undefined;

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/composables/useWorkflowSetupActions.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ function setupHarness(): Harness {
4747
credentialType: 'typeB',
4848
});
4949
const sections = computed(() => [sectionA, sectionB]);
50-
const steps = computed<WorkflowSetupStep[]>(() => [{ section: sectionA }, { section: sectionB }]);
50+
const steps = computed<WorkflowSetupStep[]>(() => [
51+
{ kind: 'section', section: sectionA },
52+
{ kind: 'section', section: sectionB },
53+
]);
5154
const currentStepIndex = ref(0);
5255
const activeStep = computed<WorkflowSetupStep | undefined>(
5356
() => steps.value[currentStepIndex.value],
@@ -278,6 +281,7 @@ describe('useWorkflowSetupActions', () => {
278281
const sections = computed(() => [sectionA, sectionB]);
279282
const steps = computed<WorkflowSetupStep[]>(() => [
280283
{
284+
kind: 'group',
281285
group: {
282286
parentNode: { name: 'Agent', type: 'agent', typeVersion: 1, id: 'agent-1' },
283287
subnodeSections: [sectionA, sectionB],
@@ -353,6 +357,7 @@ describe('useWorkflowSetupActions', () => {
353357
const sections = computed(() => [sectionA, sectionB]);
354358
const steps = computed<WorkflowSetupStep[]>(() => [
355359
{
360+
kind: 'group',
356361
group: {
357362
parentNode: { name: 'Agent', type: 'agent', typeVersion: 1, id: 'agent-1' },
358363
subnodeSections: [sectionA, sectionB],

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/composables/useWorkflowSetupActions.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,24 +137,21 @@ export function useWorkflowSetupActions(deps: {
137137

138138
isActionPending.value = true;
139139
try {
140-
// Skip only the incomplete sections in the active step. Already-complete
141-
// sections keep their input and continue to contribute to the apply
142-
// payload.
140+
// Skipping the active step only marks its incomplete sections — already-
141+
// complete sections still contribute to the apply payload.
143142
const stepSections = getStepSections(step);
144143
for (const section of stepSections) {
145144
if (!deps.inputs.isSectionComplete(section)) {
146145
deps.inputs.markSectionSkipped(section);
147146
}
148147
}
149148

150-
// Non-terminal: more steps still need handling — advance & wait.
151149
const next = nextUnhandledIndex.value;
152150
if (next >= 0) {
153151
deps.goToStep(next);
154152
return;
155153
}
156154

157-
// Terminal: every step is now complete or skipped.
158155
trackSetupInput();
159156
const completedPayload = deps.inputs.buildCompletedSetupPayload();
160157
const hasAnyCompleted =

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/composables/useWorkflowSetupGroupSections.ts

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,25 @@ import { useWorkflowSetupContext } from './useWorkflowSetupContext';
77
* Manages per-section expansion state inside a `WorkflowSetupGroup`.
88
*
99
* Only sub-node sections are collapsible: the parent section renders inline
10-
* as the group card's primary body and is always visible. The expansion
11-
* model therefore tracks `subnodeSections` only.
10+
* as the group card's primary body and is always visible.
1211
*
13-
* Sections are keyed by `section.id` (not `targetNodeName`) so the keys stay
14-
* stable as parent metadata changes.
15-
*
16-
* Behaviour:
17-
* - Initial state: open the first incomplete sub-node in display order.
18-
* - Auto-expand-next when a credential-only section completes (parent or
19-
* sub-node completion both trigger the next incomplete sub-node to open).
20-
* - Sections that contain parameters stay open so the user isn't interrupted
21-
* mid-typing.
12+
* Sections that contain parameters stay open so the user isn't interrupted
13+
* mid-typing — only credential-only sections auto-collapse on completion.
2214
*/
2315
export function useWorkflowSetupGroupSections(group: Ref<WorkflowSetupGroup>) {
2416
const ctx = useWorkflowSetupContext();
2517

26-
const expandableSections = computed<WorkflowSetupSection[]>(() => group.value.subnodeSections);
27-
28-
// Watching parent + sub-node completion together lets a parent's credential
29-
// completion still drive the next sub-node to open.
3018
const allSections = computed<WorkflowSetupSection[]>(() => getGroupSections(group.value));
3119

3220
const expandedSections = reactive<Record<string, boolean>>({});
3321

3422
function initExpandState() {
35-
for (const section of expandableSections.value) {
23+
for (const section of group.value.subnodeSections) {
3624
if (!(section.id in expandedSections)) {
3725
expandedSections[section.id] = false;
3826
}
3927
}
40-
const firstIncomplete = expandableSections.value.find(
28+
const firstIncomplete = group.value.subnodeSections.find(
4129
(section) => !ctx.isSectionComplete(section),
4230
);
4331
if (firstIncomplete) {
@@ -50,32 +38,24 @@ export function useWorkflowSetupGroupSections(group: Ref<WorkflowSetupGroup>) {
5038
expandedSections[sectionId] = !expandedSections[sectionId];
5139
}
5240

53-
// Auto-expand-next on completion of a credential-only section.
54-
const prevSectionComplete = new Map<string, boolean>();
5541
watch(
56-
// `isSectionComplete` is reactive via deps it consumes; tracking the
57-
// completion state of every section keeps this responsive.
5842
() => allSections.value.map((section) => [section.id, ctx.isSectionComplete(section)] as const),
59-
(states) => {
43+
(states, prevStates) => {
44+
const prev = new Map(prevStates ?? []);
6045
for (const [sectionId, isComplete] of states) {
61-
const wasComplete = prevSectionComplete.get(sectionId) ?? false;
62-
if (!isComplete || wasComplete) continue;
46+
if (!isComplete || prev.get(sectionId)) continue;
6347
const section = allSections.value.find((s) => s.id === sectionId);
6448
if (!section || section.parameterNames.length > 0) continue;
6549
if (sectionId in expandedSections) {
6650
expandedSections[sectionId] = false;
6751
}
68-
const nextIncomplete = expandableSections.value.find(
52+
const nextIncomplete = group.value.subnodeSections.find(
6953
(s) => !ctx.isSectionComplete(s) && s.id !== sectionId,
7054
);
7155
if (nextIncomplete) {
7256
expandedSections[nextIncomplete.id] = true;
7357
}
7458
}
75-
prevSectionComplete.clear();
76-
for (const [sectionId, isComplete] of states) {
77-
prevSectionComplete.set(sectionId, isComplete);
78-
}
7959
},
8060
{ immediate: true },
8161
);

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/composables/useWorkflowSetupInputs.ts

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,13 @@ export function useWorkflowSetupInputs(deps: {
149149
const includeParams = (section: WorkflowSetupSection) =>
150150
!isSectionSkipped(section) && areParametersComplete(section);
151151

152-
return prunePayload({
153-
nodeCredentials: buildNodeCredentials(includeCredential),
154-
nodeParameters: buildNodeParameters(includeParams),
155-
});
152+
const nodeCredentials = buildNodeCredentials(includeCredential);
153+
const nodeParameters = buildNodeParameters(includeParams);
154+
155+
return {
156+
...(Object.keys(nodeCredentials).length > 0 ? { nodeCredentials } : {}),
157+
...(Object.keys(nodeParameters).length > 0 ? { nodeParameters } : {}),
158+
};
156159
}
157160

158161
function buildNodeCredentials(
@@ -188,16 +191,6 @@ export function useWorkflowSetupInputs(deps: {
188191
return out;
189192
}
190193

191-
function seedParameterValuesForNewSections(sections: WorkflowSetupSection[]) {
192-
let nextParameters: ParameterValuesMap | null = null;
193-
for (const section of sections) {
194-
if (parameterValues.value[section.targetNodeName]) continue;
195-
nextParameters ??= { ...parameterValues.value };
196-
nextParameters[section.targetNodeName] = section.node.parameters as INodeParameters;
197-
}
198-
if (nextParameters) parameterValues.value = nextParameters;
199-
}
200-
201194
function seedCredentialSelectionsForNewSections(
202195
newSections: WorkflowSetupSection[],
203196
): Array<{ id: string; type: string }> {
@@ -235,7 +228,6 @@ export function useWorkflowSetupInputs(deps: {
235228
const previousSectionIds = new Set(oldSections?.map((section) => section.id) ?? []);
236229
const newSections = sections.filter((section) => !previousSectionIds.has(section.id));
237230

238-
seedParameterValuesForNewSections(newSections);
239231
const credentialsToTest = seedCredentialSelectionsForNewSections(newSections);
240232
for (const credential of credentialsToTest) {
241233
testCredential(credential.id, credential.type);
@@ -288,12 +280,3 @@ function setCredentialSelectionForTargetNames(
288280

289281
return nextCredentialSelections;
290282
}
291-
292-
function prunePayload(payload: WorkflowSetupApplyPayload): WorkflowSetupApplyPayload {
293-
const result: WorkflowSetupApplyPayload = {};
294-
if (payload.nodeCredentials && Object.keys(payload.nodeCredentials).length > 0)
295-
result.nodeCredentials = payload.nodeCredentials;
296-
if (payload.nodeParameters && Object.keys(payload.nodeParameters).length > 0)
297-
result.nodeParameters = payload.nodeParameters;
298-
return result;
299-
}

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/composables/useWorkflowSetupSections.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
55
import { isHttpRequestNodeType } from '@/features/setupPanel/setupPanel.utils';
66
import { NodeHelpers, type INodeParameters } from 'n8n-workflow';
77
import type { WorkflowSetupSection } from '../workflowSetup.types';
8+
import { buildSectionId } from '../workflowSetup.helpers';
89

910
export function useWorkflowSetupSections(
1011
setupRequests: Ref<InstanceAiWorkflowSetupNode[]> | ComputedRef<InstanceAiWorkflowSetupNode[]>,
@@ -41,7 +42,7 @@ export function useWorkflowSetupSections(
4142
credentialType === undefined ? null : (req.node.credentials?.[credentialType]?.id ?? null);
4243

4344
const section: WorkflowSetupSection = {
44-
id: `${req.node.name}:${credentialType ?? 'parameters'}`,
45+
id: buildSectionId(req.node.name, credentialType),
4546
...(credentialType ? { credentialType } : {}),
4647
targetNodeName: req.node.name,
4748
node,

packages/frontend/editor-ui/src/features/ai/instanceAi/workflowSetup/composables/useWorkflowSetupSteps.test.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { computed, ref } from 'vue';
22
import { describe, expect, it } from 'vitest';
33
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
44
import { makeSetupRequest, makeWorkflowSetupSection } from '../__tests__/factories';
5-
import { isWorkflowSetupGroupStep, type WorkflowSetupSection } from '../workflowSetup.types';
5+
import type { WorkflowSetupSection } from '../workflowSetup.types';
66
import { useWorkflowSetupSteps } from './useWorkflowSetupSteps';
77

88
const agent = { name: 'Agent', type: 'agent', typeVersion: 1, id: 'agent-1' };
@@ -32,7 +32,7 @@ describe('useWorkflowSetupSteps', () => {
3232
const { steps } = harness(sections, requests);
3333

3434
expect(steps.value).toHaveLength(2);
35-
expect(steps.value.every((s) => !!s.section)).toBe(true);
35+
expect(steps.value.every((s) => s.kind === 'section')).toBe(true);
3636
});
3737

3838
it('inserts the group at the first sub-node position when the parent comes after the sub-nodes', () => {
@@ -50,13 +50,14 @@ describe('useWorkflowSetupSteps', () => {
5050
const { steps } = harness(sections, requests);
5151

5252
expect(steps.value).toHaveLength(2);
53-
expect(isWorkflowSetupGroupStep(steps.value[0])).toBe(true);
5453
const groupStep = steps.value[0];
55-
if (!groupStep.group) throw new Error('expected group step');
54+
if (groupStep.kind !== 'group') throw new Error('expected group step');
5655
expect(groupStep.group.parentNode.name).toBe('Agent');
5756
expect(groupStep.group.subnodeSections.map((s) => s.id)).toEqual(['Model:openAiApi']);
5857
expect(groupStep.group.parentSection?.id).toBe('Agent:foo');
59-
expect(steps.value[1].section?.targetNodeName).toBe('Standalone');
58+
const sectionStep = steps.value[1];
59+
if (sectionStep.kind !== 'section') throw new Error('expected section step');
60+
expect(sectionStep.section.targetNodeName).toBe('Standalone');
6061
});
6162

6263
it('inserts the group at the parent position when the parent comes before its sub-nodes', () => {
@@ -74,8 +75,10 @@ describe('useWorkflowSetupSteps', () => {
7475
const { steps } = harness(sections, requests);
7576

7677
expect(steps.value).toHaveLength(2);
77-
expect(isWorkflowSetupGroupStep(steps.value[0])).toBe(true);
78-
expect(steps.value[1].section?.targetNodeName).toBe('Standalone');
78+
expect(steps.value[0].kind).toBe('group');
79+
const sectionStep = steps.value[1];
80+
if (sectionStep.kind !== 'section') throw new Error('expected section step');
81+
expect(sectionStep.section.targetNodeName).toBe('Standalone');
7982
});
8083

8184
it('emits a group with no parentSection when the parent has no setup request', () => {
@@ -86,7 +89,7 @@ describe('useWorkflowSetupSteps', () => {
8689

8790
expect(steps.value).toHaveLength(1);
8891
const step = steps.value[0];
89-
if (!step.group) throw new Error('expected group');
92+
if (step.kind !== 'group') throw new Error('expected group');
9093
expect(step.group.parentSection).toBeUndefined();
9194
expect(step.group.subnodeSections.map((s) => s.id)).toEqual(['Model:openAiApi']);
9295
expect(step.group.parentNode).toEqual(agent);
@@ -116,7 +119,7 @@ describe('useWorkflowSetupSteps', () => {
116119

117120
expect(steps.value).toHaveLength(1);
118121
const step = steps.value[0];
119-
if (!step.group) throw new Error('expected group');
122+
if (step.kind !== 'group') throw new Error('expected group');
120123
expect(step.group.subnodeSections.map((s) => s.id)).toEqual([
121124
'Model:openAiApi',
122125
'Model:parameters',
@@ -137,7 +140,9 @@ describe('useWorkflowSetupSteps', () => {
137140
const { steps } = harness(sections, requests);
138141

139142
expect(steps.value).toHaveLength(2);
140-
expect(steps.value[0].group?.parentNode.name).toBe('Agent');
141-
expect(steps.value[1].group?.parentNode.name).toBe('Agent B');
143+
const [first, second] = steps.value;
144+
if (first.kind !== 'group' || second.kind !== 'group') throw new Error('expected groups');
145+
expect(first.group.parentNode.name).toBe('Agent');
146+
expect(second.group.parentNode.name).toBe('Agent B');
142147
});
143148
});

0 commit comments

Comments
 (0)