From: Stephen Smith Date: Mon, 14 Feb 2022 15:31:50 +0000 (-0500) Subject: Merge branch '18284-vm-listing' into main. Closes #18284 X-Git-Tag: 2.4.0~12 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/5e805cf2209d3afe42699e4658d8a12e50bcd5a4?hp=87ba49100005ac43690cd2f77b2f797154998d01 Merge branch '18284-vm-listing' into main. Closes #18284 Arvados-DCO-1.1-Signed-off-by: Stephen Smith --- diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js index 51933887..f6547aae 100644 --- a/cypress/integration/collection.spec.js +++ b/cypress/integration/collection.spec.js @@ -791,6 +791,35 @@ describe('Collection panel tests', function () { }); }); + it('creates collection from selected files of another collection', () => { + cy.createCollection(adminUser.token, { + name: `Test Collection ${Math.floor(Math.random() * 999999)}`, + owner_uuid: activeUser.user.uuid, + preserve_version: true, + manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n" + }) + .as('collection').then(function () { + // Visit collection, check basic information + cy.loginAs(activeUser) + cy.goToPath(`/collections/${this.collection.uuid}`); + + cy.get('[data-cy=collection-files-panel]').within(() => { + cy.get('input[type=checkbox]').first().click(); + }); + + cy.get('[data-cy=collection-files-panel-options-btn]').click(); + cy.get('[data-cy=context-menu]').contains('Create a new collection with selected').click(); + + cy.get('[data-cy=form-dialog]').contains('Projects').click(); + + cy.get('[data-cy=form-submit-btn]').click(); + + cy.get('.layout-pane-primary', { wait: 12000 }).contains('Projects').click(); + + cy.get('main').contains(`Files extracted from: ${this.collection.name}`).should('exist'); + }); + }); + it('creates new collection with properties on home project', function () { cy.loginAs(activeUser); cy.goToPath(`/projects/${activeUser.user.uuid}`); diff --git a/cypress/integration/side-panel.spec.js b/cypress/integration/side-panel.spec.js index f9d4dca3..afe326e3 100644 --- a/cypress/integration/side-panel.spec.js +++ b/cypress/integration/side-panel.spec.js @@ -104,7 +104,7 @@ describe('Side panel tests', function() { cy.getAll('@mySharedWritableProject') .then(function ([mySharedWritableProject]) { cy.loginAs(activeUser); - + cy.get('[data-cy=side-panel-tree]').contains('Projects').click(); const newProjectName = `New project name ${mySharedWritableProject.name}`; @@ -125,19 +125,16 @@ describe('Side panel tests', function() { addToFavorites: false }); - cy.getAll('@writableProject') - .then(function ([writableProject]) { - cy.loginAs(activeUser); - - cy.get('[data-cy=side-panel-tree]').contains('Projects').click(); - - cy.get('[data-cy=side-panel-tree]').contains(writableProject.name).should('exist'); - - cy.trashGroup(activeUser.token, writableProject.uuid); - + cy.getAll('@writableProject').then(function ([writableProject]) { + cy.loginAs(activeUser); + cy.get('[data-cy=side-panel-tree]') + .contains('Projects').click(); + cy.get('[data-cy=side-panel-tree]') + .contains(writableProject.name).should('exist'); + cy.trashGroup(activeUser.token, writableProject.uuid).then(() => { cy.contains('Refresh').click(); - cy.contains(writableProject.name).should('not.exist'); }); + }); }); }) diff --git a/src/common/app-info.ts b/src/common/app-info.ts index de6708eb..a6e3af74 100644 --- a/src/common/app-info.ts +++ b/src/common/app-info.ts @@ -7,7 +7,7 @@ export const getBuildInfo = (): string => { return "v" + process.env.REACT_APP_VERSION; } else { const getBuildNumber = "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev"); - const getGitCommit = "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7); + const getGitCommit = "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substring(0, 7); return getBuildNumber + " / " + getGitCommit; } }; diff --git a/src/common/url.ts b/src/common/url.ts index 6d66778a..db12cb8e 100644 --- a/src/common/url.ts +++ b/src/common/url.ts @@ -13,7 +13,7 @@ export function normalizeURLPath(url: string) { const u = new URL(url); u.pathname = u.pathname.replace(/\/\//, '/'); if (u.pathname[u.pathname.length - 1] === '/') { - u.pathname = u.pathname.substr(0, u.pathname.length - 1); + u.pathname = u.pathname.substring(0, u.pathname.length - 1); } return u.toString(); } diff --git a/src/components/data-table/data-table.test.tsx b/src/components/data-table/data-table.test.tsx index 85379e9a..866564ac 100644 --- a/src/components/data-table/data-table.test.tsx +++ b/src/components/data-table/data-table.test.tsx @@ -101,6 +101,7 @@ describe("", () => { ]; const dataTable = mount(", () => { ]; const dataTable = mount( - { - this.props.working ? -
- -
: items.map(this.renderBodyRow) - } + { this.props.working !== undefined && !this.props.working && items.map(this.renderBodyRow) }
+ { this.props.working && +
+ +
} {items.length === 0 && this.props.working !== undefined && !this.props.working && this.renderNoItemsPlaceholder()} ; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 528a0376..41c71f7c 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -68,7 +68,7 @@ export const getResourceUrl = (uuid: string) => { export const getNavUrl = (uuid: string, config: FederationConfig) => { const path = getResourceUrl(uuid) || ""; - const cls = uuid.substr(0, 5); + const cls = uuid.substring(0, 5); if (cls === config.localCluster || extractUuidKind(uuid) === ResourceKind.USER || COLLECTION_PDH_REGEX.exec(uuid)) { return path; } else if (config.remoteHostsConfig[cls]) { diff --git a/src/services/api/url-builder.ts b/src/services/api/url-builder.ts index 32039a50..d94aab35 100644 --- a/src/services/api/url-builder.ts +++ b/src/services/api/url-builder.ts @@ -30,13 +30,13 @@ export function joinUrls(url0?: string, url1?: string) { if (url0) { let idx0 = url0.length - 1; while (url0[idx0] === '/') { --idx0; } - u0 = url0.substr(0, idx0 + 1); + u0 = url0.substring(0, idx0 + 1); } let u1 = ""; if (url1) { let idx1 = 0; while (url1[idx1] === '/') { ++idx1; } - u1 = url1.substr(idx1); + u1 = url1.substring(idx1); } let url = u0; if (u1.length > 0) { diff --git a/src/services/auth-service/auth-service.ts b/src/services/auth-service/auth-service.ts index 5b975969..548dbcaa 100644 --- a/src/services/auth-service/auth-service.ts +++ b/src/services/auth-service/auth-service.ts @@ -64,7 +64,7 @@ export class AuthService { this.getStorage().setItem(API_TOKEN_KEY, token); const sp = token.split('/'); if (sp.length === 3) { - this.getStorage().setItem(HOME_CLUSTER, sp[1].substr(0, 5)); + this.getStorage().setItem(HOME_CLUSTER, sp[1].substring(0, 5)); } } diff --git a/src/store/auth/auth-action-session.ts b/src/store/auth/auth-action-session.ts index 2712f136..7e81f2d9 100644 --- a/src/store/auth/auth-action-session.ts +++ b/src/store/auth/auth-action-session.ts @@ -99,7 +99,7 @@ export const getSaltedToken = (clusterId: string, token: string) => { throw new Error(invalidV2Token); } let salted = secret; - if (uuid.substr(0, 5) !== clusterId) { + if (uuid.substring(0, 5) !== clusterId) { shaObj.setHMACKey(secret, "TEXT"); shaObj.update(clusterId); salted = shaObj.getHMAC("HEX"); diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts index c7074704..d58a8103 100644 --- a/src/store/auth/auth-action.ts +++ b/src/store/auth/auth-action.ts @@ -86,9 +86,15 @@ export const saveApiToken = (token: string) => async (dispatch: Dispatch, getSta const auth = getState().auth; config = dispatch(getConfig); - // If federated token, get user & token data from the token issuing cluster - if (tokenParts.length === 3 && tokenParts[1].substring(0, 5) !== auth.localCluster) { - config = await getRemoteHostConfig(auth.remoteHosts[tokenParts[1].substring(0, 5)]); + // If the token is from a LoginCluster federation, get user & token data + // from the token issuing cluster. + const lc = (config as Config).loginCluster + const tokenCluster = tokenParts.length === 3 + ? tokenParts[1].substring(0, 5) + : undefined; + if (tokenCluster && tokenCluster !== auth.localCluster && + lc && lc === tokenCluster) { + config = await getRemoteHostConfig(auth.remoteHosts[tokenCluster]); } const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } }); diff --git a/src/store/auth/auth-reducer.ts b/src/store/auth/auth-reducer.ts index ce836a55..c109acaf 100644 --- a/src/store/auth/auth-reducer.ts +++ b/src/store/auth/auth-reducer.ts @@ -79,12 +79,12 @@ export const authReducer = (services: ServiceRepository) => (state = initialStat apiToken: token, apiTokenExpiration: tokenExpiration, apiTokenLocation: tokenLocation, - homeCluster: user.uuid.substr(0, 5) + homeCluster: user.uuid.substring(0, 5) }), LOGIN: () => state, LOGOUT: () => ({ ...state, apiToken: undefined }), USER_DETAILS_SUCCESS: (user: User) => - ({ ...state, user, homeCluster: user.uuid.substr(0, 5) }), + ({ ...state, user, homeCluster: user.uuid.substring(0, 5) }), SET_SSH_KEYS: (sshKeys: SshKeyResource[]) => ({ ...state, sshKeys }), ADD_SSH_KEY: (sshKey: SshKeyResource) => ({ ...state, sshKeys: state.sshKeys.concat(sshKey) }), diff --git a/src/store/collections/collection-partial-copy-actions.ts b/src/store/collections/collection-partial-copy-actions.ts index 9f478d74..a0c4e4e4 100644 --- a/src/store/collections/collection-partial-copy-actions.ts +++ b/src/store/collections/collection-partial-copy-actions.ts @@ -13,7 +13,7 @@ import { filterCollectionFilesBySelection } from '../collection-panel/collection import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions'; import { getCommonResourceServiceError, CommonResourceServiceError } from 'services/common-service/common-resource-service'; import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions"; -import { initProjectsTreePicker } from 'store/tree-picker/tree-picker-actions'; +import { initProjectsTreePicker } from "store/tree-picker/tree-picker-actions"; export const COLLECTION_PARTIAL_COPY_FORM_NAME = 'COLLECTION_PARTIAL_COPY_DIALOG'; export const COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION = 'COLLECTION_PARTIAL_COPY_TO_SELECTED_DIALOG'; @@ -33,9 +33,9 @@ export const openCollectionPartialCopyDialog = () => const currentCollection = getState().collectionPanel.item; if (currentCollection) { const initialData = { - name: currentCollection.name, + name: `Files extracted from: ${currentCollection.name}`, description: currentCollection.description, - projectUuid: '' + projectUuid: undefined }; dispatch(initialize(COLLECTION_PARTIAL_COPY_FORM_NAME, initialData)); dispatch(resetPickerProjectTree()); diff --git a/src/store/project-panel/project-panel-middleware-service.ts b/src/store/project-panel/project-panel-middleware-service.ts index 45650843..be569b49 100644 --- a/src/store/project-panel/project-panel-middleware-service.ts +++ b/src/store/project-panel/project-panel-middleware-service.ts @@ -50,7 +50,6 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService try { api.dispatch(progressIndicatorActions.START_WORKING(this.getId())); const response = await this.services.groupsService.contents(projectUuid, getParams(dataExplorer, !!isProjectTrashed)); - api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId())); const resourceUuids = response.items.map(item => item.uuid); api.dispatch(updateFavorites(resourceUuids)); api.dispatch(updatePublicFavorites(resourceUuids)); diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts index 58d8d5f1..98508f75 100644 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@ -195,35 +195,34 @@ export const loadProject = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const userUuid = getUserUuid(getState()); dispatch(setIsProjectPanelTrashed(false)); - if (userUuid) { - if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) { - // Load another users home projects - dispatch(finishLoadingProject(uuid)); - } else if (userUuid !== uuid) { - const match = await loadGroupContentsResource({ uuid, userUuid, services }); - match({ - OWNED: async project => { - await dispatch(activateSidePanelTreeItem(uuid)); - dispatch(setSidePanelBreadcrumbs(uuid)); - dispatch(finishLoadingProject(project)); - }, - SHARED: project => { - dispatch(setSharedWithMeBreadcrumbs(uuid)); - dispatch(activateSidePanelTreeItem(uuid)); - dispatch(finishLoadingProject(project)); - }, - TRASHED: project => { - dispatch(setTrashBreadcrumbs(uuid)); - dispatch(setIsProjectPanelTrashed(true)); - dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH)); - dispatch(finishLoadingProject(project)); - } - }); - } else { - await dispatch(activateSidePanelTreeItem(userUuid)); - dispatch(setSidePanelBreadcrumbs(userUuid)); - dispatch(finishLoadingProject(userUuid)); - } + if (!userUuid) { + return; + } + if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) { + // Load another users home projects + dispatch(finishLoadingProject(uuid)); + } else if (userUuid !== uuid) { + await dispatch(finishLoadingProject(uuid)); + const match = await loadGroupContentsResource({ uuid, userUuid, services }); + match({ + OWNED: async () => { + await dispatch(activateSidePanelTreeItem(uuid)); + dispatch(setSidePanelBreadcrumbs(uuid)); + }, + SHARED: async () => { + await dispatch(activateSidePanelTreeItem(uuid)); + dispatch(setSharedWithMeBreadcrumbs(uuid)); + }, + TRASHED: async () => { + await dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH)); + dispatch(setTrashBreadcrumbs(uuid)); + dispatch(setIsProjectPanelTrashed(true)); + } + }); + } else { + await dispatch(finishLoadingProject(userUuid)); + await dispatch(activateSidePanelTreeItem(userUuid)); + dispatch(setSidePanelBreadcrumbs(userUuid)); } }); diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx index ce6c02ca..a2acaca4 100644 --- a/src/views-components/data-explorer/renderers.tsx +++ b/src/views-components/data-explorer/renderers.tsx @@ -342,7 +342,7 @@ const clusterColors = [ export const ResourceCluster = (props: { uuid: string }) => { const CLUSTER_ID_LENGTH = 5; const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5; - const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substr(0, pos) : ''; + const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : ''; const ci = pos >= CLUSTER_ID_LENGTH ? ((((( (props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1)) + props.uuid.charCodeAt(2)) diff --git a/src/views-components/main-app-bar/account-menu.tsx b/src/views-components/main-app-bar/account-menu.tsx index 9356e077..7faf27c2 100644 --- a/src/views-components/main-app-bar/account-menu.tsx +++ b/src/views-components/main-app-bar/account-menu.tsx @@ -90,7 +90,7 @@ export const AccountMenuComponent = title="Account Management" key={currentRoute}> - {getUserDisplayName(user)} {user.uuid.substr(0, 5) !== localCluster && `(${user.uuid.substr(0, 5)})`} + {getUserDisplayName(user)} {user.uuid.substring(0, 5) !== localCluster && `(${user.uuid.substring(0, 5)})`} {user.isActive && accountMenuItems} diff --git a/src/views-components/main-content-bar/main-content-bar.tsx b/src/views-components/main-content-bar/main-content-bar.tsx index 480150cb..a460a518 100644 --- a/src/views-components/main-content-bar/main-content-bar.tsx +++ b/src/views-components/main-content-bar/main-content-bar.tsx @@ -12,7 +12,6 @@ import { RootState } from 'store/store'; import * as Routes from 'routes/routes'; import { toggleDetailsPanel } from 'store/details-panel/details-panel-action'; import RefreshButton from "components/refresh-button/refresh-button"; -import { reloadProjectMatchingUuid } from "store/workbench/workbench-actions"; import { loadSidePanelTreeProjects } from "store/side-panel-tree/side-panel-tree-actions"; type CssRules = "infoTooltip"; @@ -63,7 +62,6 @@ export const MainContentBar = onDetailsPanelToggle: () => dispatch(toggleDetailsPanel()), onRefreshButtonClick: (id) => { dispatch(loadSidePanelTreeProjects(id)); - dispatch(reloadProjectMatchingUuid([id])); } }))( withStyles(styles)( diff --git a/src/views/all-processes-panel/all-processes-panel.tsx b/src/views/all-processes-panel/all-processes-panel.tsx index 928b4fff..b06b08e4 100644 --- a/src/views/all-processes-panel/all-processes-panel.tsx +++ b/src/views/all-processes-panel/all-processes-panel.tsx @@ -16,7 +16,7 @@ import { ALL_PROCESSES_PANEL_ID } from 'store/all-processes-panel/all-processes- import { ProcessStatus, ResourceName, - ResourceOwner, + ResourceOwnerWithName, ResourceType, ContainerRunTime, ResourceCreatedAtDate @@ -90,7 +90,7 @@ export const allProcessesPanelColumns: DataColumns = [ selected: true, configurable: true, filters: createTree(), - render: uuid => + render: uuid => }, { name: AllProcessesPanelColumnNames.CREATED_AT, diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx index 0b6532c1..e520a59c 100644 --- a/src/views/favorite-panel/favorite-panel.tsx +++ b/src/views/favorite-panel/favorite-panel.tsx @@ -18,7 +18,7 @@ import { ResourceFileSize, ResourceLastModifiedDate, ResourceName, - ResourceOwner, + ResourceOwnerWithName, ResourceType } from 'views-components/data-explorer/renderers'; import { FavoriteIcon } from 'components/icon/icon'; @@ -97,7 +97,7 @@ export const favoritePanelColumns: DataColumns = [ selected: false, configurable: true, filters: createTree(), - render: uuid => + render: uuid => }, { name: FavoritePanelColumnNames.FILE_SIZE, diff --git a/src/views/link-account-panel/link-account-panel-root.tsx b/src/views/link-account-panel/link-account-panel-root.tsx index eb52ba18..c5c86eb2 100644 --- a/src/views/link-account-panel/link-account-panel-root.tsx +++ b/src/views/link-account-panel/link-account-panel-root.tsx @@ -55,7 +55,7 @@ function displayUser(user: UserResource, showCreatedAt: boolean = false, showClu const disp: JSX.Element[] = []; disp.push({user.email} ({user.username}, {user.uuid})); if (showCluster) { - const homeCluster = user.uuid.substr(0, 5); + const homeCluster = user.uuid.substring(0, 5); disp.push( hosted on cluster {homeCluster} and ); } if (showCreatedAt) { @@ -134,7 +134,7 @@ export const LinkAccountPanelRoot = withStyles(styles)( This a remote account. You can link a local Arvados account to this one. After linking, you can access the local account's data by logging into the {localCluster} cluster as user {targetUser.email} - from {targetUser.uuid.substr(0, 5)}. + from {targetUser.uuid.substring(0, 5)}.