login,
type,
projectKey,
+ isExpired: false,
token: Math.random()
.toString(RANDOM_RADIX)
.slice(RANDOM_PREFIX),
import { getMyProjects, getScannableProjects } from '../../../api/components';
import NotificationsMock from '../../../api/mocks/NotificationsMock';
import UserTokensMock from '../../../api/mocks/UserTokensMock';
+import { mockUserToken } from '../../../helpers/mocks/token';
import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
import { renderApp } from '../../../helpers/testReactTestingUtils';
import { Permissions } from '../../../types/permissions';
}
);
+ it('should flag expired tokens as such', async () => {
+ tokenMock.tokens.push(
+ mockUserToken({
+ name: 'expired token',
+ isExpired: true,
+ expirationDate: '2021-01-23T19:25:19+0000'
+ })
+ );
+
+ renderAccountApp(
+ mockLoggedInUser({ permissions: { global: [Permissions.Scan] } }),
+ securityPagePath
+ );
+
+ expect(await screen.findByText('users.tokens')).toBeInTheDocument();
+
+ // expired token is flagged as such
+ const expiredTokenRow = screen.getByRole('row', { name: /expired token/ });
+ expect(within(expiredTokenRow).getByText('my_account.tokens.expired')).toBeInTheDocument();
+
+ // unexpired token is not flagged
+ const unexpiredTokenRow = screen.getAllByRole('row')[0];
+ expect(
+ within(unexpiredTokenRow).queryByText('my_account.tokens.expired')
+ ).not.toBeInTheDocument();
+ });
+
it("should not suggest creating a Project token if the user doesn't have at least one scannable Projects", async () => {
(getScannableProjects as jest.Mock).mockResolvedValueOnce({
projects: []
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.account-container {
- width: 800px;
+ width: 1000px;
margin-left: auto;
margin-right: auto;
}
border-top: 1px solid var(--barBorderColor);
}
-.account-projects-list {
- margin-left: -20px;
- margin-right: -20px;
-}
-
.account-projects-list > li {
padding: 15px 20px;
}
{
name: newToken.name,
createdAt: newToken.createdAt,
+ isExpired: false,
type: newTokenType,
...(newTokenType === TokenType.Project && {
project: { key: selectedProject.key, name: selectedProject.name }
if (tokens.length <= 0) {
return (
<tr>
- <td className="note" colSpan={3}>
+ <td className="note" colSpan={7}>
{translate('users.no_tokens')}
</td>
</tr>
<th>{translate('my_account.project_name')}</th>
<th>{translate('my_account.tokens_last_usage')}</th>
<th className="text-right">{translate('created')}</th>
- <th />
+ <th className="text-right">{translate('my_account.tokens.expiration')}</th>
+ <th aria-label={translate('actions')} />
</tr>
</thead>
<tbody>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import classNames from 'classnames';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { revokeToken } from '../../../api/user-tokens';
import { Button } from '../../../components/controls/buttons';
import ConfirmButton from '../../../components/controls/ConfirmButton';
+import WarningIcon from '../../../components/icons/WarningIcon';
import DateFormatter from '../../../components/intl/DateFormatter';
import DateFromNow from '../../../components/intl/DateFromNow';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
const { deleteConfirmation, token } = this.props;
const { loading, showConfirmation } = this.state;
return (
- <tr>
+ <tr className={classNames({ 'text-muted-2': token.isExpired })}>
<td title={token.name} className="hide-overflow nowrap">
{token.name}
+ {token.isExpired && (
+ <div className="spacer-top text-warning">
+ <WarningIcon className="little-spacer-right" />
+ {translate('my_account.tokens.expired')}
+ </div>
+ )}
</td>
<td title={translate('users.tokens', token.type)} className="hide-overflow thin">
{translate('users.tokens', token.type, 'short')}
<td className="thin nowrap text-right">
<DateFormatter date={token.createdAt} long={true} />
</td>
+ <td className={classNames('thin nowrap text-right', { 'text-warning': token.isExpired })}>
+ {token.expirationDate ? <DateFormatter date={token.expirationDate} long={true} /> : '–'}
+ </td>
<td className="thin nowrap text-right">
{deleteConfirmation === 'modal' ? (
<ConfirmButton
>
created
</th>
- <th />
+ <th
+ className="text-right"
+ >
+ my_account.tokens.expiration
+ </th>
+ <th
+ aria-label="actions"
+ />
</tr>
</thead>
<tbody>
<tr>
<td
className="note"
- colSpan={3}
+ colSpan={7}
>
users.no_tokens
</td>
>
created
</th>
- <th />
+ <th
+ className="text-right"
+ >
+ my_account.tokens.expiration
+ </th>
+ <th
+ aria-label="actions"
+ />
</tr>
</thead>
<tbody>
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render correctly 1`] = `
-<tr>
+<tr
+ className=""
+>
<td
className="hide-overflow nowrap"
title="foo"
long={true}
/>
</td>
+ <td
+ className="thin nowrap text-right"
+ >
+ –
+ </td>
<td
className="thin nowrap text-right"
>
`;
exports[`should render correctly 2`] = `
-<tr>
+<tr
+ className=""
+>
<td
className="hide-overflow nowrap"
title="foo"
long={true}
/>
</td>
+ <td
+ className="thin nowrap text-right"
+ >
+ –
+ </td>
<td
className="thin nowrap text-right"
>
name: 'Token name',
createdAt: '2019-06-14T09:45:52+0200',
type: TokenType.User,
+ isExpired: false,
...overrides
};
}
name: string;
createdAt: string;
lastConnectionDate?: string;
+ expirationDate?: string;
+ isExpired: boolean;
type: TokenType;
project?: { name: string; key: string };
}
my_account.token_type=Type
my_account.project_name=Project
my_account.tokens_last_usage=Last use
+my_account.tokens.expiration=Expiration
+my_account.tokens.expired=Token is expired
my_account.projects=Projects
my_account.projects.description=Those projects are the ones you are administering.
my_account.projects.no_results=You are not administering any project yet.