From e3bc34cc2acf721821ff142677537bea35899340 Mon Sep 17 00:00:00 2001 From: Andrew Larned Date: Fri, 31 Jan 2025 12:13:14 -0500 Subject: [PATCH 1/6] feat: set up structure for managing products/versions [#OCD-4770] --- .../developers/developer/developer-view.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/pages/organizations/developers/developer/developer-view.jsx b/src/app/pages/organizations/developers/developer/developer-view.jsx index df4f64431d..bf1e947455 100755 --- a/src/app/pages/organizations/developers/developer/developer-view.jsx +++ b/src/app/pages/organizations/developers/developer/developer-view.jsx @@ -18,7 +18,7 @@ import { theme, utilStyles } from 'themes'; const useStyles = makeStyles({ ...utilStyles, - editUser:{ + focus: { display: 'flex', flexDirection: 'column-reverse', paddingTop: '16px', @@ -97,6 +97,9 @@ function ChplDeveloperView({ dispatch }) { case 'merge': dispatch(`${action}Product`, payload); break; + case 'editVersion': + setState('focusProduct'); + break; default: dispatch(action, payload); } @@ -121,7 +124,7 @@ function ChplDeveloperView({ dispatch }) { }; return ( - + { state === 'view' && ( @@ -154,9 +157,11 @@ function ChplDeveloperView({ dispatch }) { /> )} - )} + {(state === 'view' || state === 'focusProduct') && ( + + )} {(state === 'view' || state === 'editUser') && ( Date: Mon, 3 Feb 2025 13:19:47 -0500 Subject: [PATCH 2/6] feat: enable editing of version [#OCD-4770] --- src/app/api/version.jsx | 19 ++ src/app/components/products/product-view.jsx | 5 +- src/app/components/version/version-edit.jsx | 187 ++++++++++++++++++ src/app/components/version/version.jsx | 67 +++++++ .../developers/developer/developer-view.jsx | 5 +- .../developers/developer/developer.jsx | 20 +- .../developers/developer/version-edit.jsx | 108 ++++++++++ src/app/services/network.service.js | 4 - 8 files changed, 400 insertions(+), 15 deletions(-) create mode 100755 src/app/api/version.jsx create mode 100755 src/app/components/version/version-edit.jsx create mode 100755 src/app/components/version/version.jsx create mode 100755 src/app/pages/organizations/developers/developer/version-edit.jsx diff --git a/src/app/api/version.jsx b/src/app/api/version.jsx new file mode 100755 index 0000000000..8c76188ba2 --- /dev/null +++ b/src/app/api/version.jsx @@ -0,0 +1,19 @@ +import { useMutation, useQuery, useQueryClient } from 'react-query'; + +import { useAxios } from './axios'; + +const usePutVersion = () => { + const axios = useAxios(); + const queryClient = useQueryClient(); + return useMutation(async (data) => axios.put('versions', data) + .then((response) => response), { + onSuccess: () => { + queryClient.invalidateQueries('developers'); + queryClient.invalidateQueries('developers/hierarchy'); + }, + }); +}; + +export { + usePutVersion, +}; diff --git a/src/app/components/products/product-view.jsx b/src/app/components/products/product-view.jsx index 97e1e6902b..9839ca8d51 100755 --- a/src/app/components/products/product-view.jsx +++ b/src/app/components/products/product-view.jsx @@ -226,7 +226,10 @@ function ChplProductView({ product, productCount, dispatch }) { Edit Product v.id === selectedVersion), + productId: product.id, + })} disabled={selectedVersion === 'all'} > Edit Version diff --git a/src/app/components/version/version-edit.jsx b/src/app/components/version/version-edit.jsx new file mode 100755 index 0000000000..1fd42943c4 --- /dev/null +++ b/src/app/components/version/version-edit.jsx @@ -0,0 +1,187 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { + Box, + Button, + ButtonGroup, + IconButton, + Card, + CardHeader, + CardContent, + Container, + Divider, + FormControlLabel, + MenuItem, + Switch, + Table, + TableContainer, + TableRow, + TableHead, + TableCell, + TableBody, + TableFooter, + Typography, + makeStyles, +} from '@material-ui/core'; +import AddIcon from '@material-ui/icons/Add'; +import CheckIcon from '@material-ui/icons/Check'; +import CloseIcon from '@material-ui/icons/Close'; +import { + arrayOf, + bool, + func, + object, + string, +} from 'prop-types'; +import { useFormik } from 'formik'; +import * as yup from 'yup'; + +import { ChplActionBar } from 'components/action-bar'; +import { ChplTextField } from 'components/util'; +import { eventTrack } from 'services/analytics.service'; +import { getDisplayDateFormat } from 'services/date-util'; +import { DeveloperContext, UserContext, useAnalyticsContext } from 'shared/contexts'; +import { utilStyles } from 'themes'; + +const useStyles = makeStyles({ + ...utilStyles, + content: { + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: '16px', + alignItems: 'start', + }, + developerHeader: { + margin: '0', + fontSize: '1.25em', + }, +}); + +const validationSchema = yup.object({ + version: yup.string() + .required('Version is required'), +}); + +function ChplVersionEdit(props) { + const { + dispatch, + errorMessages: initialErrorMessages, + isInvalid: initialIsInvalid, + isProcessing, + isSplitting, + version, + } = props; + const { analytics } = useAnalyticsContext(); + const { developer } = useContext(DeveloperContext); + const { hasAnyRole } = useContext(UserContext); + const [errorMessages, setErrorMessages] = useState([]); + const [warnings, setWarnings] = useState([]); + const [isInvalid, setIsInvalid] = useState(false); + const classes = useStyles(); + let formik; + + useEffect(() => { + setIsInvalid(initialIsInvalid); + }, [initialIsInvalid]); + + useEffect(() => { + setErrorMessages(initialErrorMessages); + }, [initialErrorMessages]); + + const cancel = () => { + eventTrack({ + ...analytics, + event: 'Cancel Version Edit', + }); + dispatch('cancel'); + }; + + const save = () => { + const updatedVersion = { + ...version, + version: formik.values.version, + }; + dispatch('save', updatedVersion); + }; + + const handleDispatch = (action) => { + switch (action) { + case 'cancel': + cancel(); + break; + case 'save': + formik.submitForm(); + break; + // no default + } + }; + + const isActionDisabled = () => isInvalid || !formik.isValid; + + formik = useFormik({ + initialValues: { + version: version.version || '', + }, + onSubmit: () => { + save(); + }, + validationSchema, + }); + + return ( + + + { isSplitting + && ( + + )} + { !isSplitting + && ( + + )} + + + + + + + ); +} + +export default ChplVersionEdit; + +ChplVersionEdit.propTypes = { + dispatch: func.isRequired, + errorMessages: arrayOf(string).isRequired, + isInvalid: bool.isRequired, + isProcessing: bool, + isSplitting: bool.isRequired, + version: object.isRequired, +}; + +ChplVersionEdit.defaultProps = { + isProcessing: false, +}; diff --git a/src/app/components/version/version.jsx b/src/app/components/version/version.jsx new file mode 100755 index 0000000000..321bb7257d --- /dev/null +++ b/src/app/components/version/version.jsx @@ -0,0 +1,67 @@ +import React, { useEffect, useState } from 'react'; +import { + arrayOf, + bool, + func, + object, + string, +} from 'prop-types'; + +import ChplVersionEdit from './version-edit'; + +function ChplVersion({ + canEdit, + canJoin, + canSplit, + dispatch, + errorMessages, + isEditing, + isInvalid: initialIsInvalid, + isProcessing, + isSplitting, + version, +}) { + const [isInvalid, setIsInvalid] = useState(false); + + useEffect(() => { + setIsInvalid(initialIsInvalid); + }, [initialIsInvalid]); + + return ( + + ); +} + +export default ChplVersion; + +ChplVersion.propTypes = { + canEdit: func, + canJoin: func, + canSplit: func, + dispatch: func, + errorMessages: arrayOf(string), + isEditing: bool, + isInvalid: bool, + isProcessing: bool, + isSplitting: bool, + version: object.isRequired, +}; + +ChplVersion.defaultProps = { + canEdit: () => false, + canJoin: () => false, + canSplit: () => false, + dispatch: () => {}, + errorMessages: [], + isEditing: false, + isInvalid: false, + isProcessing: false, + isSplitting: false, +}; diff --git a/src/app/pages/organizations/developers/developer/developer-view.jsx b/src/app/pages/organizations/developers/developer/developer-view.jsx index bf1e947455..d4e8b50780 100755 --- a/src/app/pages/organizations/developers/developer/developer-view.jsx +++ b/src/app/pages/organizations/developers/developer/developer-view.jsx @@ -97,9 +97,6 @@ function ChplDeveloperView({ dispatch }) { case 'merge': dispatch(`${action}Product`, payload); break; - case 'editVersion': - setState('focusProduct'); - break; default: dispatch(action, payload); } @@ -159,7 +156,7 @@ function ChplDeveloperView({ dispatch }) { )} - {(state === 'view' || state === 'focusProduct') && ( + {state === 'view' && ( )} {(state === 'view' || state === 'editUser') && ( diff --git a/src/app/pages/organizations/developers/developer/developer.jsx b/src/app/pages/organizations/developers/developer/developer.jsx index 8fa3da3408..3fac52cd6b 100755 --- a/src/app/pages/organizations/developers/developer/developer.jsx +++ b/src/app/pages/organizations/developers/developer/developer.jsx @@ -13,6 +13,7 @@ import ChplDeveloperEdit from './developer-edit'; import ChplDeveloperJoin from './developer-join'; import ChplDeveloperSplit from './developer-split'; import ChplDeveloperView from './developer-view'; +import ChplVersionEdit from './version-edit'; import { useDeleteUserFromDeveloper, useFetchDeveloperHierarchy } from 'api/developer'; import { usePostCreateInvitation, usePostCreateOldInvitation } from 'api/users'; @@ -34,6 +35,7 @@ function ChplDeveloperPage({ id }) { const { mutate: createInvitation } = usePostCreateInvitation(); const { mutate: createOldInvitation } = usePostCreateOldInvitation(); const [developer, setDeveloper] = useState(undefined); + const [version, setVersion] = useState(undefined); const [state, setState] = useState('view'); const classes = useStyles(); @@ -55,6 +57,10 @@ function ChplDeveloperPage({ id }) { case 'split': setState(action); break; + case 'editVersion': + setState(action); + setVersion({ version: payload.version, productId: payload.productId }); + break; case 'cognito-invite': createInvitation({ ...payload, @@ -121,12 +127,6 @@ function ChplDeveloperPage({ id }) { productId: payload.id, }); break; - case 'editVersion': - $state.go('organizations.developers.developer.product.version.edit', { - productId: payload.product.id, - versionId: payload.version, - }); - break; case 'mergeVersion': $state.go('organizations.developers.developer.product.version.merge', { productId: payload.product.id, @@ -185,6 +185,14 @@ function ChplDeveloperPage({ id }) { dispatch={handleDispatch} /> )} + { state === 'editVersion' + && ( + + )} { state === 'join' && ( { + switch (action) { + case 'cancel': + dispatch('cancel'); + break; + case 'save': + console.log(action, payload); + setIsProcessing(true); + eventTrack({ + ...analytics, + event: 'Save Version', + }); + setErrorMessages([]); + mutate({ + ...payload, + versionIds: [payload.id], + newProductId: productId, + }, { + onSuccess: (response) => { + setIsProcessing(false); + console.log({response}); + let body; + if (body) { + enqueueSnackbar(body, { + variant: 'error', + }); + } + }, + onError: (error) => { + setIsProcessing(false); + console.log({error}); + let body = error?.response?.data?.error; + if (body) { + enqueueSnackbar(body, { + variant: 'error', + }); + } + }, + }); + break; + // no default + } + }; + + if (!version) { return ; } + + return ( + + + + + + ); +} + +export default ChplEditVersion; + +ChplEditVersion.propTypes = { + dispatch: func.isRequired, + productId: number.isRequired, + version: object.isRequired, +}; diff --git a/src/app/services/network.service.js b/src/app/services/network.service.js index b176684bd1..da0573ad4b 100644 --- a/src/app/services/network.service.js +++ b/src/app/services/network.service.js @@ -354,10 +354,6 @@ export default class NetworkService { return this.apiPUT(`/surveillance/${surveillance.id}`, surveillance); } - updateVersion(versionObject) { - return this.apiPUT('/versions', versionObject); - } - /* * Helper functions */ From 0958a9cc3a84f9b20239108b98dc15ac521549d6 Mon Sep 17 00:00:00 2001 From: Andrew Larned Date: Thu, 6 Feb 2025 12:15:54 -0500 Subject: [PATCH 3/6] fix: use correct format for version edit [#OCD-4770] --- .../developers/developer/version-edit.jsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/app/pages/organizations/developers/developer/version-edit.jsx b/src/app/pages/organizations/developers/developer/version-edit.jsx index d620dfdf3b..ea67d6f75a 100755 --- a/src/app/pages/organizations/developers/developer/version-edit.jsx +++ b/src/app/pages/organizations/developers/developer/version-edit.jsx @@ -44,7 +44,6 @@ function ChplEditVersion({ dispatch, productId, version }) { dispatch('cancel'); break; case 'save': - console.log(action, payload); setIsProcessing(true); eventTrack({ ...analytics, @@ -52,23 +51,15 @@ function ChplEditVersion({ dispatch, productId, version }) { }); setErrorMessages([]); mutate({ - ...payload, + version: payload, versionIds: [payload.id], - newProductId: productId, }, { - onSuccess: (response) => { + onSuccess: () => { setIsProcessing(false); - console.log({response}); - let body; - if (body) { - enqueueSnackbar(body, { - variant: 'error', - }); - } + dispatch('cancel'); }, onError: (error) => { setIsProcessing(false); - console.log({error}); let body = error?.response?.data?.error; if (body) { enqueueSnackbar(body, { From b23673ffc70bd05c3bb859bda055aa859bcc9905 Mon Sep 17 00:00:00 2001 From: Andrew Larned Date: Thu, 13 Feb 2025 12:45:11 -0500 Subject: [PATCH 4/6] ui: show link to Developer on Version activity reporting [#OCD-4770] --- .../pages/reports/activity/activity-view.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/app/pages/reports/activity/activity-view.jsx b/src/app/pages/reports/activity/activity-view.jsx index 63a40433a4..e17d77919d 100755 --- a/src/app/pages/reports/activity/activity-view.jsx +++ b/src/app/pages/reports/activity/activity-view.jsx @@ -148,6 +148,7 @@ function ChplActivityView() { if (![ 'CERTIFIED_PRODUCT', 'DEVELOPER', + 'VERSION', ].includes(activity.concept)) { return null; } @@ -183,6 +184,22 @@ function ChplActivityView() { router={{ sref: 'organizations.developers.developer', options: { id: activity.objectId } }} /> ); + case 'VERSION': + if (before && after && before.id !== after.id) { + return null; + } + return ( + + ); default: return null; } From c90c1eba5c012f07e6e89c052a92f291b04e98ad Mon Sep 17 00:00:00 2001 From: Andrew Larned Date: Fri, 21 Feb 2025 12:48:51 -0500 Subject: [PATCH 5/6] fix: restore split/merge version capability --- src/app/pages/organizations/organizations.state.js | 10 ---------- src/app/services/network.service.js | 4 ++++ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/app/pages/organizations/organizations.state.js b/src/app/pages/organizations/organizations.state.js index a9ddcadfec..246825f3a5 100644 --- a/src/app/pages/organizations/organizations.state.js +++ b/src/app/pages/organizations/organizations.state.js @@ -92,16 +92,6 @@ const states = [ name: 'organizations.developers.developer.product.version', url: '/versions/{versionId}', abstract: true, - }, { - name: 'organizations.developers.developer.product.version.edit', - url: '/edit', - views: { - 'view@^.^.^': 'chplVersionsEdit', - }, - data: { - title: 'CHPL Developers - Edit Version', - roles: ['chpl-admin', 'chpl-onc', 'chpl-onc-acb'], - }, }, { name: 'organizations.developers.developer.product.version.merge', url: '/merge', diff --git a/src/app/services/network.service.js b/src/app/services/network.service.js index da0573ad4b..b176684bd1 100644 --- a/src/app/services/network.service.js +++ b/src/app/services/network.service.js @@ -354,6 +354,10 @@ export default class NetworkService { return this.apiPUT(`/surveillance/${surveillance.id}`, surveillance); } + updateVersion(versionObject) { + return this.apiPUT('/versions', versionObject); + } + /* * Helper functions */ From 0b76e7ed1a12abef5ed2a0e4c672f423968538a6 Mon Sep 17 00:00:00 2001 From: Andrew Larned Date: Fri, 21 Feb 2025 12:49:35 -0500 Subject: [PATCH 6/6] style: remove unnecessary elements [#OCD-4770] --- src/app/components/version/version-edit.jsx | 21 ------------------- src/app/components/version/version.jsx | 12 ----------- .../developers/developer/version-edit.jsx | 11 ++++------ 3 files changed, 4 insertions(+), 40 deletions(-) diff --git a/src/app/components/version/version-edit.jsx b/src/app/components/version/version-edit.jsx index 1fd42943c4..5cef60c62d 100755 --- a/src/app/components/version/version-edit.jsx +++ b/src/app/components/version/version-edit.jsx @@ -1,30 +1,11 @@ import React, { useContext, useEffect, useState } from 'react'; import { - Box, - Button, - ButtonGroup, - IconButton, Card, CardHeader, CardContent, Container, - Divider, - FormControlLabel, - MenuItem, - Switch, - Table, - TableContainer, - TableRow, - TableHead, - TableCell, - TableBody, - TableFooter, - Typography, makeStyles, } from '@material-ui/core'; -import AddIcon from '@material-ui/icons/Add'; -import CheckIcon from '@material-ui/icons/Check'; -import CloseIcon from '@material-ui/icons/Close'; import { arrayOf, bool, @@ -38,7 +19,6 @@ import * as yup from 'yup'; import { ChplActionBar } from 'components/action-bar'; import { ChplTextField } from 'components/util'; import { eventTrack } from 'services/analytics.service'; -import { getDisplayDateFormat } from 'services/date-util'; import { DeveloperContext, UserContext, useAnalyticsContext } from 'shared/contexts'; import { utilStyles } from 'themes'; @@ -72,7 +52,6 @@ function ChplVersionEdit(props) { } = props; const { analytics } = useAnalyticsContext(); const { developer } = useContext(DeveloperContext); - const { hasAnyRole } = useContext(UserContext); const [errorMessages, setErrorMessages] = useState([]); const [warnings, setWarnings] = useState([]); const [isInvalid, setIsInvalid] = useState(false); diff --git a/src/app/components/version/version.jsx b/src/app/components/version/version.jsx index 321bb7257d..e0743d890a 100755 --- a/src/app/components/version/version.jsx +++ b/src/app/components/version/version.jsx @@ -10,12 +10,8 @@ import { import ChplVersionEdit from './version-edit'; function ChplVersion({ - canEdit, - canJoin, - canSplit, dispatch, errorMessages, - isEditing, isInvalid: initialIsInvalid, isProcessing, isSplitting, @@ -42,12 +38,8 @@ function ChplVersion({ export default ChplVersion; ChplVersion.propTypes = { - canEdit: func, - canJoin: func, - canSplit: func, dispatch: func, errorMessages: arrayOf(string), - isEditing: bool, isInvalid: bool, isProcessing: bool, isSplitting: bool, @@ -55,12 +47,8 @@ ChplVersion.propTypes = { }; ChplVersion.defaultProps = { - canEdit: () => false, - canJoin: () => false, - canSplit: () => false, dispatch: () => {}, errorMessages: [], - isEditing: false, isInvalid: false, isProcessing: false, isSplitting: false, diff --git a/src/app/pages/organizations/developers/developer/version-edit.jsx b/src/app/pages/organizations/developers/developer/version-edit.jsx index ea67d6f75a..5d4ec3253f 100755 --- a/src/app/pages/organizations/developers/developer/version-edit.jsx +++ b/src/app/pages/organizations/developers/developer/version-edit.jsx @@ -5,14 +5,14 @@ import { Container, makeStyles, } from '@material-ui/core'; -import { func, number, object } from 'prop-types'; +import { func, object } from 'prop-types'; import { useSnackbar } from 'notistack'; import { usePutVersion } from 'api/version'; import ChplVersion from 'components/version/version'; import { eventTrack } from 'services/analytics.service'; import { DeveloperContext, UserContext, useAnalyticsContext } from 'shared/contexts'; -import { palette, theme } from 'themes'; +import { theme } from 'themes'; const useStyles = makeStyles({ pageContainer: { @@ -28,10 +28,8 @@ const useStyles = makeStyles({ }, }); -function ChplEditVersion({ dispatch, productId, version }) { +function ChplEditVersion({ dispatch, version }) { const { analytics } = useAnalyticsContext(); - const { developer } = useContext(DeveloperContext); - const { hasAnyRole } = useContext(UserContext); const { enqueueSnackbar } = useSnackbar(); const { mutate } = usePutVersion(); const [errorMessages, setErrorMessages] = useState([]); @@ -60,7 +58,7 @@ function ChplEditVersion({ dispatch, productId, version }) { }, onError: (error) => { setIsProcessing(false); - let body = error?.response?.data?.error; + const body = error?.response?.data?.error; if (body) { enqueueSnackbar(body, { variant: 'error', @@ -94,6 +92,5 @@ export default ChplEditVersion; ChplEditVersion.propTypes = { dispatch: func.isRequired, - productId: number.isRequired, version: object.isRequired, };