From e0dfdad390b88924f15e4f6e7d8e7eadd585e06f Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Mon, 3 Jun 2024 10:48:34 -0400 Subject: [PATCH] 21225: Add project workflow runs tab. * Moves loadMissingProcessInformation to project panel run middleware * Removes processes from data tab * Removes processes from data tab filters * Adds process type filter serializer for runs tab * Removes process-related columns from data tab Arvados-DCO-1.1-Signed-off-by: Stephen Smith --- .../favorite-panel-middleware-service.ts | 2 +- .../processes/processes-middleware-service.ts | 2 +- .../project-panel-action-bind.ts | 3 + .../project-panel/project-panel-action.ts | 5 +- .../project-panel-data-middleware-service.ts | 22 +- .../project-panel-run-middleware-service.ts | 165 +++++++++++++++ .../resource-type-filters.ts | 48 +++++ .../shared-with-me-middleware-service.ts | 2 +- services/workbench2/src/store/store.ts | 5 +- .../src/store/workbench/workbench-actions.ts | 4 +- .../project-panel/project-panel-data.tsx | 63 ++---- .../views/project-panel/project-panel-run.tsx | 200 ++++++++++++++++++ .../src/views/project-panel/project-panel.tsx | 9 + 13 files changed, 459 insertions(+), 71 deletions(-) create mode 100644 services/workbench2/src/store/project-panel/project-panel-run-middleware-service.ts create mode 100644 services/workbench2/src/views/project-panel/project-panel-run.tsx diff --git a/services/workbench2/src/store/favorite-panel/favorite-panel-middleware-service.ts b/services/workbench2/src/store/favorite-panel/favorite-panel-middleware-service.ts index f61ec39240..556b795ad0 100644 --- a/services/workbench2/src/store/favorite-panel/favorite-panel-middleware-service.ts +++ b/services/workbench2/src/store/favorite-panel/favorite-panel-middleware-service.ts @@ -16,7 +16,7 @@ import { resourcesActions } from "store/resources/resources-actions"; import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions'; import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions'; import { getDataExplorer } from "store/data-explorer/data-explorer-reducer"; -import { loadMissingProcessesInformation } from "store/project-panel/project-panel-data-middleware-service"; +import { loadMissingProcessesInformation } from "store/project-panel/project-panel-run-middleware-service"; import { getDataExplorerColumnFilters } from 'store/data-explorer/data-explorer-middleware-service'; import { serializeSimpleObjectTypeFilters } from '../resource-type-filters/resource-type-filters'; import { ResourceKind } from "models/resource"; diff --git a/services/workbench2/src/store/processes/processes-middleware-service.ts b/services/workbench2/src/store/processes/processes-middleware-service.ts index f1e0936b5b..9b4ecb46c9 100644 --- a/services/workbench2/src/store/processes/processes-middleware-service.ts +++ b/services/workbench2/src/store/processes/processes-middleware-service.ts @@ -19,7 +19,7 @@ import { DataColumns } from 'components/data-table/data-table'; import { ProcessStatusFilter, buildProcessStatusFilters } from '../resource-type-filters/resource-type-filters'; import { ContainerRequestResource, containerRequestFieldsNoMounts } from 'models/container-request'; import { progressIndicatorActions } from '../progress-indicator/progress-indicator-actions'; -import { loadMissingProcessesInformation } from '../project-panel/project-panel-data-middleware-service'; +import { loadMissingProcessesInformation } from '../project-panel/project-panel-run-middleware-service'; export class ProcessesMiddlewareService extends DataExplorerMiddlewareService { constructor(private services: ServiceRepository, private actions: BoundDataExplorerActions, id: string) { diff --git a/services/workbench2/src/store/project-panel/project-panel-action-bind.ts b/services/workbench2/src/store/project-panel/project-panel-action-bind.ts index 598f6f7442..404f8ccfda 100644 --- a/services/workbench2/src/store/project-panel/project-panel-action-bind.ts +++ b/services/workbench2/src/store/project-panel/project-panel-action-bind.ts @@ -9,3 +9,6 @@ import { bindDataExplorerActions } from "store/data-explorer/data-explorer-actio export const PROJECT_PANEL_DATA_ID = "projectPanelData"; export const projectPanelDataActions = bindDataExplorerActions(PROJECT_PANEL_DATA_ID); + +export const PROJECT_PANEL_RUN_ID = "projectPanelRun"; +export const projectPanelRunActions = bindDataExplorerActions(PROJECT_PANEL_RUN_ID); diff --git a/services/workbench2/src/store/project-panel/project-panel-action.ts b/services/workbench2/src/store/project-panel/project-panel-action.ts index af961a8dab..38605752a7 100644 --- a/services/workbench2/src/store/project-panel/project-panel-action.ts +++ b/services/workbench2/src/store/project-panel/project-panel-action.ts @@ -7,7 +7,7 @@ import { propertiesActions } from "store/properties/properties-actions"; import { RootState } from "store/store"; import { getProperty } from "store/properties/properties"; import { loadProject } from "store/workbench/workbench-actions"; -import { projectPanelDataActions } from "store/project-panel/project-panel-action-bind"; +import { projectPanelRunActions, projectPanelDataActions } from "store/project-panel/project-panel-action-bind"; export const PROJECT_PANEL_CURRENT_UUID = "projectPanelCurrentUuid"; export const IS_PROJECT_PANEL_TRASHED = "isProjectPanelTrashed"; @@ -18,6 +18,9 @@ export const openProjectPanel = (projectUuid: string) => async (dispatch: Dispat dispatch(projectPanelDataActions.RESET_EXPLORER_SEARCH_VALUE()); dispatch(projectPanelDataActions.REQUEST_ITEMS()); + + dispatch(projectPanelRunActions.RESET_EXPLORER_SEARCH_VALUE()); + dispatch(projectPanelRunActions.REQUEST_ITEMS()); }; export const getProjectPanelCurrentUuid = (state: RootState) => getProperty(PROJECT_PANEL_CURRENT_UUID)(state.properties); diff --git a/services/workbench2/src/store/project-panel/project-panel-data-middleware-service.ts b/services/workbench2/src/store/project-panel/project-panel-data-middleware-service.ts index 6e599f5c34..98f2625cfa 100644 --- a/services/workbench2/src/store/project-panel/project-panel-data-middleware-service.ts +++ b/services/workbench2/src/store/project-panel/project-panel-data-middleware-service.ts @@ -27,14 +27,11 @@ import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions"; import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions"; import { DataExplorer, getDataExplorer } from "store/data-explorer/data-explorer-reducer"; import { ListResults } from "services/common-service/common-service"; -import { loadContainers } from "store/processes/processes-actions"; -import { ResourceKind } from "models/resource"; import { getSortColumn } from "store/data-explorer/data-explorer-reducer"; import { serializeResourceTypeFilters, buildProcessStatusFilters } from "store/resource-type-filters/resource-type-filters"; import { updatePublicFavorites } from "store/public-favorites/public-favorites-actions"; import { selectedFieldsOfGroup } from "models/group"; import { defaultCollectionSelectedFields } from "models/collection"; -import { containerRequestFieldsNoMounts } from "models/container-request"; import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set"; import { removeDisabledButton } from "store/multiselect/multiselect-actions"; import { dataExplorerActions } from "store/data-explorer/data-explorer-action"; @@ -62,7 +59,6 @@ export class ProjectPanelDataMiddlewareService extends DataExplorerMiddlewareSer api.dispatch(updateFavorites(resourceUuids)); api.dispatch(updatePublicFavorites(resourceUuids)); api.dispatch(updateResources(response.items)); - await api.dispatch(loadMissingProcessesInformation(response.items)); api.dispatch(setItems(response)); } catch (e) { api.dispatch( @@ -89,17 +85,6 @@ export class ProjectPanelDataMiddlewareService extends DataExplorerMiddlewareSer } } -export const loadMissingProcessesInformation = (resources: GroupContentsResource[]) => async (dispatch: Dispatch) => { - const containerUuids = resources.reduce((uuids, resource) => { - return resource.kind === ResourceKind.CONTAINER_REQUEST && resource.containerUuid && !uuids.includes(resource.containerUuid) - ? [...uuids, resource.containerUuid] - : uuids; - }, [] as string[]); - if (containerUuids.length > 0) { - await dispatch(loadContainers(containerUuids, false)); - } -}; - export const setItems = (listResults: ListResults) => projectPanelDataActions.SET_ITEMS({ ...listResultsToDataExplorerItemsMeta(listResults), @@ -111,7 +96,7 @@ export const getParams = (dataExplorer: DataExplorer, isProjectTrashed: boolean) order: getOrder(dataExplorer), filters: getFilters(dataExplorer), includeTrash: isProjectTrashed, - select: selectedFieldsOfGroup.concat(defaultCollectionSelectedFields, containerRequestFieldsNoMounts), + select: selectedFieldsOfGroup.concat(defaultCollectionSelectedFields), }); export const getFilters = (dataExplorer: DataExplorer) => { @@ -123,7 +108,6 @@ export const getFilters = (dataExplorer: DataExplorer) => { // TODO: Extract group contents name filter const nameFilters = new FilterBuilder() .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION) - .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROCESS) .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT) .getFilters(); @@ -142,9 +126,9 @@ const getOrder = (dataExplorer: DataExplorer) => { // Use createdAt as a secondary sort column so we break ties consistently. return order .addOrder(sortDirection, sortColumn.sort.field, GroupContentsResourcePrefix.COLLECTION) - .addOrder(sortDirection, sortColumn.sort.field, GroupContentsResourcePrefix.PROCESS) .addOrder(sortDirection, sortColumn.sort.field, GroupContentsResourcePrefix.PROJECT) - .addOrder(OrderDirection.DESC, "createdAt", GroupContentsResourcePrefix.PROCESS) + .addOrder(sortDirection, sortColumn.sort.field, GroupContentsResourcePrefix.WORKFLOW) + .addOrder(OrderDirection.DESC, "createdAt") .getOrder(); } else { return order.getOrder(); diff --git a/services/workbench2/src/store/project-panel/project-panel-run-middleware-service.ts b/services/workbench2/src/store/project-panel/project-panel-run-middleware-service.ts new file mode 100644 index 0000000000..eba8530dca --- /dev/null +++ b/services/workbench2/src/store/project-panel/project-panel-run-middleware-service.ts @@ -0,0 +1,165 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { + DataExplorerMiddlewareService, + dataExplorerToListParams, + getDataExplorerColumnFilters, + listResultsToDataExplorerItemsMeta, +} from "store/data-explorer/data-explorer-middleware-service"; +import { ProjectPanelRunColumnNames } from "views/project-panel/project-panel-run"; +import { RootState } from "store/store"; +import { DataColumns } from "components/data-table/data-table"; +import { ServiceRepository } from "services/services"; +import { SortDirection } from "components/data-table/data-column"; +import { OrderBuilder, OrderDirection } from "services/api/order-builder"; +import { FilterBuilder, joinFilters } from "services/api/filter-builder"; +import { GroupContentsResource, GroupContentsResourcePrefix } from "services/groups-service/groups-service"; +import { updateFavorites } from "store/favorites/favorites-actions"; +import { IS_PROJECT_PANEL_TRASHED, getProjectPanelCurrentUuid } from "store/project-panel/project-panel-action"; +import { projectPanelRunActions } from "store/project-panel/project-panel-action-bind"; +import { Dispatch, MiddlewareAPI } from "redux"; +import { ProjectResource } from "models/project"; +import { updateResources } from "store/resources/resources-actions"; +import { getProperty } from "store/properties/properties"; +import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions"; +import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions"; +import { DataExplorer, getDataExplorer } from "store/data-explorer/data-explorer-reducer"; +import { ListResults } from "services/common-service/common-service"; +import { loadContainers } from "store/processes/processes-actions"; +import { ResourceKind } from "models/resource"; +import { getSortColumn } from "store/data-explorer/data-explorer-reducer"; +import { buildProcessStatusFilters, serializeProcessTypeGroupContentsFilters } from "store/resource-type-filters/resource-type-filters"; +import { updatePublicFavorites } from "store/public-favorites/public-favorites-actions"; +import { containerRequestFieldsNoMounts } from "models/container-request"; +import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set"; +import { removeDisabledButton } from "store/multiselect/multiselect-actions"; +import { dataExplorerActions } from "store/data-explorer/data-explorer-action"; + +export class ProjectPanelRunMiddlewareService extends DataExplorerMiddlewareService { + constructor(private services: ServiceRepository, id: string) { + super(id); + } + + async requestItems(api: MiddlewareAPI, criteriaChanged?: boolean, background?: boolean) { + const state = api.getState(); + const dataExplorer = getDataExplorer(state.dataExplorer, this.getId()); + const projectUuid = getProjectPanelCurrentUuid(state); + const isProjectTrashed = getProperty(IS_PROJECT_PANEL_TRASHED)(state.properties); + if (!projectUuid) { + api.dispatch(projectPanelCurrentUuidIsNotSet()); + } else if (!dataExplorer) { + api.dispatch(projectPanelDataExplorerIsNotSet()); + } else { + try { + api.dispatch(dataExplorerActions.SET_IS_NOT_FOUND({ id: this.id, isNotFound: false })); + if (!background) { api.dispatch(progressIndicatorActions.START_WORKING(this.getId())); } + const containerRequests = await this.services.groupsService.contents(projectUuid, getParams(dataExplorer, projectUuid, !!isProjectTrashed)); + const resourceUuids = containerRequests.items.map(item => item.uuid); + api.dispatch(updateFavorites(resourceUuids)); + api.dispatch(updatePublicFavorites(resourceUuids)); + api.dispatch(updateResources(containerRequests.items)); + await api.dispatch(loadMissingProcessesInformation(containerRequests.items)); + api.dispatch(setItems(containerRequests)); + } catch (e) { + api.dispatch( + projectPanelRunActions.SET_ITEMS({ + items: [], + itemsAvailable: 0, + page: 0, + rowsPerPage: dataExplorer.rowsPerPage, + }) + ); + if (e.status === 404) { + api.dispatch(dataExplorerActions.SET_IS_NOT_FOUND({ id: this.id, isNotFound: true})); + } + else { + api.dispatch(couldNotFetchProjectContents()); + } + } finally { + if (!background) { + api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId())); + api.dispatch(removeDisabledButton(ContextMenuActionNames.MOVE_TO_TRASH)) + } + } + } + } +} + +export const loadMissingProcessesInformation = (resources: GroupContentsResource[]) => async (dispatch: Dispatch) => { + const containerUuids = resources.reduce((uuids, resource) => { + return resource.kind === ResourceKind.CONTAINER_REQUEST && resource.containerUuid && !uuids.includes(resource.containerUuid) + ? [...uuids, resource.containerUuid] + : uuids; + }, [] as string[]); + if (containerUuids.length > 0) { + await dispatch(loadContainers(containerUuids, false)); + } +}; + +export const setItems = (listResults: ListResults) => + projectPanelRunActions.SET_ITEMS({ + ...listResultsToDataExplorerItemsMeta(listResults), + items: listResults.items.map(resource => resource.uuid), + }); + +export const getParams = (dataExplorer: DataExplorer, projectUuid: string, isProjectTrashed: boolean) => ({ + ...dataExplorerToListParams(dataExplorer), + order: getOrder(dataExplorer), + filters: getFilters(dataExplorer, projectUuid), + includeTrash: isProjectTrashed, + select: containerRequestFieldsNoMounts, +}); + +export const getFilters = (dataExplorer: DataExplorer, projectUuid: string) => { + const columns = dataExplorer.columns as DataColumns; + const typeFilters = serializeProcessTypeGroupContentsFilters(getDataExplorerColumnFilters(columns, ProjectPanelRunColumnNames.TYPE)); + const statusColumnFilters = getDataExplorerColumnFilters(columns, ProjectPanelRunColumnNames.STATUS); + const activeStatusFilter = Object.keys(statusColumnFilters).find(filterName => statusColumnFilters[filterName].selected); + + // TODO: Extract group contents name filter + const nameFilters = new FilterBuilder() + .addEqual('owner_uuid', projectUuid) + .addILike("name", dataExplorer.searchValue) + .getFilters(); + + // Filter by container status + const statusFilters = buildProcessStatusFilters(new FilterBuilder(), activeStatusFilter || "", GroupContentsResourcePrefix.PROCESS).getFilters(); + + return joinFilters(statusFilters, typeFilters, nameFilters); +}; + +const getOrder = (dataExplorer: DataExplorer) => { + const sortColumn = getSortColumn(dataExplorer); + const order = new OrderBuilder(); + if (sortColumn && sortColumn.sort) { + const sortDirection = sortColumn.sort.direction === SortDirection.ASC ? OrderDirection.ASC : OrderDirection.DESC; + + // Use createdAt as a secondary sort column so we break ties consistently. + return order + .addOrder(sortDirection, sortColumn.sort.field, GroupContentsResourcePrefix.PROCESS) + .addOrder(OrderDirection.DESC, "createdAt") + .getOrder(); + } else { + return order.getOrder(); + } +}; + +const projectPanelCurrentUuidIsNotSet = () => + snackbarActions.OPEN_SNACKBAR({ + message: "Project panel is not opened.", + kind: SnackbarKind.ERROR, + }); + +const couldNotFetchProjectContents = () => + snackbarActions.OPEN_SNACKBAR({ + message: "Could not fetch project contents.", + kind: SnackbarKind.ERROR, + }); + +const projectPanelDataExplorerIsNotSet = () => + snackbarActions.OPEN_SNACKBAR({ + message: "Project panel is not ready.", + kind: SnackbarKind.ERROR, + }); diff --git a/services/workbench2/src/store/resource-type-filters/resource-type-filters.ts b/services/workbench2/src/store/resource-type-filters/resource-type-filters.ts index 791d7e8a4a..b143b010b3 100644 --- a/services/workbench2/src/store/resource-type-filters/resource-type-filters.ts +++ b/services/workbench2/src/store/resource-type-filters/resource-type-filters.ts @@ -93,6 +93,30 @@ export const getInitialResourceTypeFilters = pipe( ); +/** + * Resource type filters for Data tab (excludes main/sub process runs) + */ +export const getInitialDataResourceTypeFilters = pipe( + (): DataTableFilters => createTree(), + pipe( + initFilter(ObjectTypeFilter.PROJECT, '', true, true), + initFilter(GroupTypeFilter.PROJECT, ObjectTypeFilter.PROJECT), + initFilter(GroupTypeFilter.FILTER_GROUP, ObjectTypeFilter.PROJECT), + ), + pipe( + initFilter(ObjectTypeFilter.WORKFLOW, '', false, true), + initFilter(ObjectTypeFilter.DEFINITION, ObjectTypeFilter.WORKFLOW), + ), + pipe( + initFilter(ObjectTypeFilter.COLLECTION, '', true, true), + initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION), + initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION), + initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION, false), + initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION, false), + ), + +); + // Using pipe() with more than 7 arguments makes the return type be 'any', // causing compile issues. export const getInitialSearchTypeFilters = pipe( @@ -283,6 +307,9 @@ const buildProcessTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilde } }; +/** + * Serializes general resource type filters with prefix for group contents API + */ export const serializeResourceTypeFilters = pipe( createFiltersBuilder, serializeObjectTypeFilters, @@ -305,6 +332,27 @@ export const serializeOnlyProcessTypeFilters = pipe( ({ fb }) => fb.getFilters(), ); +/** + * Serializes process type filters with prefix for group contents request + * Uses buildProcessTypeFilters to disable filters when no process type is selected + */ +export const serializeProcessTypeGroupContentsFilters = pipe( + createFiltersBuilder, + ({fb, selectedFilters }): ReturnType => ({ + fb: fb.addIsA('uuid', [ResourceKind.PROCESS]), + selectedFilters, + }), + ({ fb, selectedFilters }: ReturnType) => pipe( + () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters), + filters => filters, + mappedFilters => ({ + fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: true }), + selectedFilters + }) + )(), + ({ fb }) => fb.getFilters(), +); + export const serializeSimpleObjectTypeFilters = (filters: Tree) => { return getSelectedNodes(filters) .map(f => f.id) diff --git a/services/workbench2/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts b/services/workbench2/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts index af45b42a94..55ff6276ac 100644 --- a/services/workbench2/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts +++ b/services/workbench2/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts @@ -9,7 +9,7 @@ import { RootState } from 'store/store'; import { getDataExplorer, DataExplorer } from 'store/data-explorer/data-explorer-reducer'; import { updateFavorites } from 'store/favorites/favorites-actions'; import { updateResources } from 'store/resources/resources-actions'; -import { loadMissingProcessesInformation } from 'store/project-panel/project-panel-data-middleware-service'; +import { loadMissingProcessesInformation } from 'store/project-panel/project-panel-run-middleware-service'; import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions'; import { sharedWithMePanelActions } from './shared-with-me-panel-actions'; import { ListResults } from 'services/common-service/common-service'; diff --git a/services/workbench2/src/store/store.ts b/services/workbench2/src/store/store.ts index 990e278687..cf86e0f61c 100644 --- a/services/workbench2/src/store/store.ts +++ b/services/workbench2/src/store/store.ts @@ -20,8 +20,9 @@ import { collectionPanelFilesReducer } from "./collection-panel/collection-panel import { dataExplorerMiddleware } from "./data-explorer/data-explorer-middleware"; import { FAVORITE_PANEL_ID } from "./favorite-panel/favorite-panel-action"; import { WORKFLOW_PROCESSES_PANEL_ID } from "./workflow-panel/workflow-panel-actions"; -import { PROJECT_PANEL_DATA_ID } from "./project-panel/project-panel-action-bind"; +import { PROJECT_PANEL_DATA_ID, PROJECT_PANEL_RUN_ID } from "./project-panel/project-panel-action-bind"; import { ProjectPanelDataMiddlewareService } from "./project-panel/project-panel-data-middleware-service"; +import { ProjectPanelRunMiddlewareService } from "./project-panel/project-panel-run-middleware-service"; import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-middleware-service"; import { AllProcessesPanelMiddlewareService } from "./all-processes-panel/all-processes-panel-middleware-service"; import { WorkflowProcessesMiddlewareService } from "./workflow-panel/workflow-middleware-service"; @@ -97,6 +98,7 @@ export function configureStore(history: History, services: ServiceRepository, co const rootReducer = createRootReducer(services); const projectPanelDataMiddleware = dataExplorerMiddleware(new ProjectPanelDataMiddlewareService(services, PROJECT_PANEL_DATA_ID)); + const projectPanelRunMiddleware = dataExplorerMiddleware(new ProjectPanelRunMiddlewareService(services, PROJECT_PANEL_RUN_ID)); const favoritePanelMiddleware = dataExplorerMiddleware(new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID)); const allProcessessPanelMiddleware = dataExplorerMiddleware(new AllProcessesPanelMiddlewareService(services, ALL_PROCESSES_PANEL_ID)); const workflowProcessessPanelMiddleware = dataExplorerMiddleware(new WorkflowProcessesMiddlewareService(services, WORKFLOW_PROCESSES_PANEL_ID)); @@ -139,6 +141,7 @@ export function configureStore(history: History, services: ServiceRepository, co authMiddleware(services), tooltipsMiddleware(services), projectPanelDataMiddleware, + projectPanelRunMiddleware, favoritePanelMiddleware, allProcessessPanelMiddleware, trashPanelMiddleware, diff --git a/services/workbench2/src/store/workbench/workbench-actions.ts b/services/workbench2/src/store/workbench/workbench-actions.ts index 02d94c56bb..bd74b3b07d 100644 --- a/services/workbench2/src/store/workbench/workbench-actions.ts +++ b/services/workbench2/src/store/workbench/workbench-actions.ts @@ -9,7 +9,7 @@ import { loadDetailsPanel } from "store/details-panel/details-panel-action"; import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions"; import { favoritePanelActions, loadFavoritePanel } from "store/favorite-panel/favorite-panel-action"; import { getProjectPanelCurrentUuid, setIsProjectPanelTrashed } from "store/project-panel/project-panel-action"; -import { projectPanelDataActions } from "store/project-panel/project-panel-action-bind"; +import { projectPanelDataActions, projectPanelRunActions } from "store/project-panel/project-panel-action-bind"; import { activateSidePanelTreeItem, initSidePanelTree, @@ -19,6 +19,7 @@ import { } from "store/side-panel-tree/side-panel-tree-actions"; import { updateResources } from "store/resources/resources-actions"; import { projectPanelDataColumns } from "views/project-panel/project-panel-data"; +import { projectPanelRunColumns } from "views/project-panel/project-panel-run"; import { favoritePanelColumns } from "views/favorite-panel/favorite-panel"; import { matchRootRoute } from "routes/routes"; import { @@ -135,6 +136,7 @@ export const loadWorkbench = () => async (dispatch: Dispatch, getState: () => Ro const { user } = auth; if (user) { dispatch(projectPanelDataActions.SET_COLUMNS({ columns: projectPanelDataColumns })); + dispatch(projectPanelRunActions.SET_COLUMNS({ columns: projectPanelRunColumns })); dispatch(favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns })); dispatch( allProcessesPanelActions.SET_COLUMNS({ diff --git a/services/workbench2/src/views/project-panel/project-panel-data.tsx b/services/workbench2/src/views/project-panel/project-panel-data.tsx index ed127f217c..59c6f22506 100644 --- a/services/workbench2/src/views/project-panel/project-panel-data.tsx +++ b/services/workbench2/src/views/project-panel/project-panel-data.tsx @@ -10,8 +10,22 @@ import { DataExplorer } from "views-components/data-explorer/data-explorer"; import { ProjectResource } from 'models/project'; import { SortDirection } from "components/data-table/data-column"; import { createTree } from "models/tree"; -import { ContainerRunTime, ResourceContainerUuid, ResourceCreatedAtDate, ResourceDeleteDate, ResourceFileCount, ResourceFileSize, ResourceLastModifiedDate, ResourceLogUuid, ResourceModifiedByUserUuid, ResourceName, ResourceOutputUuid, ResourceOwnerWithName, ResourceParentProcess, ResourcePortableDataHash, ResourceStatus, ResourceTrashDate, ResourceType, ResourceUUID, ResourceVersion } from "views-components/data-explorer/renderers"; -import { getInitialProcessStatusFilters, getInitialResourceTypeFilters } from "store/resource-type-filters/resource-type-filters"; +import { + ResourceCreatedAtDate, + ResourceDeleteDate, + ResourceFileCount, + ResourceFileSize, + ResourceLastModifiedDate, + ResourceModifiedByUserUuid, + ResourceName, + ResourceOwnerWithName, + ResourcePortableDataHash, + ResourceTrashDate, + ResourceType, + ResourceUUID, + ResourceVersion, +} from "views-components/data-explorer/renderers"; +import { getInitialDataResourceTypeFilters } from "store/resource-type-filters/resource-type-filters"; export enum ProjectPanelDataColumnNames { NAME = 'Name', @@ -44,19 +58,11 @@ export const projectPanelDataColumns: DataColumns = [ filters: createTree(), render: (uuid) => , }, - { - name: ProjectPanelDataColumnNames.STATUS, - selected: true, - configurable: true, - mutuallyExclusiveFilters: true, - filters: getInitialProcessStatusFilters(), - render: (uuid) => , - }, { name: ProjectPanelDataColumnNames.TYPE, selected: true, configurable: true, - filters: getInitialResourceTypeFilters(), + filters: getInitialDataResourceTypeFilters(), render: (uuid) => , }, { @@ -94,41 +100,6 @@ export const projectPanelDataColumns: DataColumns = [ filters: createTree(), render: (uuid) => , }, - { - name: ProjectPanelDataColumnNames.CONTAINER_UUID, - selected: false, - configurable: true, - filters: createTree(), - render: (uuid) => , - }, - { - name: ProjectPanelDataColumnNames.RUNTIME, - selected: false, - configurable: true, - filters: createTree(), - render: (uuid) => , - }, - { - name: ProjectPanelDataColumnNames.OUTPUT_UUID, - selected: false, - configurable: true, - filters: createTree(), - render: (uuid) => , - }, - { - name: ProjectPanelDataColumnNames.LOG_UUID, - selected: false, - configurable: true, - filters: createTree(), - render: (uuid) => , - }, - { - name: ProjectPanelDataColumnNames.PARENT_PROCESS, - selected: false, - configurable: true, - filters: createTree(), - render: (uuid) => , - }, { name: ProjectPanelDataColumnNames.MODIFIED_BY_USER_UUID, selected: false, diff --git a/services/workbench2/src/views/project-panel/project-panel-run.tsx b/services/workbench2/src/views/project-panel/project-panel-run.tsx new file mode 100644 index 0000000000..94d492e735 --- /dev/null +++ b/services/workbench2/src/views/project-panel/project-panel-run.tsx @@ -0,0 +1,200 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from "react"; +import { ProjectIcon } from "components/icon/icon"; +import { PROJECT_PANEL_RUN_ID } from "store/project-panel/project-panel-action-bind"; +import { DataColumns } from 'components/data-table/data-table'; +import { DataExplorer } from "views-components/data-explorer/data-explorer"; +import { ProjectResource } from 'models/project'; +import { SortDirection } from "components/data-table/data-column"; +import { createTree } from "models/tree"; +import { + ContainerRunTime, + ResourceContainerUuid, + ResourceCreatedAtDate, + ResourceDeleteDate, + ResourceLastModifiedDate, + ResourceLogUuid, + ResourceModifiedByUserUuid, + ResourceName, + ResourceOutputUuid, + ResourceOwnerWithName, + ResourceParentProcess, + ResourceStatus, + ResourceTrashDate, + ResourceType, + ResourceUUID, +} from "views-components/data-explorer/renderers"; +import { getInitialProcessStatusFilters, getInitialProcessTypeFilters } from "store/resource-type-filters/resource-type-filters"; +import { connect } from "react-redux"; +import { RootState } from "store/store"; +import { getProjectPanelCurrentUuid } from "store/project-panel/project-panel-action"; +import { getResource } from "store/resources/resources"; + +export enum ProjectPanelRunColumnNames { + NAME = 'Name', + STATUS = 'Status', + TYPE = 'Type', + OWNER = 'Owner', + PORTABLE_DATA_HASH = 'Portable Data Hash', + FILE_SIZE = 'File Size', + FILE_COUNT = 'File Count', + UUID = 'UUID', + CONTAINER_UUID = 'Container UUID', + RUNTIME = 'Runtime', + OUTPUT_UUID = 'Output UUID', + LOG_UUID = 'Log UUID', + PARENT_PROCESS = 'Parent Process UUID', + MODIFIED_BY_USER_UUID = 'Modified by User UUID', + VERSION = 'Version', + CREATED_AT = 'Date Created', + LAST_MODIFIED = 'Last Modified', + TRASH_AT = 'Trash at', + DELETE_AT = 'Delete at', +} + +export const projectPanelRunColumns: DataColumns = [ + { + name: ProjectPanelRunColumnNames.NAME, + selected: true, + configurable: true, + sort: { direction: SortDirection.NONE, field: 'name' }, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.STATUS, + selected: true, + configurable: true, + mutuallyExclusiveFilters: true, + filters: getInitialProcessStatusFilters(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.TYPE, + selected: true, + configurable: true, + filters: getInitialProcessTypeFilters(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.OWNER, + selected: false, + configurable: true, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.UUID, + selected: false, + configurable: true, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.CONTAINER_UUID, + selected: false, + configurable: true, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.RUNTIME, + selected: true, + configurable: true, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.OUTPUT_UUID, + selected: false, + configurable: true, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.LOG_UUID, + selected: false, + configurable: true, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.PARENT_PROCESS, + selected: false, + configurable: true, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.MODIFIED_BY_USER_UUID, + selected: false, + configurable: true, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.CREATED_AT, + selected: false, + configurable: true, + sort: { direction: SortDirection.NONE, field: 'createdAt' }, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.LAST_MODIFIED, + selected: true, + configurable: true, + sort: { direction: SortDirection.DESC, field: 'modifiedAt' }, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.TRASH_AT, + selected: false, + configurable: true, + sort: { direction: SortDirection.NONE, field: 'trashAt' }, + filters: createTree(), + render: (uuid) => , + }, + { + name: ProjectPanelRunColumnNames.DELETE_AT, + selected: false, + configurable: true, + sort: { direction: SortDirection.NONE, field: 'deleteAt' }, + filters: createTree(), + render: (uuid) => , + }, +]; + +const DEFAULT_VIEW_MESSAGES = ['No workflow runs found']; + +interface ProjectPanelRunProps { + project?: ProjectResource; +} + +const mapStateToProps = (state: RootState): ProjectPanelRunProps => { + const projectUuid = getProjectPanelCurrentUuid(state) || ""; + const project = getResource(projectUuid)(state.resources); + return { + project, + }; +}; + +export const ProjectPanelRun = connect(mapStateToProps)((props: ProjectPanelRunProps) => { + const handleRowClick = () => {}; + const handleRowDoubleClick = () => {}; + const handleContextMenu = () => {}; + + return ; +}); diff --git a/services/workbench2/src/views/project-panel/project-panel.tsx b/services/workbench2/src/views/project-panel/project-panel.tsx index c77858cd0c..fe01a9344f 100644 --- a/services/workbench2/src/views/project-panel/project-panel.tsx +++ b/services/workbench2/src/views/project-panel/project-panel.tsx @@ -24,6 +24,7 @@ import { deselectAllOthers, toggleOne } from 'store/multiselect/multiselect-acti import { DetailsCardRoot } from 'views-components/details-card/details-card-root'; import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view'; import { ProjectPanelData } from './project-panel-data'; +import { ProjectPanelRun } from './project-panel-run'; type CssRules = 'root' | 'button' | 'mpvRoot' | 'dataExplorer'; @@ -46,6 +47,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ const panelsData: MPVPanelState[] = [ { name: "Data", visible: true }, + { name: "Workflow Runs", visible: false }, ]; interface ProjectPanelDataProps { @@ -94,6 +96,13 @@ export const ProjectPanel = withStyles(styles)( className={classes.dataExplorer}> + + + } -- 2.30.2