17205: Added new component to fetch owner name
authorDaniel Kutyła <daniel.kutyla@contractors.roche.com>
Thu, 28 Jan 2021 21:23:29 +0000 (22:23 +0100)
committerDaniel Kutyła <daniel.kutyla@contractors.roche.com>
Thu, 28 Jan 2021 21:23:29 +0000 (22:23 +0100)
Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla@contractors.roche.com>

src/components/details-attribute/details-attribute.tsx
src/store/owner-name-uuid-enhancer/owner-name-uuid-enhancer-actions.ts [new file with mode: 0644]
src/store/owner-name-uuid-enhancer/owner-name-uuid-enhancer-reducer.ts [new file with mode: 0644]
src/store/store.ts
src/views-components/details-panel/process-details.tsx
src/views-components/details-panel/project-details.tsx
src/views-components/owner-name-uuid-enhancer/owner-name-uuid-enhancer.test.tsx [new file with mode: 0644]
src/views-components/owner-name-uuid-enhancer/owner-name-uuid-enhancer.tsx [new file with mode: 0644]
src/views/collection-panel/collection-panel.tsx

index 01276c57edbfc3d07aa2a00a75e432a3844732b0..7633b71a45685137c92ec69a32f972d4cb0c3109 100644 (file)
@@ -62,6 +62,7 @@ interface DetailsAttributeDataProps {
     onValueClick?: () => void;
     linkToUuid?: string;
     copyValue?: string;
+    uuidEnhancer?: Function;
 }
 
 type DetailsAttributeProps = DetailsAttributeDataProps & WithStyles<CssRules> & FederationConfig & DispatchProp;
@@ -84,23 +85,25 @@ export const DetailsAttribute = connect(mapStateToProps)(withStyles(styles)(
         }
 
         render() {
-            const { label, link, value, children, classes, classLabel,
+            const { uuidEnhancer, label, link, value, children, classes, classLabel,
                 classValue, lowercaseValue, onValueClick, linkToUuid,
                 localCluster, remoteHostsConfig, sessions, copyValue } = this.props;
             let valueNode: React.ReactNode;
 
             if (linkToUuid) {
+            const uuid = uuidEnhancer ? uuidEnhancer(linkToUuid) : linkToUuid;
                 const linkUrl = getNavUrl(linkToUuid || "", { localCluster, remoteHostsConfig, sessions });
                 if (linkUrl[0] === '/') {
-                    valueNode = <Link to={linkUrl} className={classes.link}>{linkToUuid}</Link>;
+                    valueNode = <Link to={linkUrl} className={classes.link}>{uuid}</Link>;
                 } else {
-                    valueNode = <a href={linkUrl} className={classes.link} target='_blank'>{linkToUuid}</a>;
+                    valueNode = <a href={linkUrl} className={classes.link} target='_blank'>{uuid}</a>;
                 }
             } else if (link) {
                 valueNode = <a href={link} className={classes.link} target='_blank'>{value}</a>;
             } else {
                 valueNode = value;
             }
+
             return <Typography component="div" className={classes.attribute}>
                 <Typography component="div" className={classnames([classes.label, classLabel])}>{label}</Typography>
                 <Typography
diff --git a/src/store/owner-name-uuid-enhancer/owner-name-uuid-enhancer-actions.ts b/src/store/owner-name-uuid-enhancer/owner-name-uuid-enhancer-actions.ts
new file mode 100644 (file)
index 0000000..cb95c12
--- /dev/null
@@ -0,0 +1,52 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from 'redux';
+import { unionize, ofType, UnionOf } from '~/common/unionize';
+import { extractUuidObjectType, ResourceObjectType } from '~/models/resource';
+import { ServiceRepository } from '~/services/services';
+import { RootState } from '../store';
+
+export type OwnerNameUuidEnhancerAction = UnionOf<typeof ownerNameUuidEnhancerActions>;
+
+export interface OwnerNameState {
+    name: string;
+    uuid: string;
+}
+
+export const ownerNameUuidEnhancerActions = unionize({
+    SET_OWNER_NAME_BY_UUID: ofType<OwnerNameState>()
+});
+
+export const fetchOwnerNameByUuid = (uuid: string) =>
+    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const objectType = extractUuidObjectType(uuid);
+
+        switch (objectType) {
+            case ResourceObjectType.USER:
+                services.userService.get(uuid, false)
+                    .then((data) =>
+                        dispatch(
+                            ownerNameUuidEnhancerActions.SET_OWNER_NAME_BY_UUID({
+                                uuid,
+                                name: (data as any).fullName,
+                            })
+                        )
+                    );
+                break;
+            case ResourceObjectType.GROUP:
+                services.groupsService.get(uuid, false)
+                    .then((data) =>
+                        dispatch(
+                            ownerNameUuidEnhancerActions.SET_OWNER_NAME_BY_UUID({
+                                uuid,
+                                name: (data as any).name,
+                            })
+                        )
+                    );
+                break;
+            default:
+                break;
+        }
+    };
\ No newline at end of file
diff --git a/src/store/owner-name-uuid-enhancer/owner-name-uuid-enhancer-reducer.ts b/src/store/owner-name-uuid-enhancer/owner-name-uuid-enhancer-reducer.ts
new file mode 100644 (file)
index 0000000..3970792
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ownerNameUuidEnhancerActions, OwnerNameUuidEnhancerAction, OwnerNameState } from './owner-name-uuid-enhancer-actions';
+
+export const ownerNameUuidEnhancerReducer = (state = {}, action: OwnerNameUuidEnhancerAction) =>
+    ownerNameUuidEnhancerActions.match(action, {
+        SET_OWNER_NAME_BY_UUID: (data: OwnerNameState) => ({...state, [data.uuid]: data.name }),
+        default: () => state,
+    });
\ No newline at end of file
index 517368aa43badea0d26c3bd6dbb54701257e0572..929ca616193287929b30c9020eb313a0caae26c0 100644 (file)
@@ -66,6 +66,7 @@ import { linkAccountPanelReducer } from './link-account-panel/link-account-panel
 import { CollectionsWithSameContentAddressMiddlewareService } from '~/store/collections-content-address-panel/collections-content-address-middleware-service';
 import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from '~/store/collections-content-address-panel/collections-content-address-panel-actions';
 import { ownerNameReducer } from '~/store/owner-name/owner-name-reducer';
+import { ownerNameUuidEnhancerReducer } from './owner-name-uuid-enhancer/owner-name-uuid-enhancer-reducer';
 import { SubprocessMiddlewareService } from '~/store/subprocess-panel/subprocess-panel-middleware-service';
 import { SUBPROCESS_PANEL_ID } from '~/store/subprocess-panel/subprocess-panel-actions';
 import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-panel-action';
@@ -178,6 +179,7 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({
     dialog: dialogReducer,
     favorites: favoritesReducer,
     ownerName: ownerNameReducer,
+    ownerNameUuidEnhancer: ownerNameUuidEnhancerReducer,
     publicFavorites: publicFavoritesReducer,
     form: formReducer,
     processLogsPanel: processLogsPanelReducer,
index aa1b3a1d73532de76b07398bc177da7f0d57ea1f..a065d91e4e37f8c96de9fd33d14417444ca515fd 100644 (file)
@@ -10,6 +10,7 @@ import { ResourceKind } from '~/models/resource';
 import { resourceLabel } from '~/common/labels';
 import { DetailsData } from "./details-data";
 import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
+import OwnerNameUuidEnhancer from '../owner-name-uuid-enhancer/owner-name-uuid-enhancer';
 
 export class ProcessDetails extends DetailsData<ProcessResource> {
 
@@ -20,7 +21,8 @@ export class ProcessDetails extends DetailsData<ProcessResource> {
     getDetails() {
         return <div>
             <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
-            <DetailsAttribute label='Owner' linkToUuid={this.item.ownerUuid} value={this.item.ownerUuid} />
+            <DetailsAttribute label='Owner' linkToUuid={this.item.ownerUuid} value={this.item.ownerUuid}
+                uuidEnhancer={(uuid: string) => <OwnerNameUuidEnhancer uuid={uuid} />} />
 
             <DetailsAttribute label='Status' value={this.item.state} />
             <DetailsAttribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
index b901abce8ba7a9d497ab17873152fae196b4c990..eb613bf2f727a3d5762322d60f71e313374d0650 100644 (file)
@@ -17,6 +17,7 @@ import { withStyles, StyleRulesCallback, WithStyles } from '@material-ui/core';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { Dispatch } from 'redux';
 import { getPropertyChip } from '../resource-properties-form/property-chip';
+import OwnerNameUuidEnhancer from '../owner-name-uuid-enhancer/owner-name-uuid-enhancer';
 
 export class ProjectDetails extends DetailsData<ProjectResource> {
     getIcon(className?: string) {
@@ -59,7 +60,8 @@ const ProjectDetailsComponent = connect(null, mapDispatchToProps)(
     withStyles(styles)(
         ({ classes, project, onClick }: ProjectDetailsComponentProps) => <div>
             <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROJECT)} />
-            <DetailsAttribute label='Owner' linkToUuid={project.ownerUuid} lowercaseValue={true} />
+            <DetailsAttribute label='Owner' linkToUuid={project.ownerUuid}
+                uuidEnhancer={(uuid: string) => <OwnerNameUuidEnhancer uuid={uuid} />} lowercaseValue={true} />
             <DetailsAttribute label='Last modified' value={formatDate(project.modifiedAt)} />
             <DetailsAttribute label='Created at' value={formatDate(project.createdAt)} />
             <DetailsAttribute label='Project UUID' linkToUuid={project.uuid} value={project.uuid} />
diff --git a/src/views-components/owner-name-uuid-enhancer/owner-name-uuid-enhancer.test.tsx b/src/views-components/owner-name-uuid-enhancer/owner-name-uuid-enhancer.test.tsx
new file mode 100644 (file)
index 0000000..1df5faf
--- /dev/null
@@ -0,0 +1,46 @@
+// 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 { OwnerNameUuidEnhancer, OwnerNameUuidEnhancerProps } from './owner-name-uuid-enhancer';
+
+configure({ adapter: new Adapter() });
+
+describe('NotFoundPanelRoot', () => {
+    let props: OwnerNameUuidEnhancerProps;
+
+    beforeEach(() => {
+        props = {
+            ownerNamesMap: {},
+            fetchOwner: () => {},
+            uuid: 'zzzz-tpzed-xxxxxxxxxxxxxxx',
+        };
+    });
+
+    it('should render uuid without name', () => {
+        // when
+        const wrapper = mount(<OwnerNameUuidEnhancer {...props} />);
+
+        // then
+        expect(wrapper.html()).toBe('<span>zzzz-tpzed-xxxxxxxxxxxxxxx</span>');
+    });
+
+    it('should render uuid with name', () => {
+        // given
+        const fullName = 'John Doe';
+
+        // setup
+        props.ownerNamesMap = {
+            [props.uuid]: fullName
+        };
+
+        // when
+        const wrapper = mount(<OwnerNameUuidEnhancer {...props} />);
+
+        // then
+        expect(wrapper.html()).toBe('<span>zzzz-tpzed-xxxxxxxxxxxxxxx (John Doe)</span>');
+    });
+});
\ No newline at end of file
diff --git a/src/views-components/owner-name-uuid-enhancer/owner-name-uuid-enhancer.tsx b/src/views-components/owner-name-uuid-enhancer/owner-name-uuid-enhancer.tsx
new file mode 100644 (file)
index 0000000..b4431fa
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Dispatch } from 'redux';
+import { RootState } from '~/store/store';
+import { connect } from 'react-redux';
+import { fetchOwnerNameByUuid } from '~/store/owner-name-uuid-enhancer/owner-name-uuid-enhancer-actions';
+
+export interface OwnerNameUuidEnhancerOwnProps {
+    uuid: string;
+}
+
+export interface OwnerNameUuidEnhancerRootDataProps {
+    ownerNamesMap: any;
+}
+
+export interface OwnerNameUuidEnhancerDispatchProps {
+    fetchOwner: Function;
+}
+
+export type OwnerNameUuidEnhancerProps = OwnerNameUuidEnhancerOwnProps & OwnerNameUuidEnhancerRootDataProps & OwnerNameUuidEnhancerDispatchProps;
+
+export const OwnerNameUuidEnhancer = ({ uuid, ownerNamesMap, fetchOwner }: OwnerNameUuidEnhancerProps) => {
+    React.useEffect(() => {
+        if (!ownerNamesMap[uuid]) {
+            fetchOwner(uuid);
+        }
+    }, [uuid, ownerNamesMap, fetchOwner]);
+
+    return <span>{uuid}{ownerNamesMap[uuid] ? ` (${ownerNamesMap[uuid]})` : ''}</span>;
+};
+
+const mapStateToProps = (state: RootState): OwnerNameUuidEnhancerRootDataProps => {
+    return {
+        ownerNamesMap: state.ownerNameUuidEnhancer,
+    };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): OwnerNameUuidEnhancerDispatchProps => ({
+    fetchOwner: (uuid: string) => dispatch<any>(fetchOwnerNameByUuid(uuid))
+});
+
+export default connect<OwnerNameUuidEnhancerRootDataProps, OwnerNameUuidEnhancerDispatchProps, OwnerNameUuidEnhancerOwnProps>(mapStateToProps, mapDispatchToProps)
+    (OwnerNameUuidEnhancer);
index 685bb78bda561cd90392afc9eecca7078e7b0784..e75073ae3463e308bea83416465a9f5ecfd77259 100644 (file)
@@ -30,6 +30,7 @@ import { UserResource } from '~/models/user';
 import { getUserUuid } from '~/common/getuser';
 import { getProgressIndicator } from '~/store/progress-indicator/progress-indicator-reducer';
 import { COLLECTION_PANEL_LOAD_FILES, loadCollectionFiles, COLLECTION_PANEL_LOAD_FILES_THRESHOLD } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
+import OwnerNameUuidEnhancer from '../../views-components/owner-name-uuid-enhancer/owner-name-uuid-enhancer';
 import { Link } from 'react-router-dom';
 import { Link as ButtonLink } from '@material-ui/core';
 
@@ -288,7 +289,7 @@ export const CollectionDetailsAttributes = (props: { item: CollectionResource, t
         <Grid item xs={12} md={mdSize}>
             <DetailsAttribute classLabel={classes.label} classValue={classes.value}
                 label={isOldVersion ? "This version's UUID" : "Collection UUID"}
-                linkToUuid={item.uuid} />
+                linkToUuid={`${item.uuid} AAAAAAAAAAAAAAAPortable data hash`} />
         </Grid>
         <Grid item xs={12} md={mdSize}>
             <DetailsAttribute classLabel={classes.label} classValue={classes.value}
@@ -297,7 +298,8 @@ export const CollectionDetailsAttributes = (props: { item: CollectionResource, t
         </Grid>
         <Grid item xs={12} md={mdSize}>
             <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                label='Owner' linkToUuid={item.ownerUuid} />
+                label='Owner' linkToUuid={item.ownerUuid}
+                uuidEnhancer={(uuid: string) => <OwnerNameUuidEnhancer uuid={uuid} />} />
         </Grid>
         <Grid item xs={12} md={mdSize}>
             <DetailsAttribute classLabel={classes.label} classValue={classes.value}
@@ -309,7 +311,7 @@ export const CollectionDetailsAttributes = (props: { item: CollectionResource, t
             <DetailsAttribute
                 classLabel={classes.label} classValue={classes.value}
                 label='Version number'
-                value={ showVersionBrowser !== undefined
+                value={showVersionBrowser !== undefined
                     ? <Tooltip title="Open version browser"><ButtonLink underline='none' className={classes.button} onClick={() => showVersionBrowser()}>
                         {<span data-cy='collection-version-number'>{item.version}</span>}
                     </ButtonLink></Tooltip>