merge master
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Thu, 30 Aug 2018 13:34:30 +0000 (15:34 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Thu, 30 Aug 2018 13:34:30 +0000 (15:34 +0200)
Feature #13859

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

29 files changed:
src/components/details-attribute/details-attribute.tsx
src/components/subprocess-filter/subprocess-filter.tsx [new file with mode: 0644]
src/models/container-request.ts
src/models/container.ts [new file with mode: 0644]
src/models/mount-types.ts [new file with mode: 0644]
src/models/resource.ts
src/models/runtime-constraints.ts [new file with mode: 0644]
src/models/scheduling-parameters.ts [new file with mode: 0644]
src/routes/routes.ts
src/services/container-request-service/container-request-service.ts [new file with mode: 0644]
src/services/container-service/container-service.ts [new file with mode: 0644]
src/services/services.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts
src/store/collections/collection-partial-copy-actions.ts
src/store/navigation/navigation-action.ts
src/store/processes/process.ts [new file with mode: 0644]
src/store/processes/processes-actions.ts [new file with mode: 0644]
src/store/resources/resources.ts
src/store/workbench/workbench-actions.ts
src/views-components/details-panel/process-details.tsx
src/views-components/dialog-forms/files-upload-collection-dialog.ts [moved from src/views-components/dialog-forms/upload-collection-files-dialog.ts with 81% similarity]
src/views-components/dialog-forms/partial-copy-collection-dialog.ts
src/views-components/process-information-card/process-information-card.ts [deleted file]
src/views/process-panel/process-information-card.tsx
src/views/process-panel/process-panel-root.tsx [new file with mode: 0644]
src/views/process-panel/process-panel.tsx
src/views/process-panel/process-subprocesses-card.tsx
src/views/process-panel/subprocesses-card.tsx [new file with mode: 0644]
src/views/workbench/workbench.tsx

index 4a94dc30d3f778a9a559631ada8026c93f561c68..78b4341d173046972385cad95e584cb735138085 100644 (file)
@@ -17,10 +17,12 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         marginBottom: theme.spacing.unit
     },
     label: {
+        boxSizing: 'border-box',
         color: theme.palette.grey["500"],
         width: '40%'
     },
     value: {
+        boxSizing: 'border-box',
         width: '60%',
         display: 'flex',
         alignItems: 'flex-start',
@@ -41,7 +43,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 interface DetailsAttributeDataProps {
     label: string;
     classLabel?: string;
-    value?: string | number;
+    value?: React.ReactNode;
     classValue?: string;
     lowercaseValue?: boolean;
     link?: string;
diff --git a/src/components/subprocess-filter/subprocess-filter.tsx b/src/components/subprocess-filter/subprocess-filter.tsx
new file mode 100644 (file)
index 0000000..58c33ee
--- /dev/null
@@ -0,0 +1,54 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { Grid, Typography, Switch } from '@material-ui/core';
+
+type CssRules = 'grid' | 'label' | 'value' | 'switch';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    grid: {
+        display: 'flex'
+    },
+    label: {
+        width: '86px',
+        color: theme.palette.grey["500"],
+        textAlign: 'right'
+    },
+    value: {
+        width: '24px',
+        paddingLeft: theme.spacing.unit
+    },
+    switch: {
+        '& span:first-child': {
+            height: '18px'
+        }
+    }
+});
+
+export interface SubprocessFilterDataProps {
+    label: string;
+    value: number;
+    checked?: boolean;
+    key?: string;
+    onToggle?: () => void;
+}
+
+type SubprocessFilterProps = SubprocessFilterDataProps & WithStyles<CssRules>;
+
+export const SubprocessFilter = withStyles(styles)(
+    ({ classes, label, value, key, checked, onToggle }: SubprocessFilterProps) =>
+        <Grid item className={classes.grid} md={12} lg={6} >
+            <Typography component="span" className={classes.label}>{label}:</Typography>
+            <Typography component="span" className={classes.value}>{value}</Typography>
+            {onToggle && <Switch classes={{ root: classes.switch }}
+                checked={checked}
+                onChange={onToggle}
+                value={key}
+                color="primary" />
+            }
+        </Grid>
+);
\ No newline at end of file
index d1bcc36c81e9a548e34006c9696e47340a422002..78891c7bdcd8fc23918ff369052548dd02b725db 100644 (file)
@@ -3,6 +3,9 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Resource, ResourceKind } from "./resource";
+import { MountType } from "~/models/mount-types";
+import { RuntimeConstraints } from './runtime-constraints';
+import { SchedulingParameters } from './scheduling-parameters';
 
 export enum ContainerRequestState {
     UNCOMMITTED = "Uncommitted",
@@ -16,12 +19,12 @@ export interface ContainerRequestResource extends Resource {
     description: string;
     properties: any;
     state: ContainerRequestState;
-    requestingContainerUuid: string;
-    containerUuid: string;
+    requestingContainerUuid: string | null;
+    containerUuid: string | null;
     containerCountMax: number;
-    mounts: any;
-    runtimeConstraints: any;
-    schedulingParameters: any;
+    mounts: MountType[];
+    runtimeConstraints: RuntimeConstraints;
+    schedulingParameters: SchedulingParameters;
     containerImage: string;
     environment: any;
     cwd: string;
@@ -29,10 +32,10 @@ export interface ContainerRequestResource extends Resource {
     outputPath: string;
     outputName: string;
     outputTtl: number;
-    priority: number;
+    priority: number | null;
     expiresAt: string;
     useExisting: boolean;
-    logUuid: string;
-    outputUuid: string;
+    logUuid: string | null;
+    outputUuid: string | null;
     filters: string;
 }
diff --git a/src/models/container.ts b/src/models/container.ts
new file mode 100644 (file)
index 0000000..99cb309
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Resource, ResourceKind } from "./resource";
+import { MountType } from '~/models/mount-types';
+import { RuntimeConstraints } from "~/models/runtime-constraints";
+import { SchedulingParameters } from './scheduling-parameters';
+
+export enum ContainerState {
+    QUEUED = 'Queued',
+    LOCKED = 'Locked',
+    RUNNING = 'Running',
+    COMPLETE = 'Complete',
+    CANCELLED = 'Cancelled',
+}
+
+export interface ContainerResource extends Resource {
+    kind: ResourceKind.CONTAINER;
+    state: string;
+    startedAt: string | null;
+    finishedAt: string | null;
+    log: string | null;
+    environment: {};
+    cwd: string;
+    command: string[];
+    outputPath: string;
+    mounts: MountType[];
+    runtimeConstraints: RuntimeConstraints;
+    schedulingParameters: SchedulingParameters;
+    output: string | null;
+    containerImage: string;
+    progress: number;
+    priority: number;
+    exitCode: number | null;
+    authUuid: string | null;
+    lockedByUuid: string | null;
+}
diff --git a/src/models/mount-types.ts b/src/models/mount-types.ts
new file mode 100644 (file)
index 0000000..ec48c85
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export enum MountKind {
+    COLLECTION = 'collection',
+    GIT_TREE = 'git_tree',
+    TEMPORARY_DIRECTORY = 'tmp',
+    KEEP = 'keep',
+    MOUNTED_FILE = 'file',
+    JSON = 'JSON'
+}
+
+export type MountType =
+    CollectionMount |
+    GitTreeMount |
+    TemporaryDirectoryMount |
+    KeepMount |
+    JSONMount;
+
+export interface CollectionMount {
+    kind: MountKind.COLLECTION;
+    uuid?: string;
+    portableDataHash?: string;
+    path?: string;
+    writable?: boolean;
+}
+
+export interface GitTreeMount {
+    kind: MountKind.GIT_TREE;
+    uuid: string;
+    commit: string;
+    path?: string;
+}
+
+export enum TemporaryDirectoryDeviceType {
+    RAM = 'ram',
+    SSD = 'ssd',
+    DISK = 'disk',
+    NETWORK = 'network',
+}
+
+export interface TemporaryDirectoryMount {
+    kind: MountKind.TEMPORARY_DIRECTORY;
+    capacity: number;
+    deviceType: TemporaryDirectoryDeviceType;
+}
+
+export interface KeepMount {
+    kind: MountKind.KEEP;
+}
+
+export interface JSONMount {
+    kind: MountKind.JSON;
+    content: string;
+}
index ff95c1a9b8df59872d6819dccdbcfb2b8ee92441..3290bdfe06f07ed60fa27accd067cff47f0d0b6b 100644 (file)
@@ -16,18 +16,21 @@ export interface Resource {
 
 export enum ResourceKind {
     COLLECTION = "arvados#collection",
+    CONTAINER = "arvados#container",
     CONTAINER_REQUEST = "arvados#containerRequest",
     GROUP = "arvados#group",
     PROCESS = "arvados#containerRequest",
     PROJECT = "arvados#group",
-    WORKFLOW = "arvados#workflow",
     USER = "arvados#user",
+    WORKFLOW = "arvados#workflow",
 }
 
 export enum ResourceObjectType {
-    USER = 'tpzed',
+    COLLECTION = '4zz18',
+    CONTAINER = 'dz642',
+    CONTAINER_REQUEST = 'xvhdp',
     GROUP = 'j7d0g',
-    COLLECTION = '4zz18'
+    USER = 'tpzed',
 }
 
 export const RESOURCE_UUID_PATTERN = '.{5}-.{5}-.{15}';
@@ -52,6 +55,10 @@ export const extractUuidKind = (uuid: string = '') => {
             return ResourceKind.GROUP;
         case ResourceObjectType.COLLECTION:
             return ResourceKind.COLLECTION;
+        case ResourceObjectType.CONTAINER_REQUEST:
+            return ResourceKind.CONTAINER_REQUEST;
+        case ResourceObjectType.CONTAINER:
+            return ResourceKind.CONTAINER;
         default:
             return undefined;
     }
diff --git a/src/models/runtime-constraints.ts b/src/models/runtime-constraints.ts
new file mode 100644 (file)
index 0000000..ba90537
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface RuntimeConstraints {
+    ram: number;
+    vcpus: number;
+    keepCacheRam: number;
+    API: boolean;
+}
diff --git a/src/models/scheduling-parameters.ts b/src/models/scheduling-parameters.ts
new file mode 100644 (file)
index 0000000..62f7224
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface SchedulingParameters {
+    partitions: string[];
+    preemptible: boolean;
+    maxRunTime: number;
+}
index 0bf7110126df7849f4571cfbe6e2c21dffa49fdd..20dd1359b34b626f66ab3b0de959e85a2c28b38f 100644 (file)
@@ -3,19 +3,20 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { History, Location } from 'history';
-import { RootStore } from '../store/store';
+import { RootStore } from '~/store/store';
 import { matchPath } from 'react-router';
 import { ResourceKind, RESOURCE_UUID_PATTERN, extractUuidKind } from '~/models/resource';
 import { getProjectUrl } from '../models/project';
 import { getCollectionUrl } from '~/models/collection';
-import { loadProject, loadFavorites, loadCollection } from '../store/workbench/workbench-actions';
+import { loadProject, loadFavorites, loadCollection } from '~/store/workbench/workbench-actions';
+import { loadProcess } from '~/store/processes/processes-actions';
 
 export const Routes = {
     ROOT: '/',
     TOKEN: '/token',
     PROJECTS: `/projects/:id(${RESOURCE_UUID_PATTERN})`,
     COLLECTIONS: `/collections/:id(${RESOURCE_UUID_PATTERN})`,
-    PROCESS: `/processes/:id(${RESOURCE_UUID_PATTERN})`,
+    PROCESSES: `/processes/:id(${RESOURCE_UUID_PATTERN})`,
     FAVORITES: '/favorites',
 };
 
@@ -31,6 +32,8 @@ export const getResourceUrl = (uuid: string) => {
     }
 };
 
+export const getProcessUrl = (uuid: string) => `/processes/${uuid}`;
+
 export const addRouteChangeHandlers = (history: History, store: RootStore) => {
     const handler = handleLocationChange(store);
     handler(history.location);
@@ -43,30 +46,32 @@ export const matchRootRoute = (route: string) =>
 export const matchFavoritesRoute = (route: string) =>
     matchPath(route, { path: Routes.FAVORITES });
 
-export interface ProjectRouteParams {
+export interface ResourceRouteParams {
     id: string;
 }
 
 export const matchProjectRoute = (route: string) =>
-    matchPath<ProjectRouteParams>(route, { path: Routes.PROJECTS });
-
-export interface CollectionRouteParams {
-    id: string;
-}
+    matchPath<ResourceRouteParams>(route, { path: Routes.PROJECTS });
 
 export const matchCollectionRoute = (route: string) =>
-    matchPath<CollectionRouteParams>(route, { path: Routes.COLLECTIONS });
+    matchPath<ResourceRouteParams>(route, { path: Routes.COLLECTIONS });
+
+export const matchProcessRoute = (route: string) =>
+    matchPath<ResourceRouteParams>(route, { path: Routes.PROCESSES });
 
 
 const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     const projectMatch = matchProjectRoute(pathname);
     const collectionMatch = matchCollectionRoute(pathname);
     const favoriteMatch = matchFavoritesRoute(pathname);
+    const processMatch = matchProcessRoute(pathname);
     if (projectMatch) {
         store.dispatch(loadProject(projectMatch.params.id));
     } else if (collectionMatch) {
         store.dispatch(loadCollection(collectionMatch.params.id));
     } else if (favoriteMatch) {
         store.dispatch(loadFavorites());
+    } else if (processMatch) {
+        store.dispatch(loadProcess(processMatch.params.id));
     }
 };
diff --git a/src/services/container-request-service/container-request-service.ts b/src/services/container-request-service/container-request-service.ts
new file mode 100644 (file)
index 0000000..8cf8e74
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { CommonResourceService } from "~/common/api/common-resource-service";
+import { AxiosInstance } from "axios";
+import { ContainerRequestResource } from '../../models/container-request';
+
+export class ContainerRequestService extends CommonResourceService<ContainerRequestResource> {
+    constructor(serverApi: AxiosInstance) {
+        super(serverApi, "container_requests");
+    }
+}
diff --git a/src/services/container-service/container-service.ts b/src/services/container-service/container-service.ts
new file mode 100644 (file)
index 0000000..698c7f5
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { CommonResourceService } from "~/common/api/common-resource-service";
+import { AxiosInstance } from "axios";
+import { ContainerResource } from '../../models/container';
+
+export class ContainerService extends CommonResourceService<ContainerResource> {
+    constructor(serverApi: AxiosInstance) {
+        super(serverApi, "containers");
+    }
+}
index 6295527bfb40d5c326b56a4a4d09dfe27d22e85e..32e7bd18b2ff9bd54ee1305af28e132e5da4cfe9 100644 (file)
@@ -17,6 +17,8 @@ import { Config } from "../common/config";
 import { UserService } from './user-service/user-service';
 import { AncestorService } from "~/services/ancestors-service/ancestors-service";
 import { ResourceKind } from "~/models/resource";
+import { ContainerRequestService } from './container-request-service/container-request-service';
+import { ContainerService } from './container-service/container-service';
 
 export type ServiceRepository = ReturnType<typeof createServices>;
 
@@ -27,32 +29,37 @@ export const createServices = (config: Config) => {
     const webdavClient = new WebDAV();
     webdavClient.defaults.baseURL = config.keepWebServiceUrl;
 
-    const authService = new AuthService(apiClient, config.rootUrl);
-    const keepService = new KeepService(apiClient);
     const groupsService = new GroupsService(apiClient);
-    const projectService = new ProjectService(apiClient);
+    const keepService = new KeepService(apiClient);
     const linkService = new LinkService(apiClient);
-    const favoriteService = new FavoriteService(linkService, groupsService);
-    const collectionService = new CollectionService(apiClient, webdavClient, authService);
-    const tagService = new TagService(linkService);
-    const collectionFilesService = new CollectionFilesService(collectionService);
+    const projectService = new ProjectService(apiClient);
     const userService = new UserService(apiClient);
+    const containerRequestService = new ContainerRequestService(apiClient);
+    const containerService = new ContainerService(apiClient);
+    
     const ancestorsService = new AncestorService(groupsService, userService);
+    const authService = new AuthService(apiClient, config.rootUrl);
+    const collectionService = new CollectionService(apiClient, webdavClient, authService);
+    const collectionFilesService = new CollectionFilesService(collectionService);
+    const favoriteService = new FavoriteService(linkService, groupsService);
+    const tagService = new TagService(linkService);
 
     return {
+        ancestorsService,
         apiClient,
-        webdavClient,
         authService,
-        keepService,
+        collectionFilesService,
+        collectionService,
+        containerRequestService,
+        containerService,
+        favoriteService,
         groupsService,
-        projectService,
+        keepService,
         linkService,
-        favoriteService,
-        collectionService,
+        projectService,
         tagService,
-        collectionFilesService,
         userService,
-        ancestorsService,
+        webdavClient,
     };
 };
 
index d509218ecfa2ca8cd0c203780ad8985ae734f85b..413fedfc1c08eccd17fe24f4c22aecd03d5a000e 100644 (file)
@@ -86,8 +86,6 @@ export const openMultipleFilesRemoveDialog = () =>
         }
     });
 
-
-
 export const RENAME_FILE_DIALOG = 'renameFileDialog';
 export interface RenameFileDialogData {
     name: string;
index f0dd2780d4f5255a0f4ed9c837e06397028490a2..a063abae4107a28438da4e503d2c33d7700a1ca6 100644 (file)
@@ -35,7 +35,7 @@ export const openCollectionPartialCopyDialog = () =>
         }
     };
 
-export const doCollectionPartialCopy = ({ name, description, projectUuid }: CollectionPartialCopyFormData) =>
+export const copyCollectionPartial = ({ name, description, projectUuid }: CollectionPartialCopyFormData) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(startSubmit(COLLECTION_PARTIAL_COPY_FORM_NAME));
         const state = getState();
index 1d36beeca27119aade65cc52eb82e8a4c1c4ac07..188acf12bd30afc55b838f6d31f279822e0b6628 100644 (file)
@@ -9,7 +9,7 @@ import { getCollectionUrl } from "~/models/collection";
 import { getProjectUrl } from "~/models/project";
 
 import { SidePanelTreeCategory } from '../side-panel-tree/side-panel-tree-actions';
-import { Routes } from '~/routes/routes';
+import { Routes, getProcessUrl } from '~/routes/routes';
 
 export const navigateTo = (uuid: string) =>
     async (dispatch: Dispatch) => {
@@ -18,6 +18,8 @@ export const navigateTo = (uuid: string) =>
             dispatch<any>(navigateToProject(uuid));
         } else if (kind === ResourceKind.COLLECTION) {
             dispatch<any>(navigateToCollection(uuid));
+        } else if (kind === ResourceKind.CONTAINER_REQUEST) {
+            dispatch<any>(navigateToProcess(uuid));
         }
         if (uuid === SidePanelTreeCategory.FAVORITES) {
             dispatch<any>(navigateToFavorites);
@@ -29,3 +31,5 @@ export const navigateToFavorites = push(Routes.FAVORITES);
 export const navigateToProject = compose(push, getProjectUrl);
 
 export const navigateToCollection = compose(push, getCollectionUrl);
+
+export const navigateToProcess = compose(push, getProcessUrl);
diff --git a/src/store/processes/process.ts b/src/store/processes/process.ts
new file mode 100644 (file)
index 0000000..46d8a25
--- /dev/null
@@ -0,0 +1,47 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ContainerRequestResource } from '../../models/container-request';
+import { ContainerResource } from '../../models/container';
+import { ResourcesState, getResource } from '~/store/resources/resources';
+import { filterResources } from '../resources/resources';
+import { ResourceKind, Resource } from '~/models/resource';
+
+export interface Process {
+    containerRequest: ContainerRequestResource;
+    container?: ContainerResource;
+}
+
+export const getProcess = (uuid: string) => (resources: ResourcesState): Process | undefined => {
+    const containerRequest = getResource<ContainerRequestResource>(uuid)(resources);
+    if (containerRequest) {
+        if (containerRequest.containerUuid) {
+            const container = getResource<ContainerResource>(containerRequest.containerUuid)(resources);
+            if (container) {
+                return { containerRequest, container };
+            }
+        }
+        return { containerRequest };
+    }
+    return;
+};
+
+export const getSubprocesses = (uuid: string) => (resources: ResourcesState) => {
+    const containerRequests = filterResources(isSubprocess(uuid))(resources) as ContainerRequestResource[];
+    return containerRequests.reduce((subprocesses, { uuid }) => {
+        const process = getProcess(uuid)(resources);
+        return process
+            ? [...subprocesses, process]
+            : subprocesses;
+    }, []);
+};
+
+export const getProcessStatus = (process: Process) =>
+    process.container
+        ? process.container.state
+        : process.containerRequest.state;
+
+const isSubprocess = (uuid: string) => (resource: Resource) =>
+    resource.kind === ResourceKind.CONTAINER_REQUEST
+    && (resource as ContainerRequestResource).requestingContainerUuid === uuid;
diff --git a/src/store/processes/processes-actions.ts b/src/store/processes/processes-actions.ts
new file mode 100644 (file)
index 0000000..d667517
--- /dev/null
@@ -0,0 +1,52 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from "redux";
+import { RootState } from '~/store/store';
+import { ServiceRepository } from '~/services/services';
+import { updateResources } from '~/store/resources/resources-actions';
+import { FilterBuilder } from '~/common/api/filter-builder';
+import { ContainerRequestResource } from '../../models/container-request';
+
+export const loadProcess = (uuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const containerRequest = await services.containerRequestService.get(uuid);
+        dispatch<any>(updateResources([containerRequest]));
+        if (containerRequest.containerUuid) {
+            const container = await services.containerService.get(containerRequest.containerUuid);
+            dispatch<any>(updateResources([container]));
+        }
+    };
+
+export const loadSubprocesses = (uuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const containerRequests = await dispatch<any>(loadContainerRequests(
+            new FilterBuilder().addEqual('requestingContainerUuid', uuid).getFilters()
+        )) as ContainerRequestResource[];
+
+        const containerUuids: string[] = containerRequests.reduce((uuids, { containerUuid }) =>
+            containerUuid
+                ? [...uuids, containerUuid]
+                : uuids, []);
+
+        if (containerUuids.length > 0) {
+            await dispatch<any>(loadContainers(
+                new FilterBuilder().addIn('uuid', containerUuids).getFilters()
+            ));
+        }
+    };
+
+export const loadContainerRequests = (filters: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const { items } = await services.containerRequestService.list({ filters });
+        dispatch<any>(updateResources(items));
+        return items;
+    };
+
+export const loadContainers = (filters: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const { items } = await services.containerService.list({ filters });
+        dispatch<any>(updateResources(items));
+        return items;
+    };
index add4efef54b4cd4d63178e0239927f4619587160..10c82ffe7f36bbd13c7ac8c193b6f1a182c94eac 100644 (file)
@@ -19,7 +19,7 @@ export const setResource = <T extends Resource>(id: string, data: T) =>
 
 export const deleteResource = (id: string) =>
     (state: ResourcesState) => {
-        const newState = {...state};
+        const newState = { ...state };
         delete newState[id];
         return newState;
     };
@@ -28,10 +28,14 @@ export const filterResources = (filter: (resource: Resource) => boolean) =>
     (state: ResourcesState) =>
         Object
             .keys(state)
-            .map(id => getResource(id)(state))
+            .reduce((resources, id) => {
+                const resource = getResource(id)(state);
+                return resource
+                    ? [...resources, resource]
+                    : resources;
+            }, [])
             .filter(filter);
 
 export const filterResourcesByKind = (kind: ResourceKind) =>
     (state: ResourcesState) =>
         filterResources(resource => resource.kind === kind)(state);
-
index 8dc64a845e22d8fc68e72376d9060684635eefef..3c68c0004519b15a73fca9a9c5553b316e3a68e5 100644 (file)
@@ -28,6 +28,8 @@ import * as collectionCreateActions from '~/store/collections/collection-create-
 import * as collectionCopyActions from '~/store/collections/collection-copy-actions';
 import * as collectionUpdateActions from '~/store/collections/collection-update-actions';
 import * as collectionMoveActions from '~/store/collections/collection-move-actions';
+import * as processesActions from '../processes/processes-actions';
+import { getProcess } from '../processes/process';
 
 
 export const loadWorkbench = () =>
@@ -172,6 +174,17 @@ export const moveCollection = (data: MoveToFormDialogData) =>
         }
     };
 
+export const loadProcess = (uuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState) => {
+        await dispatch<any>(processesActions.loadProcess(uuid));
+        const process = getProcess(uuid)(getState().resources);
+        if (process) {
+            await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
+            dispatch<any>(setCollectionBreadcrumbs(process.containerRequest.ownerUuid));
+            dispatch(loadDetailsPanel(uuid));
+        }
+    };
+
 export const resourceIsNotLoaded = (uuid: string) =>
     snackbarActions.OPEN_SNACKBAR({
         message: `Resource identified by ${uuid} is not loaded.`
index cec01b966d91ecd5eb26dcda42779c579915dc49..e3c9823d018b62d1a0b247007cca32f158230c87 100644 (file)
@@ -34,10 +34,10 @@ export class ProcessDetails extends DetailsData<ProcessResource> {
             {/* Links but we dont have view */}
             <DetailsAttribute label='Outputs' link={this.item.outputPath} value={this.item.outputPath} />
             <DetailsAttribute label='UUID' link={this.item.uuid} value={this.item.uuid} />
-            <DetailsAttribute label='Container UUID' link={this.item.containerUuid} value={this.item.containerUuid} />
+            <DetailsAttribute label='Container UUID' link={this.item.containerUuid || ''} value={this.item.containerUuid} />
 
             <DetailsAttribute label='Priority' value={this.item.priority} />
-            <DetailsAttribute label='Runtime Constraints' value={this.item.runtimeConstraints} />
+            <DetailsAttribute label='Runtime Constraints' value={JSON.stringify(this.item.runtimeConstraints)} />
             {/* Link but we dont have view */}
             <DetailsAttribute label='Docker Image locator' link={this.item.containerImage} value={this.item.containerImage} />
         </div>;
similarity index 81%
rename from src/views-components/dialog-forms/upload-collection-files-dialog.ts
rename to src/views-components/dialog-forms/files-upload-collection-dialog.ts
index 38f0333e4d146d071e13ac639324cb03f73d796b..a24490f727fd58d03fe6f40078db73827c88b639 100644 (file)
@@ -7,9 +7,9 @@ import { reduxForm } from 'redux-form';
 import { withDialog } from "~/store/dialog/with-dialog";
 import { CollectionCreateFormDialogData } from '~/store/collections/collection-create-actions';
 import { COLLECTION_UPLOAD_FILES_DIALOG, submitCollectionFiles } from '~/store/collections/collection-upload-actions';
-import { DialogCollectionFilesUpload } from '../dialog-upload/dialog-collection-files-upload';
+import { DialogCollectionFilesUpload } from '~/views-components/dialog-upload/dialog-collection-files-upload';
 
-export const UploadCollectionFilesDialog = compose(
+export const FilesUploadCollectionDialog = compose(
     withDialog(COLLECTION_UPLOAD_FILES_DIALOG),
     reduxForm<CollectionCreateFormDialogData>({
         form: COLLECTION_UPLOAD_FILES_DIALOG,
index 9a350f6307ce908f9b503b5b9b21c3ebc45ccc7b..16f8275e8fb57821c8ed2f4b8065c4b8c6c43eb9 100644 (file)
@@ -5,15 +5,15 @@
 import { compose } from "redux";
 import { reduxForm } from 'redux-form';
 import { withDialog, } from '~/store/dialog/with-dialog';
-import { CollectionPartialCopyFormData, doCollectionPartialCopy, COLLECTION_PARTIAL_COPY_FORM_NAME } from '~/store/collections/collection-partial-copy-actions';
+import { CollectionPartialCopyFormData, copyCollectionPartial, COLLECTION_PARTIAL_COPY_FORM_NAME } from '~/store/collections/collection-partial-copy-actions';
 import { DialogCollectionPartialCopy } from "~/views-components/dialog-copy/dialog-collection-partial-copy";
 
 
 export const PartialCopyCollectionDialog = compose(
     withDialog(COLLECTION_PARTIAL_COPY_FORM_NAME),
-    reduxForm({
+    reduxForm<CollectionPartialCopyFormData>({
         form: COLLECTION_PARTIAL_COPY_FORM_NAME,
-        onSubmit: (data: CollectionPartialCopyFormData, dispatch) => {
-            dispatch(doCollectionPartialCopy(data));
+        onSubmit: (data, dispatch) => {
+            dispatch(copyCollectionPartial(data));
         }
     }))(DialogCollectionPartialCopy);
\ No newline at end of file
diff --git a/src/views-components/process-information-card/process-information-card.ts b/src/views-components/process-information-card/process-information-card.ts
deleted file mode 100644 (file)
index bf9172d..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { Dispatch } from 'redux';
-import { openProcessContextMenu } from '~/store/context-menu/context-menu-actions';
-import { connect } from 'react-redux';
-import { RootState } from '~/store/store';
-import { ProcessInformationCard as InformationCardComponent, ProcessInformationCardDataProps } from '~/views/process-panel/process-information-card';
-
-type InformationCardActionProps = Pick<ProcessInformationCardDataProps, 'onContextMenu'>;
-
-const mapStateToProps = (state: RootState) => ({
-    // todo: change for processPanel
-    item: state.collectionPanel.item
-});
-
-const mapDispatchToProps = (dispatch: Dispatch): InformationCardActionProps => ({
-    onContextMenu: (event: React.MouseEvent<HTMLElement>) => {
-        dispatch<any>(openProcessContextMenu(event));
-    }
-});
-
-export const ProcessInformationCard = connect(mapStateToProps, mapDispatchToProps)(InformationCardComponent);
\ No newline at end of file
index 359c25659103262332f576f23ea686fbf36a7cfc..acc1d8f2c51bc03864c7496dd9bafaa082d580ae 100644 (file)
@@ -11,8 +11,9 @@ import * as classnames from "classnames";
 import { ArvadosTheme } from '~/common/custom-theme';
 import { MoreOptionsIcon, ProcessIcon } from '~/components/icon/icon';
 import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
-import { getBackgroundColorStatus } from '~/views/process-panel/process-panel';
-import { SubprocessesStatus } from '~/views/process-panel/process-subprocesses';
+import { Process } from '~/store/processes/process';
+import { getProcessStatus } from '~/store/processes/process';
+import { getBackgroundColorStatus } from '~/views/process-panel/process-panel-root';
 
 type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar'
     | 'headerActive' | 'headerCompleted' | 'headerQueued' | 'headerFailed' | 'headerCanceled';
@@ -82,14 +83,14 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 });
 
 export interface ProcessInformationCardDataProps {
-    item: any;
+    process: Process;
     onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
 }
 
 type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules>;
 
 export const ProcessInformationCard = withStyles(styles)(
-    ({ classes, onContextMenu }: ProcessInformationCardProps) =>
+    ({ classes, process, onContextMenu }: ProcessInformationCardProps) =>
         <Card className={classes.card}>
             <CardHeader
                 classes={{
@@ -99,8 +100,8 @@ export const ProcessInformationCard = withStyles(styles)(
                 avatar={<ProcessIcon className={classes.iconHeader} />}
                 action={
                     <div>
-                        <Chip label={SubprocessesStatus.ACTIVE}
-                            className={classnames([classes.chip, getBackgroundColorStatus(SubprocessesStatus.ACTIVE, classes)])} />
+                        <Chip label={getProcessStatus(process)}
+                            className={classnames([classes.chip, getBackgroundColorStatus(getProcessStatus(process), classes)])} />
                         <IconButton
                             aria-label="More options"
                             onClick={event => onContextMenu(event)}>
@@ -109,22 +110,22 @@ export const ProcessInformationCard = withStyles(styles)(
                     </div>
                 }
                 title={
-                    <Tooltip title="Pipeline template that generates a config file from a template">
+                    <Tooltip title={process.containerRequest.name}>
                         <Typography noWrap variant="title">
-                            Pipeline template that generates a config file from a template
+                           {process.containerRequest.name}
                         </Typography>
                     </Tooltip>
                 }
-                subheader="(no-description)" />
+                subheader={process.containerRequest.description} />
             <CardContent className={classes.content}>
                 <Grid container>
                     <Grid item xs={6}>
                         <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                            label='From' value="1:25 PM 3/23/2018" />
+                            label='From' value={process.container ? process.container.startedAt : 'N/A'} />
                         <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                            label='To' value='1:25 PM 3/23/2018' />
+                            label='To' value={process.container ? process.container.finishedAt : 'N/A'} />
                         <DetailsAttribute classLabel={classes.label} classValue={classes.link}
-                            label='Workflow' value='FastQC MultiQC' />
+                            label='Workflow' value='???' />
                     </Grid>
                     <Grid item xs={6}>
                         <DetailsAttribute classLabel={classes.link} label='Outputs' />
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
new file mode 100644 (file)
index 0000000..49ce98c
--- /dev/null
@@ -0,0 +1,91 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+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 } from '~/store/processes/process';
+import { SubprocessesCard } from './subprocesses-card';
+import { ProcessSubprocesses } from '~/views-components/process-subprocesses/process-subprocesses';
+import { SubprocessesStatus } from '~/views/process-panel/process-subprocesses';
+
+type CssRules = 'headerActive' | 'headerCompleted' | 'headerQueued' | 'headerFailed' | 'headerCanceled';
+
+export interface ProcessPanelRootDataProps {
+    process?: Process;
+}
+
+export interface ProcessPanelRootActionProps {
+    onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+}
+
+export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
+
+export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
+    props.process
+        ? <Grid container spacing={16}>
+            <Grid item xs={7}>
+                <ProcessInformationCard
+                    process={props.process}
+                    onContextMenu={props.onContextMenu} />
+            </Grid>
+            <Grid item xs={5}>
+                <SubprocessesCard
+                    subprocesses={4}
+                    filters={[
+                        {
+                            key: 'queued',
+                            value: 1,
+                            label: 'Queued',
+                            checked: true
+                        }, {
+                            key: 'active',
+                            value: 2,
+                            label: 'Active',
+                            checked: true
+                        },
+                        {
+                            key: 'completed',
+                            value: 2,
+                            label: 'Completed',
+                            checked: true
+                        },
+                        {
+                            key: 'failed',
+                            value: 2,
+                            label: 'Failed',
+                            checked: true
+                        }
+                    ]}
+                    onToggle={() => { return; }}
+                />
+            </Grid>
+            <ProcessSubprocesses />
+        </Grid>
+        : <Grid container
+            alignItems='center'
+            justify='center'>
+            <DefaultView
+                icon={ProcessIcon}
+                messages={['Process not found']} />
+        </Grid>;
+
+export const getBackgroundColorStatus = (status: string, classes: Record<CssRules, string>) => {
+    switch (status) {
+        case SubprocessesStatus.COMPLETED:
+            return classes.headerCompleted;
+        case SubprocessesStatus.CANCELED:
+            return classes.headerCanceled;
+        case SubprocessesStatus.QUEUED:
+            return classes.headerQueued;
+        case SubprocessesStatus.FAILED:
+            return classes.headerFailed;
+        case SubprocessesStatus.ACTIVE:
+            return classes.headerActive;
+        default:
+            return classes.headerQueued;
+    }
+};
index 5829a0cd1967c132ba60b614b99cc0f37889fb45..421945fed7f1756c1a6f8794b6e5e018d2a27b42 100644 (file)
@@ -3,40 +3,27 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { } from '@material-ui/core';
-import { Grid } from '@material-ui/core';
-import { ProcessInformationCard } from '~/views-components/process-information-card/process-information-card';
-import { ProcessSubprocesses } from '~/views-components/process-subprocesses/process-subprocesses';
-import { SubprocessesStatus } from '~/views/process-panel/process-subprocesses';
+import { RootState } from '~/store/store';
+import { connect } from 'react-redux';
+import { getProcess } 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';
 
-export type CssRules = 'headerActive' | 'headerCompleted' | 'headerQueued' | 'headerFailed' | 'headerCanceled';
+const mapStateToProps = ({ router, resources }: RootState): ProcessPanelRootDataProps => {
+    const pathname = router.location ? router.location.pathname : '';
+    const match = matchProcessRoute(pathname);
+    const uuid = match ? match.params.id : '';
+    return {
+        process: getProcess(uuid)(resources)
+    };
+};
 
-export class ProcessPanel extends React.Component {
-    render() {
-        return <div>
-            <Grid container>
-                <Grid item xs={7}>
-                    <ProcessInformationCard />
-                </Grid>
-            </Grid>
-            <ProcessSubprocesses />
-        </div>;
+const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({
+    onContextMenu: (event: React.MouseEvent<HTMLElement>) => {
+        dispatch<any>(openProcessContextMenu(event));
     }
-}
+});
 
-export const getBackgroundColorStatus = (status: SubprocessesStatus, classes: Record<CssRules, string>) => {
-    switch (status) {
-        case SubprocessesStatus.COMPLETED:
-            return classes.headerCompleted;
-        case SubprocessesStatus.CANCELED:
-            return classes.headerCanceled;
-        case SubprocessesStatus.QUEUED:
-            return classes.headerQueued;
-        case SubprocessesStatus.FAILED:
-            return classes.headerFailed;
-        case SubprocessesStatus.ACTIVE:
-            return classes.headerActive;
-        default:
-            return classes.headerQueued;
-    }
-};
\ No newline at end of file
+export const ProcessPanel = connect(mapStateToProps, mapDispatchToProps)(ProcessPanelRoot);
index 2ea1b7eba8b4828a91e06a29c9610994597255d4..17c44246adab4d0a2dc8f101acd77e7cc8f645a8 100644 (file)
@@ -11,7 +11,7 @@ import * as classnames from "classnames";
 import { ArvadosTheme } from '~/common/custom-theme';
 import { MoreOptionsIcon } from '~/components/icon/icon';
 import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
-import { getBackgroundColorStatus } from '~/views/process-panel/process-panel';
+import { getBackgroundColorStatus } from '~/views/process-panel/process-panel-root';
 
 export type CssRules = 'label' | 'value' | 'title' | 'content' | 'action' | 'options' | 'status' | 'rightSideHeader' | 'titleHeader'
     | 'header' | 'headerActive' | 'headerCompleted' | 'headerQueued' | 'headerFailed' | 'headerCanceled';
diff --git a/src/views/process-panel/subprocesses-card.tsx b/src/views/process-panel/subprocesses-card.tsx
new file mode 100644 (file)
index 0000000..ac60c9f
--- /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 { ArvadosTheme } from '~/common/custom-theme';
+import { StyleRulesCallback, withStyles, WithStyles, Card, CardHeader, CardContent, Grid, Switch } from '@material-ui/core';
+import { SubprocessFilter } from '~/components/subprocess-filter/subprocess-filter';
+import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter';
+
+type CssRules = 'root';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        fontSize: '0.875rem'
+    }
+});
+
+interface SubprocessesDataProps {
+    subprocesses: number;
+    filters: SubprocessFilterDataProps[];
+    onToggle: (filter: SubprocessFilterDataProps) => void;
+}
+
+type SubprocessesProps = SubprocessesDataProps & WithStyles<CssRules>;
+
+export const SubprocessesCard = withStyles(styles)(
+    ({ classes, filters, subprocesses, onToggle }: SubprocessesProps) => 
+        <Card className={classes.root}>
+            <CardHeader title="Subprocess and filters" />
+            <CardContent>
+                <Grid container direction="column" spacing={16}>
+                    <Grid item xs={12} container spacing={16}>
+                        <SubprocessFilter label='Subprocesses' value={subprocesses} />     
+                    </Grid>
+                    <Grid item xs={12} container spacing={16}>
+                        {
+                            filters.map(filter => 
+                                <SubprocessFilter {...filter} key={filter.key} onToggle={() => onToggle(filter)} />                                                     
+                            )
+                        }
+                    </Grid>
+                </Grid>
+            </CardContent>
+        </Card>
+);
\ No newline at end of file
index 1d53842b422bcbcb4484b6607c355d46fa5d8445..ef5fe215290e4e33dfac8988840d0cb4740ed83d 100644 (file)
@@ -24,8 +24,9 @@ import { AuthService } from "~/services/auth-service/auth-service";
 import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog';
 import { FileRemoveDialog } from '~/views-components/file-remove-dialog/file-remove-dialog';
 import { MultipleFilesRemoveDialog } from '~/views-components/file-remove-dialog/multiple-files-remove-dialog';
-import { SidePanel } from '~/views-components/side-panel/side-panel';
 import { Routes } from '~/routes/routes';
+import { SidePanel } from '~/views-components/side-panel/side-panel';
+import { ProcessPanel } from '~/views/process-panel/process-panel';
 import { Breadcrumbs } from '~/views-components/breadcrumbs/breadcrumbs';
 import { CreateProjectDialog } from '~/views-components/dialog-forms/create-project-dialog';
 import { CreateCollectionDialog } from '~/views-components/dialog-forms/create-collection-dialog';
@@ -34,10 +35,9 @@ import { UpdateCollectionDialog } from '~/views-components/dialog-forms/update-c
 import { UpdateProjectDialog } from '~/views-components/dialog-forms/update-project-dialog';
 import { MoveProjectDialog } from '~/views-components/dialog-forms/move-project-dialog';
 import { MoveCollectionDialog } from '~/views-components/dialog-forms/move-collection-dialog';
-import { ProcessPanel } from '~/views/process-panel/process-panel';
-import { UploadCollectionFilesDialog } from '~/views-components/dialog-forms/upload-collection-files-dialog';
-import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
 
+import { FilesUploadCollectionDialog } from '~/views-components/dialog-forms/files-upload-collection-dialog';
+import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
 
 const APP_BAR_HEIGHT = 100;
 
@@ -166,7 +166,7 @@ export const Workbench = withStyles(styles)(
                                     <Route path={Routes.PROJECTS} component={ProjectPanel} />
                                     <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
                                     <Route path={Routes.FAVORITES} component={FavoritePanel} />
-                                    <Route path={Routes.PROCESS} component={ProcessPanel} />
+                                    <Route path={Routes.PROCESSES} component={ProcessPanel} />
                                 </Switch>
                             </div>
                             {user && <DetailsPanel />}
@@ -179,9 +179,10 @@ export const Workbench = withStyles(styles)(
                         <PartialCopyCollectionDialog />
                         <FileRemoveDialog />
                         <CopyCollectionDialog />
+                        <FileRemoveDialog />
                         <MultipleFilesRemoveDialog />
                         <UpdateCollectionDialog />
-                        <UploadCollectionFilesDialog />
+                        <FilesUploadCollectionDialog />
                         <UpdateProjectDialog />
                         <MoveCollectionDialog />
                         <MoveProjectDialog />