import { SearchInput } from '../search-input/search-input';
import { ArvadosTheme } from "~/common/custom-theme";
-type CssRules = 'searchBox' | "toolbar";
+type CssRules = 'searchBox' | "toolbar" | "root";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
searchBox: {
toolbar: {
paddingTop: theme.spacing.unit * 2
},
+ root: {
+ height: '100%'
+ }
});
interface DataExplorerDataProps<T> {
items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
dataTableDefaultView
} = this.props;
- return <Paper>
+ return <Paper className={classes.root}>
<Toolbar className={classes.toolbar}>
<Grid container justify="space-between" wrap="nowrap" alignItems="center">
<div className={classes.searchBox}>
import { setCurrentTokenDialogApiHost } from '~/store/current-token-dialog/current-token-dialog-actions';
import { processResourceActionSet } from '~/views-components/context-menu/action-sets/process-resource-action-set';
import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
+import { setUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions';
import { trashedCollectionActionSet } from '~/views-components/context-menu/action-sets/trashed-collection-action-set';
const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
store.subscribe(initListener(history, store, services, config));
store.dispatch(initAuth());
store.dispatch(setCurrentTokenDialogApiHost(apiHost));
+ store.dispatch(setUuidPrefix(config.uuidPrefix));
const TokenComponent = (props: any) => <ApiToken authService={services.authService} {...props} />;
const MainPanelComponent = (props: any) => <MainPanel buildInfo={buildInfo} {...props} />;
GROUP = 'j7d0g',
LOG = '57u5n',
USER = 'tpzed',
+ WORKFLOW = '7fd4e'
}
export const RESOURCE_UUID_PATTERN = '.{5}-.{5}-.{15}';
return ResourceKind.CONTAINER;
case ResourceObjectType.LOG:
return ResourceKind.LOG;
+ case ResourceObjectType.WORKFLOW:
+ return ResourceKind.WORKFLOW;
default:
return undefined;
}
import { History, Location } from 'history';
import { RootStore } from '~/store/store';
-import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute } from './routes';
+import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchWorkflowRoute } from './routes';
import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog } from '~/store/workbench/workbench-actions';
import { navigateToRootProject } from '~/store/navigation/navigation-action';
-import { navigateToSharedWithMe } from '../store/navigation/navigation-action';
-import { loadSharedWithMe } from '../store/workbench/workbench-actions';
+import { loadSharedWithMe, loadWorkflow } from '~/store/workbench/workbench-actions';
export const addRouteChangeHandlers = (history: History, store: RootStore) => {
const handler = handleLocationChange(store);
const processMatch = matchProcessRoute(pathname);
const processLogMatch = matchProcessLogRoute(pathname);
const sharedWithMeMatch = matchSharedWithMeRoute(pathname);
+ const workflowMatch = matchWorkflowRoute(pathname);
if (projectMatch) {
store.dispatch(loadProject(projectMatch.params.id));
store.dispatch(navigateToRootProject);
} else if (sharedWithMeMatch) {
store.dispatch(loadSharedWithMe);
+ } else if (workflowMatch) {
+ store.dispatch(loadWorkflow);
}
};
TRASH: '/trash',
PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`,
SHARED_WITH_ME: '/shared-with-me',
+ WORKFLOWS: '/workflows'
};
export const getResourceUrl = (uuid: string) => {
export const matchSharedWithMeRoute = (route: string) =>
matchPath(route, { path: Routes.SHARED_WITH_ME });
+
+export const matchWorkflowRoute = (route: string) =>
+ matchPath<ResourceRouteParams>(route, { path: Routes.WORKFLOWS });
import { ContainerService } from './container-service/container-service';
import { LogService } from './log-service/log-service';
import { ApiActions } from "~/services/api/api-actions";
+import { WorkflowService } from './workflow-service/workflow-service';
export type ServiceRepository = ReturnType<typeof createServices>;
const logService = new LogService(apiClient, actions);
const projectService = new ProjectService(apiClient, actions);
const userService = new UserService(apiClient, actions);
+ const workflowService = new WorkflowService(apiClient, actions);
const ancestorsService = new AncestorService(groupsService, userService);
const authService = new AuthService(apiClient, config.rootUrl, actions);
tagService,
userService,
webdavClient,
+ workflowService
};
};
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { AxiosInstance } from "axios";
+import { WorkflowResource } from '~/models/workflow';
+import { CommonResourceService } from "~/services/common-service/common-resource-service";
+import { ApiActions } from "~/services/api/api-actions";
+
+export class WorkflowService extends CommonResourceService<WorkflowResource> {
+ constructor(serverApi: AxiosInstance, actions: ApiActions) {
+ super(serverApi, "workflows", actions);
+ }
+}
dispatch<any>(navigateToFavorites);
} else if (uuid === SidePanelTreeCategory.SHARED_WITH_ME) {
dispatch(navigateToSharedWithMe);
+ } else if (uuid === SidePanelTreeCategory.WORKFLOWS) {
+ dispatch(navigateToWorkflows);
} else if (uuid === SidePanelTreeCategory.TRASH) {
dispatch(navigateToTrash);
}
export const navigateToTrash = push(Routes.TRASH);
+export const navigateToWorkflows = push(Routes.WORKFLOWS);
+
export const navigateToProject = compose(push, getProjectUrl);
export const navigateToCollection = compose(push, getCollectionUrl);
} catch (e) {
api.dispatch(couldNotFetchSharedItems());
}
-
}
}
import { TreeItemStatus } from "~/components/tree/tree";
import { getNodeAncestors, getNodeValue, getNodeAncestorsIds, getNode } from '~/models/tree';
import { ProjectResource } from '~/models/project';
-import { progressIndicatorActions } from '../progress-indicator/progress-indicator-actions';
import { OrderBuilder } from '../../services/api/order-builder';
export enum SidePanelTreeCategory {
import { Dispatch } from 'redux';
import { isSidePanelTreeCategory, SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
-import { navigateToFavorites, navigateTo, navigateToTrash, navigateToSharedWithMe } from '../navigation/navigation-action';
+import { navigateToFavorites, navigateTo, navigateToTrash, navigateToSharedWithMe, navigateToWorkflows } from '../navigation/navigation-action';
import { snackbarActions } from '~/store/snackbar/snackbar-actions';
export const navigateFromSidePanel = (id: string) =>
return navigateToTrash;
case SidePanelTreeCategory.SHARED_WITH_ME:
return navigateToSharedWithMe;
+ case SidePanelTreeCategory.WORKFLOWS:
+ return navigateToWorkflows;
default:
return sidePanelTreeCategoryNotAvailable(id);
}
import { SHARED_WITH_ME_PANEL_ID } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
+import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
+import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
const sharedWithMePanelMiddleware = dataExplorerMiddleware(
new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID)
);
+ const workflowPanelMiddleware = dataExplorerMiddleware(
+ new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID)
+ );
const middlewares: Middleware[] = [
routerMiddleware(history),
favoritePanelMiddleware,
trashPanelMiddleware,
sharedWithMePanelMiddleware,
+ workflowPanelMiddleware
];
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
return createStore(rootReducer, enhancer);
import { sharedWithMePanelActions } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
import { loadSharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel-actions';
import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
+import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
+import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel-view';
import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
import { getProgressIndicator } from '../progress-indicator/progress-indicator-reducer';
import { ResourceKind, extractUuidKind } from '~/models/resource';
dispatch(favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns }));
dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
+ dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns }));
dispatch<any>(initSidePanelTree());
if (router.location) {
const match = matchRootRoute(router.location.pathname);
await dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME));
});
+export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
+ dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.WORKFLOWS));
+ await dispatch(loadWorkflowPanel());
+ dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS));
+});
const finishLoadingProject = (project: GroupContentsResource | string) =>
async (dispatch: Dispatch<any>) => {
const uuid = typeof project === 'string' ? project : project.uuid;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ServiceRepository } from '~/services/services';
+import { MiddlewareAPI, Dispatch } from 'redux';
+import { DataExplorerMiddlewareService, dataExplorerToListParams, listResultsToDataExplorerItemsMeta } from '~/store/data-explorer/data-explorer-middleware-service';
+import { RootState } from '~/store/store';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
+import { DataExplorer, getDataExplorer } from '~/store/data-explorer/data-explorer-reducer';
+import { updateResources } from '~/store/resources/resources-actions';
+import { FilterBuilder } from '~/services/api/filter-builder';
+import { SortDirection } from '~/components/data-table/data-column';
+import { WorkflowPanelColumnNames } from '~/views/workflow-panel/workflow-panel-view';
+import { OrderDirection, OrderBuilder } from '~/services/api/order-builder';
+import { WorkflowResource } from '~/models/workflow';
+import { ListResults } from '~/services/common-service/common-resource-service';
+import { workflowPanelActions } from './workflow-panel-actions';
+
+export class WorkflowMiddlewareService extends DataExplorerMiddlewareService {
+ constructor(private services: ServiceRepository, id: string) {
+ super(id);
+ }
+
+ async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+ const state = api.getState();
+ const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
+ try {
+ const response = await this.services.workflowService.list(getParams(dataExplorer));
+ api.dispatch(updateResources(response.items));
+ api.dispatch(setItems(response));
+ } catch {
+ api.dispatch(couldNotFetchWorkflows());
+ }
+ }
+}
+
+export const getParams = (dataExplorer: DataExplorer) => ({
+ ...dataExplorerToListParams(dataExplorer),
+ order: getOrder(dataExplorer),
+ filters: getFilters(dataExplorer)
+});
+
+export const getFilters = (dataExplorer: DataExplorer) => {
+ const filters = new FilterBuilder()
+ .addILike("name", dataExplorer.searchValue)
+ .getFilters();
+ return filters;
+};
+
+export const getOrder = (dataExplorer: DataExplorer) => {
+ const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
+ const order = new OrderBuilder<WorkflowResource>();
+ if (sortColumn) {
+ const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC
+ ? OrderDirection.ASC
+ : OrderDirection.DESC;
+ const columnName = sortColumn && sortColumn.name === WorkflowPanelColumnNames.NAME ? "name" : "modifiedAt";
+ return order
+ .addOrder(sortDirection, columnName)
+ .getOrder();
+ } else {
+ return order.getOrder();
+ }
+};
+
+export const setItems = (listResults: ListResults<WorkflowResource>) =>
+ workflowPanelActions.SET_ITEMS({
+ ...listResultsToDataExplorerItemsMeta(listResults),
+ items: listResults.items.map(resource => resource.uuid),
+ });
+
+const couldNotFetchWorkflows = () =>
+ snackbarActions.OPEN_SNACKBAR({
+ message: 'Could not fetch workflows.',
+ kind: SnackbarKind.ERROR
+ });
\ No newline at end of file
--- /dev/null
+// 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 { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
+import { propertiesActions } from '~/store/properties/properties-actions';
+
+export const WORKFLOW_PANEL_ID = "workflowPanel";
+const UUID_PREFIX_PROPERTY_NAME = 'uuidPrefix';
+
+export const workflowPanelActions = bindDataExplorerActions(WORKFLOW_PANEL_ID);
+
+export const loadWorkflowPanel = () =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(workflowPanelActions.REQUEST_ITEMS());
+ };
+
+export const setUuidPrefix = (uuidPrefix: string) =>
+ propertiesActions.SET_PROPERTY({ key: UUID_PREFIX_PROPERTY_NAME, value: uuidPrefix });
+
+export const getUuidPrefix = (state: RootState) =>{
+ return state.properties.uuidPrefix;
+};
\ No newline at end of file
interface Props {
id: string;
onRowClick: (item: any) => void;
- onContextMenu: (event: React.MouseEvent<HTMLElement>, item: any) => void;
+ onContextMenu?: (event: React.MouseEvent<HTMLElement>, item: any) => void;
onRowDoubleClick: (item: any) => void;
extractKey?: (item: any) => React.Key;
}
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { Grid, Typography, withStyles } from '@material-ui/core';
+import { Grid, Typography, withStyles, Tooltip, IconButton } from '@material-ui/core';
import { FavoriteStar } from '../favorite-star/favorite-star';
import { ResourceKind, TrashableResource } from '~/models/resource';
-import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '~/components/icon/icon';
+import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, WorkflowIcon, ShareIcon } from '~/components/icon/icon';
import { formatDate, formatFileSize } from '~/common/formatters';
import { resourceLabel } from '~/common/labels';
import { connect } from 'react-redux';
import { getProcess, Process, getProcessStatus, getProcessStatusColor } from '~/store/processes/process';
import { ArvadosTheme } from '~/common/custom-theme';
import { compose } from 'redux';
+import { WorkflowResource } from '~/models/workflow';
+import { ResourceStatus } from '~/views/workflow-panel/workflow-panel-view';
+import { getUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions';
export const renderName = (item: { name: string; uuid: string, kind: string }) =>
<Grid container alignItems="center" wrap="nowrap" spacing={16}>
return <CollectionIcon />;
case ResourceKind.PROCESS:
return <ProcessIcon />;
+ case ResourceKind.WORKFLOW:
+ return <WorkflowIcon />;
default:
return <DefaultIcon />;
}
return <Typography noWrap style={{ minWidth: '100px' }}>{formatDate(date)}</Typography>;
};
+export const renderWorkflowName = (item: { name: string; uuid: string, kind: string, ownerUuid: string }) =>
+ <Grid container alignItems="center" wrap="nowrap" spacing={16}>
+ <Grid item>
+ {renderIcon(item)}
+ </Grid>
+ <Grid item>
+ <Typography color="primary" style={{ width: '100px' }}>
+ {item.name}
+ </Typography>
+ </Grid>
+ </Grid>;
+
+export const RosurceWorkflowName = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
+ return resource || { name: '', uuid: '', kind: '', ownerUuid: '' };
+ })(renderWorkflowName);
+
+const getPublicUuid = (uuidPrefix: string) => {
+ return `${uuidPrefix}-tpzed-anonymouspublic`;
+};
+
+// do share onClick
+export const resourceShare = (uuidPrefix: string, ownerUuid?: string) => {
+ return <Tooltip title="Share">
+ <IconButton onClick={() => undefined}>
+ {ownerUuid === getPublicUuid(uuidPrefix) ? <ShareIcon /> : null}
+ </IconButton>
+ </Tooltip>;
+};
+
+export const ResourceShare = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
+ const uuidPrefix = getUuidPrefix(state);
+ return {
+ ownerUuid: resource ? resource.ownerUuid : '',
+ uuidPrefix
+ };
+ })((props: { ownerUuid?: string, uuidPrefix: string }) => resourceShare(props.uuidPrefix, props.ownerUuid));
+
+export const renderWorkflowStatus = (uuidPrefix: string, ownerUuid?: string) => {
+ if (ownerUuid === getPublicUuid(uuidPrefix)) {
+ return renderStatus(ResourceStatus.PUBLIC);
+ } else {
+ return renderStatus(ResourceStatus.PRIVATE);
+ }
+};
+
+const renderStatus = (status: string) =>
+ <Typography noWrap style={{ width: '60px' }}>{status}</Typography>;
+
+export const ResourceWorkflowStatus = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
+ const uuidPrefix = getUuidPrefix(state);
+ return {
+ ownerUuid: resource ? resource.ownerUuid : '',
+ uuidPrefix
+ };
+ })((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
+
export const ResourceLastModifiedDate = connect(
(state: RootState, props: { uuid: string }) => {
const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
import { Breadcrumbs } from "~/views-components/breadcrumbs/breadcrumbs";
import { detailsPanelActions } from "~/store/details-panel/details-panel-action";
import { connect } from 'react-redux';
+import { RootState } from '~/store/store';
+import { matchWorkflowRoute } from '~/routes/routes';
interface MainContentBarProps {
onDetailsPanelToggle: () => void;
+ buttonVisible: boolean;
}
-export const MainContentBar = connect(undefined, {
- onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL
-})((props: MainContentBarProps) =>
- <Toolbar>
- <Grid container>
- <Grid container item xs alignItems="center">
- <Breadcrumbs />
- </Grid>
- <Grid item>
- <Tooltip title="Additional Info">
- <IconButton color="inherit" onClick={props.onDetailsPanelToggle}>
- <DetailsIcon />
- </IconButton>
- </Tooltip>
+const isWorkflowPath = ({ router }: RootState) => {
+ const pathname = router.location ? router.location.pathname : '';
+ const match = matchWorkflowRoute(pathname);
+ return !!match;
+};
+
+export const MainContentBar = connect((state: RootState) => ({
+ buttonVisible: !isWorkflowPath(state)
+}), {
+ onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL
+ })((props: MainContentBarProps) =>
+ <Toolbar>
+ <Grid container>
+ <Grid container item xs alignItems="center">
+ <Breadcrumbs />
+ </Grid>
+ <Grid item>
+ {props.buttonVisible ? <Tooltip title="Additional Info">
+ <IconButton color="inherit" onClick={props.onDetailsPanelToggle}>
+ <DetailsIcon />
+ </IconButton>
+ </Tooltip> : null}
+ </Grid>
</Grid>
- </Grid>
- </Toolbar>);
+ </Toolbar>);
import { SharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel';
import SplitterLayout from 'react-splitter-layout';
import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
+import { WorkflowPanel } from '~/views/workflow-panel/workflow-panel';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
<Route path={Routes.TRASH} component={TrashPanel} />
<Route path={Routes.PROCESS_LOGS} component={ProcessLogPanel} />
<Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
+ <Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
</Switch>
</Grid>
</Grid>
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { StyleRulesCallback, WithStyles, withStyles, CardContent, Tab, Tabs, Paper } from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { WorkflowIcon } from '~/components/icon/icon';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { WorkflowResource } from '~/models/workflow';
+
+export type CssRules = 'root' | 'tab';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ height: '100%',
+ },
+ tab: {
+ minWidth: '50%'
+ }
+});
+
+interface WorkflowDetailsCardDataProps {
+ workflow?: WorkflowResource;
+}
+
+type WorkflowDetailsCardProps = WorkflowDetailsCardDataProps & WithStyles<CssRules>;
+
+export const WorkflowDetailsCard = withStyles(styles)(
+ class extends React.Component<WorkflowDetailsCardProps> {
+ state = {
+ value: 0,
+ };
+
+ handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
+ this.setState({ value });
+ }
+
+ render() {
+ const { classes } = this.props;
+ const { value } = this.state;
+ return <Paper className={classes.root}>
+ <Tabs value={value} onChange={this.handleChange} centered={true}>
+ <Tab className={classes.tab} label="Description" />
+ <Tab className={classes.tab} label="Inputs" />
+ </Tabs>
+ {value === 0 && <CardContent>
+ Description
+ <DataTableDefaultView
+ icon={WorkflowIcon}
+ messages={['Please select a workflow to see its description.']} />
+ </CardContent>}
+ {value === 1 && <CardContent>
+ Inputs
+ </CardContent>}
+ </Paper>;
+ }
+ });
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
+import { WorkflowIcon } from '~/components/icon/icon';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { WORKFLOW_PANEL_ID } from '~/store/workflow-panel/workflow-panel-actions';
+import {
+ ResourceLastModifiedDate,
+ RosurceWorkflowName,
+ ResourceWorkflowStatus,
+ ResourceShare
+} from "~/views-components/data-explorer/renderers";
+import { SortDirection } from '~/components/data-table/data-column';
+import { DataColumns } from '~/components/data-table/data-table';
+import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
+import { Grid } from '@material-ui/core';
+import { WorkflowDetailsCard } from './workflow-description-card';
+
+export enum WorkflowPanelColumnNames {
+ NAME = "Name",
+ AUTHORISATION = "Authorisation",
+ LAST_MODIFIED = "Last modified",
+ SHARE = 'Share'
+}
+
+export interface WorkflowPanelFilter extends DataTableFilterItem {
+ type: ResourceStatus;
+}
+
+interface WorkflowPanelDataProps {
+ handleRowDoubleClick: any;
+ handleRowClick: any;
+}
+
+export enum ResourceStatus {
+ PUBLIC = "Public",
+ PRIVATE = "Private",
+ SHARED = "Shared"
+}
+
+const resourceStatus = (type: string) => {
+ switch (type) {
+ case ResourceStatus.PUBLIC:
+ return "Public";
+ case ResourceStatus.PRIVATE:
+ return "Private";
+ case ResourceStatus.SHARED:
+ return "Shared";
+ default:
+ return "Unknown";
+ }
+};
+
+export const workflowPanelColumns: DataColumns<string, WorkflowPanelFilter> = [
+ {
+ name: WorkflowPanelColumnNames.NAME,
+ selected: true,
+ configurable: true,
+ sortDirection: SortDirection.ASC,
+ filters: [],
+ render: (uuid: string) => <RosurceWorkflowName uuid={uuid} />
+ },
+ {
+ name: WorkflowPanelColumnNames.AUTHORISATION,
+ selected: true,
+ configurable: true,
+ filters: [
+ {
+ name: resourceStatus(ResourceStatus.PUBLIC),
+ selected: true,
+ type: ResourceStatus.PUBLIC
+ },
+ {
+ name: resourceStatus(ResourceStatus.PRIVATE),
+ selected: true,
+ type: ResourceStatus.PRIVATE
+ },
+ {
+ name: resourceStatus(ResourceStatus.SHARED),
+ selected: true,
+ type: ResourceStatus.SHARED
+ }
+ ],
+ render: (uuid: string) => <ResourceWorkflowStatus uuid={uuid} />,
+ },
+ {
+ name: WorkflowPanelColumnNames.LAST_MODIFIED,
+ selected: true,
+ configurable: true,
+ sortDirection: SortDirection.NONE,
+ filters: [],
+ render: (uuid: string) => <ResourceLastModifiedDate uuid={uuid} />
+ },
+ {
+ name: '',
+ selected: true,
+ configurable: false,
+ filters: [],
+ render: (uuid: string) => <ResourceShare uuid={uuid} />
+ }
+];
+
+export const WorkflowPanelView = ({...props}) => {
+ return <Grid container spacing={16}>
+ <Grid item xs={6}>
+ <DataExplorer
+ id={WORKFLOW_PANEL_ID}
+ onRowClick={props.handleRowClick}
+ onRowDoubleClick={props.handleRowDoubleClick}
+ contextMenuColumn={false}
+ dataTableDefaultView={<DataTableDefaultView icon={WorkflowIcon} />} />
+ </Grid>
+ <Grid item xs={6}>
+ <WorkflowDetailsCard />
+ </Grid>
+ </Grid>;
+};
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dispatch } from "redux";
+import { connect } from "react-redux";
+import { navigateTo } from '~/store/navigation/navigation-action';
+import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { WorkflowPanelView } from '~/views/workflow-panel/workflow-panel-view';
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+
+ handleRowDoubleClick: (uuid: string) => {
+ dispatch<any>(navigateTo(uuid));
+ },
+
+ handleRowClick: (uuid: string) => {
+ dispatch(loadDetailsPanel(uuid));
+ }
+});
+
+export const WorkflowPanel= connect(undefined, mapDispatchToProps)(
+ (props) => <WorkflowPanelView {...props}/>);
\ No newline at end of file