[ARVADOS-WORKBENCH2] created: 1.4.1-397-g2ee1db29

Git user git at public.arvados.org
Fri Aug 7 18:23:12 UTC 2020


        at  2ee1db29750704c6cd381483c44c98c6cff656e0 (commit)


commit 2ee1db29750704c6cd381483c44c98c6cff656e0
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date:   Fri Aug 7 20:22:08 2020 +0200

    16659: Added copy to clipboard button for the api token
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>

diff --git a/src/components/code-snippet/code-snippet.tsx b/src/components/code-snippet/code-snippet.tsx
index 84271f0e..72d7d92b 100644
--- a/src/components/code-snippet/code-snippet.tsx
+++ b/src/components/code-snippet/code-snippet.tsx
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { StyleRulesCallback, WithStyles, Typography, withStyles } from '@material-ui/core';
 import { ArvadosTheme } from '~/common/custom-theme';
-import * as classNames from 'classnames';
+import classNames from 'classnames';
 
 type CssRules = 'root' | 'space';
 
@@ -30,8 +30,8 @@ type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
 
 export const CodeSnippet = withStyles(styles)(
     ({ classes, lines, className, apiResponse }: CodeSnippetProps) =>
-        <Typography 
-        component="div" 
+        <Typography
+        component="div"
         className={classNames(classes.root, className)}>
             {
                 lines.map((line: string, index: number) => {
diff --git a/src/components/data-table-filters/data-table-filters-popover.tsx b/src/components/data-table-filters/data-table-filters-popover.tsx
index 456d2375..80c79c39 100644
--- a/src/components/data-table-filters/data-table-filters-popover.tsx
+++ b/src/components/data-table-filters/data-table-filters-popover.tsx
@@ -18,7 +18,7 @@ import {
     Tooltip,
     IconButton
 } from "@material-ui/core";
-import * as classnames from "classnames";
+import classnames from "classnames";
 import { DefaultTransformOrigin } from "~/components/popover/helpers";
 import { createTree } from '~/models/tree';
 import { DataTableFilters, DataTableFiltersTree } from "./data-table-filters-tree";
diff --git a/src/components/default-view/default-view.tsx b/src/components/default-view/default-view.tsx
index 036fe1e4..44db79d1 100644
--- a/src/components/default-view/default-view.tsx
+++ b/src/components/default-view/default-view.tsx
@@ -7,7 +7,7 @@ import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/st
 import { ArvadosTheme } from '../../common/custom-theme';
 import { Typography } from '@material-ui/core';
 import { IconType } from '../icon/icon';
-import * as classnames from "classnames";
+import classnames from "classnames";
 
 type CssRules = 'root' | 'icon' | 'message';
 
@@ -39,7 +39,7 @@ export const DefaultView = withStyles(styles)(
         <Typography className={classnames([classes.root, classRoot])} component="div">
             <Icon className={classnames([classes.icon, classIcon])} />
             {messages.map((msg: string, index: number) => {
-                return <Typography key={index}  
+                return <Typography key={index}
                     className={classnames([classes.message, classMessage])}>{msg}</Typography>;
             })}
         </Typography>
diff --git a/src/components/list-item-text-icon/list-item-text-icon.tsx b/src/components/list-item-text-icon/list-item-text-icon.tsx
index 375538d5..3bea1e1c 100644
--- a/src/components/list-item-text-icon/list-item-text-icon.tsx
+++ b/src/components/list-item-text-icon/list-item-text-icon.tsx
@@ -7,7 +7,7 @@ import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/st
 import { ArvadosTheme } from '~/common/custom-theme';
 import { ListItemIcon, ListItemText, Typography } from '@material-ui/core';
 import { IconType } from '../icon/icon';
-import * as classnames from "classnames";
+import classnames from "classnames";
 
 type CssRules = 'root' | 'listItemText' | 'hasMargin' | 'active';
 
diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx
index 76fbf011..41498fc0 100644
--- a/src/components/tree/tree.tsx
+++ b/src/components/tree/tree.tsx
@@ -7,7 +7,7 @@ import { List, ListItem, ListItemIcon, Collapse, Checkbox, Radio } from "@materi
 import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
 import { ReactElement } from "react";
 import CircularProgress from '@material-ui/core/CircularProgress';
-import * as classnames from "classnames";
+import classnames from "classnames";
 
 import { ArvadosTheme } from '~/common/custom-theme';
 import { SidePanelRightArrowIcon } from '../icon/icon';
diff --git a/src/components/tree/virtual-tree.tsx b/src/components/tree/virtual-tree.tsx
index 59fe34b1..6db3d1e2 100644
--- a/src/components/tree/virtual-tree.tsx
+++ b/src/components/tree/virtual-tree.tsx
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import * as classnames from "classnames";
+import classnames from "classnames";
 import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
 import { ReactElement } from "react";
 import { FixedSizeList, ListChildComponentProps } from "react-window";
diff --git a/src/views-components/compute-nodes-dialog/attributes-dialog.tsx b/src/views-components/compute-nodes-dialog/attributes-dialog.tsx
index 5d9946b4..83a98381 100644
--- a/src/views-components/compute-nodes-dialog/attributes-dialog.tsx
+++ b/src/views-components/compute-nodes-dialog/attributes-dialog.tsx
@@ -12,7 +12,7 @@ import { WithDialogProps, withDialog } from "~/store/dialog/with-dialog";
 import { COMPUTE_NODE_ATTRIBUTES_DIALOG } from '~/store/compute-nodes/compute-nodes-actions';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { NodeResource, NodeProperties, NodeInfo } from '~/models/node';
-import * as classnames from "classnames";
+import classnames from "classnames";
 
 type CssRules = 'root' | 'grid';
 
diff --git a/src/views-components/current-token-dialog/current-token-dialog.test.tsx b/src/views-components/current-token-dialog/current-token-dialog.test.tsx
new file mode 100644
index 00000000..188076d7
--- /dev/null
+++ b/src/views-components/current-token-dialog/current-token-dialog.test.tsx
@@ -0,0 +1,47 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { shallow, configure } from "enzyme";
+import * as Adapter from "enzyme-adapter-react-16";
+import * as CopyToClipboard from "react-copy-to-clipboard";
+import { CurrentTokenDialogComponent } from "./current-token-dialog";
+
+configure({ adapter: new Adapter() });
+
+describe("<CurrentTokenDialog />", () => {
+  let props;
+  let wrapper;
+
+  beforeEach(() => {
+    props = {
+      classes: {},
+      data: {
+        currentToken: "123123123123",
+      },
+      dispatch: jest.fn(),
+    };
+  });
+
+  describe("copy to clipboard", () => {
+    beforeEach(() => {
+      wrapper = shallow(<CurrentTokenDialogComponent {...props} />);
+    });
+
+    it("should copy API TOKEN to the clipboard", () => {
+      // when
+      wrapper.find(CopyToClipboard).props().onCopy();
+
+      // then
+      expect(props.dispatch).toHaveBeenCalledWith({
+        payload: {
+          hideDuration: 2000,
+          kind: 1,
+          message: "Token copied to clipboard",
+        },
+        type: "OPEN_SNACKBAR",
+      });
+    });
+  });
+});
diff --git a/src/views-components/current-token-dialog/current-token-dialog.tsx b/src/views-components/current-token-dialog/current-token-dialog.tsx
index bc0071af..467fc7c8 100644
--- a/src/views-components/current-token-dialog/current-token-dialog.tsx
+++ b/src/views-components/current-token-dialog/current-token-dialog.tsx
@@ -4,14 +4,16 @@
 
 import * as React from 'react';
 import { Dialog, DialogActions, DialogTitle, DialogContent, WithStyles, withStyles, StyleRulesCallback, Button, Typography } from '@material-ui/core';
+import * as CopyToClipboard from 'react-copy-to-clipboard';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { withDialog } from '~/store/dialog/with-dialog';
 import { WithDialogProps } from '~/store/dialog/with-dialog';
-import { connect } from 'react-redux';
+import { connect, DispatchProp } from 'react-redux';
 import { CurrentTokenDialogData, getCurrentTokenDialogData, CURRENT_TOKEN_DIALOG_NAME } from '~/store/current-token-dialog/current-token-dialog-actions';
 import { DefaultCodeSnippet } from '~/components/default-code-snippet/default-code-snippet';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
 
-type CssRules = 'link' | 'paper' | 'button';
+type CssRules = 'link' | 'paper' | 'button' | 'copyButton';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     link: {
@@ -28,54 +30,78 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     button: {
         fontSize: '0.8125rem',
         fontWeight: 600
+    },
+    copyButton: {
+        boxShadow: 'none',
+        marginTop: theme.spacing.unit * 2,
+        marginBottom: theme.spacing.unit * 2,
     }
 });
 
-type CurrentTokenProps = CurrentTokenDialogData & WithDialogProps<{}> & WithStyles<CssRules>;
+type CurrentTokenProps = CurrentTokenDialogData & WithDialogProps<{}> & WithStyles<CssRules> & DispatchProp;
 
-export const CurrentTokenDialog =
-    withStyles(styles)(
-    connect(getCurrentTokenDialogData)(
-    withDialog(CURRENT_TOKEN_DIALOG_NAME)(
-    class extends React.Component<CurrentTokenProps> {
-        render() {
-            const { classes, open, closeDialog, ...data } = this.props;
-            return <Dialog
-                open={open}
-                onClose={closeDialog}
-                fullWidth={true}
-                maxWidth='md'>
-                <DialogTitle>Current Token</DialogTitle>
-                <DialogContent>
-                    <Typography  paragraph={true}>
-                        The Arvados API token is a secret key that enables the Arvados SDKs to access Arvados with the proper permissions.
-                                <Typography component='p'>
-                            For more information see
-                                    <a href='http://doc.arvados.org/user/reference/api-tokens.html' target='blank' className={classes.link}>
-                                Getting an API token.
-                                    </a>
-                        </Typography>
+export class CurrentTokenDialogComponent extends React.Component<CurrentTokenProps> {
+    onCopy = (message: string) => {
+        this.props.dispatch(snackbarActions.OPEN_SNACKBAR({
+            message,
+            hideDuration: 2000,
+            kind: SnackbarKind.SUCCESS
+        }));
+    }
+
+    getSnippet = ({ apiHost, currentToken }: CurrentTokenDialogData) =>
+        `HISTIGNORE=$HISTIGNORE:'export ARVADOS_API_TOKEN=*'
+    export ARVADOS_API_TOKEN=${currentToken}
+    export ARVADOS_API_HOST=${apiHost}
+    unset ARVADOS_API_HOST_INSECURE`;
+
+    render() {
+        const { classes, open, closeDialog, ...data } = this.props;
+        return <Dialog
+            open={open}
+            onClose={closeDialog}
+            fullWidth={true}
+            maxWidth='md'>
+            <DialogTitle>Current Token</DialogTitle>
+            <DialogContent>
+                <Typography paragraph={true}>
+                    The Arvados API token is a secret key that enables the Arvados SDKs to access Arvados with the proper permissions.
+                            <Typography component='p'>
+                        For more information see
+                                <a href='http://doc.arvados.org/user/reference/api-tokens.html' target='blank' className={classes.link}>
+                            Getting an API token.
+                                </a>
                     </Typography>
-                    <Typography  paragraph={true}>
-                        Paste the following lines at a shell prompt to set up the necessary environment for Arvados SDKs to authenticate to your klingenc account.
-                            </Typography>
-                    <DefaultCodeSnippet lines={[getSnippet(data)]} />
-                    <Typography >
-                        Arvados
-                                <a href='http://doc.arvados.org/user/reference/api-tokens.html' target='blank' className={classes.link}>virtual machines</a>
-                        do this for you automatically. This setup is needed only when you use the API remotely (e.g., from your own workstation).
-                            </Typography>
-                </DialogContent>
-                <DialogActions>
-                    <Button onClick={closeDialog} className={classes.button} color="primary">CLOSE</Button>
-                </DialogActions>
-            </Dialog>;
-        }
+                </Typography>
+                <Typography paragraph={true}>
+                    Paste the following lines at a shell prompt to set up the necessary environment for Arvados SDKs to authenticate to your klingenc account.
+                        </Typography>
+                <DefaultCodeSnippet lines={[this.getSnippet(data)]} />
+                <CopyToClipboard text={data.currentToken} onCopy={() => this.onCopy('Token copied to clipboard')}>
+                    <Button
+                        color="primary"
+                        size="small"
+                        variant="contained"
+                        className={classes.copyButton}
+                    >
+                        Copy Api Token
+                    </Button>
+                </CopyToClipboard>
+                <Typography >
+                    Arvados
+                            <a href='http://doc.arvados.org/user/reference/api-tokens.html' target='blank' className={classes.link}>virtual machines</a>
+                    do this for you automatically. This setup is needed only when you use the API remotely (e.g., from your own workstation).
+                        </Typography>
+            </DialogContent>
+            <DialogActions>
+                <Button onClick={closeDialog} className={classes.button} color="primary">CLOSE</Button>
+            </DialogActions>
+        </Dialog>;
     }
-)));
+}
+
+export const CurrentTokenDialog =
+    withStyles(styles)(
+        connect(getCurrentTokenDialogData)(
+            withDialog(CURRENT_TOKEN_DIALOG_NAME)(CurrentTokenDialogComponent)));
 
-const getSnippet = ({ apiHost, currentToken }: CurrentTokenDialogData) =>
-`HISTIGNORE=$HISTIGNORE:'export ARVADOS_API_TOKEN=*'
-export ARVADOS_API_TOKEN=${currentToken}
-export ARVADOS_API_HOST=${apiHost}
-unset ARVADOS_API_HOST_INSECURE`;
diff --git a/src/views-components/details-panel/details-panel.tsx b/src/views-components/details-panel/details-panel.tsx
index 8244e15f..b6b0cdf1 100644
--- a/src/views-components/details-panel/details-panel.tsx
+++ b/src/views-components/details-panel/details-panel.tsx
@@ -7,7 +7,7 @@ import { IconButton, Tabs, Tab, Typography, Grid, Tooltip } from '@material-ui/c
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
 import { Transition } from 'react-transition-group';
 import { ArvadosTheme } from '~/common/custom-theme';
-import * as classnames from "classnames";
+import classnames from "classnames";
 import { connect } from 'react-redux';
 import { RootState } from '~/store/store';
 import { CloseIcon } from '~/components/icon/icon';

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list