From 606a7d5f8811c9a767bed5fba50b777456a3d052 Mon Sep 17 00:00:00 2001 From: MVarshini Date: Wed, 8 Feb 2023 20:37:26 +0530 Subject: [PATCH] Allow user to edit Dataset Name (#3207) * PBENCH-1017 Allow user to change dataset.name Modify the name of the dataset using PUT /datasets/metadata API --- dashboard/src/actions/authActions.js | 5 +- dashboard/src/actions/overviewActions.js | 154 +++++++++++---- .../src/assets/constants/overviewConstants.js | 16 +- .../src/assets/constants/toastConstants.js | 1 + .../OverviewComponent/NewRunsComponent.jsx | 123 +++++------- .../OverviewComponent/SavedRunsComponent.jsx | 96 +++++----- .../OverviewComponent/common-component.jsx | 179 +++++++++++++++++- 7 files changed, 405 insertions(+), 169 deletions(-) create mode 100644 dashboard/src/assets/constants/toastConstants.js diff --git a/dashboard/src/actions/authActions.js b/dashboard/src/actions/authActions.js index a6e3f42531..1474ec412c 100644 --- a/dashboard/src/actions/authActions.js +++ b/dashboard/src/actions/authActions.js @@ -4,6 +4,7 @@ import * as TYPES from "./types"; import API from "../utils/axiosInstance"; import Cookies from "js-cookie"; +import { SUCCESS } from "assets/constants/overviewConstants"; import { showToast } from "actions/toastActions"; import { uid } from "../utils/helper"; @@ -44,7 +45,7 @@ export const makeLoginRequest = navigate(APP_ROUTES.OVERVIEW); - dispatch(showToast("success", "Logged in successfully!")); + dispatch(showToast(SUCCESS, "Logged in successfully!")); } dispatch({ type: TYPES.COMPLETED }); } catch (error) { @@ -102,7 +103,7 @@ export const registerUser = ...details, }); if (response.status === 201) { - dispatch(showToast("success", "Account created!", "Login to continue")); + dispatch(showToast(SUCCESS, "Account created!", "Login to continue")); navigate(APP_ROUTES.AUTH_LOGIN); } dispatch({ type: TYPES.COMPLETED }); diff --git a/dashboard/src/actions/overviewActions.js b/dashboard/src/actions/overviewActions.js index 606ecf5fd1..324bffa687 100644 --- a/dashboard/src/actions/overviewActions.js +++ b/dashboard/src/actions/overviewActions.js @@ -1,18 +1,8 @@ +import * as CONSTANTS from "assets/constants/overviewConstants"; import * as TYPES from "./types"; -import { - DASHBOARD_LOAD_DELAY_MS, - DASHBOARD_SAVED, - DASHBOARD_SEEN, - DATASET_ACCESS, - DATASET_CREATED, - DATASET_OWNER, - EXPIRATION_DAYS_LIMIT, - SERVER_DELETION, - USER_FAVORITE, -} from "assets/constants/overviewConstants"; - import API from "../utils/axiosInstance"; +import { DANGER } from "assets/constants/toastConstants"; import { findNoOfDays } from "utils/dateFunctions"; import { showToast } from "./toastActions"; @@ -25,13 +15,13 @@ export const getDatasets = () => async (dispatch, getState) => { dispatch({ type: TYPES.LOADING }); } const params = new URLSearchParams(); - params.append("metadata", DATASET_CREATED); - params.append("metadata", DATASET_OWNER); - params.append("metadata", DATASET_ACCESS); - params.append("metadata", SERVER_DELETION); - params.append("metadata", DASHBOARD_SAVED); - params.append("metadata", DASHBOARD_SEEN); - params.append("metadata", USER_FAVORITE); + params.append("metadata", CONSTANTS.DATASET_CREATED); + params.append("metadata", CONSTANTS.DATASET_OWNER); + params.append("metadata", CONSTANTS.DATASET_ACCESS); + params.append("metadata", CONSTANTS.SERVER_DELETION); + params.append("metadata", CONSTANTS.DASHBOARD_SAVED); + params.append("metadata", CONSTANTS.DASHBOARD_SEEN); + params.append("metadata", CONSTANTS.USER_FAVORITE); params.append("owner", username); @@ -52,7 +42,7 @@ export const getDatasets = () => async (dispatch, getState) => { } } } catch (error) { - dispatch(showToast("danger", error?.response?.data?.message)); + dispatch(showToast(DANGER, error?.response?.data?.message)); dispatch({ type: TYPES.NETWORK_ERROR }); } if (alreadyRendered) { @@ -64,14 +54,28 @@ export const getDatasets = () => async (dispatch, getState) => { const initializeRuns = () => (dispatch, getState) => { const data = getState().overview.datasets; + data.forEach((item) => { + item[CONSTANTS.IS_EDIT] = false; + item[CONSTANTS.NAME_COPY] = item.name; + item[CONSTANTS.IS_DIRTY] = false; + item[CONSTANTS.NAME_VALIDATED] = CONSTANTS.DEFAULT; + item[CONSTANTS.IS_ITEM_SEEN] = !!item?.metadata?.[CONSTANTS.DASHBOARD_SEEN]; + item[CONSTANTS.IS_ITEM_FAVORITED] = + !!item?.metadata?.[CONSTANTS.USER_FAVORITE]; + }); const defaultPerPage = getState().overview.defaultPerPage; - const savedRuns = data.filter((item) => item.metadata[DASHBOARD_SAVED]); - const newRuns = data.filter((item) => !item.metadata[DASHBOARD_SAVED]); + const savedRuns = data.filter( + (item) => item.metadata[CONSTANTS.DASHBOARD_SAVED] + ); + const newRuns = data.filter( + (item) => !item.metadata[CONSTANTS.DASHBOARD_SAVED] + ); const expiringRuns = data.filter( (item) => - findNoOfDays(item.metadata["server.deletion"]) < EXPIRATION_DAYS_LIMIT + findNoOfDays(item.metadata[CONSTANTS.SERVER_DELETION]) < + CONSTANTS.EXPIRATION_DAYS_LIMIT ); dispatch({ type: TYPES.EXPIRING_RUNS, @@ -91,9 +95,10 @@ const initializeRuns = () => (dispatch, getState) => { }); }; const metaDataActions = { - save: DASHBOARD_SAVED, - read: DASHBOARD_SEEN, - favorite: USER_FAVORITE, + save: CONSTANTS.DASHBOARD_SAVED, + read: CONSTANTS.DASHBOARD_SEEN, + favorite: CONSTANTS.USER_FAVORITE, + datasetName: CONSTANTS.DATASET_NAME, }; /** * Function which return a thunk to be passed to a Redux dispatch() call @@ -132,15 +137,20 @@ export const updateDataset = }); dispatch(initializeRuns()); } else { - dispatch(showToast("danger", response?.data?.message)); + dispatch(showToast(DANGER, response?.data?.message)); } } catch (error) { - dispatch(showToast("danger", error?.response?.data?.message)); + dispatch(showToast(DANGER, error?.response?.data?.message)); dispatch({ type: TYPES.NETWORK_ERROR }); } dispatch({ type: TYPES.COMPLETED }); }; - +/** + * Function to delete the dataset + * @function + * @param {Object} dataset - Dataset which is being updated * + * @return {Object} - dispatch the action and update the state + */ export const deleteDataset = (dataset) => async (dispatch, getState) => { try { dispatch({ type: TYPES.LOADING }); @@ -161,10 +171,10 @@ export const deleteDataset = (dataset) => async (dispatch, getState) => { }); dispatch(initializeRuns()); - dispatch(showToast("success", "Deleted!")); + dispatch(showToast(CONSTANTS.SUCCESS, "Deleted!")); } } catch (error) { - dispatch(showToast("danger", error?.response?.data?.message)); + dispatch(showToast(DANGER, error?.response?.data?.message)); dispatch({ type: TYPES.NETWORK_ERROR }); } dispatch({ type: TYPES.COMPLETED }); @@ -200,13 +210,19 @@ export const updateMultipleDataset = : method === "save" ? "Saved!" : "Updated!"; - dispatch(showToast("success", toastMsg)); + dispatch(showToast(CONSTANTS.SUCCESS, toastMsg)); dispatch(setSelectedRuns([])); } else { dispatch(showToast("warning", "Select dataset(s) for update")); } }; - +/** + * Function to publish the dataset + * @function + * @param {Object} dataset - Dataset which is being updated + * @param {string} updateValue - Access type value (Public/Private) + * @return {Object} - dispatch the action and update the state + */ export const publishDataset = (dataset, updateValue) => async (dispatch, getState) => { try { @@ -221,16 +237,16 @@ export const publishDataset = const dataIndex = savedRuns.findIndex( (item) => item.resource_id === dataset.resource_id ); - savedRuns[dataIndex].metadata[DATASET_ACCESS] = updateValue; + savedRuns[dataIndex].metadata[CONSTANTS.DATASET_ACCESS] = updateValue; dispatch({ type: TYPES.SAVED_RUNS, payload: savedRuns, }); - dispatch(showToast("success", "Updated!")); + dispatch(showToast(CONSTANTS.SUCCESS, "Updated!")); } } catch (error) { - dispatch(showToast("danger", error?.response?.data?.message)); + dispatch(showToast(DANGER, error?.response?.data?.message)); dispatch({ type: TYPES.NETWORK_ERROR }); } dispatch({ type: TYPES.COMPLETED }); @@ -244,5 +260,69 @@ export const setLoadingDoneFlag = () => async (dispatch, getState) => { sessionStorage.setItem("loadingDone", true); dispatch({ type: TYPES.SET_LOADING_FLAG, payload: true }); } - }, DASHBOARD_LOAD_DELAY_MS); + }, CONSTANTS.DASHBOARD_LOAD_DELAY_MS); +}; + +const filterDatasetType = (type, getState) => { + return type === "newRuns" + ? getState().overview.initNewRuns + : getState().overview.savedRuns; +}; + +const updateDatasetType = (data, type) => { + return { + type: type === "newRuns" ? TYPES.INIT_NEW_RUNS : TYPES.SAVED_RUNS, + payload: data, + }; }; +/** + * Function to validate the edited dataset + * @function + * @param {string} value - new value of the metadata that is being edited + * @param {string} metadata - metadata that is being edited * + * @param {string} rId - resource_id of the dataset which is being set to edit + * @param {string} type - Type of the Dataset (Saved/New) + * @return {Object} - dispatch the action and update the state + */ +export const editMetadata = + (value, metadata, rId, type) => async (dispatch, getState) => { + const data = filterDatasetType(type, getState); + + const rIndex = data.findIndex((item) => item.resource_id === rId); + data[rIndex][metadata] = value; + data[rIndex][CONSTANTS.IS_DIRTY] = true; + if (value.length > CONSTANTS.DATASET_NAME_LENGTH) { + data[rIndex][CONSTANTS.NAME_VALIDATED] = CONSTANTS.ERROR; + data[rIndex][ + CONSTANTS.NAME_ERROR_MSG + ] = `Length should be < ${CONSTANTS.DATASET_NAME_LENGTH}`; + } else if (value.length === 0) { + data[rIndex][CONSTANTS.NAME_VALIDATED] = CONSTANTS.ERROR; + data[rIndex][CONSTANTS.NAME_ERROR_MSG] = `Length cannot be 0`; + } else { + data[rIndex][CONSTANTS.NAME_VALIDATED] = CONSTANTS.SUCCESS; + } + dispatch(updateDatasetType(data, type)); + }; +/** + * Function which toggles the row of New runs or Saved runs Table to edit + * @function + * @param {string} rId - resource_id of the dataset which is being set to edit + * @param {boolean} isEdit - Set/not set to edit + * @param {string} type - Type of the Dataset (Saved/New) + * @return {Object} - dispatch the action and update the state + */ +export const setRowtoEdit = + (rId, isEdit, type) => async (dispatch, getState) => { + const data = filterDatasetType(type, getState); + + const rIndex = data.findIndex((item) => item.resource_id === rId); + data[rIndex][CONSTANTS.IS_EDIT] = isEdit; + + if (!isEdit) { + data[rIndex].name = data[rIndex][CONSTANTS.NAME_COPY]; + data[rIndex][CONSTANTS.IS_DIRTY] = false; + data[rIndex][CONSTANTS.NAME_VALIDATED] = CONSTANTS.SUCCESS; + } + dispatch(updateDatasetType(data, type)); + }; diff --git a/dashboard/src/assets/constants/overviewConstants.js b/dashboard/src/assets/constants/overviewConstants.js index 5e1888bd39..f81750463d 100644 --- a/dashboard/src/assets/constants/overviewConstants.js +++ b/dashboard/src/assets/constants/overviewConstants.js @@ -3,10 +3,22 @@ export const ROWS_PER_PAGE = 5; export const DASHBOARD_SAVED = "global.dashboard.saved"; export const DASHBOARD_SEEN = "global.dashboard.seen"; +export const DASHBOARD_LOAD_DELAY_MS = 1000; export const DATASET_ACCESS = "dataset.access"; export const DATASET_CREATED = "dataset.created"; +export const DATASET_NAME = "dataset.name"; +export const DATASET_NAME_LENGTH = 1024; export const DATASET_OWNER = "dataset.owner"; +export const DEFAULT = "default"; +export const ERROR = "error"; +export const EXPIRATION_DAYS_LIMIT = 20; +export const IS_DIRTY = "isDirty"; +export const IS_EDIT = "isEdit"; +export const IS_ITEM_FAVORITED = "isItemFavorited"; +export const IS_ITEM_SEEN = "isItemSeen"; +export const NAME_COPY = "name_copy"; +export const NAME_ERROR_MSG = "name_errorMsg"; +export const NAME_VALIDATED = "name_validated"; export const SERVER_DELETION = "server.deletion"; +export const SUCCESS = "success"; export const USER_FAVORITE = "user.dashboard.favorite"; -export const EXPIRATION_DAYS_LIMIT = 20; -export const DASHBOARD_LOAD_DELAY_MS = 1000; diff --git a/dashboard/src/assets/constants/toastConstants.js b/dashboard/src/assets/constants/toastConstants.js new file mode 100644 index 0000000000..161cfe59fc --- /dev/null +++ b/dashboard/src/assets/constants/toastConstants.js @@ -0,0 +1 @@ +export const DANGER = "danger"; diff --git a/dashboard/src/modules/components/OverviewComponent/NewRunsComponent.jsx b/dashboard/src/modules/components/OverviewComponent/NewRunsComponent.jsx index 58e5748c24..f7e28f4701 100644 --- a/dashboard/src/modules/components/OverviewComponent/NewRunsComponent.jsx +++ b/dashboard/src/modules/components/OverviewComponent/NewRunsComponent.jsx @@ -1,37 +1,33 @@ import "./index.less"; import { - ActionsColumn, - ExpandableRowContent, + DASHBOARD_SEEN, + IS_ITEM_SEEN, + ROWS_PER_PAGE, + START_PAGE_NUMBER, + USER_FAVORITE, +} from "assets/constants/overviewConstants"; +import { InnerScrollContainer, OuterScrollContainer, TableComposable, Tbody, - Td, Th, Thead, Tr, } from "@patternfly/react-table"; -import { - DASHBOARD_SEEN, - DATASET_OWNER, - ROWS_PER_PAGE, - SERVER_DELETION, - START_PAGE_NUMBER, - USER_FAVORITE, -} from "assets/constants/overviewConstants"; +import { NewRunsRow, RenderPagination } from "./common-component"; import React, { useCallback, useState } from "react"; import { deleteDataset, + editMetadata, setRows, + setRowtoEdit, setSelectedRuns, updateDataset, } from "actions/overviewActions"; import { useDispatch, useSelector } from "react-redux"; -import { RenderPagination } from "./common-component"; -import { formatDateTime } from "utils/dateFunctions"; - const NewRunsComponent = () => { const dispatch = useDispatch(); const { newRuns, initNewRuns, selectedRuns } = useSelector( @@ -86,7 +82,9 @@ const NewRunsComponent = () => { const makeFavorites = (dataset, isFavoriting = true) => { dispatch(updateDataset(dataset, "favorite", isFavoriting)); }; - + const saveRowData = (metadataType, dataset, value) => { + dispatch(updateDataset(dataset, metadataType, value)); + }; const moreActionItems = (dataset) => [ { title: "Save", @@ -122,7 +120,17 @@ const NewRunsComponent = () => { ? [...otherExpandedRunNames, run.name] : otherExpandedRunNames; }); - const isRunExpanded = (run) => expandedRunNames.includes(run.name); + const isRunExpanded = useCallback( + (run) => expandedRunNames.includes(run.name), + [expandedRunNames] + ); + const updateTblValue = (newValue, metadata, rId) => { + dispatch(editMetadata(newValue, metadata, rId, "newRuns")); + }; + const toggleEdit = useCallback( + (rId, isEdit) => dispatch(setRowtoEdit(rId, isEdit, "newRuns")), + [dispatch] + ); return (
@@ -143,71 +151,40 @@ const NewRunsComponent = () => { {columnNames.result} {columnNames.endtime} + - - {initNewRuns.map((item, rowIndex) => { - const rowActions = moreActionItems(item); - const isItemFavorited = !!item?.metadata?.[USER_FAVORITE]; - const isItemSeen = !!item?.metadata?.[DASHBOARD_SEEN]; - return ( - + + {initNewRuns.map((item, rowIndex) => { + const rowActions = moreActionItems(item); + return ( - - setRunExpanded(item, !isRunExpanded(item)), - expandId: "composable-expandable-example", - } - : undefined + + updateTblValue(val, "name", item.resource_id) } /> - - onSelectRuns(item, rowIndex, isSelecting), - isSelected: isRowSelected(item), - }} - /> - {item.name} - - {formatDateTime(item.metadata[SERVER_DELETION])} - - - makeFavorites(item, isFavoriting), - rowIndex, - }} - /> - - {rowActions ? : null} - - {item.metadata ? ( - - - - - -
Owner: {item.metadata[DATASET_OWNER]}
-
- - - ) : null} - - ); - })} + ); + })} + { const dispatch = useDispatch(); @@ -77,6 +75,18 @@ const SavedRunsComponent = () => { const makeFavorites = (dataset, isFavoriting = true) => { dispatch(updateDataset(dataset, "favorite", isFavoriting)); }; + /* Edit Dataset */ + const saveRowData = (metadataType, dataset, value) => { + dispatch(updateDataset(dataset, metadataType, value)); + }; + const toggleEdit = useCallback( + (rId, isEdit) => dispatch(setRowtoEdit(rId, isEdit, "savedRuns")), + [dispatch] + ); + const updateTblValue = (newValue, metadata, rId) => { + dispatch(editMetadata(newValue, metadata, rId, "savedRuns")); + }; + /* Edit Dataset */ const columnNames = { result: "Result", createdtime: "Created Time", @@ -106,52 +116,32 @@ const SavedRunsComponent = () => { - {savedRuns.map((item, rowIndex) => { - const rowActions = moreActionItems(item); - const isItemFavorited = !!item?.metadata?.[USER_FAVORITE]; - const isItemSeen = !!item?.metadata?.[DASHBOARD_SEEN]; - return ( - + + {savedRuns.map((item, rowIndex) => { + const rowActions = moreActionItems(item); + return ( - - onSelectRuns(item, rowIndex, isSelecting), - isSelected: isRowSelected(item), - }} - /> - - {item.name} - - - {formatDateTime(item.metadata[DATASET_CREATED])} - - - {formatDateTime(item.metadata[SERVER_DELETION])} - - {item.metadata[DATASET_ACCESS]} - - makeFavorites(item, isFavoriting), - rowIndex, - }} + + updateTblValue(val, "name", item.resource_id) + } + toggleEdit={toggleEdit} + saveRowData={saveRowData} /> - - {rowActions ? : null} - - - ); - })} + ); + })} + diff --git a/dashboard/src/modules/components/OverviewComponent/common-component.jsx b/dashboard/src/modules/components/OverviewComponent/common-component.jsx index f49f147d11..94b58ab508 100644 --- a/dashboard/src/modules/components/OverviewComponent/common-component.jsx +++ b/dashboard/src/modules/components/OverviewComponent/common-component.jsx @@ -1,5 +1,8 @@ import "./index.less"; +import * as CONSTANTS from "assets/constants/overviewConstants"; + +import { ActionsColumn, Td } from "@patternfly/react-table"; import { Button, Dropdown, @@ -8,14 +11,27 @@ import { Pagination, Text, TextContent, + TextInput, TextVariants, } from "@patternfly/react-core"; -import { CaretDownIcon, RedoIcon } from "@patternfly/react-icons"; +import { + CaretDownIcon, + CheckIcon, + PencilAltIcon, + RedoIcon, + TimesIcon, +} from "@patternfly/react-icons"; +import { + DATASET_ACCESS, + DATASET_CREATED, + EXPIRATION_DAYS_LIMIT, + SERVER_DELETION, +} from "assets/constants/overviewConstants"; import React, { useState } from "react"; import { getDatasets, updateMultipleDataset } from "actions/overviewActions"; import { useDispatch, useSelector } from "react-redux"; -import { EXPIRATION_DAYS_LIMIT } from "assets/constants/overviewConstants"; +import { formatDateTime } from "utils/dateFunctions"; export const Heading = (props) => { return ( @@ -157,3 +173,162 @@ export const RenderPagination = (props) => { /> ); }; + +export const EditRow = (props) => { + return ( +
+ {!props.item[CONSTANTS.IS_EDIT] ? ( +
+ )} +
+ ); +}; + +export const DatasetNameInput = (props) => ( + +); + +export const NewRunsRow = (props) => { + const { item, rowIndex, columnNames, isRunExpanded, setRunExpanded } = props; + + return ( + <> + setRunExpanded(item, !isRunExpanded(item)), + expandId: "new-runs-table", + } + : undefined + } + /> + + props.onSelectRuns(item, rowIndex, isSelecting), + isSelected: props.isRowSelected(item), + }} + /> + + {item[CONSTANTS.IS_EDIT] ? ( + + ) : ( + item.name + )} + + + {formatDateTime(item.metadata[SERVER_DELETION])} + + + props.makeFavorites(item, isFavoriting), + rowIndex, + }} + /> + + + + + {props.rowActions ? : null} + + + ); +}; + +export const SavedRunsRow = (props) => { + const { item, rowIndex, columnNames, rowActions } = props; + return ( + <> + + props.onSelectRuns(item, rowIndex, isSelecting), + isSelected: props.isRowSelected(item), + }} + /> + + {item[CONSTANTS.IS_EDIT] ? ( + + ) : ( + item.name + )} + + + {formatDateTime(item.metadata[DATASET_CREATED])} + + + {formatDateTime(item.metadata[SERVER_DELETION])} + + {item.metadata[DATASET_ACCESS]} + + props.makeFavorites(item, isFavoriting), + rowIndex, + }} + /> + + + + + {rowActions ? : null} + + + ); +};