From 2ee1db29750704c6cd381483c44c98c6cff656e0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Daniel=20Kuty=C5=82a?= Date: Fri, 7 Aug 2020 20:22:08 +0200 Subject: [PATCH] 16659: Added copy to clipboard button for the api token MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła --- src/components/code-snippet/code-snippet.tsx | 6 +- .../data-table-filters-popover.tsx | 2 +- src/components/default-view/default-view.tsx | 4 +- .../list-item-text-icon.tsx | 2 +- src/components/tree/tree.tsx | 2 +- src/components/tree/virtual-tree.tsx | 2 +- .../attributes-dialog.tsx | 2 +- .../current-token-dialog.test.tsx | 47 +++++++ .../current-token-dialog.tsx | 118 +++++++++++------- .../details-panel/details-panel.tsx | 2 +- 10 files changed, 130 insertions(+), 57 deletions(-) create mode 100644 src/views-components/current-token-dialog/current-token-dialog.test.tsx 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; export const CodeSnippet = withStyles(styles)( ({ classes, lines, className, apiResponse }: CodeSnippetProps) => - { 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)( {messages.map((msg: string, index: number) => { - return {msg}; })} 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("", () => { + let props; + let wrapper; + + beforeEach(() => { + props = { + classes: {}, + data: { + currentToken: "123123123123", + }, + dispatch: jest.fn(), + }; + }); + + describe("copy to clipboard", () => { + beforeEach(() => { + wrapper = shallow(); + }); + + 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 = (theme: ArvadosTheme) => ({ link: { @@ -28,54 +30,78 @@ const styles: StyleRulesCallback = (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; +type CurrentTokenProps = CurrentTokenDialogData & WithDialogProps<{}> & WithStyles & DispatchProp; -export const CurrentTokenDialog = - withStyles(styles)( - connect(getCurrentTokenDialogData)( - withDialog(CURRENT_TOKEN_DIALOG_NAME)( - class extends React.Component { - render() { - const { classes, open, closeDialog, ...data } = this.props; - return - Current Token - - - The Arvados API token is a secret key that enables the Arvados SDKs to access Arvados with the proper permissions. - - For more information see - - Getting an API token. - - +export class CurrentTokenDialogComponent extends React.Component { + 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 + Current Token + + + The Arvados API token is a secret key that enables the Arvados SDKs to access Arvados with the proper permissions. + + For more information see + + Getting an API token. + - - Paste the following lines at a shell prompt to set up the necessary environment for Arvados SDKs to authenticate to your klingenc account. - - - - Arvados - virtual machines - do this for you automatically. This setup is needed only when you use the API remotely (e.g., from your own workstation). - - - - - - ; - } + + + Paste the following lines at a shell prompt to set up the necessary environment for Arvados SDKs to authenticate to your klingenc account. + + + this.onCopy('Token copied to clipboard')}> + + + + Arvados + virtual machines + do this for you automatically. This setup is needed only when you use the API remotely (e.g., from your own workstation). + + + + + + ; } -))); +} + +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'; -- 2.30.2