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 main] UX fit-n-finish updates XII #620

Merged
merged 1 commit into from
Feb 13, 2025
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
7 changes: 7 additions & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ export enum WORKFLOW_TYPE {
CUSTOM = 'Custom Search',
UNKNOWN = 'Unknown',
}
export enum WORKFLOW_TYPE_LEGACY {
SEMANTIC_SEARCH = 'Semantic Search',
MULTIMODAL_SEARCH = 'Multimodal Search',
HYBRID_SEARCH = 'Hybrid Search',
CUSTOM = 'Custom Search',
UNKNOWN = 'Unknown',
}
// If no datasource version is found, we default to 2.17.0
export const MIN_SUPPORTED_VERSION = '2.17.0';
// Min version to support ML processors
Expand Down
4 changes: 2 additions & 2 deletions public/pages/workflow_detail/tools/errors/errors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import React, { ReactNode } from 'react';
import {
EuiCodeBlock,
EuiEmptyPrompt,
Expand All @@ -12,7 +12,7 @@ import {
} from '@elastic/eui';

interface ErrorsProps {
errorMessages: string[];
errorMessages: (string | ReactNode)[];
}

/**
Expand Down
15 changes: 11 additions & 4 deletions public/pages/workflow_detail/tools/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useEffect, useState } from 'react';
import React, { ReactNode, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from '../../../store';
import { isEmpty } from 'lodash';
Expand All @@ -26,6 +26,7 @@ import { Query } from './query';
import { Ingest } from './ingest';
import { Errors } from './errors';
import {
formatProcessorError,
hasProvisionedIngestResources,
hasProvisionedSearchResources,
} from '../../../utils';
Expand All @@ -52,7 +53,9 @@ export function Tools(props: ToolsProps) {
ingestPipeline: ingestPipelineErrors,
searchPipeline: searchPipelineErrors,
} = useSelector((state: AppState) => state.errors);
const [curErrorMessages, setCurErrorMessages] = useState<string[]>([]);
const [curErrorMessages, setCurErrorMessages] = useState<
(string | ReactNode)[]
>([]);

// Propagate any errors coming from opensearch API calls, including ingest/search pipeline verbose calls.
useEffect(() => {
Expand All @@ -66,12 +69,16 @@ export function Tools(props: ToolsProps) {
} else if (!isEmpty(ingestPipelineErrors)) {
setCurErrorMessages([
'Data not ingested. Errors found with the following ingest processor(s):',
...Object.values(ingestPipelineErrors).map((value) => value.errorMsg),
...Object.values(ingestPipelineErrors).map((ingestPipelineError) =>
formatProcessorError(ingestPipelineError)
),
]);
} else if (!isEmpty(searchPipelineErrors)) {
setCurErrorMessages([
'Errors found with the following search processor(s)',
...Object.values(searchPipelineErrors).map((value) => value.errorMsg),
...Object.values(searchPipelineErrors).map((searchPipelineError) =>
formatProcessorError(searchPipelineError)
),
]);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ export function SourceData(props: SourceDataProps) {
<h3>Import sample data</h3>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{docsPopulated ? (
{docsPopulated && (
<EuiFlexItem grow={false}>
<EuiSmallButtonEmpty
onClick={() => setIsEditModalOpen(true)}
data-testid="editSourceDataButton"
Expand All @@ -122,19 +122,8 @@ export function SourceData(props: SourceDataProps) {
>
Edit
</EuiSmallButtonEmpty>
) : (
<EuiSmallButton
fill={false}
style={{ width: '75px' }}
onClick={() => setIsEditModalOpen(true)}
data-testid="selectDataToImportButton"
iconType="plus"
iconSide="left"
>
{`Import`}
</EuiSmallButton>
)}
</EuiFlexItem>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
{props.lastIngested !== undefined && (
Expand Down Expand Up @@ -208,6 +197,17 @@ export function SourceData(props: SourceDataProps) {
<EuiText size="s">
Import a data sample to start configuring your ingest flow.
</EuiText>
<EuiSpacer size="m" />
<EuiSmallButton
fill={true}
style={{ width: '130px' }}
onClick={() => setIsEditModalOpen(true)}
data-testid="selectDataToImportButton"
iconType="plus"
iconSide="left"
>
{`Import data`}
</EuiSmallButton>
</>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ import {
EuiCompressedSuperSelect,
EuiSuperSelectOption,
EuiText,
EuiSmallButton,
EuiSmallButtonIcon,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import {
MODEL_STATE,
WorkflowFormValues,
ModelFormValue,
ML_CHOOSE_MODEL_LINK,
ML_REMOTE_MODEL_LINK,
FETCH_ALL_QUERY_LARGE,
} from '../../../../../common';
import { AppState } from '../../../../store';
import { AppState, searchModels, useAppDispatch } from '../../../../store';
import { getDataSourceId } from '../../../../utils';

interface ModelFieldProps {
fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField')
Expand All @@ -47,6 +50,8 @@ type ModelItem = ModelFormValue & {
* A specific field for selecting existing deployed models
*/
export function ModelField(props: ModelFieldProps) {
const dispatch = useAppDispatch();
const dataSourceId = getDataSourceId();
// Initial store is fetched when loading base <DetectorDetail /> page. We don't
// re-fetch here as it could overload client-side if user clicks back and forth /
// keeps re-rendering this component (and subsequently re-fetching data) as they're building flows
Expand Down Expand Up @@ -94,31 +99,6 @@ export function ModelField(props: ModelFieldProps) {
<EuiSpacer size="s" />
</>
)}
{isEmpty(deployedModels) && (
<>
<EuiCallOut
size="s"
title="No deployed models found"
iconType={'alert'}
color="warning"
>
<EuiText size="s">
To create and deploy models and make them accessible in
OpenSearch, see documentation.
</EuiText>
<EuiSpacer size="s" />
<EuiSmallButton
target="_blank"
href={ML_REMOTE_MODEL_LINK}
iconSide="right"
iconType={'popout'}
>
Documentation
</EuiSmallButton>
</EuiCallOut>
<EuiSpacer size="s" />
</>
)}
<Field name={props.fieldPath}>
{({ field, form }: FieldProps) => {
const isInvalid =
Expand All @@ -140,45 +120,64 @@ export function ModelField(props: ModelFieldProps) {
isInvalid={isInvalid}
error={props.showError && getIn(errors, `${field.name}.id`)}
>
<EuiCompressedSuperSelect
data-testid="selectDeployedModel"
fullWidth={props.fullWidth}
disabled={isEmpty(deployedModels)}
options={deployedModels.map(
(option) =>
({
value: option.id,
inputDisplay: (
<>
<EuiText size="s">{option.name}</EuiText>
</>
),
dropdownDisplay: (
<>
<EuiText size="s">{option.name}</EuiText>
<EuiText size="xs" color="subdued">
Deployed
</EuiText>
<EuiText size="xs" color="subdued">
{option.algorithm}
</EuiText>
</>
),
disabled: false,
} as EuiSuperSelectOption<string>)
)}
valueOfSelected={field.value?.id || ''}
onChange={(option: string) => {
form.setFieldTouched(props.fieldPath, true);
form.setFieldValue(props.fieldPath, {
id: option,
} as ModelFormValue);
if (props.onModelChange) {
props.onModelChange(option);
}
}}
isInvalid={isInvalid}
/>
<EuiFlexGroup direction="row" gutterSize="xs">
<EuiFlexItem grow={true}>
<EuiCompressedSuperSelect
data-testid="selectDeployedModel"
fullWidth={props.fullWidth}
disabled={isEmpty(deployedModels)}
options={deployedModels.map(
(option) =>
({
value: option.id,
inputDisplay: (
<>
<EuiText size="s">{option.name}</EuiText>
</>
),
dropdownDisplay: (
<>
<EuiText size="s">{option.name}</EuiText>
<EuiText size="xs" color="subdued">
Deployed
</EuiText>
<EuiText size="xs" color="subdued">
{option.algorithm}
</EuiText>
</>
),
disabled: false,
} as EuiSuperSelectOption<string>)
)}
valueOfSelected={field.value?.id || ''}
onChange={(option: string) => {
form.setFieldTouched(props.fieldPath, true);
form.setFieldValue(props.fieldPath, {
id: option,
} as ModelFormValue);
if (props.onModelChange) {
props.onModelChange(option);
}
}}
isInvalid={isInvalid}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSmallButtonIcon
iconType={'refresh'}
aria-label="refresh"
display="base"
onClick={() => {
dispatch(
searchModels({
apiBody: FETCH_ALL_QUERY_LARGE,
dataSourceId,
})
);
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCompressedFormRow>
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
OutputMapArrayFormValue,
EMPTY_OUTPUT_MAP_ENTRY,
ML_REMOTE_MODEL_LINK,
FETCH_ALL_QUERY_LARGE,
} from '../../../../../../common';
import { ModelField } from '../../input_fields';
import {
Expand All @@ -39,9 +40,10 @@ import {
} from '../../../../../../common';
import { OverrideQueryModal } from './modals';
import { ModelInputs } from './model_inputs';
import { AppState } from '../../../../../store';
import { AppState, searchModels, useAppDispatch } from '../../../../../store';
import {
formikToPartialPipeline,
getDataSourceId,
parseModelInputs,
parseModelOutputs,
} from '../../../../../utils';
Expand All @@ -62,6 +64,8 @@ interface MLProcessorInputsProps {
* output map configuration forms, respectively.
*/
export function MLProcessorInputs(props: MLProcessorInputsProps) {
const dispatch = useAppDispatch();
const dataSourceId = getDataSourceId();
const { models } = useSelector((state: AppState) => state.ml);
const { values, setFieldValue, setFieldTouched } = useFormikContext<
WorkflowFormValues
Expand Down Expand Up @@ -191,11 +195,30 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
color="primary"
size="s"
title={
<EuiText size="s">
You have no models registered in your cluster.{' '}
<EuiLink href={ML_REMOTE_MODEL_LINK}>Learn more</EuiLink> about
integrating ML models.
</EuiText>
<>
<EuiText size="s">
You have no models registered in your cluster.{' '}
<EuiLink href={ML_REMOTE_MODEL_LINK} target="_blank">
Learn more
</EuiLink>{' '}
about integrating ML models.
</EuiText>
<EuiSpacer size="s" />
<EuiSmallButton
iconType={'refresh'}
iconSide="left"
onClick={() => {
dispatch(
searchModels({
apiBody: FETCH_ALL_QUERY_LARGE,
dataSourceId,
})
);
}}
>
Refresh
</EuiSmallButton>
</>
}
/>
) : (
Expand Down
Loading
Loading