From 1b407acf526fcc044624a84c01691fff6144c835 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 2 Dec 2024 09:05:47 -0800 Subject: [PATCH 01/24] Add edit state on search pipeline and delete resources btn Signed-off-by: Tyler Ohlsen --- .../workflow_inputs/workflow_inputs.tsx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx index 109876e8..d5bf7473 100644 --- a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx @@ -106,9 +106,10 @@ export function WorkflowInputs(props: WorkflowInputsProps) { // provisioned resources states const [ingestProvisioned, setIngestProvisioned] = useState(false); + const [searchProvisioned, setSearchProvisioned] = useState(false); // confirm modal state - const [isModalOpen, setIsModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); // last ingested state const [lastIngested, setLastIngested] = useState( @@ -120,6 +121,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) { const onSearch = props.selectedStep === CONFIG_STEP.SEARCH; const ingestEnabled = values?.ingest?.enabled || false; const onIngestAndProvisioned = onIngest && ingestProvisioned; + const onSearchAndProvisioned = onSearch && searchProvisioned; const onIngestAndUnprovisioned = onIngest && !ingestProvisioned; const onIngestAndDisabled = onIngest && !ingestEnabled; const isProposingNoSearchResources = @@ -235,6 +237,9 @@ export function WorkflowInputs(props: WorkflowInputsProps) { useEffect(() => { setIngestProvisioned(hasProvisionedIngestResources(props.workflow)); }, [props.workflow]); + useEffect(() => { + setSearchProvisioned(hasProvisionedSearchResources(props.workflow)); + }, [props.workflow]); // maintain global states (button eligibility) const ingestRunButtonDisabled = !ingestTemplatesDifferent; @@ -585,8 +590,8 @@ export function WorkflowInputs(props: WorkflowInputsProps) { }, ]} /> - {isModalOpen && ( - setIsModalOpen(false)}> + {isDeleteModalOpen && ( + setIsDeleteModalOpen(false)}>

{`Delete resources for workflow ${getCharacterLimitedString( @@ -602,7 +607,9 @@ export function WorkflowInputs(props: WorkflowInputsProps) { - setIsModalOpen(false)}> + setIsDeleteModalOpen(false)} + > {' '} Cancel @@ -624,6 +631,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) { ) .unwrap() .then(async (result) => { + props.setSelectedStep(CONFIG_STEP.INGEST); setFieldValue('ingest.enabled', false); // @ts-ignore await dispatch( @@ -635,7 +643,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) { }) .catch((error: any) => {}) .finally(() => { - setIsModalOpen(false); + setIsDeleteModalOpen(false); setIsRunningDelete(false); }); }} @@ -660,15 +668,17 @@ export function WorkflowInputs(props: WorkflowInputsProps) {

{onIngestAndUnprovisioned ? ( 'Define ingest pipeline' - ) : onIngestAndProvisioned ? ( + ) : onIngestAndProvisioned || onSearchAndProvisioned ? ( - Edit ingest pipeline + {onIngestAndProvisioned + ? `Edit ingest pipeline` + : `Edit search pipeline`} setIsModalOpen(true)} + onClick={() => setIsDeleteModalOpen(true)} > {` `}Delete resources From 47250e4f64391e08bf619c875600e2d5e3f104af Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 2 Dec 2024 09:32:34 -0800 Subject: [PATCH 02/24] Get new button working on search to autoopen inspector panel Signed-off-by: Tyler Ohlsen --- common/constants.ts | 29 +++++++++ .../workflow_detail/resizable_workspace.tsx | 15 ++++- public/pages/workflow_detail/tools/tools.tsx | 62 +++++-------------- .../workflow_inputs/workflow_inputs.tsx | 35 ++++++++--- 4 files changed, 84 insertions(+), 57 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 2d8787ff..15142d37 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -522,3 +522,32 @@ export enum SOURCE_OPTIONS { UPLOAD = 'upload', EXISTING_INDEX = 'existing_index', } +export enum INSPECTOR_TAB_ID { + INGEST = 'ingest', + QUERY = 'query', + ERRORS = 'errors', + RESOURCES = 'resources', +} + +export const INSPECTOR_TABS = [ + { + id: INSPECTOR_TAB_ID.INGEST, + name: 'Ingest response', + disabled: false, + }, + { + id: INSPECTOR_TAB_ID.QUERY, + name: 'Search response', + disabled: false, + }, + { + id: INSPECTOR_TAB_ID.ERRORS, + name: 'Errors', + disabled: false, + }, + { + id: INSPECTOR_TAB_ID.RESOURCES, + name: 'Resources', + disabled: false, + }, +]; diff --git a/public/pages/workflow_detail/resizable_workspace.tsx b/public/pages/workflow_detail/resizable_workspace.tsx index 8600b4d2..3a8ca01f 100644 --- a/public/pages/workflow_detail/resizable_workspace.tsx +++ b/public/pages/workflow_detail/resizable_workspace.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import { CONFIG_STEP, + INSPECTOR_TAB_ID, Workflow, WorkflowConfig, customStringify, @@ -78,9 +79,13 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) { setIsToolsPanelOpen(!isToolsPanelOpen); }; - // ingest / search response states to be populated in the Tools panel + // Inspector panel state vars. Actions taken in the form can update the Inspector panel, + // hence we keep top-level vars here to pass to both form and inspector components. const [ingestResponse, setIngestResponse] = useState(''); const [queryResponse, setQueryResponse] = useState(''); + const [selectedInspectorTabId, setSelectedInspectorTabId] = useState< + INSPECTOR_TAB_ID + >(INSPECTOR_TAB_ID.INGEST); // is valid workflow state, + associated hook to set it as such const [isValidWorkflow, setIsValidWorkflow] = useState(true); @@ -132,6 +137,12 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) { setSelectedStep={props.setSelectedStep} setUnsavedIngestProcessors={props.setUnsavedIngestProcessors} setUnsavedSearchProcessors={props.setUnsavedSearchProcessors} + displaySearchPanel={() => { + if (!isToolsPanelOpen) { + onToggleToolsChange(); + } + setSelectedInspectorTabId(INSPECTOR_TAB_ID.QUERY); + }} /> @@ -198,6 +209,8 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) { workflow={props.workflow} ingestResponse={ingestResponse} queryResponse={queryResponse} + selectedTabId={selectedInspectorTabId} + setSelectedTabId={setSelectedInspectorTabId} /> diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index f4d2a098..d946bccb 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -15,7 +15,7 @@ import { EuiTabs, EuiText, } from '@elastic/eui'; -import { Workflow } from '../../../../common'; +import { INSPECTOR_TAB_ID, INSPECTOR_TABS, Workflow } from '../../../../common'; import { Resources } from './resources'; import { Query } from './query'; import { Ingest } from './ingest'; @@ -25,39 +25,10 @@ interface ToolsProps { workflow?: Workflow; ingestResponse: string; queryResponse: string; + selectedTabId: INSPECTOR_TAB_ID; + setSelectedTabId: (tabId: INSPECTOR_TAB_ID) => void; } -enum TAB_ID { - INGEST = 'ingest', - QUERY = 'query', - ERRORS = 'errors', - RESOURCES = 'resources', -} - -const inputTabs = [ - { - id: TAB_ID.INGEST, - name: 'Ingest response', - disabled: false, - }, - { - id: TAB_ID.QUERY, - name: 'Search response', - disabled: false, - }, - { - id: TAB_ID.ERRORS, - name: 'Errors', - disabled: false, - }, - { - id: TAB_ID.RESOURCES, - name: 'Resources', - disabled: false, - }, -]; - -// TODO: this may change in the future const PANEL_TITLE = 'Inspector'; /** @@ -70,9 +41,6 @@ export function Tools(props: ToolsProps) { const workflowsError = workflows.errorMessage; const [curErrorMessage, setCurErrorMessage] = useState(''); - // selected tab state - const [selectedTabId, setSelectedTabId] = useState(TAB_ID.INGEST); - // auto-navigate to errors tab if a new error has been set as a result of // executing OpenSearch or Flow Framework workflow APIs, or from the workflow state // (note that if provision/deprovision fails, there is no concrete exception returned at the API level - @@ -80,34 +48,34 @@ export function Tools(props: ToolsProps) { useEffect(() => { setCurErrorMessage(opensearchError); if (!isEmpty(opensearchError)) { - setSelectedTabId(TAB_ID.ERRORS); + props.setSelectedTabId(INSPECTOR_TAB_ID.ERRORS); } }, [opensearchError]); useEffect(() => { setCurErrorMessage(workflowsError); if (!isEmpty(workflowsError)) { - setSelectedTabId(TAB_ID.ERRORS); + props.setSelectedTabId(INSPECTOR_TAB_ID.ERRORS); } }, [workflowsError]); useEffect(() => { setCurErrorMessage(props.workflow?.error || ''); if (!isEmpty(props.workflow?.error)) { - setSelectedTabId(TAB_ID.ERRORS); + props.setSelectedTabId(INSPECTOR_TAB_ID.ERRORS); } }, [props.workflow?.error]); // auto-navigate to ingest tab if a populated value has been set, indicating ingest has been ran useEffect(() => { if (!isEmpty(props.ingestResponse)) { - setSelectedTabId(TAB_ID.INGEST); + props.setSelectedTabId(INSPECTOR_TAB_ID.INGEST); } }, [props.ingestResponse]); // auto-navigate to query tab if a populated value has been set, indicating search has been ran useEffect(() => { if (!isEmpty(props.queryResponse)) { - setSelectedTabId(TAB_ID.QUERY); + props.setSelectedTabId(INSPECTOR_TAB_ID.QUERY); } }, [props.queryResponse]); @@ -131,11 +99,11 @@ export function Tools(props: ToolsProps) { - {inputTabs.map((tab, idx) => { + {INSPECTOR_TABS.map((tab, idx) => { return ( setSelectedTabId(tab.id)} - isSelected={tab.id === selectedTabId} + onClick={() => props.setSelectedTabId(tab.id)} + isSelected={tab.id === props.selectedTabId} disabled={tab.disabled} key={idx} > @@ -149,16 +117,16 @@ export function Tools(props: ToolsProps) { <> - {selectedTabId === TAB_ID.INGEST && ( + {props.selectedTabId === INSPECTOR_TAB_ID.INGEST && ( )} - {selectedTabId === TAB_ID.QUERY && ( + {props.selectedTabId === INSPECTOR_TAB_ID.QUERY && ( )} - {selectedTabId === TAB_ID.ERRORS && ( + {props.selectedTabId === INSPECTOR_TAB_ID.ERRORS && ( )} - {selectedTabId === TAB_ID.RESOURCES && ( + {props.selectedTabId === INSPECTOR_TAB_ID.RESOURCES && ( )} diff --git a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx index d5bf7473..04a15b8b 100644 --- a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx @@ -12,7 +12,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, - EuiIcon, EuiLoadingSpinner, EuiModal, EuiModalBody, @@ -57,9 +56,9 @@ import { generateId, sleep, getResourcesToBeForceDeleted, + getDataSourceId, } from '../../../utils'; import { BooleanField } from './input_fields'; -import { getDataSourceId } from '../../../utils/utils'; // styling import '../workspace/workspace-styles.scss'; @@ -80,6 +79,7 @@ interface WorkflowInputsProps { setSelectedStep: (step: CONFIG_STEP) => void; setUnsavedIngestProcessors: (unsavedIngestProcessors: boolean) => void; setUnsavedSearchProcessors: (unsavedSearchProcessors: boolean) => void; + displaySearchPanel: () => void; } /** @@ -676,13 +676,30 @@ export function WorkflowInputs(props: WorkflowInputsProps) { : `Edit search pipeline`} - setIsDeleteModalOpen(true)} - > - - {` `}Delete resources - + + + setIsDeleteModalOpen(true)} + iconType="trash" + iconSide="left" + > + Delete resources + + + {onSearchAndProvisioned && ( + + { + props.displaySearchPanel(); + }} + > + Test pipeline + + + )} + ) : ( From 7c18291c8228039467192c5c3ca2fb5b648ca887 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 2 Dec 2024 10:15:04 -0800 Subject: [PATCH 03/24] Add searching and toggle without pipeline in tools search panel Signed-off-by: Tyler Ohlsen --- .../workflow_detail/resizable_workspace.tsx | 1 + .../workflow_detail/tools/query/query.tsx | 170 +++++++++++++++--- public/pages/workflow_detail/tools/tools.tsx | 6 +- 3 files changed, 155 insertions(+), 22 deletions(-) diff --git a/public/pages/workflow_detail/resizable_workspace.tsx b/public/pages/workflow_detail/resizable_workspace.tsx index 3a8ca01f..00154b21 100644 --- a/public/pages/workflow_detail/resizable_workspace.tsx +++ b/public/pages/workflow_detail/resizable_workspace.tsx @@ -209,6 +209,7 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) { workflow={props.workflow} ingestResponse={ingestResponse} queryResponse={queryResponse} + setQueryResponse={setQueryResponse} selectedTabId={selectedInspectorTabId} setSelectedTabId={setSelectedInspectorTabId} /> diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index 752c0dde..4be5364c 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -3,35 +3,163 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; -import { EuiCodeEditor } from '@elastic/eui'; +import React, { useState } from 'react'; +import { isEmpty } from 'lodash'; +import { useFormikContext } from 'formik'; +import { + EuiCodeEditor, + EuiComboBox, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiSmallButton, + EuiText, +} from '@elastic/eui'; +import { + customStringify, + SearchHit, + WorkflowFormValues, +} from '../../../../../common'; +import { searchIndex, useAppDispatch } from '../../../../store'; +import { getDataSourceId } from '../../../../utils'; interface QueryProps { queryResponse: string; + setQueryResponse: (queryResponse: string) => void; } +const SEARCH_OPTIONS = [ + { + label: 'FULL search pipeline', + }, + { + label: 'No search pipeline', + }, +]; + /** - * The basic query component for the Tools panel. - * Displays a read-only view of the query response after users perform search. + * The search component for the Tools panel. + * Lets users configure query parameters, execute search, and view responses. */ export function Query(props: QueryProps) { + const dispatch = useAppDispatch(); + const dataSourceId = getDataSourceId(); + + // Form state + const { values } = useFormikContext(); + + // state for if to execute search w/ or w/o any configured search pipeline + const [includePipeline, setIncludePipeline] = useState(true); + return ( - // TODO: known issue with the editor where resizing the resizablecontainer does not - // trigger vertical scroll updates. Updating the window, or reloading the component - // by switching tabs etc. will refresh it correctly - + + + + + + + Search + + + { + let searchApiBody = { + index: values?.search?.index?.name, + body: values?.search?.request, + } as { + index: string; + body: string; + searchPipeline: string | undefined; + }; + if (!includePipeline) { + searchApiBody = { + ...searchApiBody, + searchPipeline: '_none', + }; + } + dispatch( + searchIndex({ + apiBody: searchApiBody, + dataSourceId, + }) + ) + .unwrap() + .then(async (resp) => { + props.setQueryResponse( + customStringify( + resp?.hits?.hits?.map( + (hit: SearchHit) => hit._source + ) + ) + ); + }) + .catch((error: any) => { + props.setQueryResponse(''); + console.error('Error running query: ', error); + }); + }} + > + Search + + + + + + { + setIncludePipeline(!includePipeline); + }} + /> + + + + + + + Results + + + {isEmpty(props.queryResponse) ? ( + No results

} + titleSize="s" + body={ + <> + Run search to view results. + + } + /> + ) : ( + // Known issue with the editor where resizing the resizablecontainer does not + // trigger vertical scroll updates. Updating the window, or reloading the component + // by switching tabs etc. will refresh it correctly + + )} + + + + ); } diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index d946bccb..d9402e97 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -25,6 +25,7 @@ interface ToolsProps { workflow?: Workflow; ingestResponse: string; queryResponse: string; + setQueryResponse: (queryResponse: string) => void; selectedTabId: INSPECTOR_TAB_ID; setSelectedTabId: (tabId: INSPECTOR_TAB_ID) => void; } @@ -121,7 +122,10 @@ export function Tools(props: ToolsProps) { )} {props.selectedTabId === INSPECTOR_TAB_ID.QUERY && ( - + )} {props.selectedTabId === INSPECTOR_TAB_ID.ERRORS && ( From 3773e04b930480b364109236328f7c481ce11277 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 2 Dec 2024 10:59:39 -0800 Subject: [PATCH 04/24] Add empty states and guardrails for when to run search and what options are available Signed-off-by: Tyler Ohlsen --- .../workflow_detail/tools/query/query.tsx | 230 ++++++++++-------- public/pages/workflow_detail/tools/tools.tsx | 4 + 2 files changed, 132 insertions(+), 102 deletions(-) diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index 4be5364c..ee48e97d 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { isEmpty } from 'lodash'; import { useFormikContext } from 'formik'; import { @@ -26,6 +26,7 @@ import { getDataSourceId } from '../../../../utils'; interface QueryProps { queryResponse: string; setQueryResponse: (queryResponse: string) => void; + hasSearchPipeline: boolean; } const SEARCH_OPTIONS = [ @@ -48,118 +49,143 @@ export function Query(props: QueryProps) { // Form state const { values } = useFormikContext(); - // state for if to execute search w/ or w/o any configured search pipeline - const [includePipeline, setIncludePipeline] = useState(true); + // state for if to execute search w/ or w/o any configured search pipeline. + // default based on if there is an available search pipeline or not. + const [includePipeline, setIncludePipeline] = useState(false); + useEffect(() => { + setIncludePipeline(props.hasSearchPipeline); + }, [props.hasSearchPipeline]); + + // empty states + const noSearchIndex = isEmpty(values?.search?.index?.name); + const noSearchRequest = isEmpty(values?.search?.request); return ( - - - - - + <> + {noSearchIndex || noSearchRequest ? ( + Missing search configurations} + titleSize="s" + body={ + <> + + Configure a search request and an index to search against first. + + + } + /> + ) : ( + + + - Search + + + Search + + + { + dispatch( + searchIndex({ + apiBody: { + index: values?.search?.index?.name, + body: values?.search?.request, + searchPipeline: + props.hasSearchPipeline && + includePipeline && + !isEmpty(values?.search?.pipelineName) + ? values?.search?.pipelineName + : '_none', + }, + dataSourceId, + }) + ) + .unwrap() + .then(async (resp) => { + props.setQueryResponse( + customStringify( + resp?.hits?.hits?.map( + (hit: SearchHit) => hit._source + ) + ) + ); + }) + .catch((error: any) => { + props.setQueryResponse(''); + console.error('Error running query: ', error); + }); + }} + > + Search + + + - { - let searchApiBody = { - index: values?.search?.index?.name, - body: values?.search?.request, - } as { - index: string; - body: string; - searchPipeline: string | undefined; - }; - if (!includePipeline) { - searchApiBody = { - ...searchApiBody, - searchPipeline: '_none', - }; - } - dispatch( - searchIndex({ - apiBody: searchApiBody, - dataSourceId, - }) - ) - .unwrap() - .then(async (resp) => { - props.setQueryResponse( - customStringify( - resp?.hits?.hits?.map( - (hit: SearchHit) => hit._source - ) - ) - ); - }) - .catch((error: any) => { - props.setQueryResponse(''); - console.error('Error running query: ', error); - }); + { + setIncludePipeline(!includePipeline); }} - > - Search - + /> - - { - setIncludePipeline(!includePipeline); - }} - /> - - - - - - - Results - - {isEmpty(props.queryResponse) ? ( - No results} - titleSize="s" - body={ - <> - Run search to view results. - - } - /> - ) : ( - // Known issue with the editor where resizing the resizablecontainer does not - // trigger vertical scroll updates. Updating the window, or reloading the component - // by switching tabs etc. will refresh it correctly - - )} + + + Results + + + {isEmpty(props.queryResponse) ? ( + No results} + titleSize="s" + body={ + <> + Run search to view results. + + } + /> + ) : ( + // Known issue with the editor where resizing the resizablecontainer does not + // trigger vertical scroll updates. Updating the window, or reloading the component + // by switching tabs etc. will refresh it correctly + + )} + + - - + )} + ); } diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index d9402e97..601dc239 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -20,6 +20,7 @@ import { Resources } from './resources'; import { Query } from './query'; import { Ingest } from './ingest'; import { Errors } from './errors'; +import { hasProvisionedSearchResources } from '../../../utils'; interface ToolsProps { workflow?: Workflow; @@ -125,6 +126,9 @@ export function Tools(props: ToolsProps) { )} {props.selectedTabId === INSPECTOR_TAB_ID.ERRORS && ( From d890800bb8fa437d1535e863c6ca1b9028df1a9d Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 2 Dec 2024 11:32:59 -0800 Subject: [PATCH 05/24] Add note about resetting state if we want in future Signed-off-by: Tyler Ohlsen --- .../workflow_inputs/workflow_inputs.tsx | 3 + public/pages/workflows/new_workflow/index.ts | 1 + public/pages/workflows/new_workflow/utils.ts | 105 +++++++++--------- 3 files changed, 59 insertions(+), 50 deletions(-) diff --git a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx index 04a15b8b..a54eda28 100644 --- a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx @@ -619,6 +619,9 @@ export function WorkflowInputs(props: WorkflowInputsProps) { onClick={async () => { setIsRunningDelete(true); await dispatch( + // If in the future we want to start with a fresh/empty state after deleting resources, + // will need to update the workflow with an empty UI config before re-fetching the workflow. + // For now we still persist the config, just clean up / deprovision the resources. deprovisionWorkflow({ apiBody: { workflowId: props.workflow?.id as string, diff --git a/public/pages/workflows/new_workflow/index.ts b/public/pages/workflows/new_workflow/index.ts index a1918c7a..4fc77b3d 100644 --- a/public/pages/workflows/new_workflow/index.ts +++ b/public/pages/workflows/new_workflow/index.ts @@ -4,3 +4,4 @@ */ export { NewWorkflow } from './new_workflow'; +export { fetchEmptyMetadata, fetchEmptyUIConfig } from './utils'; diff --git a/public/pages/workflows/new_workflow/utils.ts b/public/pages/workflows/new_workflow/utils.ts index 30b15854..39d1ab84 100644 --- a/public/pages/workflows/new_workflow/utils.ts +++ b/public/pages/workflows/new_workflow/utils.ts @@ -27,6 +27,7 @@ import { VECTOR_PATTERN, KNN_QUERY, HYBRID_SEARCH_QUERY_MATCH_KNN, + WorkflowConfig, } from '../../../../common'; import { generateId } from '../../../utils'; @@ -74,64 +75,68 @@ export function enrichPresetWorkflowWithUiMetadata( export function fetchEmptyMetadata(): UIState { return { type: WORKFLOW_TYPE.CUSTOM, - config: { - ingest: { - enabled: { - id: 'enabled', - type: 'boolean', - value: true, - }, - pipelineName: { - id: 'pipelineName', + config: fetchEmptyUIConfig(), + }; +} + +export function fetchEmptyUIConfig(): WorkflowConfig { + return { + ingest: { + enabled: { + id: 'enabled', + type: 'boolean', + value: true, + }, + pipelineName: { + id: 'pipelineName', + type: 'string', + value: generateId('ingest_pipeline'), + }, + enrich: { + processors: [], + }, + index: { + name: { + id: 'indexName', type: 'string', - value: generateId('ingest_pipeline'), + value: generateId('my_index', 6), }, - enrich: { - processors: [], - }, - index: { - name: { - id: 'indexName', - type: 'string', - value: generateId('my_index', 6), - }, - mappings: { - id: 'indexMappings', - type: 'json', - value: customStringify({ - properties: {}, - }), - }, - settings: { - id: 'indexSettings', - type: 'json', - }, + mappings: { + id: 'indexMappings', + type: 'json', + value: customStringify({ + properties: {}, + }), }, - }, - search: { - request: { - id: 'request', + settings: { + id: 'indexSettings', type: 'json', - value: customStringify(FETCH_ALL_QUERY), }, - pipelineName: { - id: 'pipelineName', + }, + }, + search: { + request: { + id: 'request', + type: 'json', + value: customStringify(FETCH_ALL_QUERY), + }, + pipelineName: { + id: 'pipelineName', + type: 'string', + value: generateId('search_pipeline'), + }, + index: { + name: { + id: 'indexName', type: 'string', - value: generateId('search_pipeline'), - }, - index: { - name: { - id: 'indexName', - type: 'string', - }, - }, - enrichRequest: { - processors: [], - }, - enrichResponse: { - processors: [], }, }, + enrichRequest: { + processors: [], + }, + enrichResponse: { + processors: [], + }, }, }; } From 0536f425129e6eb95b94a9da04f6a5de4dcf6910 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 2 Dec 2024 13:22:24 -0800 Subject: [PATCH 06/24] Set up edit query boilerplate Signed-off-by: Tyler Ohlsen --- .../search_inputs/edit_query_modal.tsx | 134 ++++++++++++------ 1 file changed, 93 insertions(+), 41 deletions(-) diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx index 1f5bbc06..6a974298 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx @@ -16,8 +16,10 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiPopover, - EuiSpacer, EuiSmallButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiText, } from '@elastic/eui'; import { JsonField } from '../input_fields'; import { @@ -96,49 +98,99 @@ export function EditQueryModal(props: EditQueryModalProps) { > -

{`Edit query`}

+

{`Edit query definition`}

- setPopoverOpen(!popoverOpen)} - data-testid="searchQueryPresetButton" - iconSide="right" - iconType="arrowDown" - > - Choose from a preset - - } - isOpen={popoverOpen} - closePopover={() => setPopoverOpen(false)} - anchorPosition="downLeft" - > - ({ - name: preset.name, - onClick: () => { - formikProps.setFieldValue('request', preset.query); - setPopoverOpen(false); - }, - })), - }, - ]} - /> - - - + + + + + + + Query definition + + + setPopoverOpen(!popoverOpen)} + data-testid="searchQueryPresetButton" + iconSide="right" + iconType="arrowDown" + > + Query samples + + } + isOpen={popoverOpen} + closePopover={() => setPopoverOpen(false)} + anchorPosition="downLeft" + > + ({ + name: preset.name, + onClick: () => { + formikProps.setFieldValue( + 'request', + preset.query + ); + setPopoverOpen(false); + }, + }) + ), + }, + ]} + /> + + + + + + + + + + + + + + + Test query + + + { + console.log('searching...'); + }} + > + Search + + + + + TODO add query parameters + TODO add search results + + + Date: Mon, 2 Dec 2024 13:35:05 -0800 Subject: [PATCH 07/24] Add search and error handling in modal Signed-off-by: Tyler Ohlsen --- .../search_inputs/edit_query_modal.tsx | 84 ++++++++++++++++++- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx index 6a974298..d742605d 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx @@ -20,16 +20,26 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, + EuiCodeEditor, + EuiEmptyPrompt, + EuiCallOut, } from '@elastic/eui'; import { JsonField } from '../input_fields'; import { + customStringify, IConfigField, QUERY_PRESETS, QueryPreset, RequestFormValues, + SearchHit, WorkflowFormValues, } from '../../../../../common'; -import { getFieldSchema, getInitialValue } from '../../../../utils'; +import { + getDataSourceId, + getFieldSchema, + getInitialValue, +} from '../../../../utils'; +import { searchIndex, useAppDispatch } from '../../../../store'; interface EditQueryModalProps { queryFieldPath: string; @@ -41,6 +51,9 @@ interface EditQueryModalProps { * a set of pre-defined queries targeted for different use cases. */ export function EditQueryModal(props: EditQueryModalProps) { + const dispatch = useAppDispatch(); + const dataSourceId = getDataSourceId(); + // sub-form values/schema const requestFormValues = { request: getInitialValue('json'), @@ -63,6 +76,10 @@ export function EditQueryModal(props: EditQueryModalProps) { // popover state const [popoverOpen, setPopoverOpen] = useState(false); + // results state + const [tempResults, setTempResults] = useState(''); + const [tempResultsError, setTempResultsError] = useState(''); + return ( { - console.log('searching...'); + dispatch( + searchIndex({ + apiBody: { + index: values?.search?.index?.name, + body: tempRequest, + // Run the query independent of the pipeline inside this modal + searchPipeline: '_none', + }, + dataSourceId, + }) + ) + .unwrap() + .then(async (resp) => { + setTempResults( + customStringify( + resp?.hits?.hits?.map( + (hit: SearchHit) => hit._source + ) + ) + ); + setTempResultsError(''); + }) + .catch((error: any) => { + setTempResults(''); + const errorMsg = `Error running query: ${error}`; + setTempResultsError(errorMsg); + console.error(errorMsg); + }); }} > Search @@ -187,7 +231,41 @@ export function EditQueryModal(props: EditQueryModalProps) {
TODO add query parameters - TODO add search results + + <> + Results + {isEmpty(tempResults) && isEmpty(tempResultsError) ? ( + No results} + titleSize="s" + body={ + <> + + Run search to view results. + + + } + /> + ) : !isEmpty(tempResultsError) ? ( + + ) : ( + + )} + + From cb9e3b4db676f5136dce847aee136b9fa0c87a29 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 3 Dec 2024 15:23:02 -0800 Subject: [PATCH 08/24] Get auto-param generation and injection working Signed-off-by: Tyler Ohlsen --- common/interfaces.ts | 5 ++ .../search_inputs/edit_query_modal.tsx | 82 ++++++++++++++++++- public/utils/utils.ts | 42 ++++++++++ 3 files changed, 127 insertions(+), 2 deletions(-) diff --git a/common/interfaces.ts b/common/interfaces.ts index 5ace9341..41414f15 100644 --- a/common/interfaces.ts +++ b/common/interfaces.ts @@ -557,6 +557,11 @@ export type QuickConfigureFields = { llmResponseField?: string; }; +export type QueryParam = { + name: string; + value: string; +}; + /** ********** OPENSEARCH TYPES/INTERFACES ************ */ diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx index d742605d..9f464e86 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx @@ -23,21 +23,26 @@ import { EuiCodeEditor, EuiEmptyPrompt, EuiCallOut, + EuiFieldText, } from '@elastic/eui'; import { JsonField } from '../input_fields'; import { customStringify, IConfigField, QUERY_PRESETS, + QueryParam, QueryPreset, RequestFormValues, SearchHit, WorkflowFormValues, } from '../../../../../common'; import { + containsSameValues, getDataSourceId, getFieldSchema, getInitialValue, + getPlaceholdersFromQuery, + injectParameters, } from '../../../../utils'; import { searchIndex, useAppDispatch } from '../../../../store'; @@ -80,6 +85,23 @@ export function EditQueryModal(props: EditQueryModalProps) { const [tempResults, setTempResults] = useState(''); const [tempResultsError, setTempResultsError] = useState(''); + // query/request params state. Re-generate when the request has been updated, + // and if there are a new set of parameters + const [queryParams, setQueryParams] = useState([]); + useEffect(() => { + const placeholders = getPlaceholdersFromQuery(tempRequest); + if ( + !containsSameValues( + placeholders, + queryParams.map((queryParam) => queryParam.name) + ) + ) { + setQueryParams( + placeholders.map((placeholder) => ({ name: placeholder, value: '' })) + ); + } + }, [tempRequest]); + return ( - TODO add query parameters + {queryParams?.length > 0 && ( + + + + + + + Parameter + + + + + Value + + + + + {queryParams.map((queryParam, idx) => { + return ( + + + + + {queryParam.name} + + + + { + setQueryParams( + queryParams.map((qp, i) => + i === idx + ? { ...qp, value: e.target.value } + : qp + ) + ); + }} + /> + + + + ); + })} + + + )} + <> Results diff --git a/public/utils/utils.ts b/public/utils/utils.ts index d456d0ae..5bb77754 100644 --- a/public/utils/utils.ts +++ b/public/utils/utils.ts @@ -32,6 +32,7 @@ import { ModelInputMap, ModelOutputMap, OutputMapEntry, + QueryParam, } from '../../common/interfaces'; import queryString from 'query-string'; import { useLocation } from 'react-router-dom'; @@ -544,3 +545,44 @@ export function sanitizeJSONPath(path: string): string { } }); } + +// given a stringified query, extract out all unique placeholder vars +// that follow the pattern {{some-placeholder}} +export function getPlaceholdersFromQuery(queryString: string): string[] { + const regex = /\{\{([^}]+)\}\}/g; + return [ + // convert to set to collapse duplicate names + ...new Set([...queryString.matchAll(regex)].map((match) => match[1])), + ]; +} + +// simple fn to check if the values in an arr are the same. used for +// checking if the same set of placeholders exists when a new query is selected, +// or an existing query is updated. +export function containsSameValues(arr1: string[], arr2: string[]) { + if (arr1.length !== arr2.length) { + return false; + } + arr1.sort(); + arr2.sort(); + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + return true; +} + +export function injectParameters( + params: QueryParam[], + queryString: string +): string { + let finalQueryString = queryString; + params.forEach((param) => { + finalQueryString = finalQueryString.replace( + new RegExp(`{{${param.name}}}`, 'g'), + param.value + ); + }); + return finalQueryString; +} From fe685d623388c45f035c3ba95b87b2d41e4fd391 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 3 Dec 2024 15:37:20 -0800 Subject: [PATCH 09/24] Refactor to standalone component; add some error state handling Signed-off-by: Tyler Ohlsen --- public/general_components/index.ts | 1 + .../general_components/query_params_list.tsx | 72 ++++++++++++++++++ .../search_inputs/edit_query_modal.tsx | 74 +++++-------------- 3 files changed, 92 insertions(+), 55 deletions(-) create mode 100644 public/general_components/query_params_list.tsx diff --git a/public/general_components/index.ts b/public/general_components/index.ts index f475b50d..1ce91089 100644 --- a/public/general_components/index.ts +++ b/public/general_components/index.ts @@ -6,4 +6,5 @@ export { MultiSelectFilter } from './multi_select_filter'; export { ProcessorsTitle } from './processors_title'; export { ExperimentalBadge } from './experimental_badge'; +export { QueryParamsList } from './query_params_list'; export * from './service_card'; diff --git a/public/general_components/query_params_list.tsx b/public/general_components/query_params_list.tsx new file mode 100644 index 00000000..951baa5e --- /dev/null +++ b/public/general_components/query_params_list.tsx @@ -0,0 +1,72 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiFieldText } from '@elastic/eui'; +import { QueryParam } from '../../common'; + +interface QueryParamsListProps { + queryParams: QueryParam[]; + setQueryParams: (params: QueryParam[]) => void; +} + +/** + * Basic, reusable component for displaying a list of query parameters, and allowing + * users to freely enter values for each. + */ +export function QueryParamsList(props: QueryParamsListProps) { + return ( + <> + {props.queryParams?.length > 0 && ( + + + + + + + Parameter + + + + + Value + + + + + {props.queryParams.map((queryParam, idx) => { + return ( + + + + + {queryParam.name} + + + + { + props.setQueryParams( + props.queryParams.map((qp, i) => + i === idx ? { ...qp, value: e.target.value } : qp + ) + ); + }} + /> + + + + ); + })} + + + )} + + ); +} diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx index 9f464e86..bb0bf551 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx @@ -23,7 +23,6 @@ import { EuiCodeEditor, EuiEmptyPrompt, EuiCallOut, - EuiFieldText, } from '@elastic/eui'; import { JsonField } from '../input_fields'; import { @@ -45,6 +44,7 @@ import { injectParameters, } from '../../../../utils'; import { searchIndex, useAppDispatch } from '../../../../store'; +import { QueryParamsList } from '../../../../general_components'; interface EditQueryModalProps { queryFieldPath: string; @@ -88,6 +88,11 @@ export function EditQueryModal(props: EditQueryModalProps) { // query/request params state. Re-generate when the request has been updated, // and if there are a new set of parameters const [queryParams, setQueryParams] = useState([]); + + // Do a few things when the request is changed: + // 1. Check if there is a new set of query parameters, and if so, + // reset the form. + // 2. Clear any persisted error useEffect(() => { const placeholders = getPlaceholdersFromQuery(tempRequest); if ( @@ -100,8 +105,14 @@ export function EditQueryModal(props: EditQueryModalProps) { placeholders.map((placeholder) => ({ name: placeholder, value: '' })) ); } + setTempResultsError(''); }, [tempRequest]); + // Clear any error if the parameters have been updated in any way + useEffect(() => { + setTempResultsError(''); + }, [queryParams]); + return ( - {queryParams?.length > 0 && ( - - - - - - - Parameter - - - - - Value - - - - - {queryParams.map((queryParam, idx) => { - return ( - - - - - {queryParam.name} - - - - { - setQueryParams( - queryParams.map((qp, i) => - i === idx - ? { ...qp, value: e.target.value } - : qp - ) - ); - }} - /> - - - - ); - })} - - - )} - + {/** + * Note: this may return nothing if the list of params are empty + */} + <> Results From db32b9186679e7b48f2c6e85f1d76448a54cf16b Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 3 Dec 2024 15:55:04 -0800 Subject: [PATCH 10/24] Expose query params component in inspector Signed-off-by: Tyler Ohlsen --- .../workflow_detail/tools/query/query.tsx | 44 ++++++++++++++++++- .../search_inputs/edit_query_modal.tsx | 6 +-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index ee48e97d..8db926c0 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -17,11 +17,18 @@ import { } from '@elastic/eui'; import { customStringify, + QueryParam, SearchHit, WorkflowFormValues, } from '../../../../../common'; import { searchIndex, useAppDispatch } from '../../../../store'; -import { getDataSourceId } from '../../../../utils'; +import { + containsSameValues, + getDataSourceId, + getPlaceholdersFromQuery, + injectParameters, +} from '../../../../utils'; +import { QueryParamsList } from '../../../../general_components'; interface QueryProps { queryResponse: string; @@ -49,6 +56,14 @@ export function Query(props: QueryProps) { // Form state const { values } = useFormikContext(); + // Standalone / sandboxed search request state. Users can test things out + // without updating the base form / persisted value. We default to any + // set form value on initialization + // TODO: allow overriding the temp request with a sandboxed value. + const [tempRequest, setTempRequest] = useState( + values?.search?.request || '{}' + ); + // state for if to execute search w/ or w/o any configured search pipeline. // default based on if there is an available search pipeline or not. const [includePipeline, setIncludePipeline] = useState(false); @@ -56,6 +71,22 @@ export function Query(props: QueryProps) { setIncludePipeline(props.hasSearchPipeline); }, [props.hasSearchPipeline]); + // query/request params state, update when the request is changed/updated + const [queryParams, setQueryParams] = useState([]); + useEffect(() => { + const placeholders = getPlaceholdersFromQuery(tempRequest); + if ( + !containsSameValues( + placeholders, + queryParams.map((queryParam) => queryParam.name) + ) + ) { + setQueryParams( + placeholders.map((placeholder) => ({ name: placeholder, value: '' })) + ); + } + }, [tempRequest]); + // empty states const noSearchIndex = isEmpty(values?.search?.index?.name); const noSearchRequest = isEmpty(values?.search?.request); @@ -91,7 +122,7 @@ export function Query(props: QueryProps) { searchIndex({ apiBody: { index: values?.search?.index?.name, - body: values?.search?.request, + body: injectParameters(queryParams, tempRequest), searchPipeline: props.hasSearchPipeline && includePipeline && @@ -144,6 +175,15 @@ export function Query(props: QueryProps) { }} /> + + {/** + * This may return nothing if the list of params are empty + */} + + diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx index bb0bf551..ce29d88c 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx @@ -85,8 +85,7 @@ export function EditQueryModal(props: EditQueryModalProps) { const [tempResults, setTempResults] = useState(''); const [tempResultsError, setTempResultsError] = useState(''); - // query/request params state. Re-generate when the request has been updated, - // and if there are a new set of parameters + // query/request params state const [queryParams, setQueryParams] = useState([]); // Do a few things when the request is changed: @@ -208,7 +207,6 @@ export function EditQueryModal(props: EditQueryModalProps) { @@ -267,7 +265,7 @@ export function EditQueryModal(props: EditQueryModalProps) { {/** - * Note: this may return nothing if the list of params are empty + * This may return nothing if the list of params are empty */} Date: Tue, 3 Dec 2024 16:16:02 -0800 Subject: [PATCH 11/24] Add custom query as an option Signed-off-by: Tyler Ohlsen --- .../workflow_detail/resizable_workspace.tsx | 1 + .../workflow_detail/tools/query/query.tsx | 65 +++++++++++++++++-- public/pages/workflow_detail/tools/tools.tsx | 9 ++- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/public/pages/workflow_detail/resizable_workspace.tsx b/public/pages/workflow_detail/resizable_workspace.tsx index 00154b21..d681b720 100644 --- a/public/pages/workflow_detail/resizable_workspace.tsx +++ b/public/pages/workflow_detail/resizable_workspace.tsx @@ -212,6 +212,7 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) { setQueryResponse={setQueryResponse} selectedTabId={selectedInspectorTabId} setSelectedTabId={setSelectedInspectorTabId} + selectedStep={props.selectedStep} /> diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index 8db926c0..3aa68811 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -13,10 +13,13 @@ import { EuiFlexGroup, EuiFlexItem, EuiSmallButton, + EuiSwitch, EuiText, } from '@elastic/eui'; import { + CONFIG_STEP, customStringify, + FETCH_ALL_QUERY, QueryParam, SearchHit, WorkflowFormValues, @@ -34,6 +37,7 @@ interface QueryProps { queryResponse: string; setQueryResponse: (queryResponse: string) => void; hasSearchPipeline: boolean; + selectedStep: CONFIG_STEP; } const SEARCH_OPTIONS = [ @@ -56,12 +60,16 @@ export function Query(props: QueryProps) { // Form state const { values } = useFormikContext(); + // use custom query state + const [useCustomQuery, setUseCustomQuery] = useState(false); + // Standalone / sandboxed search request state. Users can test things out - // without updating the base form / persisted value. We default to any - // set form value on initialization - // TODO: allow overriding the temp request with a sandboxed value. + // without updating the base form / persisted value. We default to different values + // based on the context (ingest or search). const [tempRequest, setTempRequest] = useState( - values?.search?.request || '{}' + props.selectedStep === CONFIG_STEP.INGEST + ? customStringify(FETCH_ALL_QUERY) + : values?.search?.request || '{}' ); // state for if to execute search w/ or w/o any configured search pipeline. @@ -126,6 +134,7 @@ export function Query(props: QueryProps) { searchPipeline: props.hasSearchPipeline && includePipeline && + props.selectedStep === CONFIG_STEP.SEARCH && !isEmpty(values?.search?.pipelineName) ? values?.search?.pipelineName : '_none', @@ -161,12 +170,15 @@ export function Query(props: QueryProps) { singleSelection={{ asPlainText: true }} isClearable={false} options={ - props.hasSearchPipeline + props.hasSearchPipeline && + props.selectedStep === CONFIG_STEP.SEARCH ? SEARCH_OPTIONS : [SEARCH_OPTIONS[1]] } selectedOptions={ - props.hasSearchPipeline && includePipeline + props.hasSearchPipeline && + includePipeline && + props.selectedStep === CONFIG_STEP.SEARCH ? [SEARCH_OPTIONS[0]] : [SEARCH_OPTIONS[1]] } @@ -175,7 +187,46 @@ export function Query(props: QueryProps) { }} /> - + + setUseCustomQuery(!useCustomQuery)} + /> + + {useCustomQuery && ( + + { + setTempRequest(input); + }} + onBlur={() => { + try { + setTempRequest( + customStringify(JSON.parse(tempRequest)) + ); + } catch (error) {} + }} + readOnly={false} + setOptions={{ + fontSize: '14px', + useWorker: true, + highlightActiveLine: true, + highlightSelectedWord: true, + highlightGutterLine: true, + wrap: true, + }} + aria-label="Code Editor" + tabSize={2} + /> + + )} + {/** * This may return nothing if the list of params are empty */} diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index 601dc239..d9a5bdf3 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -15,7 +15,12 @@ import { EuiTabs, EuiText, } from '@elastic/eui'; -import { INSPECTOR_TAB_ID, INSPECTOR_TABS, Workflow } from '../../../../common'; +import { + CONFIG_STEP, + INSPECTOR_TAB_ID, + INSPECTOR_TABS, + Workflow, +} from '../../../../common'; import { Resources } from './resources'; import { Query } from './query'; import { Ingest } from './ingest'; @@ -29,6 +34,7 @@ interface ToolsProps { setQueryResponse: (queryResponse: string) => void; selectedTabId: INSPECTOR_TAB_ID; setSelectedTabId: (tabId: INSPECTOR_TAB_ID) => void; + selectedStep: CONFIG_STEP; } const PANEL_TITLE = 'Inspector'; @@ -129,6 +135,7 @@ export function Tools(props: ToolsProps) { hasSearchPipeline={hasProvisionedSearchResources( props.workflow )} + selectedStep={props.selectedStep} /> )} {props.selectedTabId === INSPECTOR_TAB_ID.ERRORS && ( From 108d283cf3484e2599474369a18b96559af079ce Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 3 Dec 2024 16:39:57 -0800 Subject: [PATCH 12/24] Override widths for most modals; add type field to queryparam Signed-off-by: Tyler Ohlsen --- common/interfaces.ts | 3 + .../general_components/query_params_list.tsx | 59 +++++++++++++++++-- .../components/export_modal.tsx | 6 +- .../ingest_inputs/source_data_modal.tsx | 6 +- .../modals/configure_expression_modal.tsx | 1 + .../configure_multi_expression_modal.tsx | 6 +- .../modals/configure_template_modal.tsx | 6 +- .../modals/override_query_modal.tsx | 6 +- .../search_inputs/edit_query_modal.tsx | 1 + .../workflow_inputs/workflow_inputs.tsx | 6 +- 10 files changed, 88 insertions(+), 12 deletions(-) diff --git a/common/interfaces.ts b/common/interfaces.ts index 41414f15..228666cc 100644 --- a/common/interfaces.ts +++ b/common/interfaces.ts @@ -557,8 +557,11 @@ export type QuickConfigureFields = { llmResponseField?: string; }; +export type QueryParamType = 'Text' | 'Binary'; + export type QueryParam = { name: string; + type: QueryParamType; value: string; }; diff --git a/public/general_components/query_params_list.tsx b/public/general_components/query_params_list.tsx index 951baa5e..34f0180e 100644 --- a/public/general_components/query_params_list.tsx +++ b/public/general_components/query_params_list.tsx @@ -4,14 +4,36 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiFieldText } from '@elastic/eui'; -import { QueryParam } from '../../common'; +import { get } from 'lodash'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiFieldText, + EuiComboBox, +} from '@elastic/eui'; +import { QueryParam, QueryParamType } from '../../common'; interface QueryParamsListProps { queryParams: QueryParam[]; setQueryParams: (params: QueryParam[]) => void; } +// The keys will be more static in general. Give more space for values where users +// will typically be writing out more complex transforms/configuration (in the case of ML inference processors). +const KEY_FLEX_RATIO = 3; +const TYPE_FLEX_RATIO = 2; +const VALUE_FLEX_RATIO = 5; + +const OPTIONS = [ + { + label: 'Text' as QueryParamType, + }, + { + label: 'Binary' as QueryParamType, + }, +]; + /** * Basic, reusable component for displaying a list of query parameters, and allowing * users to freely enter values for each. @@ -24,12 +46,17 @@ export function QueryParamsList(props: QueryParamsListProps) { - + Parameter - + + + Type + + + Value @@ -40,12 +67,32 @@ export function QueryParamsList(props: QueryParamsListProps) { return ( - + {queryParam.name} - + + { + props.setQueryParams( + props.queryParams.map((qp, i) => + i === idx + ? { ...qp, type: get(options, '0.label') } + : qp + ) + ); + }} + /> + + props.setIsExportModalOpen(false)}> + props.setIsExportModalOpen(false)} + >

{`Export ${getCharacterLimitedString( diff --git a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data_modal.tsx b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data_modal.tsx index 97c31b61..667596fe 100644 --- a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data_modal.tsx @@ -188,7 +188,11 @@ export function SourceDataModal(props: SourceDataProps) { }, [formikProps.errors]); return ( - onClose()} style={{ width: '70vw' }}> + onClose()} + style={{ width: '70vw' }} + >

{`Import data`}

diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_expression_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_expression_modal.tsx index c321dd74..86a12a4b 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_expression_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_expression_modal.tsx @@ -260,6 +260,7 @@ export function ConfigureExpressionModal(props: ConfigureExpressionModalProps) { return ( +

{`Extract data with expression`}

diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_template_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_template_modal.tsx index 8817e3bb..8cfc280f 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_template_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_template_modal.tsx @@ -281,7 +281,11 @@ export function ConfigureTemplateModal(props: ConfigureTemplateModalProps) { } return ( - +

{`Configure prompt`}

diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/override_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/override_query_modal.tsx index 430e8063..7542912f 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/override_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/override_query_modal.tsx @@ -82,7 +82,11 @@ export function OverrideQueryModal(props: OverrideQueryModalProps) { const [presetsPopoverOpen, setPresetsPopoverOpen] = useState(false); return ( - +

{`Override query`}

diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx index ce29d88c..39197f67 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx @@ -144,6 +144,7 @@ export function EditQueryModal(props: EditQueryModalProps) { onClose={() => props.setModalOpen(false)} style={{ width: '70vw' }} data-testid="editQueryModal" + maxWidth={false} > diff --git a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx index a54eda28..b332193a 100644 --- a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx @@ -591,7 +591,11 @@ export function WorkflowInputs(props: WorkflowInputsProps) { ]} /> {isDeleteModalOpen && ( - setIsDeleteModalOpen(false)}> + setIsDeleteModalOpen(false)} + >

{`Delete resources for workflow ${getCharacterLimitedString( From 5dba8d289ffe1a3c26da8ca29addcbc9f2dea107 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 3 Dec 2024 17:02:54 -0800 Subject: [PATCH 13/24] Add binary conversion for images when doing query params Signed-off-by: Tyler Ohlsen --- .../general_components/query_params_list.tsx | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/public/general_components/query_params_list.tsx b/public/general_components/query_params_list.tsx index 34f0180e..4c7408a8 100644 --- a/public/general_components/query_params_list.tsx +++ b/public/general_components/query_params_list.tsx @@ -11,6 +11,7 @@ import { EuiText, EuiFieldText, EuiComboBox, + EuiCompressedFilePicker, } from '@elastic/eui'; import { QueryParam, QueryParamType } from '../../common'; @@ -93,19 +94,51 @@ export function QueryParamsList(props: QueryParamsListProps) { /> - { - props.setQueryParams( - props.queryParams.map((qp, i) => - i === idx ? { ...qp, value: e.target.value } : qp - ) - ); - }} - /> + {queryParam.type === 'Binary' ? ( + // For binary filetypes, accept images + { + if (files && files.length > 0) { + const fileReader = new FileReader(); + fileReader.onload = (e) => { + try { + const binaryData = e.target?.result as string; + const base64Str = binaryData.split(',')[1]; + props.setQueryParams( + props.queryParams.map((qp, i) => + i === idx + ? { ...qp, value: base64Str } + : qp + ) + ); + } catch {} + }; + fileReader.readAsDataURL(files[0]); + } + }} + display="default" + /> + ) : ( + // Default to freeform text input + { + props.setQueryParams( + props.queryParams.map((qp, i) => + i === idx + ? { ...qp, value: e?.target?.value } + : qp + ) + ); + }} + /> + )} From ab0ef5481b6c0ac29263badec144007ea7331a66 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 3 Dec 2024 17:11:48 -0800 Subject: [PATCH 14/24] Fix modal width for delete workflow Signed-off-by: Tyler Ohlsen --- public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx index b332193a..d929a3f4 100644 --- a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx @@ -592,7 +592,6 @@ export function WorkflowInputs(props: WorkflowInputsProps) { /> {isDeleteModalOpen && ( setIsDeleteModalOpen(false)} > From 1f56cf2adf38c562c89bfbac177790cb3fa05ffe Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 3 Dec 2024 17:36:36 -0800 Subject: [PATCH 15/24] update test Signed-off-by: Tyler Ohlsen --- public/pages/workflow_detail/workflow_detail.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index ddb9f3fe..10a2bb5f 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -202,7 +202,7 @@ describe('WorkflowDetail Page with skip ingestion option (Hybrid Search Workflow expect(queryEditButton).toBeInTheDocument(); userEvent.click(queryEditButton); await waitFor(() => { - expect(getAllByText('Edit query').length).toBeGreaterThan(0); + expect(getAllByText('Edit query definition').length).toBeGreaterThan(0); }); const searchQueryPresetButton = getByTestId('searchQueryPresetButton'); expect(searchQueryPresetButton).toBeInTheDocument(); From 0ba11dca4613de8c719711566dde063eee1f1043 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 4 Dec 2024 08:51:04 -0800 Subject: [PATCH 16/24] Add guardrails on search btn eligibility Signed-off-by: Tyler Ohlsen --- public/pages/workflow_detail/tools/query/query.tsx | 9 ++++++++- .../search_inputs/edit_query_modal.tsx | 8 +++++++- public/utils/utils.ts | 12 ++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index 3aa68811..179921cf 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -26,6 +26,7 @@ import { } from '../../../../../common'; import { searchIndex, useAppDispatch } from '../../../../store'; import { + containsEmptyValues, containsSameValues, getDataSourceId, getPlaceholdersFromQuery, @@ -90,7 +91,11 @@ export function Query(props: QueryProps) { ) ) { setQueryParams( - placeholders.map((placeholder) => ({ name: placeholder, value: '' })) + placeholders.map((placeholder) => ({ + name: placeholder, + type: 'Text', + value: '', + })) ); } }, [tempRequest]); @@ -125,6 +130,7 @@ export function Query(props: QueryProps) { { dispatch( searchIndex({ @@ -166,6 +172,7 @@ export function Query(props: QueryProps) { ({ name: placeholder, value: '' })) + placeholders.map((placeholder) => ({ + name: placeholder, + type: 'Text', + value: '', + })) ); } setTempResultsError(''); @@ -226,6 +231,7 @@ export function EditQueryModal(props: EditQueryModalProps) { { dispatch( searchIndex({ diff --git a/public/utils/utils.ts b/public/utils/utils.ts index 5bb77754..a7b2f09b 100644 --- a/public/utils/utils.ts +++ b/public/utils/utils.ts @@ -573,6 +573,18 @@ export function containsSameValues(arr1: string[], arr2: string[]) { return true; } +// simple util fn to check for empty/missing query param values +export function containsEmptyValues(params: QueryParam[]): boolean { + let containsEmpty = false; + params.forEach((param) => { + if (isEmpty(param.value)) { + containsEmpty = true; + } + }); + return containsEmpty; +} + +// simple util fn to inject parameters in the base query string with its associated value export function injectParameters( params: QueryParam[], queryString: string From 11065549de3724552b70f86493411b846c000f3a Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 4 Dec 2024 09:09:32 -0800 Subject: [PATCH 17/24] More state management; fix some spacing Signed-off-by: Tyler Ohlsen --- public/general_components/query_params_list.tsx | 2 +- .../pages/workflow_detail/tools/query/query.tsx | 15 ++++++++++++++- .../search_inputs/edit_query_modal.tsx | 5 ++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/public/general_components/query_params_list.tsx b/public/general_components/query_params_list.tsx index 4c7408a8..6460ef00 100644 --- a/public/general_components/query_params_list.tsx +++ b/public/general_components/query_params_list.tsx @@ -43,7 +43,7 @@ export function QueryParamsList(props: QueryParamsListProps) { return ( <> {props.queryParams?.length > 0 && ( - + diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index 179921cf..e9d2ad7b 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -80,8 +80,20 @@ export function Query(props: QueryProps) { setIncludePipeline(props.hasSearchPipeline); }, [props.hasSearchPipeline]); - // query/request params state, update when the request is changed/updated + // query params state const [queryParams, setQueryParams] = useState([]); + + // listen for changes to the upstream / form query, and reset the default + useEffect(() => { + if (!isEmpty(values?.search?.request)) { + setTempRequest(values?.search?.request); + } + }, [values?.search?.request]); + + // Do a few things when the request is changed: + // 1. Check if there is a new set of query parameters, and if so, + // reset the form. + // 2. Clear any stale results useEffect(() => { const placeholders = getPlaceholdersFromQuery(tempRequest); if ( @@ -98,6 +110,7 @@ export function Query(props: QueryProps) { })) ); } + props.setQueryResponse(''); }, [tempRequest]); // empty states diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx index 14fbd344..48affada 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx @@ -93,6 +93,7 @@ export function EditQueryModal(props: EditQueryModalProps) { // 1. Check if there is a new set of query parameters, and if so, // reset the form. // 2. Clear any persisted error + // 3. Clear any stale results useEffect(() => { const placeholders = getPlaceholdersFromQuery(tempRequest); if ( @@ -110,6 +111,7 @@ export function EditQueryModal(props: EditQueryModalProps) { ); } setTempResultsError(''); + setTempResults(''); }, [tempRequest]); // Clear any error if the parameters have been updated in any way @@ -147,7 +149,7 @@ export function EditQueryModal(props: EditQueryModalProps) { return ( props.setModalOpen(false)} - style={{ width: '70vw' }} + style={{ width: '70vw', height: '70vh' }} data-testid="editQueryModal" maxWidth={false} > @@ -213,6 +215,7 @@ export function EditQueryModal(props: EditQueryModalProps) { From 5df8d53bb09f7e1ec156a99372100d19d5731986 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 4 Dec 2024 09:41:42 -0800 Subject: [PATCH 18/24] fix inspector panel overflow Signed-off-by: Tyler Ohlsen --- public/pages/workflow_detail/tools/tools.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index d9a5bdf3..656eeab0 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -92,7 +92,7 @@ export function Tools(props: ToolsProps) { paddingSize="m" borderRadius="l" grow={true} - style={{ height: '100%' }} + style={{ minHeight: '100%' }} > Date: Wed, 4 Dec 2024 09:46:58 -0800 Subject: [PATCH 19/24] Remove test query btn Signed-off-by: Tyler Ohlsen --- .../configure_search_request.tsx | 49 +------------------ 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/configure_search_request.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/configure_search_request.tsx index 04fbaee4..fea99ff3 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/configure_search_request.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/configure_search_request.tsx @@ -17,13 +17,8 @@ import { EuiCodeBlock, EuiSmallButtonEmpty, } from '@elastic/eui'; -import { - SearchHit, - WorkflowFormValues, - customStringify, -} from '../../../../../common'; -import { AppState, searchIndex, useAppDispatch } from '../../../../store'; -import { getDataSourceId } from '../../../../utils/utils'; +import { WorkflowFormValues } from '../../../../../common'; +import { AppState } from '../../../../store'; import { EditQueryModal } from './edit_query_modal'; interface ConfigureSearchRequestProps { @@ -35,9 +30,6 @@ interface ConfigureSearchRequestProps { * Input component for configuring a search request */ export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) { - const dispatch = useAppDispatch(); - const dataSourceId = getDataSourceId(); - // Form state const { values, setFieldValue, setFieldTouched } = useFormikContext< WorkflowFormValues @@ -133,43 +125,6 @@ export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) { Edit - - { - // for this test query, we don't want to involve any configured search pipelines, if any exist - // see https://opensearch.org/docs/latest/search-plugins/search-pipelines/using-search-pipeline/#disabling-the-default-pipeline-for-a-request - dispatch( - searchIndex({ - apiBody: { - index: values.search.index.name, - body: values.search.request, - searchPipeline: '_none', - }, - dataSourceId, - }) - ) - .unwrap() - .then(async (resp) => { - props.setQueryResponse( - customStringify( - resp?.hits?.hits?.map( - (hit: SearchHit) => hit._source - ) - ) - ); - }) - .catch((error: any) => { - props.setQueryResponse(''); - console.error('Error running query: ', error); - }); - }} - data-testid="searchTestButton" - iconType="play" - iconSide="left" - > - Test query - - From 4d00f8a666d776849253f2f6b0960177e0586302 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 4 Dec 2024 10:18:27 -0800 Subject: [PATCH 20/24] Add more guardrails on ingest Signed-off-by: Tyler Ohlsen --- .../workflow_detail/tools/query/query.tsx | 23 +++++++++++++++---- public/pages/workflow_detail/tools/tools.tsx | 8 ++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index e9d2ad7b..4c6d30ce 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -38,6 +38,7 @@ interface QueryProps { queryResponse: string; setQueryResponse: (queryResponse: string) => void; hasSearchPipeline: boolean; + hasIngestResources: boolean; selectedStep: CONFIG_STEP; } @@ -116,17 +117,28 @@ export function Query(props: QueryProps) { // empty states const noSearchIndex = isEmpty(values?.search?.index?.name); const noSearchRequest = isEmpty(values?.search?.request); + const onIngestAndInvalid = + props.selectedStep === CONFIG_STEP.INGEST && !props.hasIngestResources; + const onSearchAndInvalid = + props.selectedStep === CONFIG_STEP.SEARCH && + (noSearchIndex || noSearchRequest); + const indexToSearch = + props.selectedStep === CONFIG_STEP.INGEST + ? values?.ingest?.index?.name + : values?.search?.index?.name; return ( <> - {noSearchIndex || noSearchRequest ? ( + {onIngestAndInvalid || onSearchAndInvalid ? ( Missing search configurations} titleSize="s" body={ <> - Configure a search request and an index to search against first. + {onIngestAndInvalid + ? `Configure an index and ingest data first.` + : `Configure a search request and an index to search against first.`} } @@ -143,12 +155,15 @@ export function Query(props: QueryProps) { { dispatch( searchIndex({ apiBody: { - index: values?.search?.index?.name, + index: indexToSearch, body: injectParameters(queryParams, tempRequest), searchPipeline: props.hasSearchPipeline && diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index 656eeab0..156037de 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -25,7 +25,10 @@ import { Resources } from './resources'; import { Query } from './query'; import { Ingest } from './ingest'; import { Errors } from './errors'; -import { hasProvisionedSearchResources } from '../../../utils'; +import { + hasProvisionedIngestResources, + hasProvisionedSearchResources, +} from '../../../utils'; interface ToolsProps { workflow?: Workflow; @@ -135,6 +138,9 @@ export function Tools(props: ToolsProps) { hasSearchPipeline={hasProvisionedSearchResources( props.workflow )} + hasIngestResources={hasProvisionedIngestResources( + props.workflow + )} selectedStep={props.selectedStep} /> )} From d0209c0c7c6ee104b5dcf026898426b056172f21 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 4 Dec 2024 10:47:23 -0800 Subject: [PATCH 21/24] Fully fix spacing; add empty state modal for ingest in inspector Signed-off-by: Tyler Ohlsen --- .../workflow_detail/tools/ingest/ingest.tsx | 45 ++++++++++++------- .../workflow_detail/tools/query/query.tsx | 6 +-- public/pages/workflow_detail/tools/tools.tsx | 4 +- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/public/pages/workflow_detail/tools/ingest/ingest.tsx b/public/pages/workflow_detail/tools/ingest/ingest.tsx index cdae1df0..f2b3c8ec 100644 --- a/public/pages/workflow_detail/tools/ingest/ingest.tsx +++ b/public/pages/workflow_detail/tools/ingest/ingest.tsx @@ -4,7 +4,8 @@ */ import React from 'react'; -import { EuiCodeEditor } from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import { EuiCodeEditor, EuiEmptyPrompt, EuiText } from '@elastic/eui'; interface IngestProps { ingestResponse: string; @@ -19,19 +20,33 @@ export function Ingest(props: IngestProps) { // TODO: known issue with the editor where resizing the resizablecontainer does not // trigger vertical scroll updates. Updating the window, or reloading the component // by switching tabs etc. will refresh it correctly - + <> + {isEmpty(props.ingestResponse) ? ( + No data} + titleSize="s" + body={ + <> + Run ingest and view the response here. + + } + /> + ) : ( + + )} + ); } diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index 4c6d30ce..d7de4a87 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -137,7 +137,7 @@ export function Query(props: QueryProps) { <> {onIngestAndInvalid - ? `Configure an index and ingest data first.` + ? `Create an index and ingest data first.` : `Configure a search request and an index to search against first.`} @@ -230,12 +230,12 @@ export function Query(props: QueryProps) { /> {useCustomQuery && ( - + { setTempRequest(input); diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index 156037de..00ba59dd 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -95,12 +95,14 @@ export function Tools(props: ToolsProps) { paddingSize="m" borderRadius="l" grow={true} - style={{ minHeight: '100%' }} + style={{ height: '100%' }} > From 880401e9901cfa6c8b578ad105e0539652959cd0 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 4 Dec 2024 10:54:42 -0800 Subject: [PATCH 22/24] Update UT Signed-off-by: Tyler Ohlsen --- public/pages/workflow_detail/workflow_detail.test.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index 10a2bb5f..b1db4084 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -194,8 +194,6 @@ describe('WorkflowDetail Page with skip ingestion option (Hybrid Search Workflow expect(getAllByText('Define search pipeline').length).toBeGreaterThan(0); }); expect(getAllByText('Configure query').length).toBeGreaterThan(0); - const searchTestButton = getByTestId('searchTestButton'); - expect(searchTestButton).toBeInTheDocument(); // Edit Search Query const queryEditButton = getByTestId('queryEditButton'); From 423e13ef68f7caba966a2fe6df3ccee15346d8a5 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 4 Dec 2024 11:29:14 -0800 Subject: [PATCH 23/24] prevent running ingest until docs populated Signed-off-by: Tyler Ohlsen --- .../workflow_inputs/workflow_inputs.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx index d929a3f4..ec993635 100644 --- a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx @@ -241,8 +241,18 @@ export function WorkflowInputs(props: WorkflowInputsProps) { setSearchProvisioned(hasProvisionedSearchResources(props.workflow)); }, [props.workflow]); + // populated ingest docs state + const [docsPopulated, setDocsPopulated] = useState(false); + useEffect(() => { + let parsedDocsObjs = [] as {}[]; + try { + parsedDocsObjs = JSON.parse(props.ingestDocs); + } catch (e) {} + setDocsPopulated(parsedDocsObjs.length > 0 && !isEmpty(parsedDocsObjs[0])); + }, [props.ingestDocs]); + // maintain global states (button eligibility) - const ingestRunButtonDisabled = !ingestTemplatesDifferent; + const ingestRunButtonDisabled = !ingestTemplatesDifferent || !docsPopulated; const ingestToSearchButtonDisabled = ingestTemplatesDifferent || props.isRunningIngest; const searchBackButtonDisabled = From 1b7353d49015100ab44cf8ae92533c6600562f25 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 4 Dec 2024 12:43:23 -0800 Subject: [PATCH 24/24] fix UT Signed-off-by: Tyler Ohlsen --- public/pages/workflow_detail/workflow_detail.test.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index b1db4084..24359bc4 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -93,10 +93,9 @@ describe('WorkflowDetail Page with create ingestion option', () => { expect(getByRole('tab', { name: 'Errors' })).toBeInTheDocument(); expect(getByRole('tab', { name: 'Resources' })).toBeInTheDocument(); - // "Run ingestion" button should be enabled by default + // "Run ingestion" button exists const runIngestionButton = getByTestId('runIngestionButton'); expect(runIngestionButton).toBeInTheDocument(); - expect(runIngestionButton).toBeEnabled(); // "Search pipeline" button should be disabled by default const searchPipelineButton = getByTestId('searchPipelineButton');