import org.sonarqube.ws.UserTokens.GenerateWsResponse;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
-import static org.sonar.server.ws.WsUtils.checkRequest;
-import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonar.server.usertoken.ws.UserTokensWsParameters.ACTION_GENERATE;
import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_LOGIN;
import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_NAME;
+import static org.sonar.server.ws.WsUtils.checkRequest;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
public class GenerateAction implements UserTokensWsAction {
private static final int MAX_TOKEN_NAME_LENGTH = 100;
return UserTokens.GenerateWsResponse.newBuilder()
.setLogin(userTokenDto.getLogin())
.setName(userTokenDto.getName())
+ .setCreatedAt(formatDateTime(userTokenDto.getCreatedAt()))
.setToken(token)
.build();
}
*/
package org.sonar.server.usertoken.ws;
-import java.util.Date;
import java.util.List;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonarqube.ws.UserTokens.SearchWsResponse;
import static org.sonar.api.utils.DateUtils.formatDateTime;
-import static org.sonar.server.ws.WsUtils.checkFound;
-import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonar.server.usertoken.ws.UserTokensWsParameters.ACTION_SEARCH;
import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_LOGIN;
+import static org.sonar.server.ws.WsUtils.checkFound;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
public class SearchAction implements UserTokensWsAction {
private final DbClient dbClient;
userTokenBuilder
.clear()
.setName(userTokenDto.getName())
- .setCreatedAt(formatDateTime(new Date(userTokenDto.getCreatedAt())));
+ .setCreatedAt(formatDateTime(userTokenDto.getCreatedAt()));
searchWsResponse.addUserTokens(userTokenBuilder);
}
{
"login": "grace.hopper",
"name": "Third Party Application",
+ "createdAt": "2018-01-10T14:06:05+0100",
"token": "123456789"
}
.setParam(PARAM_NAME, TOKEN_NAME)
.execute().getInput();
- assertJson(response).isSimilarTo(getClass().getResource("generate-example.json"));
+ assertJson(response).ignoreFields("createdAt").isSimilarTo(getClass().getResource("generate-example.json"));
}
@Test
GenerateWsResponse response = newRequest(null, TOKEN_NAME);
assertThat(response.getLogin()).isEqualTo(GRACE_HOPPER);
+ assertThat(response.getCreatedAt()).isNotEmpty();
}
@Test
createdAt: string;
}
-/**
- * List tokens for given user login
- */
+/** List tokens for given user login */
export function getTokens(login: string): Promise<UserToken[]> {
return getJSON('/api/user_tokens/search', { login }).then(r => r.userTokens, throwGlobalError);
}
-/**
- * Generate a user token
- */
-export function generateToken(data: {
+export interface NewToken {
+ createdAt: string;
+ login: string;
name: string;
- login?: string;
-}): Promise<{ login: string; name: string; token: string }> {
+ token: string;
+}
+
+/** Generate a user token */
+export function generateToken(data: { name: string; login?: string }): Promise<NewToken> {
return postJSON('/api/user_tokens/generate', data).catch(throwGlobalError);
}
-/**
- * Revoke a user token
- */
+/** Revoke a user token */
export function revokeToken(data: { name: string; login?: string }): Promise<void | Response> {
return post('/api/user_tokens/revoke', data).catch(throwGlobalError);
}
email?: string;
homepage?: HomePage;
isLoggedIn: true;
+ login: string;
name: string;
}
return (
<div className="account-body account-container">
<Helmet title={translate('my_account.security')} />
- <Tokens user={user} />
+ <Tokens login={user.login} />
{user.local && <Password user={user} />}
</div>
);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Backbone from 'backbone';
-import React, { Component } from 'react';
-import TokensView from '../tokens-view';
-
-export default class Tokens extends Component {
- componentDidMount() {
- this.renderView();
- }
-
- componentWillUnmount() {
- this.destroyView();
- }
-
- destroyView() {
- if (this.destroyView) {
- this.tokensView.destroy();
- }
- }
-
- renderView() {
- const account = new Backbone.Model({
- id: this.props.user.login
- });
-
- this.tokensView = new TokensView({
- el: this.refs.container,
- model: account
- }).render();
- }
-
- render() {
- return <div ref="container" />;
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import TokenForm from '../../users/components/TokensForm';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ login: string;
+}
+
+export default function Tokens({ login }: Props) {
+ return (
+ <div className="boxed-group">
+ <h2>{translate('users.tokens')}</h2>
+ <div className="boxed-group-inner">
+ <div className="big-spacer-bottom big-spacer-right markdown">
+ {translate('my_account.tokens_description')}
+ </div>
+
+ <TokenForm login={login} />
+ </div>
+ </div>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Tokens from '../Tokens';
+
+it('renders', () => {
+ expect(shallow(<Tokens login="user" />)).toMatchSnapshot();
+});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+ className="boxed-group"
+>
+ <h2>
+ users.tokens
+ </h2>
+ <div
+ className="boxed-group-inner"
+ >
+ <div
+ className="big-spacer-bottom big-spacer-right markdown"
+ >
+ my_account.tokens_description
+ </div>
+ <TokensForm
+ login="user"
+ />
+ </div>
+</div>
+`;
+++ /dev/null
-<div class="boxed-group">
- <h2>{{t 'users.tokens'}}</h2>
-
-<div class="boxed-group-inner">
- <div class="big-spacer-bottom big-spacer-right markdown">
- <p>{{t 'my_account.tokens_description'}}</p>
- </div>
-
- {{#notNull tokens}}
- <table class="data">
- <thead>
- <tr>
- <th>{{t 'name'}}</th>
- <th class="text-right">{{t 'created'}}</th>
- <th> </th>
- </tr>
- </thead>
- <tbody>
- {{#each tokens}}
- <tr>
- <td>
- <div title="{{name}}">
- {{limitString name}}
- </div>
- </td>
- <td class="thin nowrap text-right">
- {{d createdAt}}
- </td>
- <td class="thin nowrap text-right">
- <div class="big-spacer-left">
- <form class="js-revoke-token-form" data-token="{{name}}">
- {{#if deleting}}
- <button class="button-red active input-small">{{t 'users.tokens.sure'}}</button>
- {{else}}
- <button class="button-red input-small">{{t 'users.tokens.revoke'}}</button>
- {{/if}}
- </form>
- </div>
- </td>
- </tr>
- {{else}}
- <tr>
- <td colspan="3">
- <span class="note">{{t 'users.no_tokens'}}</span>
- </td>
- </tr>
- {{/each}}
- </tbody>
- </table>
- {{/notNull}}
-
- {{#each errors}}
- <div class="alert alert-danger">{{msg}}</div>
- {{/each}}
-
- <form class="js-generate-token-form spacer-top panel bg-muted">
- <label>{{t 'users.generate_new_token'}}:</label>
- <input type="text" required maxlength="100" placeholder="{{t 'users.enter_token_name'}}">
- <button>{{t 'users.generate'}}</button>
- </form>
-
- {{#if newToken}}
- <div class="panel panel-white big-spacer-top">
- <div class="alert alert-warning">
- {{tp 'users.tokens.new_token_created' newToken.name}}
- </div>
-
- <table class="data">
- <tr>
- <td class="thin">
- <button class="js-copy-to-clipboard" data-clipboard-text="{{newToken.token}}">{{t 'copy'}}</button>
- </td>
- <td class="nowrap">
- <div class="monospaced text-success">{{newToken.token}}</div>
- </td>
- </tr>
- </table>
- </div>
- {{/if}}
-</div>
-</div>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import Marionette from 'backbone.marionette';
-import Clipboard from 'clipboard';
-import Template from './templates/account-tokens.hbs';
-import { getTokens, generateToken, revokeToken } from '../../api/user-tokens';
-import { translate } from '../../helpers/l10n';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- events() {
- return {
- 'submit .js-generate-token-form': 'onGenerateTokenFormSubmit',
- 'submit .js-revoke-token-form': 'onRevokeTokenFormSubmit'
- };
- },
-
- initialize() {
- this.tokens = null;
- this.newToken = null;
- this.errors = [];
- this.requestTokens();
- },
-
- requestTokens() {
- return getTokens(this.model.id).then(tokens => {
- this.tokens = tokens;
- this.render();
- });
- },
-
- onGenerateTokenFormSubmit(e) {
- e.preventDefault();
- this.errors = [];
- this.newToken = null;
- const tokenName = this.$('.js-generate-token-form input').val();
- generateToken({ name: tokenName, login: this.model.id }).then(
- response => {
- this.newToken = response;
- this.requestTokens();
- },
- () => {}
- );
- },
-
- onRevokeTokenFormSubmit(e) {
- e.preventDefault();
- const tokenName = $(e.currentTarget).data('token');
- const token = this.tokens.find(token => token.name === `${tokenName}`);
- if (token) {
- if (token.deleting) {
- revokeToken({ name: tokenName, login: this.model.id }).then(
- () => this.requestTokens(),
- () => {}
- );
- } else {
- token.deleting = true;
- this.render();
- }
- }
- },
-
- onRender() {
- const copyButton = this.$('.js-copy-to-clipboard');
- if (copyButton.length) {
- const clipboard = new Clipboard(copyButton.get(0));
- clipboard.on('success', () => {
- copyButton
- .tooltip({
- title: translate('users.tokens.copied'),
- placement: 'bottom',
- trigger: 'manual'
- })
- .tooltip('show');
- setTimeout(() => copyButton.tooltip('hide'), 1000);
- });
- }
- this.newToken = null;
- },
-
- serializeData() {
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- tokens: this.tokens,
- newToken: this.newToken,
- errors: this.errors
- };
- }
-});
this.context.router.push({ ...this.props.location, query });
};
+ updateTokensCount = (login: string, tokensCount: number) => {
+ this.setState(state => ({
+ users: state.users.map(user => (user.login === login ? { ...user, tokensCount } : user))
+ }));
+ };
+
render() {
const query = parseQuery(this.props.location.query);
const { loading, paging, users } = this.state;
identityProviders={this.state.identityProviders}
onUpdateUsers={this.fetchUsers}
organizationsEnabled={this.props.organizationsEnabled}
+ updateTokensCount={this.updateTokensCount}
users={users}
/>
{paging !== undefined && (
identityProviders: IdentityProvider[];
onUpdateUsers: () => void;
organizationsEnabled: boolean;
+ updateTokensCount: (login: string, tokensCount: number) => void;
users: User[];
}
identityProviders,
onUpdateUsers,
organizationsEnabled,
+ updateTokensCount,
users
}: Props) {
return (
key={user.login}
onUpdateUsers={onUpdateUsers}
organizationsEnabled={organizationsEnabled}
+ updateTokensCount={updateTokensCount}
user={user}
/>
))}
]}
onUpdateUsers={jest.fn()}
organizationsEnabled={true}
+ updateTokensCount={jest.fn()}
users={users}
{...props}
/>
identityProviders={Array []}
onUpdateUsers={[Function]}
organizationsEnabled={true}
+ updateTokensCount={[Function]}
users={Array []}
/>
</div>
}
onUpdateUsers={[Function]}
organizationsEnabled={true}
+ updateTokensCount={[Function]}
users={
Array [
Object {
key="luke"
onUpdateUsers={[Function]}
organizationsEnabled={true}
+ updateTokensCount={[Function]}
user={
Object {
"active": true,
key="obi"
onUpdateUsers={[Function]}
organizationsEnabled={true}
+ updateTokensCount={[Function]}
user={
Object {
"active": true,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import Modal from '../../../components/controls/Modal';
import TokensFormItem from './TokensFormItem';
import TokensFormNewToken from './TokensFormNewToken';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
-import { User } from '../../../api/users';
import { getTokens, generateToken, UserToken } from '../../../api/user-tokens';
import { translate } from '../../../helpers/l10n';
interface Props {
- user: User;
- onClose: () => void;
- onUpdateUsers: () => void;
+ login: string;
+ updateTokensCount?: (login: string, tokensCount: number) => void;
}
interface State {
generating: boolean;
- hasChanged: boolean;
loading: boolean;
newToken?: { name: string; token: string };
newTokenName: string;
mounted: boolean;
state: State = {
generating: false,
- hasChanged: false,
loading: true,
newTokenName: '',
tokens: []
this.mounted = false;
}
- fetchTokens = ({ user } = this.props) => {
+ fetchTokens = () => {
this.setState({ loading: true });
- getTokens(user.login).then(
+ getTokens(this.props.login).then(
tokens => {
if (this.mounted) {
this.setState({ loading: false, tokens });
);
};
- handleCloseClick = (evt: React.SyntheticEvent<HTMLAnchorElement>) => {
- evt.preventDefault();
- this.handleClose();
- };
-
- handleClose = () => {
- if (this.state.hasChanged) {
- this.props.onUpdateUsers();
+ updateTokensCount = () => {
+ if (this.props.updateTokensCount) {
+ this.props.updateTokensCount(this.props.login, this.state.tokens.length);
}
- this.props.onClose();
};
handleGenerateToken = (evt: React.SyntheticEvent<HTMLFormElement>) => {
evt.preventDefault();
if (this.state.newTokenName.length > 0) {
this.setState({ generating: true });
- generateToken({ name: this.state.newTokenName, login: this.props.user.login }).then(
+ generateToken({ name: this.state.newTokenName, login: this.props.login }).then(
newToken => {
if (this.mounted) {
- this.fetchTokens();
- this.setState({ generating: false, hasChanged: true, newToken, newTokenName: '' });
+ this.setState(state => {
+ const tokens = [
+ ...state.tokens,
+ { name: newToken.name, createdAt: newToken.createdAt }
+ ];
+ return { generating: false, newToken, newTokenName: '', tokens };
+ }, this.updateTokensCount);
}
},
() => {
}
};
- handleRevokeToken = () => {
- this.setState({ hasChanged: true });
- this.fetchTokens();
+ handleRevokeToken = (revokedToken: UserToken) => {
+ this.setState(
+ state => ({
+ tokens: state.tokens.filter(token => token.name !== revokedToken.name)
+ }),
+ this.updateTokensCount
+ );
};
handleNewTokenChange = (evt: React.SyntheticEvent<HTMLInputElement>) =>
return tokens.map(token => (
<TokensFormItem
key={token.name}
+ login={this.props.login}
token={token}
onRevokeToken={this.handleRevokeToken}
- user={this.props.user}
/>
));
}
render() {
const { generating, loading, newToken, newTokenName, tokens } = this.state;
- const header = translate('users.tokens');
const customSpinner = (
<tr>
<td>
</tr>
);
return (
- <Modal contentLabel={header} onRequestClose={this.handleClose}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
- <div className="modal-body modal-container">
- <h3 className="spacer-bottom">{translate('users.generate_tokens')}</h3>
- <form id="generate-token-form" onSubmit={this.handleGenerateToken} autoComplete="off">
- <input
- className="spacer-right"
- type="text"
- maxLength={100}
- onChange={this.handleNewTokenChange}
- placeholder={translate('users.enter_token_name')}
- required={true}
- value={newTokenName}
- />
- <button
- className="js-generate-token"
- disabled={generating || newTokenName.length <= 0}
- type="submit">
- {translate('users.generate')}
- </button>
- </form>
+ <>
+ <h3 className="spacer-bottom">{translate('users.generate_tokens')}</h3>
+ <form id="generate-token-form" onSubmit={this.handleGenerateToken} autoComplete="off">
+ <input
+ className="spacer-right"
+ type="text"
+ maxLength={100}
+ onChange={this.handleNewTokenChange}
+ placeholder={translate('users.enter_token_name')}
+ required={true}
+ value={newTokenName}
+ />
+ <button
+ className="js-generate-token"
+ disabled={generating || newTokenName.length <= 0}
+ type="submit">
+ {translate('users.generate')}
+ </button>
+ </form>
- {newToken && <TokensFormNewToken token={newToken} />}
+ {newToken && <TokensFormNewToken token={newToken} />}
- <table className="data zebra big-spacer-top ">
- <thead>
- <tr>
- <th>{translate('name')}</th>
- <th className="text-right">{translate('created')}</th>
- <th />
- </tr>
- </thead>
- <tbody>
- <DeferredSpinner
- customSpinner={customSpinner}
- loading={loading && tokens.length <= 0}>
- {this.renderItems()}
- </DeferredSpinner>
- </tbody>
- </table>
- </div>
- <footer className="modal-foot">
- <a className="js-modal-close" href="#" onClick={this.handleCloseClick}>
- {translate('Done')}
- </a>
- </footer>
- </Modal>
+ <table className="data zebra big-spacer-top ">
+ <thead>
+ <tr>
+ <th>{translate('name')}</th>
+ <th className="text-right">{translate('created')}</th>
+ <th />
+ </tr>
+ </thead>
+ <tbody>
+ <DeferredSpinner customSpinner={customSpinner} loading={loading && tokens.length <= 0}>
+ {this.renderItems()}
+ </DeferredSpinner>
+ </tbody>
+ </table>
+ </>
);
}
}
import Tooltip from '../../../components/controls/Tooltip';
import DateFormatter from '../../../components/intl/DateFormatter';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
-import { User } from '../../../api/users';
import { revokeToken, UserToken } from '../../../api/user-tokens';
import { limitComponentName } from '../../../helpers/path';
import { translate } from '../../../helpers/l10n';
interface Props {
+ login: string;
+ onRevokeToken: (token: UserToken) => void;
token: UserToken;
- user: User;
- onRevokeToken: () => void;
}
interface State {
handleRevoke = () => {
if (this.state.deleting) {
this.setState({ loading: true });
- revokeToken({ login: this.props.user.login, name: this.props.token.name }).then(
- this.props.onRevokeToken,
+ revokeToken({ login: this.props.login, name: this.props.token.name }).then(
+ () => this.props.onRevokeToken(this.props.token),
() => {
if (this.mounted) {
this.setState({ loading: false, deleting: false });
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Modal from '../../../components/controls/Modal';
+import TokensForm from './TokensForm';
+import { User } from '../../../api/users';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ user: User;
+ onClose: () => void;
+ updateTokensCount: (login: string, tokensCount: number) => void;
+}
+
+export default class TokensFormModal extends React.PureComponent<Props> {
+ handleCloseClick = (evt: React.SyntheticEvent<HTMLAnchorElement>) => {
+ evt.preventDefault();
+ this.props.onClose();
+ };
+
+ render() {
+ const header = translate('users.tokens');
+ return (
+ <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+ <div className="modal-body modal-container">
+ <TokensForm
+ login={this.props.user.login}
+ updateTokensCount={this.props.updateTokensCount}
+ />
+ </div>
+ <footer className="modal-foot">
+ <a className="js-modal-close" href="#" onClick={this.handleCloseClick}>
+ {translate('Done')}
+ </a>
+ </footer>
+ </Modal>
+ );
+ }
+}
import Avatar from '../../../components/ui/Avatar';
import BulletListIcon from '../../../components/icons-components/BulletListIcon';
import { ButtonIcon } from '../../../components/ui/buttons';
-import TokensForm from './TokensForm';
+import TokensFormModal from './TokensFormModal';
import UserActions from './UserActions';
import UserGroups from './UserGroups';
import UserListItemIdentity from './UserListItemIdentity';
isCurrentUser: boolean;
onUpdateUsers: () => void;
organizationsEnabled: boolean;
+ updateTokensCount: (login: string, tokensCount: number) => void;
user: User;
}
/>
</td>
{this.state.openTokenForm && (
- <TokensForm
+ <TokensFormModal
user={user}
onClose={this.handleCloseTokensForm}
- onUpdateUsers={onUpdateUsers}
+ updateTokensCount={this.props.updateTokensCount}
/>
)}
</tr>
it('should open the correct forms', () => {
const wrapper = getWrapper();
click(wrapper.find('.js-user-tokens'));
- expect(wrapper.find('TokensForm').exists()).toBeTruthy();
+ expect(wrapper.find('TokensFormModal').exists()).toBeTruthy();
});
function getWrapper(props = {}) {
isCurrentUser={false}
onUpdateUsers={jest.fn()}
organizationsEnabled={false}
+ updateTokensCount={jest.fn()}
user={user}
{...props}
/>
optional string login = 1;
optional string name = 2;
optional string token = 3;
+ optional string createdAt = 4;
}
// WS api/user_tokens/search