mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-23 00:03:46 +00:00
* fix(frontend): skip reserved ids when auto-assigning flow module ids * test: state the reserved-id invariant only beside the implementation
58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
import type { OpenFlow } from '$lib/gen'
|
|
import { dfs } from './dfs'
|
|
import type { FlowState } from './flowState'
|
|
import { charsToNumber, forbiddenIds, numberToChars } from './idUtils'
|
|
|
|
const reservedIds = new Set(forbiddenIds)
|
|
|
|
// Returns the base-26 value of a key only if it is a short, auto-generated step id
|
|
// (a, b, ..., z, aa, ...). flowState/module-id keys also include copy ids ("a2"), subflow
|
|
// result keys ("subflow:..."), reserved keys and user-renamed ids; feeding those through
|
|
// charsToNumber yields meaningless (often huge) numbers that would poison id generation and
|
|
// make new steps jump to ids like "bzw". Short non-canonical keys are rejected via a
|
|
// round-trip check; longer keys are skipped entirely, which also leaves user renames to long
|
|
// lowercase words (e.g. "process") out of the sequence.
|
|
function autoIdNumber(key: string): number | undefined {
|
|
if (key.length >= 4 || reservedIds.has(key)) {
|
|
return undefined
|
|
}
|
|
const num = charsToNumber(key)
|
|
if (num < 0 || numberToChars(num) !== key) {
|
|
return undefined
|
|
}
|
|
return num
|
|
}
|
|
|
|
// Computes the next available id
|
|
export function nextId(flowState: FlowState, fullFlow: OpenFlow): string {
|
|
const allKeys = dfs(fullFlow.value.modules, (fm) => fm.id).concat(Object.keys(flowState))
|
|
const takenIds = new Set(allKeys)
|
|
|
|
const max = allKeys.reduce((acc, key) => {
|
|
const num = autoIdNumber(key)
|
|
return num === undefined ? acc : Math.max(acc, num + 1)
|
|
}, 0)
|
|
|
|
// A reserved id (e.g. "as", after "ar") never raises `max`, so assigning
|
|
// numberToChars(max) when it is reserved would make every later call return
|
|
// it again; skip forward to a free, allowed id instead.
|
|
let candidate = max
|
|
let id = numberToChars(candidate)
|
|
while (reservedIds.has(id) || takenIds.has(id)) {
|
|
id = numberToChars(++candidate)
|
|
}
|
|
return id
|
|
}
|
|
|
|
// Computes a copy id like "a2", "a3", etc. based on the original id
|
|
export function copyId(originalId: string, flowState: FlowState, fullFlow: OpenFlow): string {
|
|
const allIds = new Set(dfs(fullFlow.value.modules, (fm) => fm.id).concat(Object.keys(flowState)))
|
|
for (let n = 2; n < 10000; n++) {
|
|
const candidate = `${originalId}${n}`
|
|
if (!allIds.has(candidate)) {
|
|
return candidate
|
|
}
|
|
}
|
|
return `${originalId}10000`
|
|
}
|