Merge branch 'master'
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 30 Aug 2018 08:47:24 +0000 (10:47 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 30 Aug 2018 08:47:24 +0000 (10:47 +0200)
Feature #14099

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

22 files changed:
src/components/details-attribute/details-attribute.tsx
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/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/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/workbench/workbench.tsx

index dce08986d34d2349e67009f6bfac5db38f39ca04..78b4341d173046972385cad95e584cb735138085 100644 (file)
@@ -43,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;
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 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>;
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 a50490c9bf25ee8b68c2b0be0d6d415ef5b9112f..53c56ca2807e9b2585a5aa5aac6275041b4675fe 100644 (file)
@@ -10,6 +10,8 @@ import {
 import { ArvadosTheme } from '~/common/custom-theme';
 import { MoreOptionsIcon, ProcessIcon } from '~/components/icon/icon';
 import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
+import { Process } from '~/store/processes/process';
+import { getProcessStatus } from '../../store/processes/process';
 
 type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'headerText' | 'link' | 'content' | 'title' | 'avatar';
 
@@ -66,14 +68,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={{
@@ -83,7 +85,7 @@ export const ProcessInformationCard = withStyles(styles)(
                 avatar={<ProcessIcon className={classes.iconHeader} />}
                 action={
                     <div>
-                        <Chip label="Complete" className={classes.chip} />
+                        <Chip label={getProcessStatus(process)} className={classes.chip} />
                         <IconButton
                             aria-label="More options"
                             onClick={event => onContextMenu(event)}>
@@ -92,22 +94,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..feada3a
--- /dev/null
@@ -0,0 +1,69 @@
+// 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';
+
+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>
+        </Grid>
+        : <Grid container
+            alignItems='center'
+            justify='center'>
+            <DefaultView
+                icon={ProcessIcon}
+                messages={['Process not found']} />
+        </Grid>;
index 2c8db99479b7c0dc2c626e2f21ac9f1e7b1a0d5f..421945fed7f1756c1a6f8794b6e5e018d2a27b42 100644 (file)
@@ -3,68 +3,27 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { Grid } from '@material-ui/core';
-import { ProcessInformationCard } from '~/views-components/process-information-card/process-information-card';
-import { SubprocessesCard } from '~/views/process-panel/subprocesses-card';
-import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter';
+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 class ProcessPanel extends React.Component {
-    state = {
-        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
-            }
-        ]
+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)
     };
+};
 
-    onToggle = (filter: SubprocessFilterDataProps) => {
-        this.setState((prev: { filters: any[] }) => {
-            return {
-                filters: prev.filters.map((f: SubprocessFilterDataProps) => {
-                    if(f.key === filter.key) {
-                        return {
-                            ...filter,
-                            checked: !filter.checked
-                        };
-                    }
-                    return f;
-                })
-            };
-        });
+const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({
+    onContextMenu: (event: React.MouseEvent<HTMLElement>) => {
+        dispatch<any>(openProcessContextMenu(event));
     }
+});
 
-    render() {
-        return <Grid container spacing={16}>
-            <Grid item xs={7}>
-                <ProcessInformationCard />
-            </Grid>
-            <Grid item xs={5}>
-                <SubprocessesCard 
-                    subprocesses={4}
-                    filters={this.state.filters}
-                    onToggle={this.onToggle}
-                />
-            </Grid>
-        </Grid>;
-    }
-}
+export const ProcessPanel = connect(mapStateToProps, mapDispatchToProps)(ProcessPanelRoot);
index a0619ae346fc7eb715547da07d49373f0bc9b799..ef5fe215290e4e33dfac8988840d0cb4740ed83d 100644 (file)
@@ -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 />}