From 48270d564b7e053de1d7879b7ab8c0da15f7082f Mon Sep 17 00:00:00 2001 From: "Kasper Fuglsang Christensen (Delegate)" Date: Thu, 19 Sep 2024 15:44:16 +0200 Subject: [PATCH 1/5] feat: select all in view functionality --- app/src/app/analysis/analysis-page.tsx | 1 + .../analysis/analysis-selection-configs.ts | 17 ++++++++ .../app/analysis/analysis-selection-menu.tsx | 41 +++++++++++++++---- .../nearest-neighbor-menu-item.tsx | 4 +- .../resistance/resistance-menu-item.tsx | 4 +- .../send-to-workspace-menu-item.tsx | 4 +- 6 files changed, 61 insertions(+), 10 deletions(-) diff --git a/app/src/app/analysis/analysis-page.tsx b/app/src/app/analysis/analysis-page.tsx index 6c54c64a..5bfa709f 100644 --- a/app/src/app/analysis/analysis-page.tsx +++ b/app/src/app/analysis/analysis-page.tsx @@ -624,6 +624,7 @@ export default function AnalysisPage() { {!pageState.isNarrowed ? ( diff --git a/app/src/app/analysis/analysis-selection-configs.ts b/app/src/app/analysis/analysis-selection-configs.ts index 67f83a05..8cfa24de 100644 --- a/app/src/app/analysis/analysis-selection-configs.ts +++ b/app/src/app/analysis/analysis-selection-configs.ts @@ -16,6 +16,10 @@ export const setSelection = createAction>( export const clearSelection = createAction("analysis/clearSelection"); +export const selectAll = createAction("analysis/selectAll"); + +export const selectAllInView = createAction("analysis/selectAllInView"); + const initialState: SelectionState = { selection: {} as DataTableSelection, }; @@ -50,5 +54,18 @@ export const selectionReducer = createReducer(initialState, (builder) => { }) .addCase(clearSelection, (state) => { state.selection = {} as DataTableSelection; + }) + .addCase(selectAll, (state) => { + console.log("Du trykkede på selectAll knappen!"); + }) + .addCase(selectAllInView, (state, action) => { + let mapped = action.payload.map(x => { + return ({ [x["sequence_id"]]: { original: x, cells: {} } } as DataTableSelection) + }); + let reduced = mapped.reduce((acc, cur) => { + return { ...acc, ...cur } + }, {} as DataTableSelection) + + state.selection = reduced; }); }); diff --git a/app/src/app/analysis/analysis-selection-menu.tsx b/app/src/app/analysis/analysis-selection-menu.tsx index f2a8973c..3dd5c61d 100644 --- a/app/src/app/analysis/analysis-selection-menu.tsx +++ b/app/src/app/analysis/analysis-selection-menu.tsx @@ -1,46 +1,73 @@ import { AnalysisResult } from "sap-client"; import { DataTableSelection } from "./data-table/data-table"; -import { HamburgerIcon, SmallCloseIcon } from "@chakra-ui/icons"; +import { HamburgerIcon, SmallCloseIcon, SmallAddIcon } from "@chakra-ui/icons"; import { ResistanceMenuItem } from "./resistance/resistance-menu-item"; import { NearestNeighborMenuItem } from "./nearest-neighbor/nearest-neighbor-menu-item"; import { Menu, MenuList, MenuButton, Button, MenuItem } from "@chakra-ui/react"; import { useCallback } from "react"; -import { clearSelection } from "./analysis-selection-configs"; +import { clearSelection, selectAll, selectAllInView } from "./analysis-selection-configs"; import { useDispatch } from "react-redux"; import { SendToWorkspaceMenuItem } from "app/workspaces/send-to-workspace-menu-item"; type Props = { selection: DataTableSelection; isNarrowed: boolean; + data: AnalysisResult[]; }; export const AnalysisSelectionMenu = (props: Props) => { - const { selection, isNarrowed } = props; + const { selection, isNarrowed, data } = props; const dispatch = useDispatch(); const onClear = useCallback(() => { dispatch(clearSelection()); }, [dispatch]); + const onSelectAllInView = useCallback(() => { + dispatch(selectAllInView(data)); + }, [dispatch, data]); + + const onSelectAll = useCallback(() => { + dispatch(selectAll()); + }, [dispatch]); + + let disabled = isNarrowed || Object.keys(selection).length == 0; + return (
} - disabled={isNarrowed || Object.keys(selection).length == 0} > Selection - - - + + + + } + onClick={onSelectAllInView} + > + Select All In View + + } + onClick={onSelectAll} + > + Select All + } onClick={onClear} + isDisabled={disabled} > Clear Selection diff --git a/app/src/app/analysis/nearest-neighbor/nearest-neighbor-menu-item.tsx b/app/src/app/analysis/nearest-neighbor/nearest-neighbor-menu-item.tsx index dbee3497..30e37801 100644 --- a/app/src/app/analysis/nearest-neighbor/nearest-neighbor-menu-item.tsx +++ b/app/src/app/analysis/nearest-neighbor/nearest-neighbor-menu-item.tsx @@ -8,10 +8,11 @@ import { NearestNeighborModal } from "./nearest-neighbor-modal"; type Props = { selection: DataTableSelection; + disabled: boolean; }; export const NearestNeighborMenuItem = (props: Props) => { - const { selection } = props; + const { selection, disabled } = props; const { isOpen, onOpen, onClose } = useDisclosure(); return ( @@ -24,6 +25,7 @@ export const NearestNeighborMenuItem = (props: Props) => { title="Nearest Neighbor" icon={} onClick={onOpen} + isDisabled={disabled} > Nearest Neighbor diff --git a/app/src/app/analysis/resistance/resistance-menu-item.tsx b/app/src/app/analysis/resistance/resistance-menu-item.tsx index 852d94b8..d723e6d4 100644 --- a/app/src/app/analysis/resistance/resistance-menu-item.tsx +++ b/app/src/app/analysis/resistance/resistance-menu-item.tsx @@ -19,12 +19,13 @@ import { ResistanceTable } from "./resistance-table"; type Props = { selection: DataTableSelection; + disabled: boolean; }; export const ResistanceMenuItem = (props: Props) => { const { t } = useTranslation(); const toast = useToast(); - const { selection } = props; + const { selection, disabled } = props; const { isOpen, onOpen, onClose } = useDisclosure(); const onClickCallback = useCallback(() => { @@ -65,6 +66,7 @@ export const ResistanceMenuItem = (props: Props) => { title="Resistance" icon={} onClick={onClickCallback} + isDisabled={disabled} > Resistance diff --git a/app/src/app/workspaces/send-to-workspace-menu-item.tsx b/app/src/app/workspaces/send-to-workspace-menu-item.tsx index b9849448..2712510e 100644 --- a/app/src/app/workspaces/send-to-workspace-menu-item.tsx +++ b/app/src/app/workspaces/send-to-workspace-menu-item.tsx @@ -8,10 +8,11 @@ import { SendToWorkspaceModal } from "./send-to-workspace-modal"; type Props = { selection: DataTableSelection; + disabled: boolean; }; export const SendToWorkspaceMenuItem = (props: Props) => { - const { selection } = props; + const { selection, disabled } = props; const { isOpen, onOpen, onClose } = useDisclosure(); return ( @@ -24,6 +25,7 @@ export const SendToWorkspaceMenuItem = (props: Props) => { title="Send to Workspace" icon={} onClick={onOpen} + isDisabled={disabled} > Send to Workspace From 2b1d3a7037d542c99b853b9b17c4eb902168f988 Mon Sep 17 00:00:00 2001 From: "Kasper Fuglsang Christensen (Delegate)" Date: Tue, 24 Sep 2024 15:19:07 +0200 Subject: [PATCH 2/5] feat: add select all button --- app/src/app/analysis/analysis-page.tsx | 37 ++++++++------- .../analysis/analysis-selection-configs.ts | 46 ++++++++++++++----- .../app/analysis/analysis-selection-menu.tsx | 9 ++-- .../app/analysis/search/analysis-search.tsx | 4 +- 4 files changed, 63 insertions(+), 33 deletions(-) diff --git a/app/src/app/analysis/analysis-page.tsx b/app/src/app/analysis/analysis-page.tsx index 5bfa709f..355c05d0 100644 --- a/app/src/app/analysis/analysis-page.tsx +++ b/app/src/app/analysis/analysis-page.tsx @@ -102,18 +102,18 @@ export default function AnalysisPage() { () => Object.keys(columnConfigs || []).map( (k) => - ({ - accessor: k, - sortType: !k.startsWith("date") - ? "alphanumeric" - : (a, b, column) => { - const aDate = a.original[column]?.getTime() ?? 0; - const bDate = b.original[column]?.getTime() ?? 0; - - return aDate - bDate; - }, - Header: t(k), - } as Column) + ({ + accessor: k, + sortType: !k.startsWith("date") + ? "alphanumeric" + : (a, b, column) => { + const aDate = a.original[column]?.getTime() ?? 0; + const bDate = b.original[column]?.getTime() ?? 0; + + return aDate - bDate; + }, + Header: t(k), + } as Column) ), [columnConfigs, t] ); @@ -159,23 +159,26 @@ export default function AnalysisPage() { (s) => s.view.view ) as UserDefinedViewInternal; + const [lastSearchQuery, setLastSearchQuery] = useState({ expression: {} }); + const onSearch = React.useCallback( - (q: AnalysisQuery) => { + (q: AnalysisQuery, pageSize: number) => { dispatch({ type: "RESET/Analysis" }); + setLastSearchQuery(q); // if we got an empty expression, just request a page if (q.expression && Object.keys(q.expression).length === 0) { dispatch( requestAsync({ - ...requestPageOfAnalysis({ pageSize: 100 }, false), + ...requestPageOfAnalysis({ pageSize: pageSize }, false), }) ); } else { dispatch( requestAsync({ - ...searchPageOfAnalysis({ query: { ...q, page_size: 100 } }), + ...searchPageOfAnalysis({ query: { ...q, page_size: pageSize } }), queryKey: JSON.stringify(q), }) - ); + ) } }, [dispatch] @@ -625,6 +628,8 @@ export default function AnalysisPage() { selection={selection} isNarrowed={pageState.isNarrowed} data={filteredData} + search={onSearch} + lastSearchQuery={lastSearchQuery} /> {!pageState.isNarrowed ? ( diff --git a/app/src/app/analysis/analysis-selection-configs.ts b/app/src/app/analysis/analysis-selection-configs.ts index 8cfa24de..d55158bb 100644 --- a/app/src/app/analysis/analysis-selection-configs.ts +++ b/app/src/app/analysis/analysis-selection-configs.ts @@ -1,11 +1,17 @@ -import { createAction, createReducer } from "@reduxjs/toolkit"; +import { createAction, createReducer, createAsyncThunk } from "@reduxjs/toolkit"; import { AnalysisResult } from "sap-client"; import { DataTableSelection } from "./data-table/data-table"; +import { AnalysisQuery } from "sap-client" interface SelectionState { selection: DataTableSelection; } +type Search = { + searchFunc: (query: AnalysisQuery, pageSize: number) => void; + query: AnalysisQuery; +} + export const updateSelectionOriginal = createAction< Record >("analysis/updateSelectionOriginal"); @@ -16,7 +22,16 @@ export const setSelection = createAction>( export const clearSelection = createAction("analysis/clearSelection"); -export const selectAll = createAction("analysis/selectAll"); +export const selectAllThunk = createAsyncThunk('analysis/selectAllThunk', async (search: Search, thunkAPI) => { + search.searchFunc(search.query, 1000); + + while (!results || Object.keys(results).length === 0 || results.length === 0) { + await new Promise(resolve => setTimeout(resolve, 10)); + var results = (thunkAPI.getState() as any).entities.analysis; + } + + return results; +}) export const selectAllInView = createAction("analysis/selectAllInView"); @@ -55,17 +70,24 @@ export const selectionReducer = createReducer(initialState, (builder) => { .addCase(clearSelection, (state) => { state.selection = {} as DataTableSelection; }) - .addCase(selectAll, (state) => { - console.log("Du trykkede på selectAll knappen!"); - }) .addCase(selectAllInView, (state, action) => { - let mapped = action.payload.map(x => { - return ({ [x["sequence_id"]]: { original: x, cells: {} } } as DataTableSelection) - }); - let reduced = mapped.reduce((acc, cur) => { - return { ...acc, ...cur } - }, {} as DataTableSelection) + state.selection = action.payload + .map(x => { + return ({ [x["sequence_id"]]: { original: x, cells: {} } } as DataTableSelection) + }) + .reduce((acc, cur) => { + return { ...acc, ...cur } + }, {} as DataTableSelection) + }) + .addCase(selectAllThunk.fulfilled, (state, action) => { + let analysis = action.payload; - state.selection = reduced; + state.selection = Object.keys(analysis) + .map(x => { + return ({ [x]: { original: analysis[x], cells: {} } } as DataTableSelection); + }) + .reduce((acc, cur) => { + return { ...acc, ...cur } + }, {} as DataTableSelection); }); }); diff --git a/app/src/app/analysis/analysis-selection-menu.tsx b/app/src/app/analysis/analysis-selection-menu.tsx index 3dd5c61d..812b336c 100644 --- a/app/src/app/analysis/analysis-selection-menu.tsx +++ b/app/src/app/analysis/analysis-selection-menu.tsx @@ -5,18 +5,21 @@ import { ResistanceMenuItem } from "./resistance/resistance-menu-item"; import { NearestNeighborMenuItem } from "./nearest-neighbor/nearest-neighbor-menu-item"; import { Menu, MenuList, MenuButton, Button, MenuItem } from "@chakra-ui/react"; import { useCallback } from "react"; -import { clearSelection, selectAll, selectAllInView } from "./analysis-selection-configs"; +import { clearSelection, selectAllInView, selectAllThunk } from "./analysis-selection-configs"; import { useDispatch } from "react-redux"; import { SendToWorkspaceMenuItem } from "app/workspaces/send-to-workspace-menu-item"; +import { AnalysisQuery } from "sap-client"; type Props = { selection: DataTableSelection; isNarrowed: boolean; data: AnalysisResult[]; + search: (query: AnalysisQuery, pageSize: number) => void; + lastSearchQuery: AnalysisQuery }; export const AnalysisSelectionMenu = (props: Props) => { - const { selection, isNarrowed, data } = props; + const { selection, isNarrowed, data, search, lastSearchQuery } = props; const dispatch = useDispatch(); const onClear = useCallback(() => { @@ -28,7 +31,7 @@ export const AnalysisSelectionMenu = (props: Props) => { }, [dispatch, data]); const onSelectAll = useCallback(() => { - dispatch(selectAll()); + dispatch(selectAllThunk({searchFunc: search, query: lastSearchQuery})); }, [dispatch]); let disabled = isNarrowed || Object.keys(selection).length == 0; diff --git a/app/src/app/analysis/search/analysis-search.tsx b/app/src/app/analysis/search/analysis-search.tsx index abec0b78..558154eb 100644 --- a/app/src/app/analysis/search/analysis-search.tsx +++ b/app/src/app/analysis/search/analysis-search.tsx @@ -16,7 +16,7 @@ import { getFieldInternalName } from "app/i18n"; import SearchHelpModal from "./search-help-modal"; type AnalysisSearchProps = { - onSubmit: (query: AnalysisQuery) => void; + onSubmit: (query: AnalysisQuery, pageSize: number) => void; isDisabled: boolean; }; @@ -52,7 +52,7 @@ const AnalysisSearch = (props: AnalysisSearchProps) => { ]); const submitQuery = React.useCallback( - (q?: string) => onSubmit({ expression: parseQuery(q || input, toast) }), + (q?: string) => onSubmit({ expression: parseQuery(q || input, toast) }, 100), [onSubmit, input, toast] ); From 21f88d79fcbfeec69056533d0c49ffc5dc421e48 Mon Sep 17 00:00:00 2001 From: "Kasper Fuglsang Christensen (Delegate)" Date: Tue, 24 Sep 2024 15:20:08 +0200 Subject: [PATCH 3/5] fix: fix null-checks in middleware --- app/src/middleware/jwt-middleware.ts | 4 ++-- app/src/middleware/selection-middleware.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/middleware/jwt-middleware.ts b/app/src/middleware/jwt-middleware.ts index bd544bf0..df3262b3 100644 --- a/app/src/middleware/jwt-middleware.ts +++ b/app/src/middleware/jwt-middleware.ts @@ -31,8 +31,8 @@ export const jwtMiddleware = (store) => (next) => (action) => { // Let the action continue, but now with the JWT header. next(updatedAction); } else if ( - (action && action.type === REQUEST_FAILURE) || - action.type === MUTATE_FAILURE + action && (action.type === REQUEST_FAILURE || + action.type === MUTATE_FAILURE) ) { // If we failed a request, if it's due to a 401, redirect to signin if (action?.status === 401) { diff --git a/app/src/middleware/selection-middleware.ts b/app/src/middleware/selection-middleware.ts index cb235992..6771be54 100644 --- a/app/src/middleware/selection-middleware.ts +++ b/app/src/middleware/selection-middleware.ts @@ -4,7 +4,7 @@ import { updateSelectionOriginal } from "app/analysis/analysis-selection-configs const { MUTATE_SUCCESS, REQUEST_SUCCESS } = actionTypes; export const selectionMiddleware = (store) => (next) => (action) => { - if ( + if (action && action.type === MUTATE_SUCCESS && action.url.indexOf("/api/analysis/changes") !== -1 ) { @@ -13,6 +13,7 @@ export const selectionMiddleware = (store) => (next) => (action) => { } if ( + action && action.type === REQUEST_SUCCESS && action.url.indexOf("/api/analysis/by_id") !== -1 ) { From 4064248269429758b96a973c72c4fd6c4990bf7c Mon Sep 17 00:00:00 2001 From: Allan Hvam Date: Fri, 27 Sep 2024 10:45:42 +0200 Subject: [PATCH 4/5] fix: react key warning in tree method checkbox group --- app/src/app/workspaces/tree-method-checkbox-group.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/app/workspaces/tree-method-checkbox-group.tsx b/app/src/app/workspaces/tree-method-checkbox-group.tsx index bd9a4a38..fdef5b95 100644 --- a/app/src/app/workspaces/tree-method-checkbox-group.tsx +++ b/app/src/app/workspaces/tree-method-checkbox-group.tsx @@ -29,8 +29,12 @@ export const TreeMethodCheckboxGroup = (props: Props) => { return ( - {treeMethods?.map((treeMethod) => { - return {treeMethod}; + {treeMethods?.map((treeMethod, index) => { + return ( + + {treeMethod} + + ); })} From a15e7cc29300e3db364fdce127daaeb0ea17551a Mon Sep 17 00:00:00 2001 From: Allan Hvam Date: Fri, 27 Sep 2024 10:47:24 +0200 Subject: [PATCH 5/5] chore: lint --- .../app/analysis/analysis-selection-menu.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/app/analysis/analysis-selection-menu.tsx b/app/src/app/analysis/analysis-selection-menu.tsx index 812b336c..6b4004dc 100644 --- a/app/src/app/analysis/analysis-selection-menu.tsx +++ b/app/src/app/analysis/analysis-selection-menu.tsx @@ -5,7 +5,11 @@ import { ResistanceMenuItem } from "./resistance/resistance-menu-item"; import { NearestNeighborMenuItem } from "./nearest-neighbor/nearest-neighbor-menu-item"; import { Menu, MenuList, MenuButton, Button, MenuItem } from "@chakra-ui/react"; import { useCallback } from "react"; -import { clearSelection, selectAllInView, selectAllThunk } from "./analysis-selection-configs"; +import { + clearSelection, + selectAllInView, + selectAllThunk, +} from "./analysis-selection-configs"; import { useDispatch } from "react-redux"; import { SendToWorkspaceMenuItem } from "app/workspaces/send-to-workspace-menu-item"; import { AnalysisQuery } from "sap-client"; @@ -15,7 +19,7 @@ type Props = { isNarrowed: boolean; data: AnalysisResult[]; search: (query: AnalysisQuery, pageSize: number) => void; - lastSearchQuery: AnalysisQuery + lastSearchQuery: AnalysisQuery; }; export const AnalysisSelectionMenu = (props: Props) => { @@ -31,18 +35,15 @@ export const AnalysisSelectionMenu = (props: Props) => { }, [dispatch, data]); const onSelectAll = useCallback(() => { - dispatch(selectAllThunk({searchFunc: search, query: lastSearchQuery})); - }, [dispatch]); + dispatch(selectAllThunk({ searchFunc: search, query: lastSearchQuery })); + }, [dispatch, lastSearchQuery, search]); - let disabled = isNarrowed || Object.keys(selection).length == 0; + const disabled = isNarrowed || Object.keys(selection).length == 0; return (
- } - > + }> Selection