From d8d029a4644504655bb5d55801e84257d33b47b2 Mon Sep 17 00:00:00 2001 From: Paul Sarando Date: Thu, 24 Oct 2024 18:08:38 -0700 Subject: [PATCH 1/4] CORE-1955 Initial App VersionsOrderingForm Also initial VersionsOrdering story. --- public/static/locales/en/app_editor.json | 1 + .../apps/editor/VersionsOrderingForm.js | 145 ++++++++++++++++++ stories/apps/VersionOrdering.stories.js | 19 +++ 3 files changed, 165 insertions(+) create mode 100644 src/components/apps/editor/VersionsOrderingForm.js create mode 100644 stories/apps/VersionOrdering.stories.js diff --git a/public/static/locales/en/app_editor.json b/public/static/locales/en/app_editor.json index 45ea316c2..8a08ffc53 100644 --- a/public/static/locales/en/app_editor.json +++ b/public/static/locales/en/app_editor.json @@ -54,6 +54,7 @@ "newParamLabel": "New {{type}}", "newSectionLabel": "New Section", "noToolWarning": "The app will not be able to run since a tool has not been selected.", + "orderAppVersions": "Order App Versions", "outputCategoryTitle": "Output", "parameters": "Parameters", "parameterLabel": "Parameter Label", diff --git a/src/components/apps/editor/VersionsOrderingForm.js b/src/components/apps/editor/VersionsOrderingForm.js new file mode 100644 index 000000000..ef6f116fa --- /dev/null +++ b/src/components/apps/editor/VersionsOrderingForm.js @@ -0,0 +1,145 @@ +/** + * A form component for setting App version order. + * + * @author psarando + */ +import React from "react"; + +import { FieldArray, Form, Formik } from "formik"; + +import { useTranslation } from "i18n"; + +import ids from "./ids"; +import styles from "./styles"; + +import buildID from "components/utils/DebugIDUtil"; + +import { + Button, + ButtonGroup, + Card, + CardHeader, + Toolbar, + Typography, +} from "@mui/material"; + +import { makeStyles } from "tss-react/mui"; + +import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; +import BackButton from "components/utils/BackButton"; + +const useStyles = makeStyles()(styles); + +function VersionOrderForm(props) { + const { baseId, version, onMoveUp, onMoveDown } = props; + + const { t } = useTranslation("common"); + const { classes } = useStyles(); + + const versionBaseId = buildID(baseId, version.version_id); + + return ( + + + + + + } + /> + + ); +} + +function VersionsOrderingForm(props) { + const { baseId, versions } = props; + + const { t } = useTranslation(["app_editor", "common"]); + + return ( + { + console.log({ versions }); + }} + > + {({ handleSubmit, dirty, values }) => { + return ( +
+ + + + {t("orderAppVersions")} + + + + + + values.versions.map((version, index) => ( + { + if (index > 0) { + arrayHelpers.move( + index, + index - 1 + ); + } + }} + onMoveDown={() => { + if ( + index + 1 < + values.versions.length + ) { + arrayHelpers.move( + index, + index + 1 + ); + } + }} + /> + )) + } + /> + + ); + }} +
+ ); +} + +export default VersionsOrderingForm; diff --git a/stories/apps/VersionOrdering.stories.js b/stories/apps/VersionOrdering.stories.js new file mode 100644 index 000000000..957de6ee7 --- /dev/null +++ b/stories/apps/VersionOrdering.stories.js @@ -0,0 +1,19 @@ +import React from "react"; + +import ids from "components/apps/editor/ids"; +import VersionsOrderingForm from "components/apps/editor/VersionsOrderingForm"; + +export default { title: "Apps / Editor" }; + +export const VersionsOrdering = () => { + return ( + + ); +}; From cbe54988d8e53c2335bb1f306e6a04e431c882b8 Mon Sep 17 00:00:00 2001 From: Paul Sarando Date: Fri, 25 Oct 2024 16:28:26 -0700 Subject: [PATCH 2/4] CORE-1955 Add API calls to `PUT /apps/:system/:id/versions` Updated VersionsOrderingForm with API calls to the `PUT /apps/:system/:id/versions` endpoint. --- .../apps/editor/VersionsOrderingForm.js | 53 ++++++++++++++++--- src/server/api/apps.js | 13 +++++ src/serviceFacades/apps.js | 9 ++++ stories/apps/VersionOrdering.stories.js | 27 ++++++++-- 4 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/components/apps/editor/VersionsOrderingForm.js b/src/components/apps/editor/VersionsOrderingForm.js index ef6f116fa..eab2080ad 100644 --- a/src/components/apps/editor/VersionsOrderingForm.js +++ b/src/components/apps/editor/VersionsOrderingForm.js @@ -6,13 +6,20 @@ import React from "react"; import { FieldArray, Form, Formik } from "formik"; +import { useMutation } from "react-query"; import { useTranslation } from "i18n"; import ids from "./ids"; import styles from "./styles"; +import { announce } from "components/announcer/CyVerseAnnouncer"; +import { SUCCESS } from "components/announcer/AnnouncerConstants"; +import withErrorAnnouncer from "components/error/withErrorAnnouncer"; import buildID from "components/utils/DebugIDUtil"; +import BackButton from "components/utils/BackButton"; + +import { setAppVersionOrder } from "serviceFacades/apps"; import { Button, @@ -26,7 +33,6 @@ import { import { makeStyles } from "tss-react/mui"; import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; -import BackButton from "components/utils/BackButton"; const useStyles = makeStyles()(styles); @@ -70,18 +76,52 @@ function VersionOrderForm(props) { } function VersionsOrderingForm(props) { - const { baseId, versions } = props; + const { baseId, app, showErrorAnnouncer } = props; + const { system_id: systemId, id: appId, versions } = app; const { t } = useTranslation(["app_editor", "common"]); + const { mutate: saveAppVersions } = useMutation( + ({ values }) => + setAppVersionOrder({ systemId, appId, versions: values }), + { + onSuccess: (resp, { onSuccess }) => { + onSuccess(resp); + }, + onError: (error, { onError }) => { + onError(error); + }, + } + ); + return ( { - console.log({ versions }); + onSubmit={(values, actions) => { + const { resetForm, setSubmitting, setStatus } = actions; + + const onSuccess = ({ versions }) => { + setSubmitting(false); + setStatus({ success: true }); + resetForm({ values: { versions } }); + + announce({ + text: t("appSaved"), + variant: SUCCESS, + }); + }; + + const onError = (errorMessage) => { + showErrorAnnouncer(t("appSaveErr"), errorMessage); + + setSubmitting(false); + setStatus({ success: false, errorMessage }); + }; + + saveAppVersions({ values, onSuccess, onError }); }} > - {({ handleSubmit, dirty, values }) => { + {({ handleSubmit, isSubmitting, dirty, values }) => { return (
@@ -99,6 +139,7 @@ function VersionsOrderingForm(props) { variant="contained" type="submit" onClick={handleSubmit} + disabled={!dirty || isSubmitting} > {t("common:save")} @@ -142,4 +183,4 @@ function VersionsOrderingForm(props) { ); } -export default VersionsOrderingForm; +export default withErrorAnnouncer(VersionsOrderingForm); diff --git a/src/server/api/apps.js b/src/server/api/apps.js index 574396425..6a29c81d5 100644 --- a/src/server/api/apps.js +++ b/src/server/api/apps.js @@ -223,6 +223,19 @@ export default function appsRouter() { }) ); + logger.info("adding the PUT /apps/:systemId/:appId/versions handler"); + api.put( + "/apps/:systemId/:appId/versions", + auth.authnTokenMiddleware, + terrainHandler({ + method: "PUT", + pathname: "/apps/:systemId/:appId/versions", + headers: { + "Content-Type": "application/json", + }, + }) + ); + logger.info( "adding the GET /apps/:systemId/:appId/versions/:versionId handler" ); diff --git a/src/serviceFacades/apps.js b/src/serviceFacades/apps.js index ad70e9546..6874fa346 100644 --- a/src/serviceFacades/apps.js +++ b/src/serviceFacades/apps.js @@ -161,6 +161,14 @@ function addAppVersion({ systemId, appId, app }) { }); } +function setAppVersionOrder({ systemId, appId, versions }) { + return callApi({ + endpoint: `/api/apps/${systemId}/${appId}/versions`, + method: "PUT", + body: versions, + }); +} + function deleteApp({ systemId, appId }) { return callApi({ endpoint: `/api/apps/${systemId}/${appId}`, @@ -571,6 +579,7 @@ export { getAppPublicationRequests, adminPublishApp, requestToPublishApp, + setAppVersionOrder, ALL_APPS_QUERY_KEY, APP_DETAILS_QUERY_KEY, APPS_IN_CATEGORY_QUERY_KEY, diff --git a/stories/apps/VersionOrdering.stories.js b/stories/apps/VersionOrdering.stories.js index 957de6ee7..8728965a5 100644 --- a/stories/apps/VersionOrdering.stories.js +++ b/stories/apps/VersionOrdering.stories.js @@ -1,19 +1,36 @@ import React from "react"; +import { mockAxios, errorResponseJSON } from "../axiosMock"; + import ids from "components/apps/editor/ids"; import VersionsOrderingForm from "components/apps/editor/VersionsOrderingForm"; +import { appDetails } from "./AppMocks"; export default { title: "Apps / Editor" }; export const VersionsOrdering = () => { + mockAxios.reset(); + mockAxios + .onPut(new RegExp("/api/apps/de/.*/versions")) + .replyOnce(500, errorResponseJSON); + mockAxios.onPut(new RegExp("/api/apps/de/.*/versions")).reply((config) => { + const versions = JSON.parse(config.data); + console.log("Update App Versions", config.url, versions); + + return [200, { ...appDetails, ...versions }]; + }); + return ( ); }; From 8ceb1191bd274ee018803798058e6bc9859af5a9 Mon Sep 17 00:00:00 2001 From: Paul Sarando Date: Tue, 29 Oct 2024 16:45:53 -0700 Subject: [PATCH 3/4] CORE-1955 Add app version ordering page and listing menu item --- public/static/locales/en/apps.json | 1 + .../apps/editor/VersionsOrderingForm.js | 15 ++++- src/components/apps/ids.js | 1 + src/components/apps/listing/RowDotMenu.js | 15 ++++- .../menuItems/EditVersionOrderMenuItem.js | 36 ++++++++++++ src/pages/apps/[systemId]/[appId]/versions.js | 57 +++++++++++++++++++ stories/apps/VersionOrdering.stories.js | 44 ++++++++++---- 7 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 src/components/apps/menuItems/EditVersionOrderMenuItem.js create mode 100644 src/pages/apps/[systemId]/[appId]/versions.js diff --git a/public/static/locales/en/apps.json b/public/static/locales/en/apps.json index df4eec4ae..a638a9a6d 100644 --- a/public/static/locales/en/apps.json +++ b/public/static/locales/en/apps.json @@ -77,6 +77,7 @@ "done": "Done", "edit": "Edit", "editApp": "Edit App", + "editAppVersionOrder": "Edit Version Order", "editWorkflow": "Edit Workflow", "embedLbl": "Embed", "emptyValue": "Empty Value", diff --git a/src/components/apps/editor/VersionsOrderingForm.js b/src/components/apps/editor/VersionsOrderingForm.js index eab2080ad..7ec8aa576 100644 --- a/src/components/apps/editor/VersionsOrderingForm.js +++ b/src/components/apps/editor/VersionsOrderingForm.js @@ -16,6 +16,8 @@ import styles from "./styles"; import { announce } from "components/announcer/CyVerseAnnouncer"; import { SUCCESS } from "components/announcer/AnnouncerConstants"; import withErrorAnnouncer from "components/error/withErrorAnnouncer"; +import WrappedErrorHandler from "components/error/WrappedErrorHandler"; +import TableLoading from "components/table/TableLoading"; import buildID from "components/utils/DebugIDUtil"; import BackButton from "components/utils/BackButton"; @@ -26,6 +28,7 @@ import { ButtonGroup, Card, CardHeader, + Table, Toolbar, Typography, } from "@mui/material"; @@ -76,8 +79,8 @@ function VersionOrderForm(props) { } function VersionsOrderingForm(props) { - const { baseId, app, showErrorAnnouncer } = props; - const { system_id: systemId, id: appId, versions } = app; + const { baseId, app, isLoading, error, showErrorAnnouncer } = props; + const { system_id: systemId, id: appId, versions } = app || {}; const { t } = useTranslation(["app_editor", "common"]); @@ -94,7 +97,13 @@ function VersionsOrderingForm(props) { } ); - return ( + return error ? ( + + ) : isLoading ? ( + + +
+ ) : ( { diff --git a/src/components/apps/ids.js b/src/components/apps/ids.js index 16442eeab..ed311635c 100644 --- a/src/components/apps/ids.js +++ b/src/components/apps/ids.js @@ -21,6 +21,7 @@ export default { CREATE_APP_VERSION_MENU_ITEM: "createAppVersionMenuItem", CREATE_MENU_ITEM: "createMenuItem", CREATE_WORKFLOW_MENU_ITEM: "createWorkflowMenuItem", + EDIT_APP_VERSION_ORDER_MENU_ITEM: "editAppVersionOrderMenuItem", DIALOG: "dialog", APP_SAVED_LAUNCH: "appSavedLaunch", LISTING_TABLE: "listingTable", diff --git a/src/components/apps/listing/RowDotMenu.js b/src/components/apps/listing/RowDotMenu.js index 1e2749dbf..0cf8942b9 100644 --- a/src/components/apps/listing/RowDotMenu.js +++ b/src/components/apps/listing/RowDotMenu.js @@ -32,6 +32,7 @@ import utilIds from "components/utils/ids"; import { getAppListingLinkRefs } from "components/apps/utils"; import { useUserProfile } from "contexts/userProfile"; import PublishAppDialog from "../PublishAppDialog"; +import EditVersionOrderMenuItem from "../menuItems/EditVersionOrderMenuItem"; function RowDotMenu(props) { const { @@ -78,6 +79,14 @@ function RowDotMenu(props) { onClose={onClose} onDetailsSelected={onDetailsSelected} />, + !isAdminView && canEditLabels && ( + + ), canEdit && ( ), - !isAdminView && canEditLabels && ( - { + onClose(); + router.push( + `/${NavigationConstants.APPS}/${app.system_id}/${app.id}/versions` + ); + }} + > + + + + + + ); +} diff --git a/src/pages/apps/[systemId]/[appId]/versions.js b/src/pages/apps/[systemId]/[appId]/versions.js new file mode 100644 index 000000000..2270883bd --- /dev/null +++ b/src/pages/apps/[systemId]/[appId]/versions.js @@ -0,0 +1,57 @@ +/** + * A page for displaying the App Versions Ordering Form. + * + * @author psarando + */ +import React from "react"; + +import { useRouter } from "next/router"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; +import { useQuery } from "react-query"; + +import { i18n, RequiredNamespaces } from "i18n"; + +import ids from "components/apps/editor/ids"; +import VersionsOrderingForm from "components/apps/editor/VersionsOrderingForm"; + +import { + getAppDescription, + APP_DESCRIPTION_QUERY_KEY, +} from "serviceFacades/apps"; + +export default function VersionsOrderEdit() { + const [app, setApp] = React.useState(null); + + const router = useRouter(); + const { systemId, appId } = router.query; + + const { isFetching, error } = useQuery({ + queryKey: [APP_DESCRIPTION_QUERY_KEY, { systemId, appId }], + queryFn: () => getAppDescription({ systemId, appId }), + enabled: !!(systemId && appId), + onSuccess: setApp, + }); + + return ( + + ); +} + +export async function getServerSideProps({ locale }) { + const title = i18n.t("app_editor:orderAppVersions"); + + return { + props: { + title, + ...(await serverSideTranslations(locale, [ + "app_editor", + ...RequiredNamespaces, + ])), + }, + }; +} diff --git a/stories/apps/VersionOrdering.stories.js b/stories/apps/VersionOrdering.stories.js index 8728965a5..cf67a0956 100644 --- a/stories/apps/VersionOrdering.stories.js +++ b/stories/apps/VersionOrdering.stories.js @@ -1,6 +1,6 @@ import React from "react"; -import { mockAxios, errorResponseJSON } from "../axiosMock"; +import { mockAxios, errorResponseJSON, mockErrorResponse } from "../axiosMock"; import ids from "components/apps/editor/ids"; import VersionsOrderingForm from "components/apps/editor/VersionsOrderingForm"; @@ -8,7 +8,7 @@ import { appDetails } from "./AppMocks"; export default { title: "Apps / Editor" }; -export const VersionsOrdering = () => { +export const VersionsOrdering = ({ loading, loadingError }) => { mockAxios.reset(); mockAxios .onPut(new RegExp("/api/apps/de/.*/versions")) @@ -23,14 +23,38 @@ export const VersionsOrdering = () => { return ( ); }; + +VersionsOrdering.args = { + loading: false, + loadingError: false, +}; + +VersionsOrdering.argTypes = { + loading: { + name: "Loading Mask", + control: { + type: "boolean", + }, + }, + loadingError: { + name: "Loading Error", + control: { + type: "boolean", + }, + }, +}; From 89fe48edd7e328409ac1539700f2565f6e9e5038 Mon Sep 17 00:00:00 2001 From: Paul Sarando Date: Mon, 4 Nov 2024 19:59:55 -0700 Subject: [PATCH 4/4] CORE-1955 Add App VersionsOrderingForm help text --- public/static/locales/en/app_editor.json | 1 + .../apps/editor/VersionsOrderingForm.js | 71 +++++++++++-------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/public/static/locales/en/app_editor.json b/public/static/locales/en/app_editor.json index 8a08ffc53..1b293d1bf 100644 --- a/public/static/locales/en/app_editor.json +++ b/public/static/locales/en/app_editor.json @@ -55,6 +55,7 @@ "newSectionLabel": "New Section", "noToolWarning": "The app will not be able to run since a tool has not been selected.", "orderAppVersions": "Order App Versions", + "orderAppVersionsHelp": "Order versions with the newest (latest) at the top, and the oldest at the bottom.", "outputCategoryTitle": "Output", "parameters": "Parameters", "parameterLabel": "Parameter Label", diff --git a/src/components/apps/editor/VersionsOrderingForm.js b/src/components/apps/editor/VersionsOrderingForm.js index 7ec8aa576..2959cb95c 100644 --- a/src/components/apps/editor/VersionsOrderingForm.js +++ b/src/components/apps/editor/VersionsOrderingForm.js @@ -27,6 +27,7 @@ import { Button, ButtonGroup, Card, + CardContent, CardHeader, Table, Toolbar, @@ -154,37 +155,45 @@ function VersionsOrderingForm(props) {
- - values.versions.map((version, index) => ( - { - if (index > 0) { - arrayHelpers.move( - index, - index - 1 - ); - } - }} - onMoveDown={() => { - if ( - index + 1 < - values.versions.length - ) { - arrayHelpers.move( - index, - index + 1 - ); - } - }} - /> - )) - } - /> + + + + + values.versions.map( + (version, index) => ( + { + if (index > 0) { + arrayHelpers.move( + index, + index - 1 + ); + } + }} + onMoveDown={() => { + if ( + index + 1 < + values.versions + .length + ) { + arrayHelpers.move( + index, + index + 1 + ); + } + }} + /> + ) + ) + } + /> + + ); }}