[ARVADOS-WORKBENCH2] updated: 1.4.1-367-g51041db2

Git user git at public.arvados.org
Mon Jun 29 20:06:18 UTC 2020


Summary of changes:
 src/common/config.ts                               |  3 +
 src/index.tsx                                      | 20 ++++---
 src/models/resource.ts                             |  3 +-
 src/routes/routes.ts                               |  4 +-
 .../ancestors-service/ancestors-service.ts         |  5 +-
 src/services/api/api-actions.ts                    |  2 +-
 src/services/common-service/common-service.ts      | 10 ++--
 src/store/navigation/navigation-action.ts          |  1 -
 .../not-found-panel/not-found-panel-root.test.tsx  | 69 ++++++++++++++++++++++
 src/views/not-found-panel/not-found-panel-root.tsx | 40 +++++++++++--
 src/views/not-found-panel/not-found-panel.tsx      |  3 +-
 11 files changed, 133 insertions(+), 27 deletions(-)
 create mode 100644 src/views/not-found-panel/not-found-panel-root.test.tsx

       via  51041db20a296235ca14c6cc73f1ff7f1db2c0b7 (commit)
      from  dd4004015ae711939d2474dd8c6e031b6be2d0ff (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 51041db20a296235ca14c6cc73f1ff7f1db2c0b7
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date:   Mon Jun 29 22:05:26 2020 +0200

    14990: added collections hash / uuid check
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>

diff --git a/src/common/config.ts b/src/common/config.ts
index 39f9fbd1..cf539f3d 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -23,6 +23,9 @@ export interface ClusterConfigJSON {
             Scheme: string
         }
     };
+    Mail?: {
+        SupportEmailAddress: string;
+    };
     Services: {
         Controller: {
             ExternalURL: string
diff --git a/src/index.tsx b/src/index.tsx
index 2cee0540..1a58dad1 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -103,15 +103,17 @@ fetchConfig()
             progressFn: (id, working) => {
                 store.dispatch(progressIndicatorActions.TOGGLE_WORKING({ id, working }));
             },
-            errorFn: (id, error) => {
-                console.error("Backend error:", error);
-                store.dispatch(snackbarActions.OPEN_SNACKBAR({
-                    message: `${error.errors
-                        ? error.errors[0]
-                        : error.message}`,
-                    kind: SnackbarKind.ERROR,
-                    hideDuration: 8000})
-                );
+            errorFn: (id, error, showSnackBar) => {
+                if (showSnackBar) {
+                    console.error("Backend error:", error);
+                    store.dispatch(snackbarActions.OPEN_SNACKBAR({
+                        message: `${error.errors
+                            ? error.errors[0]
+                            : error.message}`,
+                        kind: SnackbarKind.ERROR,
+                        hideDuration: 8000})
+                    );
+                }
             }
         });
         const store = configureStore(history, services);
diff --git a/src/models/resource.ts b/src/models/resource.ts
index d8cdd4a0..371278e5 100644
--- a/src/models/resource.ts
+++ b/src/models/resource.ts
@@ -62,8 +62,9 @@ export enum ResourceObjectType {
 }
 
 export const RESOURCE_UUID_PATTERN = '[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}';
+export const PORTABLE_DATA_HASH_PATTERN = '[a-f0-9]{32}\\+\\d+';
 export const RESOURCE_UUID_REGEX = new RegExp("^" + RESOURCE_UUID_PATTERN + "$");
-export const COLLECTION_PDH_REGEX = /^[a-f0-9]{32}\+\d+$/;
+export const COLLECTION_PDH_REGEX = new RegExp("^" + PORTABLE_DATA_HASH_PATTERN + "$");
 
 export const isResourceUuid = (uuid: string) =>
     RESOURCE_UUID_REGEX.test(uuid);
diff --git a/src/routes/routes.ts b/src/routes/routes.ts
index ebab383f..452589f6 100644
--- a/src/routes/routes.ts
+++ b/src/routes/routes.ts
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { matchPath } from 'react-router';
-import { ResourceKind, RESOURCE_UUID_PATTERN, extractUuidKind, COLLECTION_PDH_REGEX } from '~/models/resource';
+import { ResourceKind, RESOURCE_UUID_PATTERN, extractUuidKind, COLLECTION_PDH_REGEX, PORTABLE_DATA_HASH_PATTERN } from '~/models/resource';
 import { getProjectUrl } from '~/models/project';
 import { getCollectionUrl } from '~/models/collection';
 import { Config } from '~/common/config';
@@ -46,7 +46,7 @@ export const Routes = {
     GROUP_DETAILS: `/group/:id(${RESOURCE_UUID_PATTERN})`,
     LINKS: '/links',
     PUBLIC_FAVORITES: '/public-favorites',
-    COLLECTIONS_CONTENT_ADDRESS: '/collections/:id',
+    COLLECTIONS_CONTENT_ADDRESS: `/collections/:id(${PORTABLE_DATA_HASH_PATTERN})`,
     ALL_PROCESSES: '/all_processes',
     NO_MATCH: '*',
 };
diff --git a/src/services/ancestors-service/ancestors-service.ts b/src/services/ancestors-service/ancestors-service.ts
index 23e7729f..8f5b9032 100644
--- a/src/services/ancestors-service/ancestors-service.ts
+++ b/src/services/ancestors-service/ancestors-service.ts
@@ -27,7 +27,7 @@ export class AncestorService {
         const service = this.getService(extractUuidObjectType(startUuid));
         if (service) {
             try {
-                const resource = await service.get(startUuid);
+                const resource = await service.get(startUuid, false);
                 if (startUuid === endUuid) {
                     return [resource];
                 } else {
@@ -39,9 +39,8 @@ export class AncestorService {
             } catch (e) {
                 return [];
             }
-        } else {
-            return [];
         }
+        return [];
     }
 
     private getService = (objectType?: string) => {
diff --git a/src/services/api/api-actions.ts b/src/services/api/api-actions.ts
index f986786d..00b18229 100644
--- a/src/services/api/api-actions.ts
+++ b/src/services/api/api-actions.ts
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 export type ProgressFn = (id: string, working: boolean) => void;
-export type ErrorFn = (id: string, error: any) => void;
+export type ErrorFn = (id: string, error: any, showSnackBar?: boolean) => void;
 
 export interface ApiActions {
     progressFn: ProgressFn;
diff --git a/src/services/common-service/common-service.ts b/src/services/common-service/common-service.ts
index 44233eb1..e00a3d7d 100644
--- a/src/services/common-service/common-service.ts
+++ b/src/services/common-service/common-service.ts
@@ -66,7 +66,7 @@ export class CommonService<T> {
             }
         }
 
-    static defaultResponse<R>(promise: AxiosPromise<R>, actions: ApiActions, mapKeys = true): Promise<R> {
+    static defaultResponse<R>(promise: AxiosPromise<R>, actions: ApiActions, mapKeys = true, showErrors = true): Promise<R> {
         const reqId = uuid();
         actions.progressFn(reqId, true);
         return promise
@@ -80,7 +80,7 @@ export class CommonService<T> {
             .catch(({ response }) => {
                 actions.progressFn(reqId, false);
                 const errors = CommonService.mapResponseKeys(response) as Errors;
-                actions.errorFn(reqId, errors);
+                actions.errorFn(reqId, errors, showErrors);
                 throw errors;
             });
     }
@@ -101,11 +101,13 @@ export class CommonService<T> {
         );
     }
 
-    get(uuid: string) {
+    get(uuid: string, showErrors?: boolean) {
         return CommonService.defaultResponse(
             this.serverApi
                 .get<T>(this.resourceType + '/' + uuid),
-            this.actions
+            this.actions,
+            true, // mapKeys
+            showErrors
         );
     }
 
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index cb2eb186..d663ae37 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -14,7 +14,6 @@ import { GROUPS_PANEL_LABEL } from '~/store/breadcrumbs/breadcrumbs-actions';
 export const navigateTo = (uuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState) => {
         const kind = extractUuidKind(uuid);
-
         switch (kind) {
             case ResourceKind.PROJECT:
             case ResourceKind.USER:
diff --git a/src/views/not-found-panel/not-found-panel-root.test.tsx b/src/views/not-found-panel/not-found-panel-root.test.tsx
new file mode 100644
index 00000000..1d84a662
--- /dev/null
+++ b/src/views/not-found-panel/not-found-panel-root.test.tsx
@@ -0,0 +1,69 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { mount, configure } from 'enzyme';
+import * as Adapter from "enzyme-adapter-react-16";
+import { StyledComponentProps, MuiThemeProvider } from '@material-ui/core';
+import { ClusterConfigJSON } from '~/common/config';
+import { CustomTheme } from '~/common/custom-theme';
+import { NotFoundPanelRoot, NotFoundPanelRootDataProps, CssRules } from './not-found-panel-root';
+
+configure({ adapter: new Adapter() });
+
+describe('NotFoundPanelRoot', () => {
+    let props: NotFoundPanelRootDataProps & StyledComponentProps<CssRules>;
+
+    beforeEach(() => {
+        props = {
+            classes: {
+                root: 'root',
+                title: 'title',
+                active: 'active',
+            },
+            clusterConfig: {
+                Mail: {
+                    SupportEmailAddress: 'support at example.com'
+                }
+            } as ClusterConfigJSON,
+            location: null,
+        };
+    });
+
+    it('should render component', () => {
+        // given
+        const expectedMessage = 'The page you requested was not found, email us us if you suspect this is a bug.';
+
+        // when
+        const wrapper = mount(
+            <MuiThemeProvider theme={CustomTheme}>
+                <NotFoundPanelRoot {...props} />
+            </MuiThemeProvider>
+            );
+
+        // then
+        expect(wrapper.find('p').text()).toEqual(expectedMessage);
+    });
+
+    it('should render component with additional message', () => {
+        // given
+        const hash = '123hash123';
+        const pathname = `/collections/${hash}`;
+
+        // setup
+        props.location = {
+            pathname,
+        } as any;
+
+        // when
+        const wrapper = mount(
+            <MuiThemeProvider theme={CustomTheme}>
+                <NotFoundPanelRoot {...props} />
+            </MuiThemeProvider>
+            );
+
+        // then
+        expect(wrapper.find('p').first().text()).toContain(hash);
+    });
+});
\ No newline at end of file
diff --git a/src/views/not-found-panel/not-found-panel-root.tsx b/src/views/not-found-panel/not-found-panel-root.tsx
index 7d8ed3fb..439807fd 100644
--- a/src/views/not-found-panel/not-found-panel-root.tsx
+++ b/src/views/not-found-panel/not-found-panel-root.tsx
@@ -3,11 +3,12 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+import { Location } from 'history';
 import { StyleRulesCallback, WithStyles, withStyles, Paper, Grid } from '@material-ui/core';
-import { User } from "~/models/user";
 import { ArvadosTheme } from '~/common/custom-theme';
+import { ClusterConfigJSON } from '~/common/config';
 
-type CssRules = 'root' | 'title';
+export type CssRules = 'root' | 'title' | 'active';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
@@ -18,23 +19,52 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     title: {
         paddingLeft: theme.spacing.unit * 3,
         paddingTop: theme.spacing.unit * 3,
+        paddingBottom: theme.spacing.unit * 3,
         fontSize: '18px'
+    },
+    active: {
+        color: theme.customs.colors.green700,
+        textDecoration: 'none',
     }
 });
 
 export interface NotFoundPanelRootDataProps {
-    user?: User;
+    location: Location<any> | null;
+    clusterConfig: ClusterConfigJSON;
 }
 
 type NotFoundPanelRootProps = NotFoundPanelRootDataProps & WithStyles<CssRules>;
 
+const getAdditionalMessage = (location: Location | null) => {
+    if (!location) {
+        return null;
+    }
+
+    const { pathname } = location;
+
+    if (pathname.indexOf('collections') > -1) {
+        const uuidHash = pathname.replace('/collections/', '');
+
+        return (
+            <p>
+                Please make sure that provided UUID/ObjectHash '{uuidHash}' is valid.
+            </p>
+        );
+    }
+
+    return null;
+};
+
 export const NotFoundPanelRoot = withStyles(styles)(
-    ({ classes }: NotFoundPanelRootProps) =>
+    ({ classes, clusterConfig, location }: NotFoundPanelRootProps) =>
     <Paper>
         <Grid container justify="space-between" wrap="nowrap" alignItems="center">
             <div className={classes.title}>
                 <h2>Not Found</h2>
-                <p>The page you requested was not found.</p>
+                { getAdditionalMessage(location) }
+                <p>
+                    The page you requested was not found, <a className={classes.active} href={`mailto:${clusterConfig.Mail!.SupportEmailAddress}`}>email us</a> us if you suspect this is a bug.
+                </p>
             </div>
         </Grid>
     </Paper>
diff --git a/src/views/not-found-panel/not-found-panel.tsx b/src/views/not-found-panel/not-found-panel.tsx
index 9631c815..adfaf720 100644
--- a/src/views/not-found-panel/not-found-panel.tsx
+++ b/src/views/not-found-panel/not-found-panel.tsx
@@ -8,7 +8,8 @@ import { NotFoundPanelRoot, NotFoundPanelRootDataProps } from '~/views/not-found
 
 const mapStateToProps = (state: RootState): NotFoundPanelRootDataProps => {
     return {
-        user: state.auth.user,
+        location: state.router.location,
+        clusterConfig: state.auth.config.clusterConfig,
     };
 };
 

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list