Merge branch 'master' into 14137-new-button
authorJanicki Artur <artur.janicki@contractors.roche.com>
Thu, 13 Sep 2018 08:39:21 +0000 (10:39 +0200)
committerJanicki Artur <artur.janicki@contractors.roche.com>
Thu, 13 Sep 2018 08:39:21 +0000 (10:39 +0200)
refs #14137

Arvados-DCO-1.1-Signed-off-by: Janicki Artur <artur.janicki@contractors.roche.com>

18 files changed:
src/components/code-snippet/code-snippet.tsx
src/components/default-code-snippet/default-code-snippet.tsx
src/services/api/filter-builder.test.ts
src/services/api/filter-builder.ts
src/store/context-menu/context-menu-actions.ts
src/store/processes/process-command-actions.ts [new file with mode: 0644]
src/store/trash-panel/trash-panel-middleware-service.ts
src/views-components/context-menu/action-sets/process-action-set.ts
src/views-components/process-command-dialog/process-command-dialog.tsx [new file with mode: 0644]
src/views/process-log-panel/process-log-code-snippet.tsx
src/views/process-log-panel/process-log-main-card.tsx
src/views/process-log-panel/process-log-panel-root.tsx
src/views/process-log-panel/process-log-panel.tsx
src/views/process-panel/process-panel-root.tsx
src/views/process-panel/process-panel.tsx
src/views/process-panel/process-subprocesses.tsx
src/views/trash-panel/trash-panel.tsx
src/views/workbench/workbench.tsx

index eb0e709a9b0dfa572860e15b796b436e065b41e3..6cba299f1580a70d8076afc98606af1479ab5fb1 100644 (file)
@@ -5,15 +5,13 @@
 import * as React from 'react';
 import { StyleRulesCallback, WithStyles, Typography, withStyles, Theme } from '@material-ui/core';
 import { ArvadosTheme } from '~/common/custom-theme';
+import * as classNames from 'classnames';
 
 type CssRules = 'root';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
         boxSizing: 'border-box',
-        width: '100%',
-        height: 'auto',
-        maxHeight: '550px',
         overflow: 'auto',
         padding: theme.spacing.unit
     }
@@ -21,13 +19,16 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 
 export interface CodeSnippetDataProps {
     lines: string[];
+    className?: string;
 }
 
 type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
 
 export const CodeSnippet = withStyles(styles)(
-    ({ classes, lines }: CodeSnippetProps) =>
-        <Typography component="div" className={classes.root}>
+    ({ classes, lines, className }: CodeSnippetProps) =>
+        <Typography 
+        component="div" 
+        className={classNames(classes.root, className)}>
             {
                 lines.map((line: string, index: number) => {
                     return <Typography key={index} component="pre">{line}</Typography>;
index b8c0a7be93ba96acebca6e77f4c973859b79876d..e8b89f321ca809302bcbdab1f09f4c13d09f6d38 100644 (file)
@@ -23,9 +23,7 @@ const theme = createMuiTheme({
     }
 });
 
-type DefaultCodeSnippet = CodeSnippetDataProps;
-
-export const DefaultCodeSnippet = (props: DefaultCodeSnippet) => 
+export const DefaultCodeSnippet = (props: CodeSnippetDataProps) => 
     <MuiThemeProvider theme={theme}>
-        <CodeSnippet lines={props.lines} />
+        <CodeSnippet {...props} />
     </MuiThemeProvider>;
\ No newline at end of file
index de2ba4cba65bbd008f581d20c4db5c16b1cbdc05..5f646de5f72911af6708ae78b80e2155ffe8a1a1 100644 (file)
@@ -12,12 +12,18 @@ describe("FilterBuilder", () => {
         filters = new FilterBuilder();
     });
 
-    it("should add 'equal' rule", () => {
+    it("should add 'equal' rule (string)", () => {
         expect(
             filters.addEqual("etag", "etagValue").getFilters()
         ).toEqual(`["etag","=","etagValue"]`);
     });
 
+    it("should add 'equal' rule (boolean)", () => {
+        expect(
+            filters.addEqual("is_trashed", true).getFilters()
+        ).toEqual(`["is_trashed","=",true]`);
+    });
+
     it("should add 'like' rule", () => {
         expect(
             filters.addLike("etag", "etagValue").getFilters()
index b5558dbb16a291f8ecb81fdbd642f742bb99b9d3..06a040e3cc373f8206c83783c90e8cdf010d095a 100644 (file)
@@ -11,7 +11,7 @@ export function joinFilters(filters0?: string, filters1?: string) {
 export class FilterBuilder {
     constructor(private filters = "") { }
 
-    public addEqual(field: string, value?: string, resourcePrefix?: string) {
+    public addEqual(field: string, value?: string | boolean, resourcePrefix?: string) {
         return this.addCondition(field, "=", value, "", "", resourcePrefix );
     }
 
@@ -35,11 +35,15 @@ export class FilterBuilder {
         return this.filters;
     }
 
-    private addCondition(field: string, cond: string, value?: string | string[], prefix: string = "", postfix: string = "", resourcePrefix?: string) {
+    private addCondition(field: string, cond: string, value?: string | string[] | boolean, prefix: string = "", postfix: string = "", resourcePrefix?: string) {
         if (value) {
-            value = typeof value === "string"
-                ? `"${prefix}${value}${postfix}"`
-                : `["${value.join(`","`)}"]`;
+            if (typeof value === "string") {
+                value = `"${prefix}${value}${postfix}"`;
+            } else if (Array.isArray(value)) {
+                value = `["${value.join(`","`)}"]`;
+            } else {
+                value = value ? "true" : "false";
+            }
 
             const resPrefix = resourcePrefix
                 ? _.snakeCase(resourcePrefix) + "."
index eabf41ae715ad715e6072ca46383c0499461d170..d85059d6e1e25a82dfb20c051a55c1c63ffcab0d 100644 (file)
@@ -13,6 +13,7 @@ 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';
+import { Process } from '~/store/processes/process';
 
 export const contextMenuActions = unionize({
     OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
@@ -84,14 +85,10 @@ export const openSidePanelContextMenu = (event: React.MouseEvent<HTMLElement>, i
         }
     };
 
-export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>) =>
+export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>, process: Process) =>
     (dispatch: Dispatch, getState: () => RootState) => {
-        const { location } = getState().router;
-        const pathname = location ? location.pathname : '';
-        const match = matchProcessRoute(pathname);
-        const uuid = match ? match.params.id : '';
         const resource = {
-            uuid,
+            uuid: process.containerRequest.uuid,
             ownerUuid: '',
             kind: ResourceKind.PROCESS,
             name: '',
diff --git a/src/store/processes/process-command-actions.ts b/src/store/processes/process-command-actions.ts
new file mode 100644 (file)
index 0000000..de55a2c
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { dialogActions } from '~/store/dialog/dialog-actions';
+import { RootState } from '../store';
+import { Dispatch } from 'redux';
+import { getProcess } from '~/store/processes/process';
+
+export const PROCESS_COMMAND_DIALOG_NAME = 'processCommandDialog';
+
+export interface ProcessCommandDialogData {
+    command: string;
+    processName: string;
+}
+
+export const openProcessCommandDialog = (processUuid: string) =>
+    (dispatch: Dispatch<any>, getState: () => RootState) => {
+        const process = getProcess(processUuid)(getState().resources);
+        if (process) {
+            const data: ProcessCommandDialogData = {
+                command: process.containerRequest.command.join(' '),
+                processName: process.containerRequest.name,
+            };
+            dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_COMMAND_DIALOG_NAME, data }));
+        }
+    };
index 19ed3be13194982a97527417adb3b4eff8438389..6e8fa542478766368968e0912fc636da5f4c9b6b 100644 (file)
@@ -19,7 +19,6 @@ import { TrashPanelColumnNames, TrashPanelFilter } from "~/views/trash-panel/tra
 import { ProjectResource } from "~/models/project";
 import { ProjectPanelColumnNames } from "~/views/project-panel/project-panel";
 import { updateFavorites } from "~/store/favorites/favorites-actions";
-import { TrashableResource } from "~/models/resource";
 import { snackbarActions } from "~/store/snackbar/snackbar-actions";
 import { updateResources } from "~/store/resources/resources-actions";
 
@@ -57,14 +56,13 @@ export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
                         .addIsA("uuid", typeFilters.map(f => f.type))
                         .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION)
                         .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT)
+                        .addEqual("is_trashed", true)
                         .getFilters(),
                     recursive: true,
                     includeTrash: true
                 });
 
-            const items = listResults.items
-                .filter(it => (it as TrashableResource).isTrashed)
-                .map(it => it.uuid);
+            const items = listResults.items.map(it => it.uuid);
 
             api.dispatch(trashPanelActions.SET_ITEMS({
                 ...listResultsToDataExplorerItemsMeta(listResults),
index 1a291dfd506d11b9cb358bb5d7ce732542d3dabd..107f1828c609f1d25f65073c4fbdba581282740e 100644 (file)
@@ -14,6 +14,7 @@ import { navigateToProcessLogs } from '~/store/navigation/navigation-action';
 import { openMoveProcessDialog } from '~/store/processes/process-move-actions';
 import { openProcessUpdateDialog } from "~/store/processes/process-update-actions";
 import { openCopyProcessDialog } from '~/store/processes/process-copy-actions';
+import { openProcessCommandDialog } from '../../../store/processes/process-command-actions';
 
 export const processActionSet: ContextMenuActionSet = [[
     {
@@ -71,7 +72,7 @@ export const processActionSet: ContextMenuActionSet = [[
         icon: CommandIcon,
         name: "Command",
         execute: (dispatch, resource) => {
-            // add code
+            dispatch<any>(openProcessCommandDialog(resource.uuid));
         }
     },
     {
diff --git a/src/views-components/process-command-dialog/process-command-dialog.tsx b/src/views-components/process-command-dialog/process-command-dialog.tsx
new file mode 100644 (file)
index 0000000..4bde68d
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dialog, DialogTitle, DialogActions, Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
+import { withDialog } from "~/store/dialog/with-dialog";
+import { PROCESS_COMMAND_DIALOG_NAME } from '~/store/processes/process-command-actions';
+import { WithDialogProps } from '~/store/dialog/with-dialog';
+import { ProcessCommandDialogData } from '~/store/processes/process-command-actions';
+import { DefaultCodeSnippet } from "~/components/default-code-snippet/default-code-snippet";
+import { compose } from 'redux';
+
+type CssRules = 'codeSnippet';
+
+const styles: StyleRulesCallback<CssRules> = theme => ({
+    codeSnippet: {
+        marginLeft: theme.spacing.unit * 3,
+        marginRight: theme.spacing.unit * 3,
+    }
+});
+
+export const ProcessCommandDialog = compose(
+    withDialog(PROCESS_COMMAND_DIALOG_NAME),
+    withStyles(styles),
+)(
+    (props: WithDialogProps<ProcessCommandDialogData> & WithStyles<CssRules>) =>
+        <Dialog
+            open={props.open}
+            maxWidth="md"
+            onClose={props.closeDialog}
+            style={{ alignSelf: 'stretch' }}>
+            <DialogTitle>{`Command - ${props.data.processName}`}</DialogTitle>
+            <DefaultCodeSnippet
+                className={props.classes.codeSnippet}
+                lines={[props.data.command]} />
+            <DialogActions>
+                <Button
+                    variant='flat'
+                    color='primary'
+                    onClick={props.closeDialog}>
+                    Close
+                </Button>
+            </DialogActions>
+        </Dialog>
+);
\ No newline at end of file
index ff6320eeaf03f76c711044b3bfc819a0c5c6e837..d02fc02ce1431c74a2aefdaf4137991d0552ba61 100644 (file)
@@ -3,10 +3,18 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
-import { CodeSnippet, CodeSnippetDataProps } from '~/components/code-snippet/code-snippet';
+import { MuiThemeProvider, createMuiTheme, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
+import { CodeSnippet } from '~/components/code-snippet/code-snippet';
 import grey from '@material-ui/core/colors/grey';
 
+type CssRules = 'codeSnippet';
+
+const styles: StyleRulesCallback<CssRules> = () => ({
+    codeSnippet: {
+        maxHeight: '550px',
+    }
+});
+
 const theme = createMuiTheme({
     overrides: {
         MuiTypography: {
@@ -23,9 +31,12 @@ const theme = createMuiTheme({
     }
 });
 
-type ProcessLogCodeSnippet = CodeSnippetDataProps;
+interface ProcessLogCodeSnippetProps {
+    lines: string[];
+}
 
-export const ProcessLogCodeSnippet = (props: ProcessLogCodeSnippet) => 
-    <MuiThemeProvider theme={theme}>
-        <CodeSnippet lines={props.lines} />
-    </MuiThemeProvider>;
\ No newline at end of file
+export const ProcessLogCodeSnippet = withStyles(styles)(
+    (props: ProcessLogCodeSnippetProps & WithStyles<CssRules>) =>
+        <MuiThemeProvider theme={theme}>
+            <CodeSnippet lines={props.lines} className={props.classes.codeSnippet} />
+        </MuiThemeProvider>);
\ No newline at end of file
index 66811f47b89209191c7de02d329efa4d2259da49..626568d179e2e6a4428f52db8952bcd5d95f1483 100644 (file)
@@ -53,10 +53,18 @@ interface ProcessLogMainCardDataProps {
     process: Process;
 }
 
-export type ProcessLogMainCardProps = ProcessLogMainCardDataProps & CodeSnippetDataProps & ProcessLogFormDataProps & ProcessLogFormActionProps;
+export interface ProcessLogMainCardActionProps {
+    onContextMenu: (event: React.MouseEvent<any>, process: Process) => void;
+}
+
+export type ProcessLogMainCardProps = ProcessLogMainCardDataProps
+    & ProcessLogMainCardActionProps
+    & CodeSnippetDataProps
+    & ProcessLogFormDataProps
+    & ProcessLogFormActionProps;
 
 export const ProcessLogMainCard = withStyles(styles)(
-    ({ classes, process, selectedFilter, filters, onChange, lines }: ProcessLogMainCardProps & WithStyles<CssRules>) =>
+    ({ classes, process, selectedFilter, filters, onChange, lines, onContextMenu }: ProcessLogMainCardProps & WithStyles<CssRules>) =>
         <Grid item xs={12}>
             <Link to={`/processes/${process.containerRequest.uuid}`} className={classes.backLink}>
                 <BackIcon className={classes.backIcon} /> Back
@@ -65,34 +73,35 @@ export const ProcessLogMainCard = withStyles(styles)(
                 <CardHeader
                     avatar={<ProcessIcon className={classes.iconHeader} />}
                     action={
-                        <div>
-                            <IconButton aria-label="More options">
-                                <Tooltip title="More options">
-                                    <MoreOptionsIcon />
-                                </Tooltip>
-                            </IconButton>
-                        </div>
-                    }
+                        <IconButton onClick={event => onContextMenu(event, process)} aria-label="More options">
+                            <Tooltip title="More options">
+                                <MoreOptionsIcon />
+                            </Tooltip>
+                        </IconButton>}
                     title={
                         <Tooltip title={process.containerRequest.name} placement="bottom-start">
                             <Typography noWrap variant="title" className={classes.title}>
                                 {process.containerRequest.name}
                             </Typography>
-                        </Tooltip>
-                    }
+                        </Tooltip>}
                     subheader={process.containerRequest.description} />
                 <CardContent>
                     {lines.length > 0
-                        ? < Grid container spacing={24} alignItems='center'>
-                            <Grid item xs={6}>
-                                <ProcessLogForm selectedFilter={selectedFilter} filters={filters} onChange={onChange} />
-                            </Grid>
-                            <Grid item xs={6} className={classes.link}>
-                                <Typography component='div'>
-                                    Go to Log collection
+                        ? < Grid
+                            container
+                            spacing={24}
+                            direction='column'>
+                            <Grid container item>
+                                <Grid item xs={6}>
+                                    <ProcessLogForm selectedFilter={selectedFilter} filters={filters} onChange={onChange} />
+                                </Grid>
+                                <Grid item xs={6} className={classes.link}>
+                                    <Typography component='div'>
+                                        Go to Log collection
                                 </Typography>
+                                </Grid>
                             </Grid>
-                            <Grid item xs={12}>
+                            <Grid item xs>
                                 <ProcessLogCodeSnippet lines={lines} />
                             </Grid>
                         </Grid>
index 0845a4109780d1f8995e5d47d80eca99eead0b43..38870c402759814d3e9b4a0d5abaed08b5d4991c 100644 (file)
@@ -10,14 +10,13 @@ import { ProcessLogFormDataProps, ProcessLogFormActionProps } from '~/views/proc
 import { DefaultView } from '~/components/default-view/default-view';
 import { ProcessIcon } from '~/components/icon/icon';
 import { CodeSnippetDataProps } from '~/components/code-snippet/code-snippet';
+import { ProcessLogMainCardActionProps } from './process-log-main-card';
 
 export type ProcessLogPanelRootDataProps = {
     process?: Process;
 } & ProcessLogFormDataProps & CodeSnippetDataProps;
 
-export type ProcessLogPanelRootActionProps = {
-    onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
-} & ProcessLogFormActionProps;
+export type ProcessLogPanelRootActionProps = ProcessLogMainCardActionProps & ProcessLogFormActionProps;
 
 export type ProcessLogPanelRootProps = ProcessLogPanelRootDataProps & ProcessLogPanelRootActionProps;
 
index b467a14e38bd6c5b750f9a08a3c6bda963c26b7c..d578e784566b5d1c1d5bdd3caeb9672998c5e95f 100644 (file)
@@ -2,7 +2,6 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import * as React from 'react';
 import { RootState } from '~/store/store';
 import { connect } from 'react-redux';
 import { getProcess } from '~/store/processes/process';
@@ -38,10 +37,10 @@ const mapStateToProps = (state: RootState): ProcessLogPanelRootDataProps => {
 };
 
 const mapDispatchToProps = (dispatch: Dispatch): ProcessLogPanelRootActionProps => ({
-    onContextMenu: (event: React.MouseEvent<HTMLElement>) => {
-        dispatch<any>(openProcessContextMenu(event));
+    onContextMenu: (event, process) => {
+        dispatch<any>(openProcessContextMenu(event, process));
     },
-    onChange: (filter: FilterOption) => {
+    onChange: filter => {
         dispatch(setProcessLogsPanelFilter(filter.value));
     }
 });
index d12704bcf4b69c87ec59283dc824feba3e1dd308..ab8af36ffbb5e619e69fa22e2fbbb62b07d0a634 100644 (file)
@@ -20,19 +20,19 @@ export interface ProcessPanelRootDataProps {
 }
 
 export interface ProcessPanelRootActionProps {
-    onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
     onToggle: (status: string) => void;
 }
 
 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
 
-export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
-    props.process
+export const ProcessPanelRoot = ({process, ...props}: ProcessPanelRootProps) =>
+    process
         ? <Grid container spacing={16} alignItems="stretch">
             <Grid item sm={12} md={7}>
                 <ProcessInformationCard
-                    process={props.process}
-                    onContextMenu={props.onContextMenu} />
+                    process={process}
+                    onContextMenu={event => props.onContextMenu(event, process)} />
             </Grid>
             <Grid item sm={12} md={5}>
                 <SubprocessesCard
index b3e36ab77c1742c646db51d2672b76d198a2c0b7..4f283a6c9cadb56826b74b47d09a19501a0253df 100644 (file)
@@ -27,8 +27,8 @@ const mapStateToProps = ({ router, resources, processPanel }: RootState): Proces
 };
 
 const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({
-    onContextMenu: event => {
-        dispatch<any>(openProcessContextMenu(event));
+    onContextMenu: (event, process) => {
+        dispatch<any>(openProcessContextMenu(event, process));
     },
     onToggle: status => {
         dispatch<any>(toggleProcessPanelFilter(status));
index 6e00deb0f1a0963ce8f69280ec84ed86c0c072dd..d3f87701fc73724799a8968df0b5da29d394d4bb 100644 (file)
@@ -9,14 +9,16 @@ import { Process } from '~/store/processes/process';
 
 export interface ProcessSubprocessesDataProps {
     subprocesses: Array<Process>;
-    onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
 }
 
 export const ProcessSubprocesses = ({ onContextMenu, subprocesses }: ProcessSubprocessesDataProps) => {
     return <Grid container spacing={16}>
         {subprocesses.map(subprocess =>
             <Grid item xs={12} sm={6} md={4} lg={2} key={subprocess.containerRequest.uuid}>
-                <ProcessSubprocessesCard onContextMenu={onContextMenu} subprocess={subprocess} />
+                <ProcessSubprocessesCard
+                    onContextMenu={event => onContextMenu(event, subprocess)}
+                    subprocess={subprocess} />
             </Grid>
         )}
     </Grid>;
index 4a1e197d540dc059a8cfe15c6d642c218a308a0d..db7d8f688096e34cb80ca4a1e7bc796b1e0d11bb 100644 (file)
@@ -99,11 +99,6 @@ export const trashPanelColumns: DataColumns<string, TrashPanelFilter> = [
                 selected: true,
                 type: ResourceKind.COLLECTION
             },
-            {
-                name: resourceLabel(ResourceKind.PROCESS),
-                selected: true,
-                type: ResourceKind.PROCESS
-            },
             {
                 name: resourceLabel(ResourceKind.PROJECT),
                 selected: true,
index 3f61c5217f3cd7479753ca277ddf3cd3644ead8b..db389572467c5dbd7d2fddbc4a938d21c57d0e21 100644 (file)
@@ -40,8 +40,9 @@ import { MoveCollectionDialog } from '~/views-components/dialog-forms/move-colle
 import { FilesUploadCollectionDialog } from '~/views-components/dialog-forms/files-upload-collection-dialog';
 import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
 import { TrashPanel } from "~/views/trash-panel/trash-panel";
-import { MainContentBar } from '../../views-components/main-content-bar/main-content-bar';
+import { MainContentBar } from '~/views-components/main-content-bar/main-content-bar';
 import { Grid } from '@material-ui/core';
+import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
 
 type CssRules = 'root' | 'asidePanel' | 'contentWrapper' | 'content' | 'appBar';
 
@@ -134,24 +135,25 @@ export const Workbench = withStyles(styles)(
                             </Grid>}
                     </Grid>
                     <ContextMenu />
-                    <Snackbar />
-                    <CreateProjectDialog />
-                    <CreateCollectionDialog />
-                    <RenameFileDialog />
-                    <PartialCopyCollectionDialog />
-                    <FileRemoveDialog />
                     <CopyCollectionDialog />
                     <CopyProcessDialog />
+                    <CreateCollectionDialog />
+                    <CreateProjectDialog />
+                    <CurrentTokenDialog />
+                    <FileRemoveDialog />
                     <FileRemoveDialog />
-                    <MultipleFilesRemoveDialog />
-                    <UpdateCollectionDialog />
-                    <UpdateProcessDialog />
                     <FilesUploadCollectionDialog />
-                    <UpdateProjectDialog />
                     <MoveCollectionDialog />
                     <MoveProcessDialog />
                     <MoveProjectDialog />
-                    <CurrentTokenDialog />
+                    <MultipleFilesRemoveDialog />
+                    <PartialCopyCollectionDialog />
+                    <ProcessCommandDialog />
+                    <RenameFileDialog />
+                    <Snackbar />
+                    <UpdateCollectionDialog />
+                    <UpdateProcessDialog />
+                    <UpdateProjectDialog />
                 </>;
             }