diff --git a/frontend/src/lib/components/InputTransformForm.svelte b/frontend/src/lib/components/InputTransformForm.svelte index 706d00b7ae..7dc11e9229 100644 --- a/frontend/src/lib/components/InputTransformForm.svelte +++ b/frontend/src/lib/components/InputTransformForm.svelte @@ -1,3 +1,12 @@ + + diff --git a/frontend/src/lib/components/flows/content/FlowConstants.svelte b/frontend/src/lib/components/flows/content/FlowConstants.svelte index df0688f177..76859bbcff 100644 --- a/frontend/src/lib/components/flows/content/FlowConstants.svelte +++ b/frontend/src/lib/components/flows/content/FlowConstants.svelte @@ -77,6 +77,8 @@ .filter(([i, f, m]) => f.length > 0) setContext('PropPickerWrapper', { + inputMatches: writable(undefined), + filteredPickableProperties: writable(undefined), focusProp: () => {}, propPickerConfig: writable(undefined), clearFocus: () => {} diff --git a/frontend/src/lib/components/flows/previousResults.ts b/frontend/src/lib/components/flows/previousResults.ts index 94c028af23..e922c55978 100644 --- a/frontend/src/lib/components/flows/previousResults.ts +++ b/frontend/src/lib/components/flows/previousResults.ts @@ -260,3 +260,40 @@ declare const approvers: string } ` } + +export function buildPrefixRegex(words: string[]): Array<{ regex: RegExp; word: string }> { + return words.map((word) => { + const prefixes: string[] = [] + for (let i = 1; i <= word.length; i++) { + prefixes.push(word.slice(0, i) + '$') + } + prefixes.push(word + '\\.') + prefixes.push(word + '\\[') + + return { + regex: new RegExp(`^(${prefixes.join('|')}).*`), + word + } + }) +} + +export function filterNestedObject(obj: any, nestedKeys: string[]) { + if (nestedKeys.length === 0) return {} + if (nestedKeys.length === 1) { + if (nestedKeys[0] === '') { + return obj + } + const regexes = buildPrefixRegex(Object.keys(obj)) + const matches = regexes.filter(({ regex }) => regex.test(nestedKeys[0])) + return Object.fromEntries( + Object.entries(obj).filter(([key]) => matches.some(({ word }) => word === key)) + ) + } + const [key, ...rest] = nestedKeys + if (obj && typeof obj === 'object' && key in obj) { + const result = {} + result[key] = filterNestedObject(obj[key], rest) + return result + } + return {} +} diff --git a/frontend/src/lib/components/flows/propPicker/PropPickerWrapper.svelte b/frontend/src/lib/components/flows/propPicker/PropPickerWrapper.svelte index c180463f36..7ff05d9b4f 100644 --- a/frontend/src/lib/components/flows/propPicker/PropPickerWrapper.svelte +++ b/frontend/src/lib/components/flows/propPicker/PropPickerWrapper.svelte @@ -11,6 +11,8 @@ export type PropPickerWrapperContext = { propPickerConfig: Writable + inputMatches: Writable<{ word: string; value: string }[] | undefined> + filteredPickableProperties: Writable focusProp: (propName: string, insertionMode: InsertionMode, onSelect: SelectCallback) => void clearFocus: () => void } @@ -37,10 +39,14 @@ export let noPadding: boolean = false const propPickerConfig = writable(undefined) + const filteredPickableProperties = writable(undefined) + const inputMatches = writable<{ word: string; value: string }[] | undefined>(undefined) const dispatch = createEventDispatcher() setContext('PropPickerWrapper', { propPickerConfig, + inputMatches, + filteredPickableProperties, focusProp: (propName, insertionMode, onSelect) => { propPickerConfig.set({ propName, diff --git a/frontend/src/lib/components/propertyPicker/PropPicker.svelte b/frontend/src/lib/components/propertyPicker/PropPicker.svelte index 0e27cab996..e4d598d793 100644 --- a/frontend/src/lib/components/propertyPicker/PropPicker.svelte +++ b/frontend/src/lib/components/propertyPicker/PropPicker.svelte @@ -9,6 +9,7 @@ import { keepByKey } from './utils' import type { PickableProperties } from '../flows/previousResults' import ClearableInput from '../common/clearableInput/ClearableInput.svelte' + import { filterNestedObject } from '../flows/previousResults' export let pickableProperties: PickableProperties export let displayContext = true @@ -20,18 +21,31 @@ let resources: Record = {} let displayVariable = false let displayResources = false + let allResultsCollapsed = true + let collapsableInitialState: + | { + allResultsCollapsed: boolean + displayVariable: boolean + displayResources: boolean + } + | undefined + + let filterActive = false const EMPTY_STRING = '' let search = '' - const { propPickerConfig } = getContext('PropPickerWrapper') + const { propPickerConfig, filteredPickableProperties, inputMatches } = + getContext('PropPickerWrapper') + $filteredPickableProperties = { ...pickableProperties } let flowInputsFiltered = pickableProperties.flow_input let resultByIdFiltered = pickableProperties.priorIds let timeout: NodeJS.Timeout function onSearch(search: string) { + filterActive = false clearTimeout(timeout) setTimeout(() => { flowInputsFiltered = @@ -72,29 +86,124 @@ ).map((resource) => [resource.path, resource.description ?? '']) ) } + + async function filterPickableProperties() { + if (!filterActive) { + return + } + + if (!$inputMatches?.some((match) => match.word === 'flow_input')) { + flowInputsFiltered = [] + } + if (!$inputMatches?.some((match) => match.word === 'results')) { + resultByIdFiltered = [] + } + if ($inputMatches?.length == 1) { + if ($inputMatches[0].word === 'flow_input') { + flowInputsFiltered = pickableProperties.flow_input + let [, ...nestedKeys] = $inputMatches[0].value.split('.') + let filtered = filterNestedObject(flowInputsFiltered, nestedKeys) + if (Object.keys(filtered).length > 0) { + flowInputsFiltered = filtered + } + } else if ($inputMatches[0].word === 'results') { + resultByIdFiltered = pickableProperties.priorIds + let [, ...nestedKeys] = $inputMatches[0].value.split('.') + let filtered = filterNestedObject(resultByIdFiltered, nestedKeys) + if (Object.keys(filtered).length > 0) { + resultByIdFiltered = filtered + } + } + } + + if ($filteredPickableProperties) { + resultByIdFiltered && ($filteredPickableProperties.priorIds = resultByIdFiltered) + flowInputsFiltered && ($filteredPickableProperties.flow_input = flowInputsFiltered) + } + } + + async function updateCollapsable() { + if (!$inputMatches || $inputMatches.length !== 1) { + resetCollapsable() + return + } + + if (!collapsableInitialState) { + collapsableInitialState = { allResultsCollapsed, displayVariable, displayResources } + } + + if ($inputMatches[0].word === 'variable') { + await loadVariables() + displayVariable = true + return + } + if ($inputMatches[0].word === 'resource') { + await loadResources() + displayResources = true + return + } + if ($inputMatches[0].word === 'results') { + allResultsCollapsed = false + return + } + } + + function resetCollapsable() { + if (!collapsableInitialState) { + return + } + ;({ allResultsCollapsed, displayVariable, displayResources } = collapsableInitialState) + collapsableInitialState = undefined + } + + async function updateFilterActive() { + const prev = filterActive + + filterActive = Boolean( + $inputMatches && + $inputMatches?.length > 0 && + $propPickerConfig?.insertionMode === 'insert' && + search === EMPTY_STRING + ) + + if (prev && !filterActive) { + flowInputsFiltered = pickableProperties.flow_input + resultByIdFiltered = pickableProperties.priorIds + } + } + + async function updateState() { + await updateFilterActive() + await filterPickableProperties() + await updateCollapsable() + } + + $: search, $inputMatches, $propPickerConfig, updateState()
-
-
- Flow Input -
-
-
- -
+
+ {#if flowInputsFiltered && (Object.keys(flowInputsFiltered).length > 0 || !filterActive)} +
+ Flow Input +
+
+
+ +
+ {/if} {#if error} Error -
+
0} {#if suggestedPropsFiltered && Object.keys(suggestedPropsFiltered).length > 0} Suggested Results -
+
{/if} All Results -
+
{/if} {:else} - {#if previousId} + {@const json = Object.fromEntries( + Object.entries(resultByIdFiltered).filter(([k, v]) => k == previousId) + )} + {#if previousId && Object.keys(json).length > 0} Previous Result -
+
Resume payloads -
+
{/if} {#if Object.keys(pickableProperties.priorIds).length > 0} - {#if suggestedPropsFiltered && Object.keys(suggestedPropsFiltered).length > 0} + {#if !filterActive && suggestedPropsFiltered && Object.keys(suggestedPropsFiltered).length > 0} Suggested Results -
+
{/if} -
- All Results - {#if !allResultsCollapsed} + {#if Object.keys(resultByIdFiltered).length > 0} +
+ All Results + + +
+ {/if} + {/if} + {/if} + + {#if displayContext} + {#if !filterActive || $inputMatches?.some((match) => match.word === 'variable')} +
+ Variables: + + {#if displayVariable} - {/if} - + + {:else} + + {/if} +
+ {/if} + {#if !filterActive || $inputMatches?.some((match) => match.word === 'resource')} +
+ Resources: + + {#if displayResources} + + + {:else} + + {/if}
{/if} {/if} - - {#if displayContext} -
- Variables: - - {#if displayVariable} - - - - {:else} - - {/if} -
- -
- Resources: - - {#if displayResources} - - - {:else} - - {/if} -
- {/if}