Skip to content

Commit a6ecd90

Browse files
authored
UX fit-n-finish updates III (opensearch-project#559)
* fix import edge cases Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * remove help text Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Add callouts in quick config and ML processori inputs when no models found Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Specially render one_to_one for ML response processors Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Add expression entry by default on initial multi expression modal load Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Flexibly grow workflow list and presets page contents Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Update EuiCard layout Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Clean up form inputs on ml inputs/outputs Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * update form headers in advanced transform modals Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * add default if name is empty for expression modal Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Align advanced settings accordion contents Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Add modal to update workflow name/description Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Fix the data processing badges; move to generic component Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Update styles for all modals; move to css file Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> * Handle edge case of obj parsing params in template modal Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> --------- Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
1 parent 402ca20 commit a6ecd90

23 files changed

+552
-198
lines changed

public/general_components/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ export { MultiSelectFilter } from './multi_select_filter';
77
export { ProcessorsTitle } from './processors_title';
88
export { QueryParamsList } from './query_params_list';
99
export { JsonPathExamplesTable } from './jsonpath_examples_table';
10+
export { ProcessingBadge } from './processing_badge';
1011
export * from './results';
1112
export * from './service_card';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React from 'react';
7+
import { EuiBadge, EuiFlexItem } from '@elastic/eui';
8+
import { PROCESSOR_CONTEXT } from '../../common';
9+
10+
interface ProcessingBadgeProps {
11+
context: PROCESSOR_CONTEXT;
12+
oneToOne: boolean;
13+
}
14+
15+
/**
16+
* Simple component to display a badge describing how the input data is processed
17+
*/
18+
export function ProcessingBadge(props: ProcessingBadgeProps) {
19+
return (
20+
<>
21+
{props.context !== PROCESSOR_CONTEXT.SEARCH_REQUEST && (
22+
<EuiFlexItem grow={false}>
23+
<EuiBadge>{`${
24+
props.context === PROCESSOR_CONTEXT.INGEST
25+
? 'One to one processing'
26+
: props.oneToOne
27+
? 'One to one processing'
28+
: 'Many to one processing'
29+
}`}</EuiBadge>
30+
</EuiFlexItem>
31+
)}
32+
</>
33+
);
34+
}

public/global-styles.scss

+5
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@
1111
height: 100%;
1212
width: 100%;
1313
}
14+
15+
.configuration-modal {
16+
width: 70vw;
17+
height: 70vh;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React, { useEffect, useState } from 'react';
7+
import * as yup from 'yup';
8+
import { Formik, getIn, useFormikContext } from 'formik';
9+
import { isEmpty } from 'lodash';
10+
import {
11+
EuiFlexGroup,
12+
EuiFlexItem,
13+
EuiModal,
14+
EuiModalHeader,
15+
EuiModalHeaderTitle,
16+
EuiModalBody,
17+
EuiModalFooter,
18+
EuiSmallButtonEmpty,
19+
EuiSmallButton,
20+
} from '@elastic/eui';
21+
import {
22+
MAX_STRING_LENGTH,
23+
Workflow,
24+
WORKFLOW_NAME_REGEXP,
25+
WorkflowConfig,
26+
WorkflowFormValues,
27+
WorkflowTemplate,
28+
} from '../../../../common';
29+
import {
30+
formikToUiConfig,
31+
getDataSourceId,
32+
getInitialValue,
33+
} from '../../../utils';
34+
import { TextField } from '../workflow_inputs/input_fields';
35+
import { getWorkflow, updateWorkflow, useAppDispatch } from '../../../store';
36+
37+
interface EditWorkflowMetadataModalProps {
38+
workflow?: Workflow;
39+
setIsModalOpen(isOpen: boolean): void;
40+
}
41+
/**
42+
* Modal to allow editing workflow metadata, like name & description.
43+
*/
44+
export function EditWorkflowMetadataModal(
45+
props: EditWorkflowMetadataModalProps
46+
) {
47+
const dispatch = useAppDispatch();
48+
const dataSourceId = getDataSourceId();
49+
const { values } = useFormikContext<WorkflowFormValues>();
50+
51+
// sub-form values/schema
52+
const metadataFormValues = {
53+
name: getInitialValue('string'),
54+
description: getInitialValue('string'),
55+
};
56+
const metadataFormSchema = yup.object({
57+
name: yup
58+
.string()
59+
.test('workflowName', 'Invalid workflow name', (name) => {
60+
return !(
61+
name === undefined ||
62+
name === '' ||
63+
name.length > 100 ||
64+
WORKFLOW_NAME_REGEXP.test(name) === false
65+
);
66+
})
67+
.required('Required') as yup.Schema,
68+
desription: yup
69+
.string()
70+
.trim()
71+
.min(0)
72+
.max(MAX_STRING_LENGTH, 'Too long')
73+
.optional() as yup.Schema,
74+
}) as yup.Schema;
75+
76+
// persist standalone values
77+
const [tempName, setTempName] = useState<string>(props.workflow?.name || '');
78+
const [tempDescription, setTempDescription] = useState<string>(
79+
props.workflow?.description || ''
80+
);
81+
const [tempErrors, setTempErrors] = useState<boolean>(false);
82+
83+
// button updating state
84+
const [isUpdating, setIsUpdating] = useState<boolean>(false);
85+
86+
// if saving, take the updated name/description (along with any other unsaved form values)
87+
// and execute the update.
88+
async function onSave() {
89+
setIsUpdating(true);
90+
const updatedTemplate = {
91+
name: tempName,
92+
description: tempDescription,
93+
ui_metadata: {
94+
...props.workflow?.ui_metadata,
95+
config: formikToUiConfig(
96+
values,
97+
props.workflow?.ui_metadata?.config as WorkflowConfig
98+
),
99+
},
100+
} as WorkflowTemplate;
101+
await dispatch(
102+
updateWorkflow({
103+
apiBody: {
104+
workflowId: props.workflow?.id as string,
105+
workflowTemplate: updatedTemplate,
106+
updateFields: true,
107+
reprovision: false,
108+
},
109+
dataSourceId,
110+
})
111+
)
112+
.unwrap()
113+
.then(async (result) => {
114+
new Promise((f) => setTimeout(f, 1000)).then(async () => {
115+
dispatch(
116+
getWorkflow({
117+
workflowId: props.workflow?.id as string,
118+
dataSourceId,
119+
})
120+
);
121+
});
122+
})
123+
.catch((error: any) => {
124+
console.error('Error updating workflow: ', error);
125+
})
126+
.finally(() => {
127+
setIsUpdating(false);
128+
props.setIsModalOpen(false);
129+
});
130+
}
131+
132+
return (
133+
<Formik
134+
enableReinitialize={false}
135+
initialValues={metadataFormValues}
136+
validationSchema={metadataFormSchema}
137+
onSubmit={(values) => {}}
138+
validate={(values) => {}}
139+
>
140+
{(formikProps) => {
141+
// override to parent form values when changes detected
142+
useEffect(() => {
143+
formikProps.setFieldValue('name', props.workflow?.name);
144+
}, [props.workflow?.name]);
145+
useEffect(() => {
146+
formikProps.setFieldValue('description', props.workflow?.description);
147+
}, [props.workflow?.description]);
148+
149+
// update temp vars when form changes are detected
150+
useEffect(() => {
151+
setTempName(getIn(formikProps.values, 'name'));
152+
}, [getIn(formikProps.values, 'name')]);
153+
useEffect(() => {
154+
setTempDescription(getIn(formikProps.values, 'description'));
155+
}, [getIn(formikProps.values, 'description')]);
156+
157+
// update tempErrors if errors detected
158+
useEffect(() => {
159+
setTempErrors(!isEmpty(formikProps.errors));
160+
}, [formikProps.errors]);
161+
162+
return (
163+
<EuiModal
164+
maxWidth={false}
165+
style={{ width: '50vw' }}
166+
onClose={() => props.setIsModalOpen(false)}
167+
>
168+
<EuiModalHeader>
169+
<EuiModalHeaderTitle>
170+
<p>Update workflow metadata</p>
171+
</EuiModalHeaderTitle>
172+
</EuiModalHeader>
173+
<EuiModalBody>
174+
<EuiFlexGroup direction="column">
175+
<EuiFlexItem>
176+
<TextField
177+
label="Workflow name"
178+
fullWidth={false}
179+
fieldPath={`name`}
180+
showError={true}
181+
/>
182+
</EuiFlexItem>
183+
<EuiFlexItem>
184+
<TextField
185+
label="Workflow description"
186+
fullWidth={true}
187+
fieldPath={`description`}
188+
showError={true}
189+
/>
190+
</EuiFlexItem>
191+
</EuiFlexGroup>
192+
</EuiModalBody>
193+
<EuiModalFooter>
194+
<EuiSmallButtonEmpty
195+
onClick={() => props.setIsModalOpen(false)}
196+
color="primary"
197+
data-testid="closeEditMetadataButton"
198+
>
199+
Cancel
200+
</EuiSmallButtonEmpty>
201+
<EuiSmallButton
202+
onClick={() => {
203+
formikProps
204+
.submitForm()
205+
.then(() => {
206+
onSave();
207+
})
208+
.catch((err: any) => {});
209+
}}
210+
isLoading={isUpdating}
211+
isDisabled={tempErrors} // blocking update until valid input is given
212+
fill={true}
213+
color="primary"
214+
data-testid="updateEditMetadataButton"
215+
>
216+
Save
217+
</EuiSmallButton>
218+
</EuiModalFooter>
219+
</EuiModal>
220+
);
221+
}}
222+
</Formik>
223+
);
224+
}

public/pages/workflow_detail/components/export_modal.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
getCharacterLimitedString,
2929
} from '../../../../common';
3030
import { reduceToTemplate } from '../../../utils';
31+
import '../../../global-styles.scss';
3132

3233
interface ExportModalProps {
3334
workflow?: Workflow;
@@ -77,7 +78,7 @@ export function ExportModal(props: ExportModalProps) {
7778
return (
7879
<EuiModal
7980
maxWidth={false}
80-
style={{ width: '70vw' }}
81+
className="configuration-modal"
8182
onClose={() => props.setIsExportModalOpen(false)}
8283
>
8384
<EuiModalHeader>

public/pages/workflow_detail/components/header.tsx

+22-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
EuiSmallButtonEmpty,
1414
EuiSmallButton,
1515
EuiSmallButtonIcon,
16+
EuiButtonIcon,
1617
} from '@elastic/eui';
1718
import {
1819
PLUGIN_ID,
@@ -54,6 +55,7 @@ import { MountPoint } from '../../../../../../src/core/public';
5455
import { getWorkflow, updateWorkflow, useAppDispatch } from '../../../store';
5556
import { useFormikContext } from 'formik';
5657
import { isEmpty, isEqual } from 'lodash';
58+
import { EditWorkflowMetadataModal } from './edit_workflow_metadata_modal';
5759

5860
interface WorkflowDetailHeaderProps {
5961
workflow?: Workflow;
@@ -86,8 +88,11 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
8688
props.workflow?.lastUpdated
8789
).toString();
8890

89-
// export modal state
91+
// modal states
9092
const [isExportModalOpen, setIsExportModalOpen] = useState<boolean>(false);
93+
const [isEditWorkflowModalOpen, setIsEditWorkflowModalOpen] = useState<
94+
boolean
95+
>(false);
9196

9297
const dataSourceEnabled = getDataSourceEnabled().enabled;
9398
const dataSourceId = getDataSourceId();
@@ -276,6 +281,12 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
276281
setIsExportModalOpen={setIsExportModalOpen}
277282
/>
278283
)}
284+
{isEditWorkflowModalOpen && (
285+
<EditWorkflowMetadataModal
286+
workflow={props.workflow}
287+
setIsModalOpen={setIsEditWorkflowModalOpen}
288+
/>
289+
)}
279290
{USE_NEW_HOME_PAGE ? (
280291
<>
281292
<TopNavMenu
@@ -358,8 +369,17 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
358369
{dataSourceEnabled && DataSourceComponent}
359370
<EuiPageHeader
360371
pageTitle={
361-
<EuiFlexGroup direction="row" alignItems="flexEnd" gutterSize="m">
372+
<EuiFlexGroup direction="row" gutterSize="s">
362373
<EuiFlexItem grow={false}>{workflowName}</EuiFlexItem>
374+
<EuiFlexItem grow={false} style={{ marginTop: '18px' }}>
375+
<EuiButtonIcon
376+
iconType={'gear'}
377+
aria-label="editWorkflowMetadata"
378+
onClick={() => {
379+
setIsEditWorkflowModalOpen(true);
380+
}}
381+
/>
382+
</EuiFlexItem>
363383
</EuiFlexGroup>
364384
}
365385
rightSideItems={[

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
getInitialValue,
5151
} from '../../../../utils';
5252
import { getProcessorInfo } from './source_data';
53+
import '../../../../global-styles.scss';
5354

5455
interface SourceDataProps {
5556
workflow: Workflow | undefined;
@@ -191,7 +192,7 @@ export function SourceDataModal(props: SourceDataProps) {
191192
<EuiModal
192193
maxWidth={false}
193194
onClose={() => onClose()}
194-
style={{ width: '70vw' }}
195+
className="configuration-modal"
195196
>
196197
<EuiModalHeader>
197198
<EuiModalHeaderTitle>

0 commit comments

Comments
 (0)