From 6d13daf35e5a15e235cd14a855f60f03a370ee43 Mon Sep 17 00:00:00 2001 From: Timothy Bula <trbula@us.ibm.com> Date: Mon, 11 Sep 2023 20:03:14 -0500 Subject: [PATCH] feat: get params working --- src/ApiServer/fixtures/changelogs.js | 24 ++++----- src/ApiServer/fixtures/workflowCompose.js | 1 + src/ApiServer/index.js | 3 +- src/Features/Editor/ChangeLog/ChangeLog.tsx | 37 +++---------- .../ChangeLogTable/ChangeLogTable.tsx | 22 ++++---- src/Features/Editor/Configure/Configure.tsx | 18 ------- src/Features/Editor/Editor.tsx | 54 ++++++++++--------- src/Features/Editor/Parameters/Parameters.tsx | 36 +++---------- .../PropertiesModal/PropertiesModal.tsx | 5 +- .../PropertiesModalContent.tsx | 35 +++++------- src/State/reducers/workflowRevision.ts | 12 +++-- src/Types/index.tsx | 1 + 12 files changed, 93 insertions(+), 155 deletions(-) diff --git a/src/ApiServer/fixtures/changelogs.js b/src/ApiServer/fixtures/changelogs.js index f4db770c6..ad5ee6f3b 100644 --- a/src/ApiServer/fixtures/changelogs.js +++ b/src/ApiServer/fixtures/changelogs.js @@ -1,21 +1,21 @@ const changelog = [ { - date: "2020-05-28T19:32:48.710+0000", - reason: "Sorry. Removing changes.", - revisionId: "5ed011e0eddd5f0001eeb7d0", - userId: "5dbb17ed418ad600013cedfc", - userName: "Boomerang/chicago/ibm Boomerang/chicago/ibm", + author: null, + reason: "400ms", + date: "2023-09-11T23:06:16.346+00:00", + version: 3, + }, + { + author: "61d38d133aa9034ded32cae6", + reason: "", + date: "2022-01-07T07:43:05.304+00:00", version: 2, - workflowId: "5eb2c4085a92d80001a16d87", }, { - date: "2020-05-28T17:42:18.593+0000", - reason: "hello sir", - revisionId: "5ecff7faeddd5f0001ee5473", - userId: "5dbb17ed418ad600013cedfc", - userName: "Boomerang/chicago/ibm Boomerang/chicago/ibm", + author: "61d38d133aa9034ded32cae6", + reason: "Create workflow", + date: "2022-01-07T07:42:50.919+00:00", version: 1, - workflowId: "5eb2c4085a92d80001a16d87", }, ]; diff --git a/src/ApiServer/fixtures/workflowCompose.js b/src/ApiServer/fixtures/workflowCompose.js index 0fd4ebc5e..ef641eacb 100644 --- a/src/ApiServer/fixtures/workflowCompose.js +++ b/src/ApiServer/fixtures/workflowCompose.js @@ -16,6 +16,7 @@ const workflowsCompose = [ webhook: { enable: false, token: "", topic: null }, custom: { enable: false, token: null, topic: null }, }, + config: [], tokens: [{ token: "268CD9268194A7B58888DC4B8FB4E6BF1358D01CEBB97F8670C544B4F076DD63", label: "default" }], nodes: [ { diff --git a/src/ApiServer/index.js b/src/ApiServer/index.js index f91117420..a4948aeb5 100644 --- a/src/ApiServer/index.js +++ b/src/ApiServer/index.js @@ -373,8 +373,7 @@ export function startApiServer({ environment = "test", timing = 0 } = {}) { // Workflow Changelog this.get(serviceUrl.getWorkflowChangelog({ id: ":id" }), (schema, request) => { - let { workflowId } = request.params; - return schema.changelogs.where({ workflowId }); + return schema.db.changelogs; }); //Workflow Available Parameters diff --git a/src/Features/Editor/ChangeLog/ChangeLog.tsx b/src/Features/Editor/ChangeLog/ChangeLog.tsx index b5e199579..9ef6083ca 100644 --- a/src/Features/Editor/ChangeLog/ChangeLog.tsx +++ b/src/Features/Editor/ChangeLog/ChangeLog.tsx @@ -1,47 +1,22 @@ import React from "react"; import { Helmet } from "react-helmet"; -import qs from "query-string"; -import { useQuery } from "Hooks"; -import { DataTableSkeleton, SearchSkeleton } from "@carbon/react"; -import { DelayedRender } from "@boomerang-io/carbon-addons-boomerang-react"; -import ErrorDragon from "Components/ErrorDragon"; import ChangeLogTable from "./ChangeLogTable"; -import { serviceUrl } from "Config/servicesConfig"; -import { WorkflowSummary } from "Types"; +import { ChangeLog as ChangeLogType } from "Types"; import styles from "./changeLog.module.scss"; interface ChangeLogProps { - summaryData: WorkflowSummary; + changeLogData: ChangeLogType; } -const ChangeLog: React.FC<ChangeLogProps> = ({ summaryData }) => { - const getWorkflowChangelogUrl = serviceUrl.getWorkflowChangelog({ - workflowId: summaryData.id, - query: qs.stringify({ sort: "version", order: "DESC" }), - }); - const { data, error, isLoading } = useQuery(getWorkflowChangelogUrl); - if (isLoading) - return ( - <DelayedRender> - <div className={styles.container}> - <div className={styles.searchSkeleton}> - <SearchSkeleton small /> - </div> - <DataTableSkeleton /> - </div> - </DelayedRender> - ); - - if (error) return <ErrorDragon />; - +function ChangeLog({ changeLogData }: ChangeLogProps) { return ( <div className={styles.container}> <Helmet> - <title>{`Change Log - ${summaryData.name}`}</title> + <title>Change Log</title> </Helmet> - <ChangeLogTable changeLog={data} /> + <ChangeLogTable changeLog={changeLogData} /> </div> ); -}; +} export default ChangeLog; diff --git a/src/Features/Editor/ChangeLog/ChangeLogTable/ChangeLogTable.tsx b/src/Features/Editor/ChangeLog/ChangeLogTable/ChangeLogTable.tsx index 32d69cd64..81abda9f1 100644 --- a/src/Features/Editor/ChangeLog/ChangeLogTable/ChangeLogTable.tsx +++ b/src/Features/Editor/ChangeLog/ChangeLogTable/ChangeLogTable.tsx @@ -2,7 +2,7 @@ import React, { Component } from "react"; import { matchSorter } from "match-sorter"; import PropTypes from "prop-types"; import moment from "moment"; -import { DataTable, Search, Pagination } from "@carbon/react"; +import { DataTable, Pagination, Layer, Search } from "@carbon/react"; import EmptyState from "Components/EmptyState"; import { ChangeLog } from "Types"; import styles from "./changeLogTable.module.scss"; @@ -58,7 +58,7 @@ class ChangeLogTable extends Component<ChangeLogTableProps> { handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => { const searchQuery = e.target.value; const { changeLog } = this.props; - const changeLogList = changeLog.length !== 0 ? changeLog.map((log) => ({ ...log, id: log.revisionId })) : []; + const changeLogList = changeLog.length !== 0 ? changeLog.map((log) => ({ ...log, id: log.version })) : []; const newLogs = searchQuery ? matchSorter(changeLogList, searchQuery, { keys: ["version", "userName", "reason"] }) @@ -90,14 +90,16 @@ class ChangeLogTable extends Component<ChangeLogTableProps> { return ( <div className={styles.tableContainer}> - <Search - className={styles.search} - data-testid="change-log-search" - id="change-log-table-search" - labelText="Search" - onChange={this.handleSearchChange} - placeholder="Search" - /> + <Layer> + <Search + className={styles.search} + data-testid="change-log-search" + id="change-log-table-search" + labelText="Search" + onChange={this.handleSearchChange} + placeholder="Search" + /> + </Layer> {totalItems > 0 ? ( <> <DataTable diff --git a/src/Features/Editor/Configure/Configure.tsx b/src/Features/Editor/Configure/Configure.tsx index 1d0fbaffc..767272712 100644 --- a/src/Features/Editor/Configure/Configure.tsx +++ b/src/Features/Editor/Configure/Configure.tsx @@ -631,24 +631,6 @@ class Configure extends Component<ConfigureProps, ConfigureState> { <CustomLabel formikPropsSetFieldValue={setFieldValue} labels={values.labels} /> </div> </div> - <hr className={styles.delimiter} /> - <div className={styles.saveChangesContainer}> - <Button - size="md" - disabled={!dirty || isLoading} - iconDescription="Save" - onClick={(e: any) => { - e.preventDefault(); - handleSubmit(); - }} - renderIcon={Save} - > - {isLoading ? "Saving..." : "Save"} - </Button> - <p className={styles.saveText}> - Save the configuration. Versioning functionality only applies to the Workflow. - </p> - </div> </section> </div> ); diff --git a/src/Features/Editor/Editor.tsx b/src/Features/Editor/Editor.tsx index a4f554f0f..64dcb861d 100644 --- a/src/Features/Editor/Editor.tsx +++ b/src/Features/Editor/Editor.tsx @@ -24,9 +24,11 @@ import { TaskTemplate, WorkflowView, WorkflowCanvas, + DataDrivenInput, } from "Types"; import type { ReactFlowInstance } from "reactflow"; import styles from "./editor.module.scss"; +import { set } from "cypress/types/lodash"; export default function EditorContainer() { const { team } = useTeamContext(); @@ -52,12 +54,12 @@ export default function EditorContainer() { /** * Queries */ - const changeLogQuery = useQuery(getChangelogUrl); + const changeLogQuery = useQuery<ChangeLogType>(getChangelogUrl); const workflowQuery = useQuery<WorkflowCanvas>(getWorkflowUrl); const workflowsQuery = useQuery<PaginatedWorkflowResponse>(getWorkflowsUrl); const taskTemplatesQuery = useQuery(getTaskTemplatesUrl); const taskTemplatesTeamQuery = useQuery(getTaskTemplatesTeamUrl); - const availableParametersQuery = useQuery(getAvailableParametersUrl); + const availableParametersQuery = useQuery(getAvailableParametersUrl, {}); /** * Mutations @@ -165,25 +167,7 @@ const EditorStateContainer: React.FC<EditorStateContainerProps> = ({ ); const [workflow, setWorkflow] = React.useState<ReactFlowInstance | null>(null); - - // //Triggers the POST request for refresh availableParameters - // useEffect(() => { - // if (JSON.stringify(revisionConfig) !== JSON.stringify(revisionState)) { - // const normilzedConfig = Object.values(revisionState.config).map((config: any) => ({ - // ...config, - // currentVersion: undefined, - // taskVersion: config.currentVersion || config.taskVersion, - // })); - // const revisionConfig = { nodes: Object.values(normilzedConfig) }; - // const revision = { - // changelog: revisionState.changelog, - // config: revisionConfig, - // dag: revisionState.dag, - // }; - // setRevisionConfig(revisionState); - // parametersMutator.mutateAsync({ workflowId, body: revision }); - // } - // }, [parametersMutator, workflowId, revisionState, revisionConfig]); + const [availableParameters, setAvailableParameters] = React.useState(availableParametersQueryData); const handleCreateRevision = useCallback( async ({ reason = "Update workflow", callback }) => { @@ -255,6 +239,24 @@ const EditorStateContainer: React.FC<EditorStateContainerProps> = ({ [revisionDispatch] ); + const handleUpdateParams = useCallback( + (parameters: Array<DataDrivenInput>) => { + revisionDispatch({ + type: RevisionActionTypes.UpdateConfig, + data: { parameters }, + }); + + // Create new available parameters values so user doesn't have to create a + // a new version to use newly created parameters + const newAvailableParameters = [...availableParameters]; + newAvailableParameters.push( + ...parameters.map((param) => [`workflow.params.${param.key}`, `params.${param.key}`]).flat() + ); + setAvailableParameters(Array.from(new Set(newAvailableParameters))); + }, + [revisionDispatch, availableParameters, setAvailableParameters] + ); + /** * Simply update the parent state to use a different revision to fetch it w/ react-query * @param {string} revisionNumber @@ -270,15 +272,15 @@ const EditorStateContainer: React.FC<EditorStateContainerProps> = ({ const store = useMemo(() => { const taskTemplatesData = groupTaskTemplatesByName(taskTemplatesList); return { - availableParametersQueryData, + availableParameters, mode, revisionDispatch, revisionState, taskTemplatesData, workflowsQueryData, }; - }, [availableParametersQueryData, mode, revisionDispatch, revisionState, taskTemplatesList, workflowsQueryData]); - + }, [availableParameters, mode, revisionDispatch, revisionState, taskTemplatesList, workflowsQueryData]); + console.log({ availableParameters }); return ( // Must create context to share state w/ nodes that are created by the DAG engine <EditorContextProvider value={store}> @@ -314,13 +316,13 @@ const EditorStateContainer: React.FC<EditorStateContainerProps> = ({ /> </Route> <Route path={AppPath.EditorProperties}> - <Parameters workflow={revisionState} /> + <Parameters workflow={revisionState} handleUpdateParams={handleUpdateParams} /> </Route> <Route path={AppPath.EditorSchedule}> <Schedule summaryData={revisionState} /> </Route> <Route path={AppPath.EditorChangelog}> - <ChangeLog summaryData={revisionState} /> + <ChangeLog changeLogData={changeLogData} /> </Route> </Switch> <Route diff --git a/src/Features/Editor/Parameters/Parameters.tsx b/src/Features/Editor/Parameters/Parameters.tsx index 5e38d3e99..e7777ede7 100644 --- a/src/Features/Editor/Parameters/Parameters.tsx +++ b/src/Features/Editor/Parameters/Parameters.tsx @@ -1,18 +1,14 @@ -// @ts-nocheck import React from "react"; -import { useMutation, useQueryClient } from "react-query"; import { Helmet } from "react-helmet"; -import capitalize from "lodash/capitalize"; -import { ConfirmModal, notify, ToastNotification } from "@boomerang-io/carbon-addons-boomerang-react"; +import { ConfirmModal } from "@boomerang-io/carbon-addons-boomerang-react"; import WorkflowCloseButton from "./WorkflowCloseButton"; import WorkflowPropertiesModal from "./PropertiesModal"; -import { serviceUrl, resolver } from "Config/servicesConfig"; import { InputType, WorkflowPropertyUpdateType } from "Constants"; import { DataDrivenInput, ModalTriggerProps, WorkflowCanvas } from "Types"; import { stringToPassword } from "Utils/stringHelper"; import styles from "./Parameters.module.scss"; -const formatDefaultValue = ({ type, value }: { type: string | undefined; value: string | undefined }) => { +const formatDefaultValue = ({ type, value }: { type?: string; value?: string }) => { if (!value) { return "---"; } else if (type === InputType.Password) { @@ -37,8 +33,8 @@ const WorkflowPropertyRow: React.FC<WorkflowPropertyRowProps> = ({ title, value }; interface WorkflowPropertyHeaderProps { - label: string; - description: string | undefined; + label?: string; + description?: string; } const WorkflowPropertyHeader: React.FC<WorkflowPropertyHeaderProps> = ({ label, description }) => { @@ -52,12 +48,10 @@ const WorkflowPropertyHeader: React.FC<WorkflowPropertyHeaderProps> = ({ label, interface ParametersProps { workflow: WorkflowCanvas; + handleUpdateParams: (parameters: Array<DataDrivenInput>) => void; } -const Parameters: React.FC<ParametersProps> = ({ workflow }) => { - const queryClient = useQueryClient(); - const configMutator = useMutation(resolver.patchUpdateWorkflowProperties); - +const Parameters: React.FC<ParametersProps> = ({ workflow, handleUpdateParams }) => { const handleUpdateProperties = async ({ param, type }: { param: DataDrivenInput; type: string }) => { let parameters = [...workflow.config]; if (type === WorkflowPropertyUpdateType.Update) { @@ -74,21 +68,7 @@ const Parameters: React.FC<ParametersProps> = ({ workflow }) => { parameters.push(param); } - try { - //TODO - update the compose object and send back - there is no individual params endpoint - const { data } = await configMutator.mutateAsync({ workflowId: workflow.id, body: parameters }); - queryClient.invalidateQueries(serviceUrl.workflowAvailableParameters({ workflowId: workflow.id })); - notify( - <ToastNotification - kind="success" - title={`${capitalize(type)} parameter`} - subtitle={`Successfully performed operation`} - /> - ); - queryClient.setQueryData(serviceUrl.getWorkflowCompose({ id: workflow.id }), data); - } catch (e) { - notify(<ToastNotification kind="error" title="Something's wrong" subtitle={`Failed to ${type} parameter`} />); - } + handleUpdateParams(parameters); }; const deleteParameter = (param: DataDrivenInput) => { @@ -134,7 +114,6 @@ const Parameters: React.FC<ParametersProps> = ({ workflow }) => { <> <WorkflowPropertiesModal isEdit - isLoading={configMutator.isLoading} propertyKeys={paramKeys.filter((propertyName: string) => propertyName !== configParam.key)} property={configParam} updateWorkflowProperties={handleUpdateProperties} @@ -164,7 +143,6 @@ const Parameters: React.FC<ParametersProps> = ({ workflow }) => { ))} <WorkflowPropertiesModal isEdit={false} - isloading={configMutator.isLoading} propertyKeys={paramKeys} updateWorkflowProperties={handleUpdateProperties} /> diff --git a/src/Features/Editor/Parameters/PropertiesModal/PropertiesModal.tsx b/src/Features/Editor/Parameters/PropertiesModal/PropertiesModal.tsx index 9c90cae9a..ac23f5ff8 100644 --- a/src/Features/Editor/Parameters/PropertiesModal/PropertiesModal.tsx +++ b/src/Features/Editor/Parameters/PropertiesModal/PropertiesModal.tsx @@ -8,10 +8,9 @@ import styles from "./PropertiesModal.module.scss"; interface PropertiesModalProps { isEdit: boolean; - isLoading: boolean; property: DataDrivenInput; propertyKeys: Array<string>; - updateWorkflowProperties: (args: { property: DataDrivenInput; type: string }) => Promise<any>; + updateWorkflowProperties: (args: { param: DataDrivenInput; type: string }) => Promise<any>; } const PropertiesModal: React.FC<PropertiesModalProps> = (props) => { @@ -32,7 +31,7 @@ const PropertiesModal: React.FC<PropertiesModalProps> = (props) => { ) : ( <button className={styles.createPropertyCard} onClick={openModal} data-testid="create-parameter-button"> <div className={styles.createContainer}> - <Add className={styles.createIcon} aria-label="Add" size={32}/> + <Add className={styles.createIcon} aria-label="Add" size={32} /> <p className={styles.createText}>Create a new parameter</p> </div> </button> diff --git a/src/Features/Editor/Parameters/PropertiesModal/PropertiesModalContent/PropertiesModalContent.tsx b/src/Features/Editor/Parameters/PropertiesModal/PropertiesModalContent/PropertiesModalContent.tsx index 87515ec1c..2ab53ff2c 100644 --- a/src/Features/Editor/Parameters/PropertiesModal/PropertiesModalContent/PropertiesModalContent.tsx +++ b/src/Features/Editor/Parameters/PropertiesModal/PropertiesModalContent/PropertiesModalContent.tsx @@ -3,7 +3,6 @@ import React, { Component } from "react"; import { ComboBox, Creatable, - Loading, ModalFlowForm, TextArea, TextInput, @@ -41,10 +40,9 @@ const inputTypeItems = [ interface PropertiesModalContentProps { closeModal(): void; isEdit: boolean; - isLoading: boolean; property: DataDrivenInput; propertyKeys: string[]; - updateWorkflowProperties: (args: { property: DataDrivenInput; type: string }) => Promise<any>; + updateWorkflowProperties: (args: { param: DataDrivenInput; type: string }) => Promise<any>; } class PropertiesModalContent extends Component<PropertiesModalContentProps> { @@ -73,25 +71,25 @@ class PropertiesModalContent extends Component<PropertiesModalContentProps> { }; handleConfirm = (values: DataDrivenInput) => { - let property = clonedeep(values); - property.type = property.type.value; + let param = clonedeep(values); + param.type = param.type.value; // Remove in case they are present if the user changed their mind - if (property.type !== InputType.Select) { - delete property.options; + if (param.type !== InputType.Select) { + delete param.options; } else { // Create options in correct type for service - { key, value } - property.options = property?.options.map((property) => ({ key: property, value: property })); + param.options = param?.options.map((param) => ({ key: param, value: param })); } - if (property.type === InputType.Boolean) { - if (!property.defaultValue) property.defaultValue = false; + if (param.type === InputType.Boolean) { + if (!param.defaultValue) param.defaultValue = false; } if (this.props.isEdit) { this.props .updateWorkflowProperties({ - property, + param, type: WorkflowPropertyUpdateType.Update, }) .then(() => { @@ -101,7 +99,7 @@ class PropertiesModalContent extends Component<PropertiesModalContentProps> { } else { this.props .updateWorkflowProperties({ - property, + param, type: WorkflowPropertyUpdateType.Create, }) .then(() => { @@ -213,7 +211,7 @@ class PropertiesModalContent extends Component<PropertiesModalContentProps> { }; render() { - const { property, isEdit, propertyKeys, isLoading } = this.props; + const { property, isEdit, propertyKeys } = this.props; let defaultValueType = this.state.defaultValueType; return ( @@ -260,9 +258,8 @@ class PropertiesModalContent extends Component<PropertiesModalContentProps> { const { dirty, values, touched, errors, handleBlur, handleChange, handleSubmit, setFieldValue, isValid } = formikProps; return ( - <ModalFlowForm onSubmit={handleSubmit} disabled={isLoading}> + <ModalFlowForm onSubmit={handleSubmit}> <ModalBody aria-label="inputs" className={styles.container}> - {isLoading && <Loading />} <TextInput readOnly={isEdit} helperText="Reference value for parameter in workflow. It can't be changed after parameter creation." @@ -336,12 +333,8 @@ class PropertiesModalContent extends Component<PropertiesModalContentProps> { <Button kind="secondary" onClick={this.props.closeModal} type="button"> Cancel </Button> - <Button - disabled={!isValid || !dirty || isLoading} - type="submit" - data-testid="parameter-modal-confirm-button" - > - {isEdit ? (isLoading ? "Saving..." : "Save") : isLoading ? "Creating..." : "Create"} + <Button disabled={!isValid || !dirty} type="submit" data-testid="parameter-modal-confirm-button"> + {isEdit ? "Save" : "Create"} </Button> </ModalFooter> </ModalFlowForm> diff --git a/src/State/reducers/workflowRevision.ts b/src/State/reducers/workflowRevision.ts index e1ac02cf8..660ef41fd 100644 --- a/src/State/reducers/workflowRevision.ts +++ b/src/State/reducers/workflowRevision.ts @@ -4,6 +4,7 @@ import { WorkflowCanvas, WorkflowCanvasState } from "Types"; export const RevisionActionTypes = { UpdateNodes: "UPDATE_NODES", UpdateEdges: "UPDATE_EDGES", + UpdateConfig: "UPDATE_CONFIG", Reset: "RESET", Set: "SET", UpdateNodeConfig: "UPDATE_NODE_CONFIG", @@ -13,17 +14,17 @@ export const RevisionActionTypes = { type RevisionActionType = typeof RevisionActionTypes[keyof typeof RevisionActionTypes]; - export function revisionReducer(state: WorkflowCanvasState, action: { data: any; type: RevisionActionType }) { + console.log({ action }); switch (action.type) { case RevisionActionTypes.UpdateEdges: { state.hasUnsavedUpdates = true; - state.edges = action.data + state.edges = action.data; return state; } case RevisionActionTypes.UpdateNodes: { state.hasUnsavedUpdates = true; - state.nodes = action.data + state.nodes = action.data; return state; } case RevisionActionTypes.UpdateNotes: { @@ -35,6 +36,11 @@ export function revisionReducer(state: WorkflowCanvasState, action: { data: any; case RevisionActionTypes.Set: { return action.data; } + case RevisionActionTypes.UpdateConfig: { + const { parameters } = action.data; + state.config = parameters; + return state; + } case RevisionActionTypes.Reset: { return action.data; } diff --git a/src/Types/index.tsx b/src/Types/index.tsx index 4975748fb..2485ce885 100644 --- a/src/Types/index.tsx +++ b/src/Types/index.tsx @@ -89,6 +89,7 @@ export interface DataDrivenInput { type: string; min?: number; max?: number; + jsonPath?: string; } export interface ResultParameter {