Merge branch '20526-sort-order' refs #20526
authorPeter Amstutz <peter.amstutz@curii.com>
Mon, 23 Oct 2023 15:39:12 +0000 (11:39 -0400)
committerPeter Amstutz <peter.amstutz@curii.com>
Mon, 23 Oct 2023 15:39:12 +0000 (11:39 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

src/index.tsx
src/store/context-menu/context-menu-actions.ts
src/views-components/context-menu/action-sets/process-resource-action-set.ts
src/views-components/context-menu/context-menu.tsx
src/views/all-processes-panel/all-processes-panel.tsx
src/views/process-panel/process-panel.tsx
src/views/subprocess-panel/subprocess-panel.tsx

index d2af0952867dee9b45debda3b57d7971f1b7781d..ccbc06309675be55293df7c12a6f8857cc3a0b08 100644 (file)
@@ -59,7 +59,9 @@ import { addRouteChangeHandlers } from "./routes/route-change-handlers";
 import { setTokenDialogApiHost } from "store/token-dialog/token-dialog-actions";
 import {
     processResourceActionSet,
+    runningProcessResourceActionSet,
     processResourceAdminActionSet,
+    runningProcessResourceAdminActionSet,
     readOnlyProcessResourceActionSet,
 } from "views-components/context-menu/action-sets/process-resource-action-set";
 import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
@@ -114,6 +116,7 @@ addMenuActionSet(ContextMenuKind.READONLY_COLLECTION, readOnlyCollectionActionSe
 addMenuActionSet(ContextMenuKind.OLD_VERSION_COLLECTION, oldCollectionVersionActionSet);
 addMenuActionSet(ContextMenuKind.TRASHED_COLLECTION, trashedCollectionActionSet);
 addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet);
+addMenuActionSet(ContextMenuKind.RUNNING_PROCESS_RESOURCE, runningProcessResourceActionSet);
 addMenuActionSet(ContextMenuKind.READONLY_PROCESS_RESOURCE, readOnlyProcessResourceActionSet);
 addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
 addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet);
@@ -127,6 +130,7 @@ addMenuActionSet(ContextMenuKind.GROUPS, groupActionSet);
 addMenuActionSet(ContextMenuKind.GROUP_MEMBER, groupMemberActionSet);
 addMenuActionSet(ContextMenuKind.COLLECTION_ADMIN, collectionAdminActionSet);
 addMenuActionSet(ContextMenuKind.PROCESS_ADMIN, processResourceAdminActionSet);
+addMenuActionSet(ContextMenuKind.RUNNING_PROCESS_ADMIN, runningProcessResourceAdminActionSet);
 addMenuActionSet(ContextMenuKind.PROJECT_ADMIN, projectAdminActionSet);
 addMenuActionSet(ContextMenuKind.FROZEN_PROJECT, frozenActionSet);
 addMenuActionSet(ContextMenuKind.FROZEN_PROJECT_ADMIN, frozenAdminActionSet);
index f846d25ba5089b8abfa859edfecd72d4ff07581f..464314877ff645328d838f2ddbbb1e4cd2a99ec7 100644 (file)
@@ -11,7 +11,7 @@ import { getResource, getResourceWithEditableStatus } from "../resources/resourc
 import { UserResource } from "models/user";
 import { isSidePanelTreeCategory } from "store/side-panel-tree/side-panel-tree-actions";
 import { extractUuidKind, ResourceKind, EditableResource, Resource } from "models/resource";
-import { Process } from "store/processes/process";
+import { Process, isProcessCancelable } from "store/processes/process";
 import { RepositoryResource } from "models/repositories";
 import { SshKeyResource } from "models/ssh-key";
 import { VirtualMachinesResource } from "models/virtual-machines";
@@ -23,6 +23,7 @@ import { GroupContentsResource } from "services/groups-service/groups-service";
 import { LinkResource } from "models/link";
 import { resourceIsFrozen } from "common/frozen-resources";
 import { ProjectResource } from "models/project";
+import { getProcess } from "store/processes/process";
 import { filterCollectionFilesBySelection } from "store/collection-panel/collection-panel-files/collection-panel-files-state";
 
 export const contextMenuActions = unionize({
@@ -214,7 +215,7 @@ export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>, pro
                 description: res.description,
                 outputUuid: res.outputUuid || "",
                 workflowUuid: res.properties.template_uuid || "",
-                menuKind: ContextMenuKind.PROCESS_RESOURCE,
+                menuKind: isProcessCancelable(process) ? ContextMenuKind.RUNNING_PROCESS_RESOURCE : ContextMenuKind.PROCESS_RESOURCE
             })
         );
     }
@@ -289,9 +290,13 @@ export const resourceUuidToContextMenuKind =
                     : ContextMenuKind.READONLY_COLLECTION;
             case ResourceKind.PROCESS:
                 return isAdminUser && isEditable
-                    ? ContextMenuKind.PROCESS_ADMIN
+                    ? resource && isProcessCancelable(getProcess(resource.uuid)(getState().resources) as Process)
+                        ? ContextMenuKind.RUNNING_PROCESS_ADMIN
+                        : ContextMenuKind.PROCESS_ADMIN
                     : readonly
                     ? ContextMenuKind.READONLY_PROCESS_RESOURCE
+                    : resource && isProcessCancelable(getProcess(resource.uuid)(getState().resources) as Process)
+                    ? ContextMenuKind.RUNNING_PROCESS_RESOURCE
                     : ContextMenuKind.PROCESS_RESOURCE;
             case ResourceKind.USER:
                 return ContextMenuKind.ROOT_PROJECT;
index 7d039b13d75d752c7095115f295d604151b7f1ad..64b90ff45c5d84a57b7eb832831b5bf0667dee45 100644 (file)
@@ -15,6 +15,7 @@ import {
     OutputIcon,
     AdvancedIcon,
     OpenIcon,
+    StopIcon,
 } from "components/icon/icon";
 import { favoritePanelActions } from "store/favorite-panel/favorite-panel-action";
 import { openMoveProcessDialog } from "store/processes/process-move-actions";
@@ -29,6 +30,7 @@ import { TogglePublicFavoriteAction } from "../actions/public-favorite-action";
 import { togglePublicFavorite } from "store/public-favorites/public-favorites-actions";
 import { publicFavoritePanelActions } from "store/public-favorites-panel/public-favorites-action";
 import { openInNewTabAction } from "store/open-in-new-tab/open-in-new-tab.actions";
+import { cancelRunningWorkflow } from "store/processes/processes-actions";
 
 export const readOnlyProcessResourceActionSet: ContextMenuActionSet = [
     [
@@ -114,6 +116,18 @@ export const processResourceActionSet: ContextMenuActionSet = [
     ],
 ];
 
+const runningProcessOnlyActionSet: ContextMenuActionSet = [
+    [
+        {
+            name: "CANCEL",
+            icon: StopIcon,
+            execute: (dispatch, resources) => {
+                dispatch<any>(cancelRunningWorkflow(resources[0].uuid));
+            },
+        },
+    ]
+];
+
 export const processResourceAdminActionSet: ContextMenuActionSet = [
     [
         ...processResourceActionSet.reduce((prev, next) => prev.concat(next), []),
@@ -128,3 +142,17 @@ export const processResourceAdminActionSet: ContextMenuActionSet = [
         },
     ],
 ];
+
+export const runningProcessResourceActionSet = [
+    [
+        ...processResourceActionSet.reduce((prev, next) => prev.concat(next), []),
+        ...runningProcessOnlyActionSet.reduce((prev, next) => prev.concat(next), []),
+    ],
+];
+
+export const runningProcessResourceAdminActionSet: ContextMenuActionSet = [
+    [
+        ...processResourceAdminActionSet.reduce((prev, next) => prev.concat(next), []),
+        ...runningProcessOnlyActionSet.reduce((prev, next) => prev.concat(next), []),
+    ],
+];
index 157d4a728f6b1f44027766410c2ebb7d9b6efffe..2a5cccc0a549231d2860ec93bb5beec9d5435c1e 100644 (file)
@@ -103,7 +103,9 @@ export enum ContextMenuKind {
     OLD_VERSION_COLLECTION = "OldVersionCollection",
     TRASHED_COLLECTION = "TrashedCollection",
     PROCESS = "Process",
+    RUNNING_PROCESS_ADMIN = "RunningProcessAdmin",
     PROCESS_ADMIN = "ProcessAdmin",
+    RUNNING_PROCESS_RESOURCE = "RunningProcessResource",
     PROCESS_RESOURCE = "ProcessResource",
     READONLY_PROCESS_RESOURCE = "ReadOnlyProcessResource",
     PROCESS_LOGS = "ProcessLogs",
index 4914da6233180bc04ac7b32405724d8057e5b4e5..ee53f99c3fe94e39c4fa2da096d8e7175d774223 100644 (file)
@@ -2,49 +2,49 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React from 'react';
-import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
+import React from "react";
+import { StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core";
 import { DataExplorer } from "views-components/data-explorer/data-explorer";
-import { connect, DispatchProp } from 'react-redux';
-import { DataColumns } from 'components/data-table/data-table';
-import { RouteComponentProps } from 'react-router';
-import { DataTableFilterItem } from 'components/data-table-filters/data-table-filters';
-import { SortDirection } from 'components/data-table/data-column';
-import { ResourceKind } from 'models/resource';
-import { ArvadosTheme } from 'common/custom-theme';
-import { ALL_PROCESSES_PANEL_ID } from 'store/all-processes-panel/all-processes-panel-action';
+import { connect, DispatchProp } from "react-redux";
+import { DataColumns } from "components/data-table/data-table";
+import { RouteComponentProps } from "react-router";
+import { DataTableFilterItem } from "components/data-table-filters/data-table-filters";
+import { SortDirection } from "components/data-table/data-column";
+import { ResourceKind } from "models/resource";
+import { ArvadosTheme } from "common/custom-theme";
+import { ALL_PROCESSES_PANEL_ID } from "store/all-processes-panel/all-processes-panel-action";
 import {
     ProcessStatus,
     ResourceName,
     ResourceOwnerWithName,
     ResourceType,
     ContainerRunTime,
-    ResourceCreatedAtDate
-} from 'views-components/data-explorer/renderers';
-import { ProcessIcon } from 'components/icon/icon';
-import { openProcessContextMenu } from 'store/context-menu/context-menu-actions';
-import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
-import { navigateTo } from 'store/navigation/navigation-action';
+    ResourceCreatedAtDate,
+} from "views-components/data-explorer/renderers";
+import { ProcessIcon } from "components/icon/icon";
+import { openProcessContextMenu } from "store/context-menu/context-menu-actions";
+import { loadDetailsPanel } from "store/details-panel/details-panel-action";
+import { navigateTo } from "store/navigation/navigation-action";
 import { ContainerRequestResource, ContainerRequestState } from "models/container-request";
-import { RootState } from 'store/store';
-import { createTree } from 'models/tree';
-import { getInitialProcessStatusFilters, getInitialProcessTypeFilters } from 'store/resource-type-filters/resource-type-filters';
-import { getProcess } from 'store/processes/process';
-import { ResourcesState } from 'store/resources/resources';
+import { RootState } from "store/store";
+import { createTree } from "models/tree";
+import { getInitialProcessStatusFilters, getInitialProcessTypeFilters } from "store/resource-type-filters/resource-type-filters";
+import { getProcess } from "store/processes/process";
+import { ResourcesState } from "store/resources/resources";
 
 type CssRules = "toolbar" | "button" | "root";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbar: {
         paddingBottom: theme.spacing.unit * 3,
-        textAlign: "right"
+        textAlign: "right",
     },
     button: {
-        marginLeft: theme.spacing.unit
+        marginLeft: theme.spacing.unit,
     },
     root: {
-        width: '100%',
-    }
+        width: "100%",
+    },
 });
 
 export enum AllProcessesPanelColumnNames {
@@ -53,7 +53,7 @@ export enum AllProcessesPanelColumnNames {
     TYPE = "Type",
     OWNER = "Owner",
     CREATED_AT = "Created at",
-    RUNTIME = "Run Time"
+    RUNTIME = "Run Time",
 }
 
 export interface AllProcessesPanelFilter extends DataTableFilterItem {
@@ -65,9 +65,9 @@ export const allProcessesPanelColumns: DataColumns<string, ContainerRequestResou
         name: AllProcessesPanelColumnNames.NAME,
         selected: true,
         configurable: true,
-        sort: {direction: SortDirection.NONE, field: "name"},
+        sort: { direction: SortDirection.NONE, field: "name" },
         filters: createTree(),
-        render: uuid => <ResourceName uuid={uuid} />
+        render: uuid => <ResourceName uuid={uuid} />,
     },
     {
         name: AllProcessesPanelColumnNames.STATUS,
@@ -75,37 +75,37 @@ export const allProcessesPanelColumns: DataColumns<string, ContainerRequestResou
         configurable: true,
         mutuallyExclusiveFilters: true,
         filters: getInitialProcessStatusFilters(),
-        render: uuid => <ProcessStatus uuid={uuid} />
+        render: uuid => <ProcessStatus uuid={uuid} />,
     },
     {
         name: AllProcessesPanelColumnNames.TYPE,
         selected: true,
         configurable: true,
         filters: getInitialProcessTypeFilters(),
-        render: uuid => <ResourceType uuid={uuid} />
+        render: uuid => <ResourceType uuid={uuid} />,
     },
     {
         name: AllProcessesPanelColumnNames.OWNER,
         selected: true,
         configurable: true,
         filters: createTree(),
-        render: uuid => <ResourceOwnerWithName uuid={uuid} />
+        render: uuid => <ResourceOwnerWithName uuid={uuid} />,
     },
     {
         name: AllProcessesPanelColumnNames.CREATED_AT,
         selected: true,
         configurable: true,
-        sort: {direction: SortDirection.DESC, field: "createdAt"},
+        sort: { direction: SortDirection.DESC, field: "createdAt" },
         filters: createTree(),
-        render: uuid => <ResourceCreatedAtDate uuid={uuid} />
+        render: uuid => <ResourceCreatedAtDate uuid={uuid} />,
     },
     {
         name: AllProcessesPanelColumnNames.RUNTIME,
         selected: true,
         configurable: true,
         filters: createTree(),
-        render: uuid => <ContainerRunTime uuid={uuid} />
-    }
+        render: uuid => <ContainerRunTime uuid={uuid} />,
+    },
 ];
 
 interface AllProcessesPanelDataProps {
@@ -117,12 +117,15 @@ interface AllProcessesPanelActionProps {
     onDialogOpen: (ownerUuid: string) => void;
     onItemDoubleClick: (item: string) => void;
 }
-const mapStateToProps = (state : RootState): AllProcessesPanelDataProps => ({
-    resources: state.resources
+const mapStateToProps = (state: RootState): AllProcessesPanelDataProps => ({
+    resources: state.resources,
 });
 
-type AllProcessesPanelProps = AllProcessesPanelDataProps & AllProcessesPanelActionProps & DispatchProp
-    & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
+type AllProcessesPanelProps = AllProcessesPanelDataProps &
+    AllProcessesPanelActionProps &
+    DispatchProp &
+    WithStyles<CssRules> &
+    RouteComponentProps<{ id: string }>;
 
 export const AllProcessesPanel = withStyles(styles)(
     connect(mapStateToProps)(
@@ -133,26 +136,30 @@ export const AllProcessesPanel = withStyles(styles)(
                     this.props.dispatch<any>(openProcessContextMenu(event, process));
                 }
                 this.props.dispatch<any>(loadDetailsPanel(resourceUuid));
-            }
+            };
 
             handleRowDoubleClick = (uuid: string) => {
                 this.props.dispatch<any>(navigateTo(uuid));
-            }
+            };
 
             handleRowClick = (uuid: string) => {
                 this.props.dispatch<any>(loadDetailsPanel(uuid));
-            }
+            };
 
             render() {
-                return <div className={this.props.classes.root}><DataExplorer
-                    id={ALL_PROCESSES_PANEL_ID}
-                    onRowClick={this.handleRowClick}
-                    onRowDoubleClick={this.handleRowDoubleClick}
-                    onContextMenu={this.handleContextMenu}
-                    contextMenuColumn={true}
-                    defaultViewIcon={ProcessIcon}
-                    defaultViewMessages={['Processes list empty.']} />
-                </div>
+                return (
+                    <div className={this.props.classes.root}>
+                        <DataExplorer
+                            id={ALL_PROCESSES_PANEL_ID}
+                            onRowClick={this.handleRowClick}
+                            onRowDoubleClick={this.handleRowDoubleClick}
+                            onContextMenu={this.handleContextMenu}
+                            contextMenuColumn={true}
+                            defaultViewIcon={ProcessIcon}
+                            defaultViewMessages={["Processes list empty."]}
+                        />
+                    </div>
+                );
             }
         }
     )
index f9e02540eb92a62b931c34798faace31aa9fb843..4a6b5fd33344600e1a5e6af1d71e4ecbd09b0a29 100644 (file)
@@ -51,7 +51,9 @@ const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps =>
         );
     },
     onContextMenu: (event, process) => {
-        dispatch<any>(openProcessContextMenu(event, process));
+        if (process) {
+            dispatch<any>(openProcessContextMenu(event, process));
+        }
     },
     onToggle: status => {
         dispatch<any>(toggleProcessPanelFilter(status));
index c46a1c52e26125be63a7c8fd50b630477eac01d0..0aa02d52701824b52d4f9611ebf2d5b49beee131 100644 (file)
@@ -4,8 +4,8 @@
 
 import { Dispatch } from "redux";
 import { connect } from "react-redux";
-import { openProcessContextMenu } from 'store/context-menu/context-menu-actions';
-import { SubprocessPanelRoot, SubprocessPanelActionProps, SubprocessPanelDataProps } from 'views/subprocess-panel/subprocess-panel-root';
+import { openProcessContextMenu } from "store/context-menu/context-menu-actions";
+import { SubprocessPanelRoot, SubprocessPanelActionProps, SubprocessPanelDataProps } from "views/subprocess-panel/subprocess-panel-root";
 import { RootState } from "store/store";
 import { navigateTo } from "store/navigation/navigation-action";
 import { loadDetailsPanel } from "store/details-panel/details-panel-action";
@@ -23,11 +23,11 @@ const mapDispatchToProps = (dispatch: Dispatch): SubprocessPanelActionProps => (
     },
     onItemDoubleClick: uuid => {
         dispatch<any>(navigateTo(uuid));
-    }
+    },
 });
 
 const mapStateToProps = (state: RootState): SubprocessPanelDataProps => ({
-    resources: state.resources
+    resources: state.resources,
 });
 
-export const SubprocessPanel = connect(mapStateToProps, mapDispatchToProps)(SubprocessPanelRoot);
\ No newline at end of file
+export const SubprocessPanel = connect(mapStateToProps, mapDispatchToProps)(SubprocessPanelRoot);