[arvados] created: 2.7.0-5315-g049f1a1e93
git repository hosting
git at public.arvados.org
Tue Nov 7 19:37:23 UTC 2023
at 049f1a1e93539bf6f98d294e1b3365d6ac228598 (commit)
commit 049f1a1e93539bf6f98d294e1b3365d6ac228598
Author: Tom Clegg <tom at curii.com>
Date: Tue Nov 7 14:36:37 2023 -0500
18874: Use test auth instead of adding real accounts on test host.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curii.com>
diff --git a/services/workbench2/cypress/integration/login.spec.js b/services/workbench2/cypress/integration/login.spec.js
index 2c539e4902..58f8874d55 100644
--- a/services/workbench2/cypress/integration/login.spec.js
+++ b/services/workbench2/cypress/integration/login.spec.js
@@ -27,17 +27,10 @@ describe('Login tests', function() {
.as('inactiveUser').then(function() {
inactiveUser = this.inactiveUser;
}
- );
- randomUser.username = `randomuser${Math.floor(Math.random() * 999999)}`;
- randomUser.password = {
- crypt: 'zpAReoZzPnwmQ',
- clear: 'topsecret',
- };
- cy.exec(`useradd ${randomUser.username} -p ${randomUser.password.crypt}`);
- })
-
- after(function() {
- cy.exec(`userdel ${randomUser.username}`);
+ );
+ // Username/password match Login.Test section of arvados_config.yml
+ randomUser.username = 'randomuser1234';
+ randomUser.password = 'topsecret';
})
beforeEach(function() {
@@ -128,14 +121,14 @@ describe('Login tests', function() {
cy.get('#username').type(randomUser.username);
cy.get('#password').type('wrong password');
cy.get("button span:contains('Log in')").click();
- cy.get('p#password-helper-text').should('contain', 'PAM: Authentication failure');
+ cy.get('p#password-helper-text').should('contain', 'Authentication failed');
cy.url().should('not.contain', '/projects/');
})
it('successfully authenticates using the login form', function() {
cy.visit('/');
cy.get('#username').type(randomUser.username);
- cy.get('#password').type(randomUser.password.clear);
+ cy.get('#password').type(randomUser.password);
cy.get("button span:contains('Log in')").click();
cy.url().should('contain', '/projects/');
cy.get('div#root').should('contain', 'Arvados Workbench (zzzzz)');
diff --git a/services/workbench2/tools/arvados_config.yml b/services/workbench2/tools/arvados_config.yml
index 1ef77b86ce..ba41c51b63 100644
--- a/services/workbench2/tools/arvados_config.yml
+++ b/services/workbench2/tools/arvados_config.yml
@@ -18,8 +18,12 @@ Clusters:
original_owner_uuid: {Function: original_owner, Protected: true}
Login:
TrustPrivateNetworks: true
- PAM:
+ Test:
Enable: true
+ Users:
+ randomuser1234:
+ Email: randomuser1234 at example.invalid
+ Password: topsecret
StorageClasses:
default:
Default: true
commit b34c4b9234777d68b675aebf77680b8dd8708a6d
Author: Tom Clegg <tom at curii.com>
Date: Tue Nov 7 13:48:42 2023 -0500
18874: Fix errant it.only().
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curii.com>
diff --git a/services/workbench2/cypress/integration/project.spec.js b/services/workbench2/cypress/integration/project.spec.js
index a8663d8622..e61138219d 100644
--- a/services/workbench2/cypress/integration/project.spec.js
+++ b/services/workbench2/cypress/integration/project.spec.js
@@ -564,7 +564,7 @@ describe("Project tests", function () {
);
});
- it.only("sorts displayed items correctly", () => {
+ it("sorts displayed items correctly", () => {
cy.loginAs(activeUser);
cy.get('[data-cy=project-panel] button[title="Select columns"]').click();
commit 4ad6191d53207a8b2d4c0c8a30b18119daaa5fbc
Merge: 56a2e5c346 8e913832ad
Author: Tom Clegg <tom at curii.com>
Date: Mon Nov 6 12:14:51 2023 -0500
Merge branch 'main' from workbench2.git
refs #18874
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curii.com>
commit 8e913832ad289fd28700f4081046b7de48688130
Merge: 5c43d68767 5b30fab055
Author: Peter Amstutz <peter.amstutz at curii.com>
Date: Tue Oct 31 13:53:33 2023 -0400
Merge branch '21067-process-panel-error' refs #21067
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz at curii.com>
commit 5b30fab0552f623a35ff5b89b90dab82dfcee258
Author: Peter Amstutz <peter.amstutz at curii.com>
Date: Mon Oct 30 15:24:39 2023 -0400
21067: Fix bug deleting projects from shared-with-me
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz at curii.com>
diff --git a/cypress/integration/page-not-found.spec.js b/cypress/integration/page-not-found.spec.js
index 4df4135c87..6eab27c827 100644
--- a/cypress/integration/page-not-found.spec.js
+++ b/cypress/integration/page-not-found.spec.js
@@ -45,8 +45,7 @@ describe('Page not found tests', function() {
cy.goToPath(path);
// then
- cy.get('[data-cy=not-found-page]').should('not.exist');
- cy.get('[data-cy=not-found-content]').should('exist');
+ cy.get('[data-cy=not-found-view]').should('exist');
});
});
-})
\ No newline at end of file
+})
diff --git a/src/store/trash/trash-actions.ts b/src/store/trash/trash-actions.ts
index 884293a90e..62b669220e 100644
--- a/src/store/trash/trash-actions.ts
+++ b/src/store/trash/trash-actions.ts
@@ -9,104 +9,112 @@ import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
import { trashPanelActions } from "store/trash-panel/trash-panel-action";
import { activateSidePanelTreeItem, loadSidePanelTreeProjects } from "store/side-panel-tree/side-panel-tree-actions";
import { projectPanelActions } from "store/project-panel/project-panel-action-bind";
+import { sharedWithMePanelActions } from "store/shared-with-me-panel/shared-with-me-panel-actions";
import { ResourceKind } from "models/resource";
import { navigateTo, navigateToTrash } from "store/navigation/navigation-action";
-import { matchCollectionRoute } from "routes/routes";
+import { matchCollectionRoute, matchSharedWithMeRoute } from "routes/routes";
export const toggleProjectTrashed =
(uuid: string, ownerUuid: string, isTrashed: boolean, isMulti: boolean) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
- let errorMessage = "";
- let successMessage = "";
- let untrashedResource;
- try {
- if (isTrashed) {
- errorMessage = "Could not restore project from trash";
- successMessage = "Restored project from trash";
- untrashedResource = await services.groupsService.untrash(uuid);
- dispatch<any>(isMulti || !untrashedResource ? navigateToTrash : navigateTo(uuid));
- dispatch<any>(activateSidePanelTreeItem(uuid));
- } else {
- errorMessage = "Could not move project to trash";
- successMessage = "Added project to trash";
- await services.groupsService.trash(uuid);
- dispatch<any>(loadSidePanelTreeProjects(ownerUuid));
- dispatch<any>(navigateTo(ownerUuid));
- }
- if (untrashedResource) {
- dispatch(
- snackbarActions.OPEN_SNACKBAR({
- message: successMessage,
- hideDuration: 2000,
- kind: SnackbarKind.SUCCESS,
- })
- );
- }
- } catch (e) {
- if (e.status === 422) {
- dispatch(
- snackbarActions.OPEN_SNACKBAR({
- message: "Could not restore project from trash: Duplicate name at destination",
- kind: SnackbarKind.ERROR,
- })
- );
- } else {
- dispatch(
- snackbarActions.OPEN_SNACKBAR({
- message: errorMessage,
- kind: SnackbarKind.ERROR,
- })
- );
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+ let errorMessage = "";
+ let successMessage = "";
+ let untrashedResource;
+ try {
+ if (isTrashed) {
+ errorMessage = "Could not restore project from trash";
+ successMessage = "Restored project from trash";
+ untrashedResource = await services.groupsService.untrash(uuid);
+ dispatch<any>(isMulti || !untrashedResource ? navigateToTrash : navigateTo(uuid));
+ dispatch<any>(activateSidePanelTreeItem(uuid));
+ } else {
+ errorMessage = "Could not move project to trash";
+ successMessage = "Added project to trash";
+ await services.groupsService.trash(uuid);
+ dispatch<any>(loadSidePanelTreeProjects(ownerUuid));
+
+ const { location } = getState().router;
+ if (matchSharedWithMeRoute(location ? location.pathname : "")) {
+ dispatch(sharedWithMePanelActions.REQUEST_ITEMS());
+ }
+ else {
+ dispatch<any>(navigateTo(ownerUuid));
+ }
+ }
+ if (untrashedResource) {
+ dispatch(
+ snackbarActions.OPEN_SNACKBAR({
+ message: successMessage,
+ hideDuration: 2000,
+ kind: SnackbarKind.SUCCESS,
+ })
+ );
+ }
+ } catch (e) {
+ if (e.status === 422) {
+ dispatch(
+ snackbarActions.OPEN_SNACKBAR({
+ message: "Could not restore project from trash: Duplicate name at destination",
+ kind: SnackbarKind.ERROR,
+ })
+ );
+ } else {
+ dispatch(
+ snackbarActions.OPEN_SNACKBAR({
+ message: errorMessage,
+ kind: SnackbarKind.ERROR,
+ })
+ );
+ }
}
- }
- };
+ };
export const toggleCollectionTrashed =
(uuid: string, isTrashed: boolean) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
- let errorMessage = "";
- let successMessage = "";
- try {
- if (isTrashed) {
- const { location } = getState().router;
- errorMessage = "Could not restore collection from trash";
- successMessage = "Restored from trash";
- await services.collectionService.untrash(uuid);
- if (matchCollectionRoute(location ? location.pathname : "")) {
- dispatch(navigateToTrash);
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+ let errorMessage = "";
+ let successMessage = "";
+ try {
+ if (isTrashed) {
+ const { location } = getState().router;
+ errorMessage = "Could not restore collection from trash";
+ successMessage = "Restored from trash";
+ await services.collectionService.untrash(uuid);
+ if (matchCollectionRoute(location ? location.pathname : "")) {
+ dispatch(navigateToTrash);
+ }
+ dispatch(trashPanelActions.REQUEST_ITEMS());
+ } else {
+ errorMessage = "Could not move collection to trash";
+ successMessage = "Added to trash";
+ await services.collectionService.trash(uuid);
+ dispatch(projectPanelActions.REQUEST_ITEMS());
}
- dispatch(trashPanelActions.REQUEST_ITEMS());
- } else {
- errorMessage = "Could not move collection to trash";
- successMessage = "Added to trash";
- await services.collectionService.trash(uuid);
- dispatch(projectPanelActions.REQUEST_ITEMS());
- }
- dispatch(
- snackbarActions.OPEN_SNACKBAR({
- message: successMessage,
- hideDuration: 2000,
- kind: SnackbarKind.SUCCESS,
- })
- );
- } catch (e) {
- if (e.status === 422) {
dispatch(
snackbarActions.OPEN_SNACKBAR({
- message: "Could not restore collection from trash: Duplicate name at destination",
- kind: SnackbarKind.ERROR,
- })
- );
- } else {
- dispatch(
- snackbarActions.OPEN_SNACKBAR({
- message: errorMessage,
- kind: SnackbarKind.ERROR,
+ message: successMessage,
+ hideDuration: 2000,
+ kind: SnackbarKind.SUCCESS,
})
);
+ } catch (e) {
+ if (e.status === 422) {
+ dispatch(
+ snackbarActions.OPEN_SNACKBAR({
+ message: "Could not restore collection from trash: Duplicate name at destination",
+ kind: SnackbarKind.ERROR,
+ })
+ );
+ } else {
+ dispatch(
+ snackbarActions.OPEN_SNACKBAR({
+ message: errorMessage,
+ kind: SnackbarKind.ERROR,
+ })
+ );
+ }
}
- }
- };
+ };
export const toggleTrashed = (kind: ResourceKind, uuid: string, ownerUuid: string, isTrashed: boolean) => (dispatch: Dispatch) => {
if (kind === ResourceKind.PROJECT) {
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index eed9c7de71..d93d6e9258 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -37,7 +37,7 @@ import { Link as ButtonLink } from '@material-ui/core';
import { ResourceWithName, ResponsiblePerson } from 'views-components/data-explorer/renderers';
import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
import { resourceIsFrozen } from 'common/frozen-resources';
-import { DefaultView } from "components/default-view/default-view";
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
type CssRules = 'root'
| 'button'
@@ -230,16 +230,10 @@ export const CollectionPanel = withStyles(styles)(connect(
</Card>
</MPVPanelContent>
</MPVContainer >
- : <Grid
- container
- alignItems="center"
- justify="center"
- style={{ minHeight: "100%" }}>
- <DefaultView
- icon={CollectionIcon}
- messages={["Collection not found"]}
- />
- </Grid>
+ : <NotFoundView
+ icon={CollectionIcon}
+ messages={["Collection not found"]}
+ />
;
}
diff --git a/src/views/not-found-panel/not-found-panel.tsx b/src/views/not-found-panel/not-found-panel.tsx
index 148c331e29..f54c00c32a 100644
--- a/src/views/not-found-panel/not-found-panel.tsx
+++ b/src/views/not-found-panel/not-found-panel.tsx
@@ -3,8 +3,12 @@
// SPDX-License-Identifier: AGPL-3.0
import { RootState } from 'store/store';
+import React from 'react';
import { connect } from 'react-redux';
import { NotFoundPanelRoot, NotFoundPanelRootDataProps } from 'views/not-found-panel/not-found-panel-root';
+import { Grid } from '@material-ui/core';
+import { DefaultView } from "components/default-view/default-view";
+import { IconType } from 'components/icon/icon';
const mapStateToProps = (state: RootState): NotFoundPanelRootDataProps => {
return {
@@ -17,3 +21,26 @@ const mapDispatchToProps = null;
export const NotFoundPanel = connect(mapStateToProps, mapDispatchToProps)
(NotFoundPanelRoot) as any;
+
+export interface NotFoundViewDataProps {
+ messages: string[];
+ icon?: IconType;
+}
+
+// TODO: optionally pass in the UUID and check if the
+// reason the item is not found is because
+// it or a parent project is actually in the trash.
+// If so, offer to untrash the item or the parent project.
+export const NotFoundView =
+ ({ messages, icon: Icon }: NotFoundViewDataProps) =>
+ <Grid
+ container
+ alignItems="center"
+ justify="center"
+ style={{ minHeight: "100%" }}
+ data-cy="not-found-view">
+ <DefaultView
+ icon={Icon}
+ messages={messages}
+ />
+ </Grid>;
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index d019d1418f..7a24089901 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -3,8 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0
import React from "react";
-import { Grid, StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core";
-import { DefaultView } from "components/default-view/default-view";
+import { StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core";
import { ProcessIcon } from "components/icon/icon";
import { Process } from "store/processes/process";
import { SubprocessPanel } from "views/subprocess-panel/subprocess-panel";
@@ -24,6 +23,7 @@ import { AuthState } from "store/auth/auth-reducer";
import { ProcessCmdCard } from "./process-cmd-card";
import { ContainerRequestResource } from "models/container-request";
import { OutputDetails, NodeInstanceType } from "store/process-panel/process-panel";
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
type CssRules = "root";
@@ -209,16 +209,10 @@ export const ProcessPanelRoot = withStyles(styles)(
</MPVPanelContent>
</MPVContainer>
) : (
- <Grid
- container
- alignItems="center"
- justify="center"
- style={{ minHeight: "100%" }}>
- <DefaultView
- icon={ProcessIcon}
- messages={["Process not found"]}
- />
- </Grid>
+ <NotFoundView
+ icon={ProcessIcon}
+ messages={["Process not found"]}
+ />
);
}
);
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 2f274c97d5..2cc751bffd 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import withStyles from '@material-ui/core/styles/withStyles';
import { DispatchProp, connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
-import { StyleRulesCallback, WithStyles, Grid } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles } from '@material-ui/core';
import { DataExplorer } from 'views-components/data-explorer/data-explorer';
import { DataColumns } from 'components/data-table/data-table';
@@ -51,7 +51,7 @@ import { GroupClass, GroupResource } from 'models/group';
import { CollectionResource } from 'models/collection';
import { resourceIsFrozen } from 'common/frozen-resources';
import { ProjectResource } from 'models/project';
-import { DefaultView } from "components/default-view/default-view";
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
type CssRules = 'root' | 'button';
@@ -276,16 +276,11 @@ export const ProjectPanel = withStyles(styles)(
defaultViewMessages={DEFAULT_VIEW_MESSAGES}
/>
</div>
- : <Grid
- container
- alignItems="center"
- justify="center"
- style={{ minHeight: "100%" }}>
- <DefaultView
- icon={ProjectIcon}
- messages={["Project not found"]}
- />
- </Grid>;
+ :
+ <NotFoundView
+ icon={ProjectIcon}
+ messages={["Project not found"]}
+ />
}
isCurrentItemChild = (resource: Resource) => {
diff --git a/src/views/workflow-panel/registered-workflow-panel.tsx b/src/views/workflow-panel/registered-workflow-panel.tsx
index da273719bf..50192e543d 100644
--- a/src/views/workflow-panel/registered-workflow-panel.tsx
+++ b/src/views/workflow-panel/registered-workflow-panel.tsx
@@ -12,8 +12,7 @@ import {
Card,
CardHeader,
CardContent,
- IconButton,
- Grid
+ IconButton
} from '@material-ui/core';
import { connect, DispatchProp } from "react-redux";
import { RouteComponentProps } from 'react-router';
@@ -27,7 +26,7 @@ import { getResource } from 'store/resources/resources';
import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
import { ProcessIOCard, ProcessIOCardType } from 'views/process-panel/process-io-card';
-import { DefaultView } from "components/default-view/default-view";
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
type CssRules = 'root'
| 'button'
@@ -202,16 +201,11 @@ export const RegisteredWorkflowPanel = withStyles(styles)(connect(
</Card>
</MPVPanelContent>
</MPVContainer>
- : <Grid
- container
- alignItems="center"
- justify="center"
- style={{ minHeight: "100%" }}>
- <DefaultView
- icon={WorkflowIcon}
- messages={["Workflow not found"]}
- />
- </Grid>;
+ :
+ <NotFoundView
+ icon={WorkflowIcon}
+ messages={["Workflow not found"]}
+ />
}
handleContextMenu = (event: React.MouseEvent<any>) => {
commit 26e2f985f4c2b511c59977e4c63158649f296624
Merge: e11c6c520d 5c43d68767
Author: Peter Amstutz <peter.amstutz at curii.com>
Date: Fri Oct 27 13:52:35 2023 -0400
Merge branch 'main' into 21067-process-panel-error
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz at curii.com>
commit e11c6c520d8d01397f58a5cda1fe7e5e5e06acab
Author: Peter Amstutz <peter.amstutz at curii.com>
Date: Thu Oct 26 17:15:51 2023 -0400
21067: Remove unused imports
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz at curii.com>
diff --git a/src/index.tsx b/src/index.tsx
index b0bda23ba8..ede257dc5d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -64,7 +64,6 @@ import {
runningProcessResourceAdminActionSet,
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";
import { getBuildInfo } from "common/app-info";
@@ -89,8 +88,6 @@ import {
} from "views-components/context-menu/action-sets/project-admin-action-set";
import { permissionEditActionSet } from "views-components/context-menu/action-sets/permission-edit-action-set";
import { workflowActionSet, readOnlyWorkflowActionSet } from "views-components/context-menu/action-sets/workflow-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";
import { searchResultsActionSet } from "views-components/context-menu/action-sets/search-results-action-set";
diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts
index 66de2f69f3..81f8dd6ba0 100644
--- a/src/store/process-panel/process-panel-actions.ts
+++ b/src/store/process-panel/process-panel-actions.ts
@@ -8,10 +8,9 @@ import { Dispatch } from "redux";
import { ProcessStatus } from "store/processes/process";
import { RootState } from "store/store";
import { ServiceRepository } from "services/services";
-import { navigateTo, navigateToWorkflows } from "store/navigation/navigation-action";
+import { navigateTo } from "store/navigation/navigation-action";
import { snackbarActions } from "store/snackbar/snackbar-actions";
import { SnackbarKind } from "../snackbar/snackbar-actions";
-import { showWorkflowDetails } from "store/workflow-panel/workflow-panel-actions";
import { loadSubprocessPanel, subprocessPanelActions } from "../subprocess-panel/subprocess-panel-actions";
import { initProcessLogsPanel, processLogsPanelActions } from "store/process-logs-panel/process-logs-panel-actions";
import { CollectionFile } from "models/collection-file";
diff --git a/src/store/project-panel/project-panel-middleware-service.ts b/src/store/project-panel/project-panel-middleware-service.ts
index b2ddb46c66..b72058d56e 100644
--- a/src/store/project-panel/project-panel-middleware-service.ts
+++ b/src/store/project-panel/project-panel-middleware-service.ts
@@ -35,7 +35,6 @@ import { updatePublicFavorites } from "store/public-favorites/public-favorites-a
import { selectedFieldsOfGroup } from "models/group";
import { defaultCollectionSelectedFields } from "models/collection";
import { containerRequestFieldsNoMounts } from "models/container-request";
-import { openNotFoundDialog } from "store/not-found-panel/not-found-panel-action";
export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
constructor(private services: ServiceRepository, id: string) {
commit 75b017ae9d566d523e448aaeb863c4d89b3548fe
Author: Peter Amstutz <peter.amstutz at curii.com>
Date: Thu Oct 26 16:56:28 2023 -0400
21067: Better handling of missing output/logs on process panel.
A few other important changes:
* Similar to the change in #21077, this removes the default error
snackbar popups any time a 400 error happens. This reduces user
confusion, particularly when "harmless" errors would occur.
* The collection files component will no longer give an "unhandled
rejection" React failure when it can't load the file list (this took
forever to track down.)
* Collections, projects and workflows will now default to a "not
found" panel if they fail to load. Previously, collections and
workflows would show nothing at all, and projects would show an empty
list.
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz at curii.com>
diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx
index 83de48dec8..f1e50e0f0b 100644
--- a/src/components/collection-panel-files/collection-panel-files.tsx
+++ b/src/components/collection-panel-files/collection-panel-files.tsx
@@ -311,6 +311,8 @@ export const CollectionPanelFiles = withStyles(styles)(
return { ...next, ...prev };
}, {});
setPathData(state => ({ ...state, ...newState }));
+ }, () => {
+ // Nothing to do
})
.finally(() => {
setIsLoading(false);
diff --git a/src/index.tsx b/src/index.tsx
index a3f6c1ee79..b0bda23ba8 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -153,25 +153,13 @@ fetchConfig().then(({ config, apiHost }) => {
const services = createServices(config, {
progressFn: (id, working) => {
- //store.dispatch(progressIndicatorActions.TOGGLE_WORKING({ id, working }));
},
errorFn: (id, error, showSnackBar: boolean) => {
if (showSnackBar) {
console.error("Backend error:", error);
-
- if (error.status === 404) {
- store.dispatch(openNotFoundDialog());
- } else if (error.status === 401 && error.errors[0].indexOf("Not logged in") > -1) {
+ if (error.status === 401 && error.errors[0].indexOf("Not logged in") > -1) {
// Catch auth errors when navigating and redirect to login preserving url location
store.dispatch(logout(false, true));
- } else {
- store.dispatch(
- snackbarActions.OPEN_SNACKBAR({
- message: `${error.errors ? error.errors[0] : error.message}`,
- kind: SnackbarKind.ERROR,
- hideDuration: 8000,
- })
- );
}
}
},
diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
index de8f258708..e50e5ed350 100644
--- a/src/services/collection-service/collection-service.ts
+++ b/src/services/collection-service/collection-service.ts
@@ -53,11 +53,14 @@ export class CollectionService extends TrashableResourceService<CollectionResour
}
async files(uuid: string) {
- const request = await this.keepWebdavClient.propfind(`c=${uuid}`);
- if (request.responseXML != null) {
- return extractFilesData(request.responseXML);
+ try {
+ const request = await this.keepWebdavClient.propfind(`c=${uuid}`);
+ if (request.responseXML != null) {
+ return extractFilesData(request.responseXML);
+ }
+ } catch (e) {
+ return Promise.reject(e);
}
-
return Promise.reject();
}
diff --git a/src/store/process-logs-panel/process-logs-panel-actions.ts b/src/store/process-logs-panel/process-logs-panel-actions.ts
index 87a2fa12aa..4e52431eeb 100644
--- a/src/store/process-logs-panel/process-logs-panel-actions.ts
+++ b/src/store/process-logs-panel/process-logs-panel-actions.ts
@@ -13,7 +13,7 @@ import { Process, getProcess } from 'store/processes/process';
import { navigateTo } from 'store/navigation/navigation-action';
import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
import { CollectionFile, CollectionFileType } from "models/collection-file";
-import { ContainerRequestResource } from "models/container-request";
+import { ContainerRequestResource, ContainerRequestState } from "models/container-request";
const SNIPLINE = `================ ✀ ================ ✀ ========= Some log(s) were skipped ========= ✀ ================ ✀ ================`;
const LOG_TIMESTAMP_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{9}Z/;
@@ -40,15 +40,16 @@ export const setProcessLogsPanelFilter = (filter: string) =>
export const initProcessLogsPanel = (processUuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, { logService }: ServiceRepository) => {
+ let process: Process | undefined;
try {
dispatch(processLogsPanelActions.RESET_PROCESS_LOGS_PANEL());
- const process = getProcess(processUuid)(getState().resources);
+ process = getProcess(processUuid)(getState().resources);
if (process?.containerRequest?.uuid) {
// Get log file size info
const logFiles = await loadContainerLogFileList(process.containerRequest, logService);
// Populate lastbyte 0 for each file
- const filesWithProgress = logFiles.map((file) => ({file, lastByte: 0}));
+ const filesWithProgress = logFiles.map((file) => ({ file, lastByte: 0 }));
// Fetch array of LogFragments
const logLines = await loadContainerLogFileContents(filesWithProgress, logService, process);
@@ -57,13 +58,16 @@ export const initProcessLogsPanel = (processUuid: string) =>
const initialState = createInitialLogPanelState(logFiles, logLines);
dispatch(processLogsPanelActions.INIT_PROCESS_LOGS_PANEL(initialState));
}
- } catch(e) {
+ } catch (e) {
// On error, populate empty state to allow polling to start
const initialState = createInitialLogPanelState([], []);
dispatch(processLogsPanelActions.INIT_PROCESS_LOGS_PANEL(initialState));
// Only show toast on errors other than 404 since 404 is expected when logs do not exist yet
if (e.status !== 404) {
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not load process logs', hideDuration: 2000, kind: SnackbarKind.ERROR }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Error loading process logs', hideDuration: 4000, kind: SnackbarKind.ERROR }));
+ }
+ if (e.status === 404 && process?.containerRequest.state === ContainerRequestState.FINAL) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Log collection was trashed or deleted.', hideDuration: 4000, kind: SnackbarKind.WARNING }));
}
}
};
@@ -88,7 +92,7 @@ export const pollProcessLogs = (processUuid: string) =>
const isChanged = !isNew && currentStateLogLastByte < updatedFile.size;
if (isNew || isChanged) {
- return acc.concat({file: updatedFile, lastByte: currentStateLogLastByte});
+ return acc.concat({ file: updatedFile, lastByte: currentStateLogLastByte });
} else {
return acc;
}
@@ -132,17 +136,17 @@ const loadContainerLogFileList = async (containerRequest: ContainerRequestResour
* @returns LogFragment[] containing a single LogFragment corresponding to each input file
*/
const loadContainerLogFileContents = async (logFilesWithProgress: FileWithProgress[], logService: LogService, process: Process) => (
- (await Promise.allSettled(logFilesWithProgress.filter(({file}) => file.size > 0).map(({file, lastByte}) => {
+ (await Promise.allSettled(logFilesWithProgress.filter(({ file }) => file.size > 0).map(({ file, lastByte }) => {
const requestSize = file.size - lastByte;
if (requestSize > maxLogFetchSize) {
const chunkSize = Math.floor(maxLogFetchSize / 2);
- const firstChunkEnd = lastByte+chunkSize-1;
+ const firstChunkEnd = lastByte + chunkSize - 1;
return Promise.all([
logService.getLogFileContents(process.containerRequest, file, lastByte, firstChunkEnd),
- logService.getLogFileContents(process.containerRequest, file, file.size-chunkSize, file.size-1)
+ logService.getLogFileContents(process.containerRequest, file, file.size - chunkSize, file.size - 1)
] as Promise<(LogFragment)>[]);
} else {
- return Promise.all([logService.getLogFileContents(process.containerRequest, file, lastByte, file.size-1)]);
+ return Promise.all([logService.getLogFileContents(process.containerRequest, file, lastByte, file.size - 1)]);
}
})).then((res) => {
if (res.length && res.every(promiseResult => (promiseResult.status === 'rejected'))) {
@@ -150,7 +154,7 @@ const loadContainerLogFileContents = async (logFilesWithProgress: FileWithProgre
// error if every request failed
const error = res.find(
(promiseResult): promiseResult is PromiseRejectedResult => promiseResult.status === 'rejected'
- )?.reason;
+ )?.reason;
return Promise.reject(error);
}
return res.filter((promiseResult): promiseResult is PromiseFulfilledResult<LogFragment[]> => (
@@ -161,16 +165,16 @@ const loadContainerLogFileContents = async (logFilesWithProgress: FileWithProgre
// (prevent incorrect snipline generation or an un-resumable situation)
!!promiseResult.value.every(logFragment => logFragment.contents.length)
)).map(one => one.value)
- })).map((logResponseSet)=> {
+ })).map((logResponseSet) => {
// For any multi fragment response set, modify the last line of non-final chunks to include a line break and snip line
// Don't add snip line as a separate line so that sorting won't reorder it
for (let i = 1; i < logResponseSet.length; i++) {
- const fragment = logResponseSet[i-1];
- const lastLineIndex = fragment.contents.length-1;
+ const fragment = logResponseSet[i - 1];
+ const lastLineIndex = fragment.contents.length - 1;
const lastLineContents = fragment.contents[lastLineIndex];
const newLastLine = `${lastLineContents}\n${SNIPLINE}`;
- logResponseSet[i-1].contents[lastLineIndex] = newLastLine;
+ logResponseSet[i - 1].contents[lastLineIndex] = newLastLine;
}
// Merge LogFragment Array (representing multiple log line arrays) into single LogLine[] / LogFragment
@@ -181,7 +185,7 @@ const loadContainerLogFileContents = async (logFilesWithProgress: FileWithProgre
})
);
-const createInitialLogPanelState = (logFiles: CollectionFile[], logFragments: LogFragment[]): {filters: string[], logs: ProcessLogs} => {
+const createInitialLogPanelState = (logFiles: CollectionFile[], logFragments: LogFragment[]): { filters: string[], logs: ProcessLogs } => {
const logs = groupLogs(logFiles, logFragments);
const filters = Object.keys(logs);
return { filters, logs };
@@ -201,12 +205,12 @@ const groupLogs = (logFiles: CollectionFile[], logFragments: LogFragment[]): Pro
const groupedLogs = logFragments.reduce((grouped, fragment) => ({
...grouped,
- [fragment.logType as string]: {lastByte: fetchLastByteNumber(logFiles, fragment.logType), contents: fragment.contents}
+ [fragment.logType as string]: { lastByte: fetchLastByteNumber(logFiles, fragment.logType), contents: fragment.contents }
}), {});
return {
- [MAIN_FILTER_TYPE]: {lastByte: undefined, contents: mainLogs},
- [ALL_FILTER_TYPE]: {lastByte: undefined, contents: allLogs},
+ [MAIN_FILTER_TYPE]: { lastByte: undefined, contents: mainLogs },
+ [ALL_FILTER_TYPE]: { lastByte: undefined, contents: allLogs },
...groupedLogs,
}
};
@@ -233,9 +237,9 @@ const mergeMultilineLoglines = (logFragments: LogFragment[]) => (
// Partial line without timestamp detected
if (i > 0) {
// If not first line, copy line to previous line
- const previousLineContents = fragmentCopy.contents[i-1];
+ const previousLineContents = fragmentCopy.contents[i - 1];
const newPreviousLineContents = `${previousLineContents}\n${lineContents}`;
- fragmentCopy.contents[i-1] = newPreviousLineContents;
+ fragmentCopy.contents[i - 1] = newPreviousLineContents;
}
// Delete the current line and prevent iterating
fragmentCopy.contents.splice(i, 1);
@@ -283,7 +287,7 @@ export const navigateToLogCollection = (uuid: string) =>
await services.collectionService.get(uuid);
dispatch<any>(navigateTo(uuid));
} catch {
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not request collection', hideDuration: 2000, kind: SnackbarKind.ERROR }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Log collection was trashed or deleted.', hideDuration: 4000, kind: SnackbarKind.WARNING }));
}
};
diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts
index 03e36aac98..66de2f69f3 100644
--- a/src/store/process-panel/process-panel-actions.ts
+++ b/src/store/process-panel/process-panel-actions.ts
@@ -59,7 +59,7 @@ export const navigateToOutput = (uuid: string) => async (dispatch: Dispatch<any>
await services.collectionService.get(uuid);
dispatch<any>(navigateTo(uuid));
} catch {
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: "This collection does not exists!", hideDuration: 2000, kind: SnackbarKind.ERROR }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Output collection was trashed or deleted.", hideDuration: 4000, kind: SnackbarKind.WARNING }));
}
};
@@ -159,8 +159,7 @@ export const updateOutputParams = () => async (dispatch: Dispatch<any>, getState
};
export const openWorkflow = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- dispatch<any>(navigateToWorkflows);
- dispatch<any>(showWorkflowDetails(uuid));
+ dispatch<any>(navigateTo(uuid));
};
export const initProcessPanelFilters = processPanelActions.SET_PROCESS_PANEL_FILTERS([
diff --git a/src/store/project-panel/project-panel-middleware-service.ts b/src/store/project-panel/project-panel-middleware-service.ts
index c0c0cd1873..b2ddb46c66 100644
--- a/src/store/project-panel/project-panel-middleware-service.ts
+++ b/src/store/project-panel/project-panel-middleware-service.ts
@@ -35,6 +35,7 @@ import { updatePublicFavorites } from "store/public-favorites/public-favorites-a
import { selectedFieldsOfGroup } from "models/group";
import { defaultCollectionSelectedFields } from "models/collection";
import { containerRequestFieldsNoMounts } from "models/container-request";
+import { openNotFoundDialog } from "store/not-found-panel/not-found-panel-action";
export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
constructor(private services: ServiceRepository, id: string) {
@@ -69,7 +70,12 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService
rowsPerPage: dataExplorer.rowsPerPage,
})
);
- api.dispatch(couldNotFetchProjectContents());
+ if (e.status === 404) {
+ // It'll just show up as not found
+ }
+ else {
+ api.dispatch(couldNotFetchProjectContents());
+ }
} finally {
if (!background) { api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId())); }
}
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index b03400d5ae..f2dae2c524 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -109,6 +109,12 @@ export const isWorkbenchLoading = (state: RootState) => {
export const handleFirstTimeLoad = (action: any) => async (dispatch: Dispatch<any>, getState: () => RootState) => {
try {
await dispatch(action);
+ } catch (e) {
+ snackbarActions.OPEN_SNACKBAR({
+ message: "Error " + e,
+ hideDuration: 8000,
+ kind: SnackbarKind.WARNING,
+ })
} finally {
if (isWorkbenchLoading(getState())) {
dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index 8cf19c03fe..eed9c7de71 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -37,6 +37,7 @@ import { Link as ButtonLink } from '@material-ui/core';
import { ResourceWithName, ResponsiblePerson } from 'views-components/data-explorer/renderers';
import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
import { resourceIsFrozen } from 'common/frozen-resources';
+import { DefaultView } from "components/default-view/default-view";
type CssRules = 'root'
| 'button'
@@ -229,7 +230,17 @@ export const CollectionPanel = withStyles(styles)(connect(
</Card>
</MPVPanelContent>
</MPVContainer >
- : null;
+ : <Grid
+ container
+ alignItems="center"
+ justify="center"
+ style={{ minHeight: "100%" }}>
+ <DefaultView
+ icon={CollectionIcon}
+ messages={["Collection not found"]}
+ />
+ </Grid>
+ ;
}
handleContextMenu = (event: React.MouseEvent<any>) => {
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 4c94ab8d2d..2f274c97d5 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import withStyles from '@material-ui/core/styles/withStyles';
import { DispatchProp, connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
-import { StyleRulesCallback, WithStyles } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles, Grid } from '@material-ui/core';
import { DataExplorer } from 'views-components/data-explorer/data-explorer';
import { DataColumns } from 'components/data-table/data-table';
@@ -51,6 +51,7 @@ import { GroupClass, GroupResource } from 'models/group';
import { CollectionResource } from 'models/collection';
import { resourceIsFrozen } from 'common/frozen-resources';
import { ProjectResource } from 'models/project';
+import { DefaultView } from "components/default-view/default-view";
type CssRules = 'root' | 'button';
@@ -238,6 +239,7 @@ const DEFAULT_VIEW_MESSAGES = ['Your project is empty.', 'Please create a projec
interface ProjectPanelDataProps {
currentItemId: string;
resources: ResourcesState;
+ project: GroupResource;
isAdmin: boolean;
userUuid: string;
dataExplorerItems: any;
@@ -245,17 +247,24 @@ interface ProjectPanelDataProps {
type ProjectPanelProps = ProjectPanelDataProps & DispatchProp & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
-export const ProjectPanel = withStyles(styles)(
- connect((state: RootState) => ({
- currentItemId: getProperty(PROJECT_PANEL_CURRENT_UUID)(state.properties),
+const mapStateToProps = (state: RootState) => {
+ const currentItemId = getProperty<string>(PROJECT_PANEL_CURRENT_UUID)(state.properties);
+ const project = getResource<GroupResource>(currentItemId || "")(state.resources);
+ return {
+ currentItemId,
+ project,
resources: state.resources,
userUuid: state.auth.user!.uuid,
- }))(
+ };
+}
+
+export const ProjectPanel = withStyles(styles)(
+ connect(mapStateToProps)(
class extends React.Component<ProjectPanelProps> {
render() {
const { classes } = this.props;
- return (
+ return this.props.project ?
<div data-cy='project-panel' className={classes.root}>
<DataExplorer
id={PROJECT_PANEL_ID}
@@ -267,7 +276,16 @@ export const ProjectPanel = withStyles(styles)(
defaultViewMessages={DEFAULT_VIEW_MESSAGES}
/>
</div>
- );
+ : <Grid
+ container
+ alignItems="center"
+ justify="center"
+ style={{ minHeight: "100%" }}>
+ <DefaultView
+ icon={ProjectIcon}
+ messages={["Project not found"]}
+ />
+ </Grid>;
}
isCurrentItemChild = (resource: Resource) => {
diff --git a/src/views/workflow-panel/registered-workflow-panel.tsx b/src/views/workflow-panel/registered-workflow-panel.tsx
index 5973efedc8..da273719bf 100644
--- a/src/views/workflow-panel/registered-workflow-panel.tsx
+++ b/src/views/workflow-panel/registered-workflow-panel.tsx
@@ -13,6 +13,7 @@ import {
CardHeader,
CardContent,
IconButton,
+ Grid
} from '@material-ui/core';
import { connect, DispatchProp } from "react-redux";
import { RouteComponentProps } from 'react-router';
@@ -26,6 +27,7 @@ import { getResource } from 'store/resources/resources';
import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
import { ProcessIOCard, ProcessIOCardType } from 'views/process-panel/process-io-card';
+import { DefaultView } from "components/default-view/default-view";
type CssRules = 'root'
| 'button'
@@ -200,7 +202,16 @@ export const RegisteredWorkflowPanel = withStyles(styles)(connect(
</Card>
</MPVPanelContent>
</MPVContainer>
- : null;
+ : <Grid
+ container
+ alignItems="center"
+ justify="center"
+ style={{ minHeight: "100%" }}>
+ <DefaultView
+ icon={WorkflowIcon}
+ messages={["Workflow not found"]}
+ />
+ </Grid>;
}
handleContextMenu = (event: React.MouseEvent<any>) => {
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list