Skip to content

Commit 7213d4a

Browse files
UX fit-n-finish updates XII (#618)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> (cherry picked from commit 9462779) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent ef82af5 commit 7213d4a

File tree

13 files changed

+243
-165
lines changed

13 files changed

+243
-165
lines changed

common/constants.ts

+7
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ export enum WORKFLOW_TYPE {
170170
CUSTOM = 'Custom Search',
171171
UNKNOWN = 'Unknown',
172172
}
173+
export enum WORKFLOW_TYPE_LEGACY {
174+
SEMANTIC_SEARCH = 'Semantic Search',
175+
MULTIMODAL_SEARCH = 'Multimodal Search',
176+
HYBRID_SEARCH = 'Hybrid Search',
177+
CUSTOM = 'Custom Search',
178+
UNKNOWN = 'Unknown',
179+
}
173180
// If no datasource version is found, we default to 2.17.0
174181
export const MIN_SUPPORTED_VERSION = '2.17.0';
175182
// Min version to support ML processors

public/pages/workflow_detail/tools/errors/errors.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import React from 'react';
6+
import React, { ReactNode } from 'react';
77
import {
88
EuiCodeBlock,
99
EuiEmptyPrompt,
@@ -12,7 +12,7 @@ import {
1212
} from '@elastic/eui';
1313

1414
interface ErrorsProps {
15-
errorMessages: string[];
15+
errorMessages: (string | ReactNode)[];
1616
}
1717

1818
/**

public/pages/workflow_detail/tools/tools.tsx

+11-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import React, { useEffect, useState } from 'react';
6+
import React, { ReactNode, useEffect, useState } from 'react';
77
import { useSelector } from 'react-redux';
88
import { AppState } from '../../../store';
99
import { isEmpty } from 'lodash';
@@ -26,6 +26,7 @@ import { Query } from './query';
2626
import { Ingest } from './ingest';
2727
import { Errors } from './errors';
2828
import {
29+
formatProcessorError,
2930
hasProvisionedIngestResources,
3031
hasProvisionedSearchResources,
3132
} from '../../../utils';
@@ -52,7 +53,9 @@ export function Tools(props: ToolsProps) {
5253
ingestPipeline: ingestPipelineErrors,
5354
searchPipeline: searchPipelineErrors,
5455
} = useSelector((state: AppState) => state.errors);
55-
const [curErrorMessages, setCurErrorMessages] = useState<string[]>([]);
56+
const [curErrorMessages, setCurErrorMessages] = useState<
57+
(string | ReactNode)[]
58+
>([]);
5659

5760
// Propagate any errors coming from opensearch API calls, including ingest/search pipeline verbose calls.
5861
useEffect(() => {
@@ -66,12 +69,16 @@ export function Tools(props: ToolsProps) {
6669
} else if (!isEmpty(ingestPipelineErrors)) {
6770
setCurErrorMessages([
6871
'Data not ingested. Errors found with the following ingest processor(s):',
69-
...Object.values(ingestPipelineErrors).map((value) => value.errorMsg),
72+
...Object.values(ingestPipelineErrors).map((ingestPipelineError) =>
73+
formatProcessorError(ingestPipelineError)
74+
),
7075
]);
7176
} else if (!isEmpty(searchPipelineErrors)) {
7277
setCurErrorMessages([
7378
'Errors found with the following search processor(s)',
74-
...Object.values(searchPipelineErrors).map((value) => value.errorMsg),
79+
...Object.values(searchPipelineErrors).map((searchPipelineError) =>
80+
formatProcessorError(searchPipelineError)
81+
),
7582
]);
7683
}
7784
} else {

public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx

+15-15
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ export function SourceData(props: SourceDataProps) {
112112
<h3>Import sample data</h3>
113113
</EuiText>
114114
</EuiFlexItem>
115-
<EuiFlexItem grow={false}>
116-
{docsPopulated ? (
115+
{docsPopulated && (
116+
<EuiFlexItem grow={false}>
117117
<EuiSmallButtonEmpty
118118
onClick={() => setIsEditModalOpen(true)}
119119
data-testid="editSourceDataButton"
@@ -122,19 +122,8 @@ export function SourceData(props: SourceDataProps) {
122122
>
123123
Edit
124124
</EuiSmallButtonEmpty>
125-
) : (
126-
<EuiSmallButton
127-
fill={false}
128-
style={{ width: '75px' }}
129-
onClick={() => setIsEditModalOpen(true)}
130-
data-testid="selectDataToImportButton"
131-
iconType="plus"
132-
iconSide="left"
133-
>
134-
{`Import`}
135-
</EuiSmallButton>
136-
)}
137-
</EuiFlexItem>
125+
</EuiFlexItem>
126+
)}
138127
</EuiFlexGroup>
139128
</EuiFlexItem>
140129
{props.lastIngested !== undefined && (
@@ -208,6 +197,17 @@ export function SourceData(props: SourceDataProps) {
208197
<EuiText size="s">
209198
Import a data sample to start configuring your ingest flow.
210199
</EuiText>
200+
<EuiSpacer size="m" />
201+
<EuiSmallButton
202+
fill={true}
203+
style={{ width: '130px' }}
204+
onClick={() => setIsEditModalOpen(true)}
205+
data-testid="selectDataToImportButton"
206+
iconType="plus"
207+
iconSide="left"
208+
>
209+
{`Import data`}
210+
</EuiSmallButton>
211211
</>
212212
}
213213
/>

public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx

+66-67
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@ import {
1515
EuiCompressedSuperSelect,
1616
EuiSuperSelectOption,
1717
EuiText,
18-
EuiSmallButton,
18+
EuiSmallButtonIcon,
19+
EuiFlexGroup,
20+
EuiFlexItem,
1921
} from '@elastic/eui';
2022
import {
2123
MODEL_STATE,
2224
WorkflowFormValues,
2325
ModelFormValue,
2426
ML_CHOOSE_MODEL_LINK,
25-
ML_REMOTE_MODEL_LINK,
27+
FETCH_ALL_QUERY_LARGE,
2628
} from '../../../../../common';
27-
import { AppState } from '../../../../store';
29+
import { AppState, searchModels, useAppDispatch } from '../../../../store';
30+
import { getDataSourceId } from '../../../../utils';
2831

2932
interface ModelFieldProps {
3033
fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField')
@@ -47,6 +50,8 @@ type ModelItem = ModelFormValue & {
4750
* A specific field for selecting existing deployed models
4851
*/
4952
export function ModelField(props: ModelFieldProps) {
53+
const dispatch = useAppDispatch();
54+
const dataSourceId = getDataSourceId();
5055
// Initial store is fetched when loading base <DetectorDetail /> page. We don't
5156
// re-fetch here as it could overload client-side if user clicks back and forth /
5257
// keeps re-rendering this component (and subsequently re-fetching data) as they're building flows
@@ -94,31 +99,6 @@ export function ModelField(props: ModelFieldProps) {
9499
<EuiSpacer size="s" />
95100
</>
96101
)}
97-
{isEmpty(deployedModels) && (
98-
<>
99-
<EuiCallOut
100-
size="s"
101-
title="No deployed models found"
102-
iconType={'alert'}
103-
color="warning"
104-
>
105-
<EuiText size="s">
106-
To create and deploy models and make them accessible in
107-
OpenSearch, see documentation.
108-
</EuiText>
109-
<EuiSpacer size="s" />
110-
<EuiSmallButton
111-
target="_blank"
112-
href={ML_REMOTE_MODEL_LINK}
113-
iconSide="right"
114-
iconType={'popout'}
115-
>
116-
Documentation
117-
</EuiSmallButton>
118-
</EuiCallOut>
119-
<EuiSpacer size="s" />
120-
</>
121-
)}
122102
<Field name={props.fieldPath}>
123103
{({ field, form }: FieldProps) => {
124104
const isInvalid =
@@ -140,45 +120,64 @@ export function ModelField(props: ModelFieldProps) {
140120
isInvalid={isInvalid}
141121
error={props.showError && getIn(errors, `${field.name}.id`)}
142122
>
143-
<EuiCompressedSuperSelect
144-
data-testid="selectDeployedModel"
145-
fullWidth={props.fullWidth}
146-
disabled={isEmpty(deployedModels)}
147-
options={deployedModels.map(
148-
(option) =>
149-
({
150-
value: option.id,
151-
inputDisplay: (
152-
<>
153-
<EuiText size="s">{option.name}</EuiText>
154-
</>
155-
),
156-
dropdownDisplay: (
157-
<>
158-
<EuiText size="s">{option.name}</EuiText>
159-
<EuiText size="xs" color="subdued">
160-
Deployed
161-
</EuiText>
162-
<EuiText size="xs" color="subdued">
163-
{option.algorithm}
164-
</EuiText>
165-
</>
166-
),
167-
disabled: false,
168-
} as EuiSuperSelectOption<string>)
169-
)}
170-
valueOfSelected={field.value?.id || ''}
171-
onChange={(option: string) => {
172-
form.setFieldTouched(props.fieldPath, true);
173-
form.setFieldValue(props.fieldPath, {
174-
id: option,
175-
} as ModelFormValue);
176-
if (props.onModelChange) {
177-
props.onModelChange(option);
178-
}
179-
}}
180-
isInvalid={isInvalid}
181-
/>
123+
<EuiFlexGroup direction="row" gutterSize="xs">
124+
<EuiFlexItem grow={true}>
125+
<EuiCompressedSuperSelect
126+
data-testid="selectDeployedModel"
127+
fullWidth={props.fullWidth}
128+
disabled={isEmpty(deployedModels)}
129+
options={deployedModels.map(
130+
(option) =>
131+
({
132+
value: option.id,
133+
inputDisplay: (
134+
<>
135+
<EuiText size="s">{option.name}</EuiText>
136+
</>
137+
),
138+
dropdownDisplay: (
139+
<>
140+
<EuiText size="s">{option.name}</EuiText>
141+
<EuiText size="xs" color="subdued">
142+
Deployed
143+
</EuiText>
144+
<EuiText size="xs" color="subdued">
145+
{option.algorithm}
146+
</EuiText>
147+
</>
148+
),
149+
disabled: false,
150+
} as EuiSuperSelectOption<string>)
151+
)}
152+
valueOfSelected={field.value?.id || ''}
153+
onChange={(option: string) => {
154+
form.setFieldTouched(props.fieldPath, true);
155+
form.setFieldValue(props.fieldPath, {
156+
id: option,
157+
} as ModelFormValue);
158+
if (props.onModelChange) {
159+
props.onModelChange(option);
160+
}
161+
}}
162+
isInvalid={isInvalid}
163+
/>
164+
</EuiFlexItem>
165+
<EuiFlexItem grow={false}>
166+
<EuiSmallButtonIcon
167+
iconType={'refresh'}
168+
aria-label="refresh"
169+
display="base"
170+
onClick={() => {
171+
dispatch(
172+
searchModels({
173+
apiBody: FETCH_ALL_QUERY_LARGE,
174+
dataSourceId,
175+
})
176+
);
177+
}}
178+
/>
179+
</EuiFlexItem>
180+
</EuiFlexGroup>
182181
</EuiCompressedFormRow>
183182
);
184183
}}

public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/ml_processor_inputs.tsx

+29-6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
OutputMapArrayFormValue,
3232
EMPTY_OUTPUT_MAP_ENTRY,
3333
ML_REMOTE_MODEL_LINK,
34+
FETCH_ALL_QUERY_LARGE,
3435
} from '../../../../../../common';
3536
import { ModelField } from '../../input_fields';
3637
import {
@@ -39,9 +40,10 @@ import {
3940
} from '../../../../../../common';
4041
import { OverrideQueryModal } from './modals';
4142
import { ModelInputs } from './model_inputs';
42-
import { AppState } from '../../../../../store';
43+
import { AppState, searchModels, useAppDispatch } from '../../../../../store';
4344
import {
4445
formikToPartialPipeline,
46+
getDataSourceId,
4547
parseModelInputs,
4648
parseModelOutputs,
4749
} from '../../../../../utils';
@@ -62,6 +64,8 @@ interface MLProcessorInputsProps {
6264
* output map configuration forms, respectively.
6365
*/
6466
export function MLProcessorInputs(props: MLProcessorInputsProps) {
67+
const dispatch = useAppDispatch();
68+
const dataSourceId = getDataSourceId();
6569
const { models } = useSelector((state: AppState) => state.ml);
6670
const { values, setFieldValue, setFieldTouched } = useFormikContext<
6771
WorkflowFormValues
@@ -191,11 +195,30 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
191195
color="primary"
192196
size="s"
193197
title={
194-
<EuiText size="s">
195-
You have no models registered in your cluster.{' '}
196-
<EuiLink href={ML_REMOTE_MODEL_LINK}>Learn more</EuiLink> about
197-
integrating ML models.
198-
</EuiText>
198+
<>
199+
<EuiText size="s">
200+
You have no models registered in your cluster.{' '}
201+
<EuiLink href={ML_REMOTE_MODEL_LINK} target="_blank">
202+
Learn more
203+
</EuiLink>{' '}
204+
about integrating ML models.
205+
</EuiText>
206+
<EuiSpacer size="s" />
207+
<EuiSmallButton
208+
iconType={'refresh'}
209+
iconSide="left"
210+
onClick={() => {
211+
dispatch(
212+
searchModels({
213+
apiBody: FETCH_ALL_QUERY_LARGE,
214+
dataSourceId,
215+
})
216+
);
217+
}}
218+
>
219+
Refresh
220+
</EuiSmallButton>
221+
</>
199222
}
200223
/>
201224
) : (

0 commit comments

Comments
 (0)