From: Peter Amstutz Date: Mon, 5 Apr 2021 14:32:46 +0000 (-0400) Subject: Merge branch '17426-plug-ins' refs #17426 X-Git-Tag: 2.1.2.1~3 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/31e84a9315728c2f58a26bf0e9e1d2b38326fb86?hp=-c Merge branch '17426-plug-ins' refs #17426 Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- 31e84a9315728c2f58a26bf0e9e1d2b38326fb86 diff --combined src/index.tsx index 522d8dc1,6f4d9dc2..43cfb5fb --- a/src/index.tsx +++ b/src/index.tsx @@@ -21,7 -21,7 +21,7 @@@ import { CustomTheme } from '~/common/c import { fetchConfig } from '~/common/config'; import { addMenuActionSet, ContextMenuKind } from '~/views-components/context-menu/context-menu'; import { rootProjectActionSet } from "~/views-components/context-menu/action-sets/root-project-action-set"; -import { projectActionSet, readOnlyProjectActionSet } from "~/views-components/context-menu/action-sets/project-action-set"; +import { filterGroupActionSet, projectActionSet, readOnlyProjectActionSet } from "~/views-components/context-menu/action-sets/project-action-set"; import { resourceActionSet } from '~/views-components/context-menu/action-sets/resource-action-set'; import { favoriteActionSet } from "~/views-components/context-menu/action-sets/favorite-action-set"; import { collectionFilesActionSet, readOnlyCollectionFilesActionSet } from '~/views-components/context-menu/action-sets/collection-files-action-set'; @@@ -37,7 -37,7 +37,7 @@@ import { initWebSocket } from '~/websoc import { Config } from '~/common/config'; import { addRouteChangeHandlers } from './routes/route-change-handlers'; import { setTokenDialogApiHost } from '~/store/token-dialog/token-dialog-actions'; -import { processResourceActionSet } from '~/views-components/context-menu/action-sets/process-resource-action-set'; +import { processResourceActionSet, readOnlyProcessResourceActionSet } from '~/views-components/context-menu/action-sets/process-resource-action-set'; import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions'; import { trashedCollectionActionSet } from '~/views-components/context-menu/action-sets/trashed-collection-action-set'; import { setBuildInfo } from '~/store/app-info/app-info-actions'; @@@ -58,7 -58,7 +58,7 @@@ import { groupMemberActionSet } from '~ import { linkActionSet } from '~/views-components/context-menu/action-sets/link-action-set'; import { loadFileViewersConfig } from '~/store/file-viewers/file-viewers-actions'; import { processResourceAdminActionSet } from '~/views-components/context-menu/action-sets/process-resource-admin-action-set'; -import { projectAdminActionSet } from '~/views-components/context-menu/action-sets/project-admin-action-set'; +import { filterGroupAdminActionSet, projectAdminActionSet } from '~/views-components/context-menu/action-sets/project-admin-action-set'; import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions"; import { openNotFoundDialog } from './store/not-found-panel/not-found-panel-action'; import { storeRedirects } from './common/redirect-to'; @@@ -68,7 -68,6 +68,7 @@@ console.log(`Starting arvados [${getBui addMenuActionSet(ContextMenuKind.ROOT_PROJECT, rootProjectActionSet); addMenuActionSet(ContextMenuKind.PROJECT, projectActionSet); addMenuActionSet(ContextMenuKind.READONLY_PROJECT, readOnlyProjectActionSet); +addMenuActionSet(ContextMenuKind.FILTER_GROUP, filterGroupActionSet); addMenuActionSet(ContextMenuKind.RESOURCE, resourceActionSet); addMenuActionSet(ContextMenuKind.FAVORITE, favoriteActionSet); addMenuActionSet(ContextMenuKind.COLLECTION_FILES, collectionFilesActionSet); @@@ -82,7 -81,6 +82,7 @@@ addMenuActionSet(ContextMenuKind.OLD_VE addMenuActionSet(ContextMenuKind.TRASHED_COLLECTION, trashedCollectionActionSet); addMenuActionSet(ContextMenuKind.PROCESS, processActionSet); addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet); +addMenuActionSet(ContextMenuKind.READONLY_PROCESS_RESOURCE, readOnlyProcessResourceActionSet); addMenuActionSet(ContextMenuKind.TRASH, trashActionSet); addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet); addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet); @@@ -97,7 -95,6 +97,7 @@@ addMenuActionSet(ContextMenuKind.GROUP_ addMenuActionSet(ContextMenuKind.COLLECTION_ADMIN, collectionAdminActionSet); addMenuActionSet(ContextMenuKind.PROCESS_ADMIN, processResourceAdminActionSet); addMenuActionSet(ContextMenuKind.PROJECT_ADMIN, projectAdminActionSet); +addMenuActionSet(ContextMenuKind.FILTER_GROUP_ADMIN, filterGroupAdminActionSet); storeRedirects(); @@@ -122,7 -119,8 +122,8 @@@ fetchConfig( ? error.errors[0] : error.message}`, kind: SnackbarKind.ERROR, - hideDuration: 8000}) + hideDuration: 8000 + }) ); } } diff --combined src/store/context-menu/context-menu-actions.ts index 83335f83,2982d052..1997b2a6 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@@ -18,7 -18,7 +18,7 @@@ import { VirtualMachinesResource } fro import { KeepServiceResource } from '~/models/keep-services'; import { ProcessResource } from '~/models/process'; import { CollectionResource } from '~/models/collection'; -import { GroupResource } from '~/models/group'; +import { GroupClass, GroupResource } from '~/models/group'; import { GroupContentsResource } from '~/services/groups-service/groups-service'; export const contextMenuActions = unionize({ @@@ -34,7 -34,7 +34,7 @@@ export type ContextMenuResource = ownerUuid: string; description?: string; kind: ResourceKind, - menuKind: ContextMenuKind; + menuKind: ContextMenuKind | string; isTrashed?: boolean; isEditable?: boolean; outputUuid?: string; @@@ -167,7 -167,7 +167,7 @@@ export const openProjectContextMenu = ( kind: res.kind, menuKind, ownerUuid: res.ownerUuid, - isTrashed: ('isTrashed' in res) ? res.isTrashed: false, + isTrashed: ('isTrashed' in res) ? res.isTrashed : false, })); } }; @@@ -201,24 -201,19 +201,24 @@@ export const openProcessContextMenu = ( } }; -export const resourceUuidToContextMenuKind = (uuid: string) => +export const resourceUuidToContextMenuKind = (uuid: string, readonly = false) => (dispatch: Dispatch, getState: () => RootState) => { const { isAdmin: isAdminUser, uuid: userUuid } = getState().auth.user!; const kind = extractUuidKind(uuid); const resource = getResourceWithEditableStatus(uuid, userUuid)(getState().resources); - const isEditable = isAdminUser || (resource || {} as EditableResource).isEditable; + + const isEditable = (isAdminUser || (resource || {} as EditableResource).isEditable) && !readonly; switch (kind) { case ResourceKind.PROJECT: - return !isAdminUser - ? isEditable - ? ContextMenuKind.PROJECT - : ContextMenuKind.READONLY_PROJECT - : ContextMenuKind.PROJECT_ADMIN; + return (isAdminUser && !readonly) + ? (resource && resource.groupClass !== GroupClass.FILTER) + ? ContextMenuKind.PROJECT_ADMIN + : ContextMenuKind.FILTER_GROUP_ADMIN + : isEditable + ? (resource && resource.groupClass !== GroupClass.FILTER) + ? ContextMenuKind.PROJECT + : ContextMenuKind.FILTER_GROUP + : ContextMenuKind.READONLY_PROJECT; case ResourceKind.COLLECTION: const c = getResource(uuid)(getState().resources); if (c === undefined) { return; } @@@ -228,17 -223,15 +228,17 @@@ ? ContextMenuKind.OLD_VERSION_COLLECTION : (isTrashed && isEditable) ? ContextMenuKind.TRASHED_COLLECTION - : isAdminUser + : (isAdminUser && !readonly) ? ContextMenuKind.COLLECTION_ADMIN : isEditable ? ContextMenuKind.COLLECTION : ContextMenuKind.READONLY_COLLECTION; case ResourceKind.PROCESS: - return !isAdminUser - ? ContextMenuKind.PROCESS_RESOURCE - : ContextMenuKind.PROCESS_ADMIN; + return (isAdminUser && !readonly) + ? ContextMenuKind.PROCESS_ADMIN + : readonly + ? ContextMenuKind.READONLY_PROCESS_RESOURCE + : ContextMenuKind.PROCESS_RESOURCE; case ResourceKind.USER: return ContextMenuKind.ROOT_PROJECT; case ResourceKind.LINK: diff --combined src/store/side-panel-tree/side-panel-tree-actions.ts index 05d61927,dd0f5e68..6152b99f --- a/src/store/side-panel-tree/side-panel-tree-actions.ts +++ b/src/store/side-panel-tree/side-panel-tree-actions.ts @@@ -16,6 -16,8 +16,8 @@@ import { OrderBuilder } from '~/service import { ResourceKind } from '~/models/resource'; import { GroupContentsResourcePrefix } from '~/services/groups-service/groups-service'; import { GroupClass } from '~/models/group'; + import { CategoriesListReducer } from '~/common/plugintypes'; + import { pluginConfig } from '~/plugins'; export enum SidePanelTreeCategory { PROJECTS = 'Projects', @@@ -44,34 -46,48 +46,48 @@@ export const getSidePanelTreeBranch = ( return []; }; - const SIDE_PANEL_CATEGORIES = [ + let SIDE_PANEL_CATEGORIES: string[] = [ + SidePanelTreeCategory.PROJECTS, + SidePanelTreeCategory.SHARED_WITH_ME, SidePanelTreeCategory.PUBLIC_FAVORITES, SidePanelTreeCategory.FAVORITES, SidePanelTreeCategory.WORKFLOWS, SidePanelTreeCategory.ALL_PROCESSES, - SidePanelTreeCategory.TRASH, + SidePanelTreeCategory.TRASH ]; + const reduceCatsFn: (a: string[], + b: CategoriesListReducer) => string[] = (a, b) => b(a); + + SIDE_PANEL_CATEGORIES = pluginConfig.sidePanelCategories.reduce(reduceCatsFn, SIDE_PANEL_CATEGORIES); + export const isSidePanelTreeCategory = (id: string) => SIDE_PANEL_CATEGORIES.some(category => category === id); + export const initSidePanelTree = () => (dispatch: Dispatch, getState: () => RootState, { authService }: ServiceRepository) => { const rootProjectUuid = getUserUuid(getState()); if (!rootProjectUuid) { return; } - const nodes = SIDE_PANEL_CATEGORIES.map(id => initTreeNode({ id, value: id })); - const projectsNode = initTreeNode({ id: rootProjectUuid, value: SidePanelTreeCategory.PROJECTS }); - const sharedNode = initTreeNode({ id: SidePanelTreeCategory.SHARED_WITH_ME, value: SidePanelTreeCategory.SHARED_WITH_ME }); + const nodes = SIDE_PANEL_CATEGORIES.map(id => { + if (id === SidePanelTreeCategory.PROJECTS) { + return initTreeNode({ id: rootProjectUuid, value: SidePanelTreeCategory.PROJECTS }); + } else { + return initTreeNode({ id, value: id }); + } + }); dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', pickerId: SIDE_PANEL_TREE, - nodes: [projectsNode, sharedNode, ...nodes] + nodes })); SIDE_PANEL_CATEGORIES.forEach(category => { - dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ - id: category, - pickerId: SIDE_PANEL_TREE, - nodes: [] - })); + if (category !== SidePanelTreeCategory.PROJECTS && category !== SidePanelTreeCategory.SHARED_WITH_ME) { + dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ + id: category, + pickerId: SIDE_PANEL_TREE, + nodes: [] + })); + } }); }; @@@ -112,7 -128,7 +128,7 @@@ const loadSharedRoot = async (dispatch const params = { filters: `[${new FilterBuilder() .addIsA('uuid', ResourceKind.PROJECT) - .addEqual('group_class', GroupClass.PROJECT) + .addIn('group_class', [GroupClass.PROJECT, GroupClass.FILTER]) .addDistinct('uuid', getState().auth.config.uuidPrefix + '-j7d0g-publicfavorites') .getFilters()}]`, order: new OrderBuilder() diff --combined src/views-components/side-panel-button/side-panel-button.tsx index bf03bf6c,151cfb68..fb5ea11f --- a/src/views-components/side-panel-button/side-panel-button.tsx +++ b/src/views-components/side-panel-button/side-panel-button.tsx @@@ -15,9 -15,12 +15,12 @@@ import { navigateToRunProcess } from '~ import { runProcessPanelActions } from '~/store/run-process-panel/run-process-panel-actions'; import { getUserUuid } from '~/common/getuser'; import { matchProjectRoute } from '~/routes/routes'; -import { GroupResource } from '~/models/group'; +import { GroupClass, GroupResource } from '~/models/group'; import { ResourcesState, getResource } from '~/store/resources/resources'; import { extractUuidKind, ResourceKind } from '~/models/resource'; + import { pluginConfig } from '~/plugins'; + import { ElementListReducer } from '~/common/plugintypes'; + import { Location } from 'history'; type CssRules = 'button' | 'menuItem' | 'icon'; @@@ -37,7 -40,7 +40,7 @@@ const styles: StyleRulesCallback(currentItemId)(resources); if (currentProject && currentProject.writableBy.indexOf(currentUserUUID || '') >= 0 && - !isProjectTrashed(currentProject, resources)) { + !isProjectTrashed(currentProject, resources) && + currentProject.groupClass !== GroupClass.FILTER) { enabled = true; } } + + for (const enableFn of pluginConfig.enableNewButtonMatchers) { + if (enableFn(location, currentItemId, currentUserUUID, resources)) { + enabled = true; + } + } + + let menuItems = <> + + New collection + + + Run a process + + + New project + + ; + + const reduceItemsFn: (a: React.ReactElement[], b: ElementListReducer) => React.ReactElement[] = + (a, b) => b(a, classes.menuItem); + + menuItems = React.createElement(React.Fragment, null, + pluginConfig.newButtonMenuList.reduce(reduceItemsFn, React.Children.toArray(menuItems.props.children))); + return @@@ -110,15 -137,7 +138,7 @@@ onClose={this.handleClose} onClick={this.handleClose} transformOrigin={transformOrigin}> - - New collection - - - Run a process - - - New project - + {menuItems}