From afdec3d1b09b526eadfdd502709925e692a66aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 3 Oct 2018 09:43:10 +0200 Subject: [PATCH 1/4] Revert "Revert "Org users to react"" --- .../OrgActionBar/OrgActionBar.test.tsx | 23 + .../components/OrgActionBar/OrgActionBar.tsx | 38 ++ .../__snapshots__/OrgActionBar.test.tsx.snap} | 5 +- .../datasources/DataSourcesActionBar.test.tsx | 23 - .../datasources/DataSourcesActionBar.tsx | 62 --- .../datasources/DataSourcesListPage.test.tsx | 3 + .../datasources/DataSourcesListPage.tsx | 45 +- .../DataSourcesActionBar.test.tsx.snap | 42 -- .../DataSourcesListPage.test.tsx.snap | 12 +- public/app/features/org/all.ts | 2 - public/app/features/org/org_users_ctrl.ts | 87 ---- .../app/features/org/partials/orgUsers.html | 105 ----- .../features/plugins/PluginActionBar.test.tsx | 31 -- .../app/features/plugins/PluginActionBar.tsx | 62 --- .../features/plugins/PluginListPage.test.tsx | 3 + .../app/features/plugins/PluginListPage.tsx | 30 +- .../PluginListPage.test.tsx.snap | 13 +- public/app/features/plugins/state/actions.ts | 2 +- .../app/features/users/InviteesTable.test.tsx | 32 ++ public/app/features/users/InviteesTable.tsx | 64 +++ .../features/users/UsersActionBar.test.tsx | 51 ++ public/app/features/users/UsersActionBar.tsx | 80 ++++ .../app/features/users/UsersListPage.test.tsx | 55 +++ public/app/features/users/UsersListPage.tsx | 125 +++++ public/app/features/users/UsersTable.test.tsx | 33 ++ public/app/features/users/UsersTable.tsx | 67 +++ .../app/features/users/__mocks__/userMocks.ts | 56 +++ .../__snapshots__/InviteesTable.test.tsx.snap | 318 +++++++++++++ .../UsersActionBar.test.tsx.snap | 141 ++++++ .../__snapshots__/UsersListPage.test.tsx.snap | 21 + .../__snapshots__/UsersTable.test.tsx.snap | 444 ++++++++++++++++++ public/app/features/users/state/actions.ts | 79 ++++ public/app/features/users/state/reducers.ts | 32 ++ public/app/features/users/state/selectors.ts | 18 + public/app/routes/routes.ts | 8 +- public/app/store/configureStore.ts | 2 + public/app/types/index.ts | 9 +- public/app/types/user.ts | 40 +- public/app/types/users.ts | 37 ++ 39 files changed, 1860 insertions(+), 440 deletions(-) create mode 100644 public/app/core/components/OrgActionBar/OrgActionBar.test.tsx create mode 100644 public/app/core/components/OrgActionBar/OrgActionBar.tsx rename public/app/{features/plugins/__snapshots__/PluginActionBar.test.tsx.snap => core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap} (84%) delete mode 100644 public/app/features/datasources/DataSourcesActionBar.test.tsx delete mode 100644 public/app/features/datasources/DataSourcesActionBar.tsx delete mode 100644 public/app/features/datasources/__snapshots__/DataSourcesActionBar.test.tsx.snap delete mode 100644 public/app/features/org/org_users_ctrl.ts delete mode 100644 public/app/features/org/partials/orgUsers.html delete mode 100644 public/app/features/plugins/PluginActionBar.test.tsx delete mode 100644 public/app/features/plugins/PluginActionBar.tsx create mode 100644 public/app/features/users/InviteesTable.test.tsx create mode 100644 public/app/features/users/InviteesTable.tsx create mode 100644 public/app/features/users/UsersActionBar.test.tsx create mode 100644 public/app/features/users/UsersActionBar.tsx create mode 100644 public/app/features/users/UsersListPage.test.tsx create mode 100644 public/app/features/users/UsersListPage.tsx create mode 100644 public/app/features/users/UsersTable.test.tsx create mode 100644 public/app/features/users/UsersTable.tsx create mode 100644 public/app/features/users/__mocks__/userMocks.ts create mode 100644 public/app/features/users/__snapshots__/InviteesTable.test.tsx.snap create mode 100644 public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap create mode 100644 public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap create mode 100644 public/app/features/users/__snapshots__/UsersTable.test.tsx.snap create mode 100644 public/app/features/users/state/actions.ts create mode 100644 public/app/features/users/state/reducers.ts create mode 100644 public/app/features/users/state/selectors.ts create mode 100644 public/app/types/users.ts diff --git a/public/app/core/components/OrgActionBar/OrgActionBar.test.tsx b/public/app/core/components/OrgActionBar/OrgActionBar.test.tsx new file mode 100644 index 00000000000..d838a2d5c34 --- /dev/null +++ b/public/app/core/components/OrgActionBar/OrgActionBar.test.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import OrgActionBar, { Props } from './OrgActionBar'; + +const setup = (propOverrides?: object) => { + const props: Props = { + searchQuery: '', + setSearchQuery: jest.fn(), + linkButton: { href: 'some/url', title: 'test' }, + }; + + Object.assign(props, propOverrides); + + return shallow(); +}; + +describe('Render', () => { + it('should render component', () => { + const wrapper = setup(); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/core/components/OrgActionBar/OrgActionBar.tsx b/public/app/core/components/OrgActionBar/OrgActionBar.tsx new file mode 100644 index 00000000000..de91c6cc6b3 --- /dev/null +++ b/public/app/core/components/OrgActionBar/OrgActionBar.tsx @@ -0,0 +1,38 @@ +import React, { PureComponent } from 'react'; +import LayoutSelector, { LayoutMode } from '../LayoutSelector/LayoutSelector'; + +export interface Props { + searchQuery: string; + layoutMode?: LayoutMode; + setLayoutMode?: (mode: LayoutMode) => {}; + setSearchQuery: (value: string) => {}; + linkButton: { href: string; title: string }; +} + +export default class OrgActionBar extends PureComponent { + render() { + const { searchQuery, layoutMode, setLayoutMode, linkButton, setSearchQuery } = this.props; + + return ( +
+
+ + setLayoutMode(mode)} /> +
+ + ); + } +} diff --git a/public/app/features/plugins/__snapshots__/PluginActionBar.test.tsx.snap b/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap similarity index 84% rename from public/app/features/plugins/__snapshots__/PluginActionBar.test.tsx.snap rename to public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap index 30cb53cea27..dc53e7863ea 100644 --- a/public/app/features/plugins/__snapshots__/PluginActionBar.test.tsx.snap +++ b/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap @@ -22,7 +22,6 @@ exports[`Render should render component 1`] = ` />
@@ -31,10 +30,10 @@ exports[`Render should render component 1`] = ` /> - Find more plugins on Grafana.com + test `; diff --git a/public/app/features/datasources/DataSourcesActionBar.test.tsx b/public/app/features/datasources/DataSourcesActionBar.test.tsx deleted file mode 100644 index 8337271271e..00000000000 --- a/public/app/features/datasources/DataSourcesActionBar.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { DataSourcesActionBar, Props } from './DataSourcesActionBar'; -import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector'; - -const setup = (propOverrides?: object) => { - const props: Props = { - layoutMode: LayoutModes.Grid, - searchQuery: '', - setDataSourcesLayoutMode: jest.fn(), - setDataSourcesSearchQuery: jest.fn(), - }; - - return shallow(); -}; - -describe('Render', () => { - it('should render component', () => { - const wrapper = setup(); - - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/public/app/features/datasources/DataSourcesActionBar.tsx b/public/app/features/datasources/DataSourcesActionBar.tsx deleted file mode 100644 index d28089b1f21..00000000000 --- a/public/app/features/datasources/DataSourcesActionBar.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { PureComponent } from 'react'; -import { connect } from 'react-redux'; -import LayoutSelector, { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector'; -import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions'; -import { getDataSourcesLayoutMode, getDataSourcesSearchQuery } from './state/selectors'; - -export interface Props { - searchQuery: string; - layoutMode: LayoutMode; - setDataSourcesLayoutMode: typeof setDataSourcesLayoutMode; - setDataSourcesSearchQuery: typeof setDataSourcesSearchQuery; -} - -export class DataSourcesActionBar extends PureComponent { - onSearchQueryChange = event => { - this.props.setDataSourcesSearchQuery(event.target.value); - }; - - render() { - const { searchQuery, layoutMode, setDataSourcesLayoutMode } = this.props; - - return ( -
-
- - setDataSourcesLayoutMode(mode)} - /> -
- - ); - } -} - -function mapStateToProps(state) { - return { - searchQuery: getDataSourcesSearchQuery(state.dataSources), - layoutMode: getDataSourcesLayoutMode(state.dataSources), - }; -} - -const mapDispatchToProps = { - setDataSourcesLayoutMode, - setDataSourcesSearchQuery, -}; - -export default connect(mapStateToProps, mapDispatchToProps)(DataSourcesActionBar); diff --git a/public/app/features/datasources/DataSourcesListPage.test.tsx b/public/app/features/datasources/DataSourcesListPage.test.tsx index fed7954d716..96f6c304b16 100644 --- a/public/app/features/datasources/DataSourcesListPage.test.tsx +++ b/public/app/features/datasources/DataSourcesListPage.test.tsx @@ -12,6 +12,9 @@ const setup = (propOverrides?: object) => { loadDataSources: jest.fn(), navModel: {} as NavModel, dataSourcesCount: 0, + searchQuery: '', + setDataSourcesSearchQuery: jest.fn(), + setDataSourcesLayoutMode: jest.fn(), }; Object.assign(props, propOverrides); diff --git a/public/app/features/datasources/DataSourcesListPage.tsx b/public/app/features/datasources/DataSourcesListPage.tsx index c6db6ee7889..2d18d67a5d2 100644 --- a/public/app/features/datasources/DataSourcesListPage.tsx +++ b/public/app/features/datasources/DataSourcesListPage.tsx @@ -2,21 +2,29 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; import PageHeader from '../../core/components/PageHeader/PageHeader'; -import DataSourcesActionBar from './DataSourcesActionBar'; +import OrgActionBar from '../../core/components/OrgActionBar/OrgActionBar'; +import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA'; import DataSourcesList from './DataSourcesList'; -import { loadDataSources } from './state/actions'; -import { getDataSources, getDataSourcesCount, getDataSourcesLayoutMode } from './state/selectors'; -import { getNavModel } from '../../core/selectors/navModel'; import { DataSource, NavModel } from 'app/types'; import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector'; -import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA'; +import { loadDataSources, setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions'; +import { getNavModel } from '../../core/selectors/navModel'; +import { + getDataSources, + getDataSourcesCount, + getDataSourcesLayoutMode, + getDataSourcesSearchQuery, +} from './state/selectors'; export interface Props { navModel: NavModel; dataSources: DataSource[]; dataSourcesCount: number; layoutMode: LayoutMode; + searchQuery: string; loadDataSources: typeof loadDataSources; + setDataSourcesLayoutMode: typeof setDataSourcesLayoutMode; + setDataSourcesSearchQuery: typeof setDataSourcesSearchQuery; } const emptyListModel = { @@ -40,7 +48,20 @@ export class DataSourcesListPage extends PureComponent { } render() { - const { dataSources, dataSourcesCount, navModel, layoutMode } = this.props; + const { + dataSources, + dataSourcesCount, + navModel, + layoutMode, + searchQuery, + setDataSourcesSearchQuery, + setDataSourcesLayoutMode, + } = this.props; + + const linkButton = { + href: 'datasources/new', + title: 'Add data source', + }; return (
@@ -50,7 +71,14 @@ export class DataSourcesListPage extends PureComponent { ) : ( [ - , + setDataSourcesLayoutMode(mode)} + setSearchQuery={query => setDataSourcesSearchQuery(query)} + linkButton={linkButton} + key="action-bar" + />, , ] )} @@ -66,11 +94,14 @@ function mapStateToProps(state) { dataSources: getDataSources(state.dataSources), layoutMode: getDataSourcesLayoutMode(state.dataSources), dataSourcesCount: getDataSourcesCount(state.dataSources), + searchQuery: getDataSourcesSearchQuery(state.dataSources), }; } const mapDispatchToProps = { loadDataSources, + setDataSourcesSearchQuery, + setDataSourcesLayoutMode, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourcesListPage)); diff --git a/public/app/features/datasources/__snapshots__/DataSourcesActionBar.test.tsx.snap b/public/app/features/datasources/__snapshots__/DataSourcesActionBar.test.tsx.snap deleted file mode 100644 index 24f9f2126d0..00000000000 --- a/public/app/features/datasources/__snapshots__/DataSourcesActionBar.test.tsx.snap +++ /dev/null @@ -1,42 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Render should render component 1`] = ` -
-
- - -
- -`; diff --git a/public/app/features/datasources/__snapshots__/DataSourcesListPage.test.tsx.snap b/public/app/features/datasources/__snapshots__/DataSourcesListPage.test.tsx.snap index c19ee641e1b..973b38f1ff3 100644 --- a/public/app/features/datasources/__snapshots__/DataSourcesListPage.test.tsx.snap +++ b/public/app/features/datasources/__snapshots__/DataSourcesListPage.test.tsx.snap @@ -8,8 +8,18 @@ exports[`Render should render action bar and datasources 1`] = `
- { - this.users = users; - this.unfiltered = users; - }); - this.backendSrv.get('/api/org/invites').then(pendingInvites => { - this.pendingInvites = pendingInvites; - }); - } - - onQueryUpdated() { - const regex = new RegExp(this.searchQuery, 'ig'); - this.users = _.filter(this.unfiltered, item => { - return regex.test(item.email) || regex.test(item.login); - }); - } - - updateOrgUser(user) { - this.backendSrv.patch('/api/org/users/' + user.userId, user); - } - - removeUser(user) { - this.$scope.appEvent('confirm-modal', { - title: 'Delete', - text: 'Are you sure you want to delete user ' + user.login + '?', - yesText: 'Delete', - icon: 'fa-warning', - onConfirm: () => { - this.removeUserConfirmed(user); - }, - }); - } - - removeUserConfirmed(user) { - this.backendSrv.delete('/api/org/users/' + user.userId).then(this.get.bind(this)); - } - - revokeInvite(invite, evt) { - evt.stopPropagation(); - this.backendSrv.patch('/api/org/invites/' + invite.code + '/revoke').then(this.get.bind(this)); - } - - copyInviteToClipboard(evt) { - evt.stopPropagation(); - } - - getInviteUrl(invite) { - return invite.url; - } -} - -coreModule.controller('OrgUsersCtrl', OrgUsersCtrl); diff --git a/public/app/features/org/partials/orgUsers.html b/public/app/features/org/partials/orgUsers.html deleted file mode 100644 index 8f75625b506..00000000000 --- a/public/app/features/org/partials/orgUsers.html +++ /dev/null @@ -1,105 +0,0 @@ - - -
-
- - -
- - -
- -
- - - - Invite - - - - - {{ctrl.externalUserMngLinkName}} - -
- -
- -
- -
- - - - - - - - - - - - - - - - - - - -
LoginEmail - Seen - Time since user was seen using Grafana - Role
- - {{user.login}}{{user.email}}{{user.lastSeenAtAge}} -
- -
-
- - - -
-
- -
- - - - - - - - - - - - - - - -
EmailName
{{invite.email}}{{invite.name}} - -   - - -
-
-
- diff --git a/public/app/features/plugins/PluginActionBar.test.tsx b/public/app/features/plugins/PluginActionBar.test.tsx deleted file mode 100644 index be3f37e89fa..00000000000 --- a/public/app/features/plugins/PluginActionBar.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { PluginActionBar, Props } from './PluginActionBar'; -import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector'; - -const setup = (propOverrides?: object) => { - const props: Props = { - searchQuery: '', - layoutMode: LayoutModes.Grid, - setLayoutMode: jest.fn(), - setPluginsSearchQuery: jest.fn(), - }; - - Object.assign(props, propOverrides); - - const wrapper = shallow(); - const instance = wrapper.instance() as PluginActionBar; - - return { - wrapper, - instance, - }; -}; - -describe('Render', () => { - it('should render component', () => { - const { wrapper } = setup(); - - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/public/app/features/plugins/PluginActionBar.tsx b/public/app/features/plugins/PluginActionBar.tsx deleted file mode 100644 index 301b432ff5c..00000000000 --- a/public/app/features/plugins/PluginActionBar.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { PureComponent } from 'react'; -import { connect } from 'react-redux'; -import LayoutSelector, { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector'; -import { setLayoutMode, setPluginsSearchQuery } from './state/actions'; -import { getPluginsSearchQuery, getLayoutMode } from './state/selectors'; - -export interface Props { - searchQuery: string; - layoutMode: LayoutMode; - setLayoutMode: typeof setLayoutMode; - setPluginsSearchQuery: typeof setPluginsSearchQuery; -} - -export class PluginActionBar extends PureComponent { - onSearchQueryChange = event => { - this.props.setPluginsSearchQuery(event.target.value); - }; - - render() { - const { searchQuery, layoutMode, setLayoutMode } = this.props; - - return ( -
-
- - setLayoutMode(mode)} /> -
- - ); - } -} - -function mapStateToProps(state) { - return { - searchQuery: getPluginsSearchQuery(state.plugins), - layoutMode: getLayoutMode(state.plugins), - }; -} - -const mapDispatchToProps = { - setPluginsSearchQuery, - setLayoutMode, -}; - -export default connect(mapStateToProps, mapDispatchToProps)(PluginActionBar); diff --git a/public/app/features/plugins/PluginListPage.test.tsx b/public/app/features/plugins/PluginListPage.test.tsx index 452a89837c7..b173ef51a2a 100644 --- a/public/app/features/plugins/PluginListPage.test.tsx +++ b/public/app/features/plugins/PluginListPage.test.tsx @@ -8,6 +8,9 @@ const setup = (propOverrides?: object) => { const props: Props = { navModel: {} as NavModel, plugins: [] as Plugin[], + searchQuery: '', + setPluginsSearchQuery: jest.fn(), + setPluginsLayoutMode: jest.fn(), layoutMode: LayoutModes.Grid, loadPlugins: jest.fn(), }; diff --git a/public/app/features/plugins/PluginListPage.tsx b/public/app/features/plugins/PluginListPage.tsx index de2968b126c..c24d44d6826 100644 --- a/public/app/features/plugins/PluginListPage.tsx +++ b/public/app/features/plugins/PluginListPage.tsx @@ -1,20 +1,23 @@ import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; -import PageHeader from '../../core/components/PageHeader/PageHeader'; -import PluginActionBar from './PluginActionBar'; +import PageHeader from 'app/core/components/PageHeader/PageHeader'; +import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar'; import PluginList from './PluginList'; -import { NavModel, Plugin } from '../../types'; -import { loadPlugins } from './state/actions'; +import { NavModel, Plugin } from 'app/types'; +import { loadPlugins, setPluginsLayoutMode, setPluginsSearchQuery } from './state/actions'; import { getNavModel } from '../../core/selectors/navModel'; -import { getLayoutMode, getPlugins } from './state/selectors'; +import { getLayoutMode, getPlugins, getPluginsSearchQuery } from './state/selectors'; import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector'; export interface Props { navModel: NavModel; plugins: Plugin[]; layoutMode: LayoutMode; + searchQuery: string; loadPlugins: typeof loadPlugins; + setPluginsLayoutMode: typeof setPluginsLayoutMode; + setPluginsSearchQuery: typeof setPluginsSearchQuery; } export class PluginListPage extends PureComponent { @@ -27,13 +30,23 @@ export class PluginListPage extends PureComponent { } render() { - const { navModel, plugins, layoutMode } = this.props; + const { navModel, plugins, layoutMode, setPluginsLayoutMode, setPluginsSearchQuery, searchQuery } = this.props; + const linkButton = { + href: 'https://grafana.com/plugins?utm_source=grafana_plugin_list', + title: 'Find more plugins on Grafana.com', + }; return (
- + setPluginsLayoutMode(mode)} + setSearchQuery={query => setPluginsSearchQuery(query)} + linkButton={linkButton} + /> {plugins && }
@@ -46,11 +59,14 @@ function mapStateToProps(state) { navModel: getNavModel(state.navIndex, 'plugins'), plugins: getPlugins(state.plugins), layoutMode: getLayoutMode(state.plugins), + searchQuery: getPluginsSearchQuery(state.plugins), }; } const mapDispatchToProps = { loadPlugins, + setPluginsLayoutMode, + setPluginsSearchQuery, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(PluginListPage)); diff --git a/public/app/features/plugins/__snapshots__/PluginListPage.test.tsx.snap b/public/app/features/plugins/__snapshots__/PluginListPage.test.tsx.snap index 74b23d8850a..7e837d1ec7d 100644 --- a/public/app/features/plugins/__snapshots__/PluginListPage.test.tsx.snap +++ b/public/app/features/plugins/__snapshots__/PluginListPage.test.tsx.snap @@ -8,7 +8,18 @@ exports[`Render should render component 1`] = `
- + ({ +export const setPluginsLayoutMode = (mode: LayoutMode): SetLayoutModeAction => ({ type: ActionTypes.SetLayoutMode, payload: mode, }); diff --git a/public/app/features/users/InviteesTable.test.tsx b/public/app/features/users/InviteesTable.test.tsx new file mode 100644 index 00000000000..9aae4d765b7 --- /dev/null +++ b/public/app/features/users/InviteesTable.test.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import InviteesTable, { Props } from './InviteesTable'; +import { Invitee } from 'app/types'; +import { getMockInvitees } from './__mocks__/userMocks'; + +const setup = (propOverrides?: object) => { + const props: Props = { + invitees: [] as Invitee[], + revokeInvite: jest.fn(), + }; + + Object.assign(props, propOverrides); + + return shallow(); +}; + +describe('Render', () => { + it('should render component', () => { + const wrapper = setup(); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should render invitees', () => { + const wrapper = setup({ + invitees: getMockInvitees(5), + }); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/users/InviteesTable.tsx b/public/app/features/users/InviteesTable.tsx new file mode 100644 index 00000000000..3157bf4fe38 --- /dev/null +++ b/public/app/features/users/InviteesTable.tsx @@ -0,0 +1,64 @@ +import React, { createRef, PureComponent } from 'react'; +import { Invitee } from 'app/types'; + +export interface Props { + invitees: Invitee[]; + revokeInvite: (code: string) => void; +} + +export default class InviteesTable extends PureComponent { + private copyUrlRef = createRef(); + + copyToClipboard = () => { + const node = this.copyUrlRef.current; + + if (node) { + node.select(); + document.execCommand('copy'); + } + }; + + render() { + const { invitees, revokeInvite } = this.props; + + return ( + + + + + + + + + {invitees.map((invitee, index) => { + return ( + + + +
EmailName + +
{invitee.email}{invitee.name} +