Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.x] Finalize edit flow for ingest inputs #203

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,22 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import {
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiSuperSelect,
EuiSuperSelectOption,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { useFormikContext } from 'formik';
import { IConfigField, WorkspaceFormValues } from '../../../../../common';
import { JsonField } from '../input_fields';
import { AppState, catIndices, useAppDispatch } from '../../../../store';

interface ConfigureSearchRequestProps {
setQuery: (query: string) => void;
Expand All @@ -18,7 +29,20 @@ interface ConfigureSearchRequestProps {
* Input component for configuring a search request
*/
export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) {
const dispatch = useAppDispatch();

// Form state
const { values } = useFormikContext<WorkspaceFormValues>();
const indexName = values.ingest.index.name;
const ingestEnabled = values.ingest.enabled;

// All indices state
const indices = useSelector((state: AppState) => state.opensearch.indices);

// Selected index state
const [selectedIndex, setSelectedIndex] = useState<string | undefined>(
undefined
);

// Hook to listen when the query form value changes.
// Try to set the query request if possible
Expand All @@ -28,13 +52,44 @@ export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) {
}
}, [values?.search?.request]);

// Initialization hook to fetch available indices (if applicable)
useEffect(() => {
if (!ingestEnabled) {
// Fetch all indices besides system indices
dispatch(catIndices('*,-.*'));
}
}, []);

return (
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<EuiTitle size="s">
<h2>Configure query</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFormRow label="Retrieval index">
{ingestEnabled ? (
<EuiFieldText value={indexName} readOnly={true} />
) : (
<EuiSuperSelect
options={Object.values(indices).map(
(option) =>
({
value: option.name,
inputDisplay: <EuiText>{option.name}</EuiText>,
disabled: false,
} as EuiSuperSelectOption<string>)
)}
valueOfSelected={selectedIndex}
onChange={(option) => {
setSelectedIndex(option);
}}
isInvalid={selectedIndex !== undefined}
/>
)}
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<JsonField
// We want to integrate query into the form, but not persist in the config.
Expand Down
129 changes: 96 additions & 33 deletions public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useFormikContext } from 'formik';
import { isEmpty } from 'lodash';
import {
Expand All @@ -12,7 +13,13 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiIcon,
EuiLoadingSpinner,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiPanel,
EuiSpacer,
EuiStepsHorizontal,
Expand All @@ -27,10 +34,12 @@ import {
import { IngestInputs } from './ingest_inputs';
import { SearchInputs } from './search_inputs';
import {
AppState,
deprovisionWorkflow,
getWorkflow,
ingest,
provisionWorkflow,
removeDirty,
searchIndex,
updateWorkflow,
useAppDispatch,
Expand All @@ -41,6 +50,7 @@ import {
reduceToTemplate,
configToTemplateFlows,
hasProvisionedIngestResources,
hasProvisionedSearchResources,
} from '../../../utils';
import { BooleanField } from './input_fields';
import { ExportOptions } from './export_options';
Expand Down Expand Up @@ -78,16 +88,23 @@ enum INGEST_OPTION {
*/

export function WorkflowInputs(props: WorkflowInputsProps) {
const { submitForm, validateForm, values } = useFormikContext<
const { submitForm, validateForm, setFieldValue, values } = useFormikContext<
WorkflowFormValues
>();
const dispatch = useAppDispatch();

// Overall workspace state
const { isDirty } = useSelector((state: AppState) => state.workspace);

// selected step state
const [selectedStep, setSelectedStep] = useState<STEP>(STEP.INGEST);

// ingest state
// provisioned resources states
const [ingestProvisioned, setIngestProvisioned] = useState<boolean>(false);
const [searchProvisioned, setSearchProvisioned] = useState<boolean>(false);

// confirm modal state
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

// maintain global states
const onIngest = selectedStep === STEP.INGEST;
Expand All @@ -100,6 +117,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) {

useEffect(() => {
setIngestProvisioned(hasProvisionedIngestResources(props.workflow));
setSearchProvisioned(hasProvisionedSearchResources(props.workflow));
}, [props.workflow]);

// Utility fn to update the workflow, including any updated/new resources
Expand Down Expand Up @@ -203,6 +221,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
.unwrap()
.then(async (resp) => {
props.setIngestResponse(JSON.stringify(resp, undefined, 2));
dispatch(removeDirty());
})
.catch((error: any) => {
getCore().notifications.toasts.addDanger(error);
Expand Down Expand Up @@ -235,6 +254,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
.then(async (resp) => {
const hits = resp.hits.hits;
props.setQueryResponse(JSON.stringify(hits, undefined, 2));
dispatch(removeDirty());
})
.catch((error: any) => {
getCore().notifications.toasts.addDanger(error);
Expand Down Expand Up @@ -286,7 +306,50 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
},
]}
/>
{onIngest && (
{isModalOpen && (
<EuiModal onClose={() => setIsModalOpen(false)}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<p>{`Delete resources for workflow ${props.workflow.name}?`}</p>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiText>
The resources for this workflow will be permanently deleted.
This action cannot be undone.
</EuiText>
</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty onClick={() => setIsModalOpen(false)}>
{' '}
Cancel
</EuiButtonEmpty>
<EuiButton
onClick={async () => {
// @ts-ignore
await dispatch(deprovisionWorkflow(props.workflow.id))
.unwrap()
.then(async (result) => {
setFieldValue('ingest.enabled', false);
// @ts-ignore
await dispatch(getWorkflow(props.workflow.id));
})
.catch((error: any) => {
getCore().notifications.toasts.addDanger(error);
})
.finally(() => {
setIsModalOpen(false);
});
}}
fill={true}
color="danger"
>
Delete resources
</EuiButton>
</EuiModalFooter>
</EuiModal>
)}
{onIngestAndUnprovisioned && (
<>
<EuiSpacer size="m" />
<BooleanField
Expand Down Expand Up @@ -327,13 +390,31 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
<EuiFlexItem grow={false}>
<EuiTitle>
<h2>
{onIngestAndUnprovisioned
? 'Define ingest pipeline'
: onIngestAndProvisioned
? 'Edit ingest pipeline'
: onSearch
? 'Define search pipeline'
: 'Export project as'}
{onIngestAndUnprovisioned ? (
'Define ingest pipeline'
) : onIngestAndProvisioned ? (
<EuiFlexGroup
direction="row"
justifyContent="spaceBetween"
>
<EuiFlexItem grow={false}>
Edit ingest pipeline
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="danger"
onClick={() => setIsModalOpen(true)}
>
<EuiIcon type="trash" />
{` `}Delete resources
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
) : onSearch ? (
'Define search pipeline'
) : (
'Export project as'
)}
</h2>
</EuiTitle>
</EuiFlexItem>
Expand Down Expand Up @@ -380,34 +461,15 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
{`Search pipeline >`}
</EuiButton>
</EuiFlexItem>
) : onIngestAndUnprovisioned ? (
<>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={() => setSelectedStep(STEP.SEARCH)}
>
Skip
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
fill={true}
onClick={() => {
validateAndRunIngestion();
}}
>
Run ingestion
</EuiButton>
</EuiFlexItem>
</>
) : onIngestAndProvisioned ? (
) : onIngest ? (
<>
<EuiFlexItem grow={false}>
<EuiButton
fill={false}
onClick={() => {
validateAndRunIngestion();
}}
disabled={ingestProvisioned && !isDirty}
>
Run ingestion
</EuiButton>
Expand All @@ -416,6 +478,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
<EuiButton
fill={true}
onClick={() => setSelectedStep(STEP.SEARCH)}
disabled={!ingestProvisioned || isDirty}
>
{`Search pipeline >`}
</EuiButton>
Expand All @@ -432,7 +495,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
disabled={false}
disabled={searchProvisioned && !isDirty}
fill={false}
onClick={() => {
validateAndRunQuery();
Expand All @@ -443,7 +506,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
disabled={false}
disabled={!searchProvisioned || isDirty}
fill={false}
onClick={() => {
setSelectedStep(STEP.EXPORT);
Expand Down
14 changes: 14 additions & 0 deletions public/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,17 @@ export function hasProvisionedIngestResources(
});
return result;
}

export function hasProvisionedSearchResources(
workflow: Workflow | undefined
): boolean {
let result = false;
workflow?.resourcesCreated?.some((resource) => {
if (
resource.stepType === WORKFLOW_STEP_TYPE.CREATE_SEARCH_PIPELINE_STEP_TYPE
) {
result = true;
}
});
return result;
}
Loading