Commit 3581d619 by Daniel Lee Committed by GitHub

Merge pull request #14499 from grafana/14483/copy-invite-link-fix

Fix for copy invite link
parents 82adf539 d37dae34
import React, { createRef, PureComponent } from 'react';
import { connect } from 'react-redux';
import { Invitee } from 'app/types';
import { revokeInvite } from './state/actions';
export interface Props {
invitee: Invitee;
revokeInvite: typeof revokeInvite;
}
class InviteeRow extends PureComponent<Props> {
private copyUrlRef = createRef<HTMLTextAreaElement>();
copyToClipboard = () => {
const node = this.copyUrlRef.current;
if (node) {
node.select();
document.execCommand('copy');
}
};
render() {
const { invitee, revokeInvite } = this.props;
return (
<tr>
<td>{invitee.email}</td>
<td>{invitee.name}</td>
<td className="text-right">
<button className="btn btn-inverse btn-mini" onClick={this.copyToClipboard}>
<textarea
readOnly={true}
value={invitee.url}
style={{ position: 'absolute', right: -1000 }}
ref={this.copyUrlRef}
/>
<i className="fa fa-clipboard" /> Copy Invite
</button>
&nbsp;
</td>
<td>
<button className="btn btn-danger btn-mini" onClick={() => revokeInvite(invitee.code)}>
<i className="fa fa-remove" />
</button>
</td>
</tr>
);
}
}
const mapDispatchToProps = {
revokeInvite,
};
export default connect(() => {
return {};
}, mapDispatchToProps)(InviteeRow);
...@@ -7,7 +7,6 @@ import { getMockInvitees } from './__mocks__/userMocks'; ...@@ -7,7 +7,6 @@ import { getMockInvitees } from './__mocks__/userMocks';
const setup = (propOverrides?: object) => { const setup = (propOverrides?: object) => {
const props: Props = { const props: Props = {
invitees: [] as Invitee[], invitees: [] as Invitee[],
onRevokeInvite: jest.fn(),
}; };
Object.assign(props, propOverrides); Object.assign(props, propOverrides);
......
import React, { createRef, PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Invitee } from 'app/types'; import { Invitee } from 'app/types';
import InviteeRow from './InviteeRow';
export interface Props { export interface Props {
invitees: Invitee[]; invitees: Invitee[];
onRevokeInvite: (code: string) => void;
} }
export default class InviteesTable extends PureComponent<Props> { export default class InviteesTable extends PureComponent<Props> {
private copyUrlRef = createRef<HTMLTextAreaElement>();
copyToClipboard = () => {
const node = this.copyUrlRef.current;
if (node) {
node.select();
document.execCommand('copy');
}
};
render() { render() {
const { invitees, onRevokeInvite } = this.props; const { invitees } = this.props;
return ( return (
<table className="filter-table form-inline"> <table className="filter-table form-inline">
...@@ -33,29 +22,7 @@ export default class InviteesTable extends PureComponent<Props> { ...@@ -33,29 +22,7 @@ export default class InviteesTable extends PureComponent<Props> {
</thead> </thead>
<tbody> <tbody>
{invitees.map((invitee, index) => { {invitees.map((invitee, index) => {
return ( return <InviteeRow key={`${invitee.id}-${index}`} invitee={invitee} />;
<tr key={`${invitee.id}-${index}`}>
<td>{invitee.email}</td>
<td>{invitee.name}</td>
<td className="text-right">
<button className="btn btn-inverse btn-mini" onClick={this.copyToClipboard}>
<textarea
readOnly={true}
value={invitee.url}
style={{ position: 'absolute', right: -1000 }}
ref={this.copyUrlRef}
/>
<i className="fa fa-clipboard" /> Copy Invite
</button>
&nbsp;
</td>
<td>
<button className="btn btn-danger btn-mini" onClick={() => onRevokeInvite(invitee.code)}>
<i className="fa fa-remove" />
</button>
</td>
</tr>
);
})} })}
</tbody> </tbody>
</table> </table>
......
...@@ -16,7 +16,6 @@ const setup = (propOverrides?: object) => { ...@@ -16,7 +16,6 @@ const setup = (propOverrides?: object) => {
invitees: [] as Invitee[], invitees: [] as Invitee[],
searchQuery: '', searchQuery: '',
externalUserMngInfo: '', externalUserMngInfo: '',
revokeInvite: jest.fn(),
loadInvitees: jest.fn(), loadInvitees: jest.fn(),
loadUsers: jest.fn(), loadUsers: jest.fn(),
updateUser: jest.fn(), updateUser: jest.fn(),
......
...@@ -9,7 +9,7 @@ import UsersTable from './UsersTable'; ...@@ -9,7 +9,7 @@ import UsersTable from './UsersTable';
import InviteesTable from './InviteesTable'; import InviteesTable from './InviteesTable';
import { Invitee, NavModel, OrgUser } from 'app/types'; import { Invitee, NavModel, OrgUser } from 'app/types';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { loadUsers, loadInvitees, revokeInvite, setUsersSearchQuery, updateUser, removeUser } from './state/actions'; import { loadUsers, loadInvitees, setUsersSearchQuery, updateUser, removeUser } from './state/actions';
import { getNavModel } from '../../core/selectors/navModel'; import { getNavModel } from '../../core/selectors/navModel';
import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors'; import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors';
...@@ -25,7 +25,6 @@ export interface Props { ...@@ -25,7 +25,6 @@ export interface Props {
setUsersSearchQuery: typeof setUsersSearchQuery; setUsersSearchQuery: typeof setUsersSearchQuery;
updateUser: typeof updateUser; updateUser: typeof updateUser;
removeUser: typeof removeUser; removeUser: typeof removeUser;
revokeInvite: typeof revokeInvite;
} }
export interface State { export interface State {
...@@ -79,10 +78,6 @@ export class UsersListPage extends PureComponent<Props, State> { ...@@ -79,10 +78,6 @@ export class UsersListPage extends PureComponent<Props, State> {
}); });
}; };
onRevokeInvite = code => {
this.props.revokeInvite(code);
};
onShowInvites = () => { onShowInvites = () => {
this.setState(prevState => ({ this.setState(prevState => ({
showInvites: !prevState.showInvites, showInvites: !prevState.showInvites,
...@@ -93,7 +88,7 @@ export class UsersListPage extends PureComponent<Props, State> { ...@@ -93,7 +88,7 @@ export class UsersListPage extends PureComponent<Props, State> {
const { invitees, users } = this.props; const { invitees, users } = this.props;
if (this.state.showInvites) { if (this.state.showInvites) {
return <InviteesTable invitees={invitees} onRevokeInvite={code => this.onRevokeInvite(code)} />; return <InviteesTable invitees={invitees} />;
} else { } else {
return ( return (
<UsersTable <UsersTable
...@@ -141,7 +136,6 @@ const mapDispatchToProps = { ...@@ -141,7 +136,6 @@ const mapDispatchToProps = {
setUsersSearchQuery, setUsersSearchQuery,
updateUser, updateUser,
removeUser, removeUser,
revokeInvite,
}; };
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(UsersListPage)); export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(UsersListPage));
...@@ -48,7 +48,7 @@ export const getMockInvitees = (amount: number) => { ...@@ -48,7 +48,7 @@ export const getMockInvitees = (amount: number) => {
orgId: 1, orgId: 1,
role: 'viewer', role: 'viewer',
status: 'not accepted', status: 'not accepted',
url: `localhost/invite/$${i}`, url: `localhost/invite/${i}`,
}); });
} }
......
...@@ -49,270 +49,132 @@ exports[`Render should render invitees 1`] = ` ...@@ -49,270 +49,132 @@ exports[`Render should render invitees 1`] = `
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <Connect(InviteeRow)
invitee={
Object {
"code": "asdfasdfsadf-0",
"createdOn": "2018-10-02",
"email": "invitee-0@test.com",
"emailSent": true,
"emailSentOn": "2018-10-02",
"id": 0,
"invitedByEmail": "admin@grafana.com",
"invitedByLogin": "admin",
"invitedByName": "admin",
"name": "invitee-0",
"orgId": 1,
"role": "viewer",
"status": "not accepted",
"url": "localhost/invite/0",
}
}
key="0-0" key="0-0"
> />
<td> <Connect(InviteeRow)
invitee-0@test.com invitee={
</td> Object {
<td> "code": "asdfasdfsadf-1",
invitee-0 "createdOn": "2018-10-02",
</td> "email": "invitee-1@test.com",
<td "emailSent": true,
className="text-right" "emailSentOn": "2018-10-02",
> "id": 1,
<button "invitedByEmail": "admin@grafana.com",
className="btn btn-inverse btn-mini" "invitedByLogin": "admin",
onClick={[Function]} "invitedByName": "admin",
> "name": "invitee-1",
<textarea "orgId": 1,
readOnly={true} "role": "viewer",
style={ "status": "not accepted",
Object { "url": "localhost/invite/1",
"position": "absolute", }
"right": -1000, }
}
}
value="localhost/invite/$0"
/>
<i
className="fa fa-clipboard"
/>
Copy Invite
</button>
 
</td>
<td>
<button
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</button>
</td>
</tr>
<tr
key="1-1" key="1-1"
> />
<td> <Connect(InviteeRow)
invitee-1@test.com invitee={
</td> Object {
<td> "code": "asdfasdfsadf-2",
invitee-1 "createdOn": "2018-10-02",
</td> "email": "invitee-2@test.com",
<td "emailSent": true,
className="text-right" "emailSentOn": "2018-10-02",
> "id": 2,
<button "invitedByEmail": "admin@grafana.com",
className="btn btn-inverse btn-mini" "invitedByLogin": "admin",
onClick={[Function]} "invitedByName": "admin",
> "name": "invitee-2",
<textarea "orgId": 1,
readOnly={true} "role": "viewer",
style={ "status": "not accepted",
Object { "url": "localhost/invite/2",
"position": "absolute", }
"right": -1000, }
}
}
value="localhost/invite/$1"
/>
<i
className="fa fa-clipboard"
/>
Copy Invite
</button>
 
</td>
<td>
<button
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</button>
</td>
</tr>
<tr
key="2-2" key="2-2"
> />
<td> <Connect(InviteeRow)
invitee-2@test.com invitee={
</td> Object {
<td> "code": "asdfasdfsadf-3",
invitee-2 "createdOn": "2018-10-02",
</td> "email": "invitee-3@test.com",
<td "emailSent": true,
className="text-right" "emailSentOn": "2018-10-02",
> "id": 3,
<button "invitedByEmail": "admin@grafana.com",
className="btn btn-inverse btn-mini" "invitedByLogin": "admin",
onClick={[Function]} "invitedByName": "admin",
> "name": "invitee-3",
<textarea "orgId": 1,
readOnly={true} "role": "viewer",
style={ "status": "not accepted",
Object { "url": "localhost/invite/3",
"position": "absolute", }
"right": -1000, }
}
}
value="localhost/invite/$2"
/>
<i
className="fa fa-clipboard"
/>
Copy Invite
</button>
 
</td>
<td>
<button
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</button>
</td>
</tr>
<tr
key="3-3" key="3-3"
> />
<td> <Connect(InviteeRow)
invitee-3@test.com invitee={
</td> Object {
<td> "code": "asdfasdfsadf-4",
invitee-3 "createdOn": "2018-10-02",
</td> "email": "invitee-4@test.com",
<td "emailSent": true,
className="text-right" "emailSentOn": "2018-10-02",
> "id": 4,
<button "invitedByEmail": "admin@grafana.com",
className="btn btn-inverse btn-mini" "invitedByLogin": "admin",
onClick={[Function]} "invitedByName": "admin",
> "name": "invitee-4",
<textarea "orgId": 1,
readOnly={true} "role": "viewer",
style={ "status": "not accepted",
Object { "url": "localhost/invite/4",
"position": "absolute", }
"right": -1000, }
}
}
value="localhost/invite/$3"
/>
<i
className="fa fa-clipboard"
/>
Copy Invite
</button>
 
</td>
<td>
<button
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</button>
</td>
</tr>
<tr
key="4-4" key="4-4"
> />
<td> <Connect(InviteeRow)
invitee-4@test.com invitee={
</td> Object {
<td> "code": "asdfasdfsadf-5",
invitee-4 "createdOn": "2018-10-02",
</td> "email": "invitee-5@test.com",
<td "emailSent": true,
className="text-right" "emailSentOn": "2018-10-02",
> "id": 5,
<button "invitedByEmail": "admin@grafana.com",
className="btn btn-inverse btn-mini" "invitedByLogin": "admin",
onClick={[Function]} "invitedByName": "admin",
> "name": "invitee-5",
<textarea "orgId": 1,
readOnly={true} "role": "viewer",
style={ "status": "not accepted",
Object { "url": "localhost/invite/5",
"position": "absolute", }
"right": -1000, }
}
}
value="localhost/invite/$4"
/>
<i
className="fa fa-clipboard"
/>
Copy Invite
</button>
 
</td>
<td>
<button
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</button>
</td>
</tr>
<tr
key="5-5" key="5-5"
> />
<td>
invitee-5@test.com
</td>
<td>
invitee-5
</td>
<td
className="text-right"
>
<button
className="btn btn-inverse btn-mini"
onClick={[Function]}
>
<textarea
readOnly={true}
style={
Object {
"position": "absolute",
"right": -1000,
}
}
value="localhost/invite/$5"
/>
<i
className="fa fa-clipboard"
/>
Copy Invite
</button>
 
</td>
<td>
<button
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</button>
</td>
</tr>
</tbody> </tbody>
</table> </table>
`; `;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment