From: Pawel Kowalczyk Date: Tue, 4 Sep 2018 10:17:22 +0000 (+0200) Subject: 13860-process-statuses-filters X-Git-Tag: 1.3.0~119^2~5 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/5cc899fc13465c57f16adf69a593a4354e6e0eb3 13860-process-statuses-filters Feature #13860 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- diff --git a/src/common/custom-theme.ts b/src/common/custom-theme.ts index c4d3dbc3..20790100 100644 --- a/src/common/custom-theme.ts +++ b/src/common/custom-theme.ts @@ -27,6 +27,7 @@ interface Colors { red900: string; blue500: string; grey500: string; + grey700: string; } const red900 = red["900"]; @@ -47,6 +48,7 @@ export const themeOptions: ArvadosThemeOptions = { red900: red['900'], blue500: blue['500'], grey500, + grey700 } }, overrides: { diff --git a/src/components/subprocess-filter/subprocess-filter.tsx b/src/components/subprocess-filter/subprocess-filter.tsx index 58c33ee5..f1366858 100644 --- a/src/components/subprocess-filter/subprocess-filter.tsx +++ b/src/components/subprocess-filter/subprocess-filter.tsx @@ -11,18 +11,23 @@ type CssRules = 'grid' | 'label' | 'value' | 'switch'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ grid: { - display: 'flex' + display: 'flex', + height: '20px', + paddingTop: '0px!important', + paddingBottom: '0px!important', + marginBottom: theme.spacing.unit }, label: { width: '86px', color: theme.palette.grey["500"], - textAlign: 'right' + textAlign: 'right', }, value: { width: '24px', - paddingLeft: theme.spacing.unit + paddingLeft: theme.spacing.unit, }, switch: { + height: '20px', '& span:first-child': { height: '18px' } diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 6901d875..30040e6d 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -9,7 +9,7 @@ import { ResourceKind, RESOURCE_UUID_PATTERN, extractUuidKind } from '~/models/r import { getProjectUrl } from '../models/project'; import { getCollectionUrl } from '~/models/collection'; import { loadProject, loadFavorites, loadCollection, loadProcessLog } from '~/store/workbench/workbench-actions'; -import { loadProcess } from '~/store/processes/processes-actions'; +import { loadProcessPanel } from '~/store/process-panel/process-panel-actions'; export const Routes = { ROOT: '/', @@ -79,7 +79,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { } else if (favoriteMatch) { store.dispatch(loadFavorites()); } else if (processMatch) { - store.dispatch(loadProcess(processMatch.params.id)); + store.dispatch(loadProcessPanel(processMatch.params.id)); } else if (processLogMatch) { store.dispatch(loadProcessLog(processLogMatch.params.id)); } diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index 3440a305..e6f09542 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -12,7 +12,6 @@ import { ProjectResource } from '~/models/project'; import { UserResource } from '../../models/user'; import { isSidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions'; import { extractUuidKind, ResourceKind } from '~/models/resource'; -import { matchProcessRoute } from '~/routes/routes'; export const contextMenuActions = unionize({ OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(), diff --git a/src/store/process-logs-panel/process-logs-panel-reducer.ts b/src/store/process-logs-panel/process-logs-panel-reducer.ts index 38a31364..c7d694c0 100644 --- a/src/store/process-logs-panel/process-logs-panel-reducer.ts +++ b/src/store/process-logs-panel/process-logs-panel-reducer.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0 import { ProcessLogsPanel } from './process-logs-panel'; -import { RootState } from '~/store/store'; import { ProcessLogsPanelAction, processLogsPanelActions } from './process-logs-panel-actions'; const initialState: ProcessLogsPanel = { diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts new file mode 100644 index 00000000..902a5c9c --- /dev/null +++ b/src/store/process-panel/process-panel-actions.ts @@ -0,0 +1,29 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { unionize, ofType, UnionOf } from "~/common/unionize"; +import { loadProcess } from '~/store/processes/processes-actions'; +import { Dispatch } from 'redux'; + +export const procesPanelActions = unionize({ + INIT_PROCESS_PANEL_FILTERS: ofType(), + TOGGLE_PROCESS_PANEL_FILTER: ofType(), +}); + +export type ProcessPanelAction = UnionOf; + +export const toggleProcessPanelFilter = procesPanelActions.TOGGLE_PROCESS_PANEL_FILTER; + +export const loadProcessPanel = (uuid: string) => + (dispatch: Dispatch) => { + dispatch(loadProcess(uuid)); + dispatch(initProcessPanelFilters); + }; + +export const initProcessPanelFilters = procesPanelActions.INIT_PROCESS_PANEL_FILTERS([ + 'Queued', + 'Complete', + 'Active', + 'Failed' +]); diff --git a/src/store/process-panel/process-panel-reducer.ts b/src/store/process-panel/process-panel-reducer.ts new file mode 100644 index 00000000..487b092b --- /dev/null +++ b/src/store/process-panel/process-panel-reducer.ts @@ -0,0 +1,24 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ProcessPanel } from '~/store/process-panel/process-panel'; +import { ProcessPanelAction, procesPanelActions } from '~/store/process-panel/process-panel-actions'; + +const initialState: ProcessPanel = { + filters: {} +}; + +export const processPanelReducer = (state = initialState, action: ProcessPanelAction): ProcessPanel => + procesPanelActions.match(action, { + INIT_PROCESS_PANEL_FILTERS: statuses => { + const filters = statuses.reduce((filters, status) => ({ ...filters, [status]: true }), {}); + return { filters }; + }, + TOGGLE_PROCESS_PANEL_FILTER: status => { + const filters = { ...state.filters, [status]: !state.filters[status] }; + return { filters }; + }, + default: () => state, + }); + \ No newline at end of file diff --git a/src/store/process-panel/process-panel.ts b/src/store/process-panel/process-panel.ts new file mode 100644 index 00000000..b521a672 --- /dev/null +++ b/src/store/process-panel/process-panel.ts @@ -0,0 +1,7 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +export interface ProcessPanel { + filters: { [status: string]: boolean }; +} diff --git a/src/store/processes/process.ts b/src/store/processes/process.ts index 467fc8a9..7253d9fc 100644 --- a/src/store/processes/process.ts +++ b/src/store/processes/process.ts @@ -8,14 +8,22 @@ import { ResourcesState, getResource } from '~/store/resources/resources'; import { filterResources } from '../resources/resources'; import { ResourceKind, Resource } from '~/models/resource'; import { getTimeDiff } from '~/common/formatters'; -import { SubprocessesStatus } from '~/views/process-panel/process-subprocesses-card'; import { ArvadosTheme } from '~/common/custom-theme'; +import { groupBy } from 'lodash'; export interface Process { containerRequest: ContainerRequestResource; container?: ContainerResource; } +enum ProcessStatus { + ACTIVE = 'Active', + COMPLETED = 'Complete', + QUEUED = 'Queued', + FAILED = 'Failed', + CANCELED = 'Canceled' +} + export const getProcess = (uuid: string) => (resources: ResourcesState): Process | undefined => { const containerRequest = getResource(uuid)(resources); if (containerRequest) { @@ -51,15 +59,15 @@ export const getProcessRuntime = ({ container }: Process) => export const getProcessStatusColor = (status: string, { customs }: ArvadosTheme) => { switch (status) { - case SubprocessesStatus.COMPLETED: + case ProcessStatus.COMPLETED: return customs.colors.green700; - case SubprocessesStatus.CANCELED: + case ProcessStatus.CANCELED: return customs.colors.red900; - case SubprocessesStatus.QUEUED: + case ProcessStatus.QUEUED: return customs.colors.grey500; - case SubprocessesStatus.FAILED: + case ProcessStatus.FAILED: return customs.colors.red900; - case SubprocessesStatus.ACTIVE: + case ProcessStatus.ACTIVE: return customs.colors.blue500; default: return customs.colors.grey500; @@ -74,4 +82,3 @@ export const getProcessStatus = (process: Process) => const isSubprocess = (containerUuid: string) => (resource: Resource) => resource.kind === ResourceKind.CONTAINER_REQUEST && (resource as ContainerRequestResource).requestingContainerUuid === containerUuid; - diff --git a/src/store/store.ts b/src/store/store.ts index d0c0dd67..63396fb9 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -29,6 +29,7 @@ import { propertiesReducer } from './properties/properties-reducer'; import { RootState } from './store'; import { fileUploaderReducer } from './file-uploader/file-uploader-reducer'; import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer'; +import { processPanelReducer } from '~/store/process-panel/process-panel-reducer'; const composeEnhancers = (process.env.NODE_ENV === 'development' && @@ -76,4 +77,5 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({ snackbar: snackbarReducer, treePicker: treePickerReducer, fileUploader: fileUploaderReducer, + processPanel: processPanelReducer }); diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx index 4aa51d8e..9db16bc1 100644 --- a/src/views/process-panel/process-panel-root.tsx +++ b/src/views/process-panel/process-panel-root.tsx @@ -7,19 +7,20 @@ import { Grid } from '@material-ui/core'; import { ProcessInformationCard } from './process-information-card'; import { DefaultView } from '~/components/default-view/default-view'; import { ProcessIcon } from '~/components/icon/icon'; -import { Process, getProcessStatus } from '~/store/processes/process'; +import { Process } from '~/store/processes/process'; import { SubprocessesCard } from './subprocesses-card'; -import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter'; -import { groupBy } from 'lodash'; import { ProcessSubprocesses } from '~/views/process-panel/process-subprocesses'; +import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter'; export interface ProcessPanelRootDataProps { process?: Process; subprocesses: Array; + filters: Array; } export interface ProcessPanelRootActionProps { onContextMenu: (event: React.MouseEvent) => void; + onToggle: (status: string) => void; } export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps; @@ -31,13 +32,13 @@ export const ProcessPanelRoot = (props: ProcessPanelRootProps) => - {console.log(props.subprocesses)} { return; }} /> + filters={props.filters} + onToggle={props.onToggle} + /> messages={['Process not found']} /> ; -const groupSubprocessesByStatus = (processes: Process[]) => - groupBy(processes, getProcessStatus); - -const mapGroupedProcessesToFilters = (groupedProcesses: { [status: string]: Process[] }): SubprocessFilterDataProps[] => - Object - .keys(groupedProcesses) - .map(status => ({ - label: status, - key: status, - value: groupedProcesses[status].length - })); diff --git a/src/views/process-panel/process-panel.tsx b/src/views/process-panel/process-panel.tsx index 9f1d0adb..e9bba3ad 100644 --- a/src/views/process-panel/process-panel.tsx +++ b/src/views/process-panel/process-panel.tsx @@ -2,29 +2,48 @@ // // SPDX-License-Identifier: AGPL-3.0 -import * as React from 'react'; import { RootState } from '~/store/store'; import { connect } from 'react-redux'; -import { getProcess, getSubprocesses } from '~/store/processes/process'; +import { getProcess, getSubprocesses, Process, getProcessStatus } from '~/store/processes/process'; import { Dispatch } from 'redux'; import { openProcessContextMenu } from '~/store/context-menu/context-menu-actions'; import { matchProcessRoute } from '~/routes/routes'; import { ProcessPanelRootDataProps, ProcessPanelRootActionProps, ProcessPanelRoot } from './process-panel-root'; +import { ProcessPanel as ProcessPanelState} from '~/store/process-panel/process-panel'; +import { groupBy } from 'lodash'; +import { toggleProcessPanelFilter } from '~/store/process-panel/process-panel-actions'; -const mapStateToProps = ({ router, resources }: RootState): ProcessPanelRootDataProps => { +const mapStateToProps = ({ router, resources, processPanel }: RootState): ProcessPanelRootDataProps => { const pathname = router.location ? router.location.pathname : ''; const match = matchProcessRoute(pathname); const uuid = match ? match.params.id : ''; + const subprocesses = getSubprocesses(uuid)(resources); return { process: getProcess(uuid)(resources), - subprocesses: getSubprocesses(uuid)(resources) + subprocesses: subprocesses.filter(subprocess => processPanel.filters[getProcessStatus(subprocess)]), + filters: getFilters(processPanel, subprocesses) }; }; const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({ - onContextMenu: (event: React.MouseEvent) => { + onContextMenu: event => { dispatch(openProcessContextMenu(event)); + }, + onToggle: status => { + dispatch(toggleProcessPanelFilter(status)); } }); export const ProcessPanel = connect(mapStateToProps, mapDispatchToProps)(ProcessPanelRoot); + +export const getFilters = (processPanel: ProcessPanelState, processes: Process[]) => { + const grouppedProcesses = groupBy(processes, getProcessStatus); + return Object + .keys(processPanel.filters) + .map(filter => ({ + label: filter, + value: (grouppedProcesses[filter] || []).length, + checked: processPanel.filters[filter], + key: filter, + })); + }; \ No newline at end of file diff --git a/src/views/process-panel/process-subprocesses-card.tsx b/src/views/process-panel/process-subprocesses-card.tsx index 57c127a0..8f6ccbc2 100644 --- a/src/views/process-panel/process-subprocesses-card.tsx +++ b/src/views/process-panel/process-subprocesses-card.tsx @@ -58,14 +58,6 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ }, }); -export enum SubprocessesStatus { - ACTIVE = 'Active', - COMPLETED = 'Completed', - QUEUED = 'Queued', - FAILED = 'Failed', - CANCELED = 'Canceled' -} - export interface SubprocessItemProps { title: string; status: string; diff --git a/src/views/process-panel/process-subprocesses.tsx b/src/views/process-panel/process-subprocesses.tsx index cfd517cf..5ee5938e 100644 --- a/src/views/process-panel/process-subprocesses.tsx +++ b/src/views/process-panel/process-subprocesses.tsx @@ -16,7 +16,7 @@ export const ProcessSubprocesses = ({ onContextMenu, subprocesses }: ProcessSubp return {subprocesses.map(subprocess => - + )} ; diff --git a/src/views/process-panel/subprocesses-card.tsx b/src/views/process-panel/subprocesses-card.tsx index 0ff62851..8033222c 100644 --- a/src/views/process-panel/subprocesses-card.tsx +++ b/src/views/process-panel/subprocesses-card.tsx @@ -4,40 +4,51 @@ import * as React from 'react'; import { ArvadosTheme } from '~/common/custom-theme'; -import { StyleRulesCallback, withStyles, WithStyles, Card, CardHeader, CardContent, Grid, Switch } from '@material-ui/core'; +import { StyleRulesCallback, withStyles, WithStyles, Card, CardHeader, CardContent, Grid, Switch, Typography } from '@material-ui/core'; import { SubprocessFilter } from '~/components/subprocess-filter/subprocess-filter'; import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter'; import { Process } from '~/store/processes/process'; -type CssRules = 'root'; +type CssRules = 'root' | 'subtitle' | 'title'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { fontSize: '0.875rem' + }, + subtitle: { + paddingBottom: '28px!important' + }, + title: { + color: theme.customs.colors.grey700 } }); interface SubprocessesDataProps { subprocesses: Array; filters: SubprocessFilterDataProps[]; - onToggle: (filter: SubprocessFilterDataProps) => void; + onToggle: (status: string) => void; } type SubprocessesProps = SubprocessesDataProps & WithStyles; export const SubprocessesCard = withStyles(styles)( - ({ classes, filters, subprocesses, onToggle }: SubprocessesProps) => + ({ classes, filters, subprocesses, onToggle }: SubprocessesProps) => - + + Subprocess and filters + } /> - - + + { - filters.map(filter => - onToggle(filter)} /> + filters.map(filter => + onToggle(filter.label)} /> ) }