Skip to content

Commit 127ac54

Browse files
authored
Merge pull request #6450 from Turbo87/token-display
settings/tokens: Display endpoint/crate scopes if they exist
2 parents 3b3d6bd + 422adae commit 127ac54

File tree

8 files changed

+118
-37
lines changed

8 files changed

+118
-37
lines changed

app/components/settings/api-tokens.hbs

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,50 @@
7171
{{token.name}}
7272
</h3>
7373

74-
<div title={{token.last_used_at}} local-class="last-used-at" data-test-last-used-at>
75-
{{#if token.last_used_at}}
76-
Last used {{date-format-distance-to-now token.last_used_at addSuffix=true}}
77-
{{else}}
78-
Never used
79-
{{/if}}
80-
</div>
74+
{{#if (or token.endpoint_scopes token.crate_scopes)}}
75+
<div local-class="scopes">
76+
{{#if token.endpoint_scopes}}
77+
<div local-class="endpoint-scopes" data-test-endpoint-scopes>
78+
Scopes:
79+
80+
{{#each (this.listToParts token.endpoint_scopes) as |part|~}}
81+
{{#if (eq part.type "element")}}
82+
<strong>{{part.value}}<EmberTooltip @text={{this.scopeDescription part.value}} /></strong>
83+
{{~else~}}
84+
{{part.value}}
85+
{{/if}}
86+
{{~/each}}
87+
</div>
88+
{{/if}}
89+
90+
{{#if token.crate_scopes}}
91+
<div local-class="crate-scopes" data-test-crate-scopes>
92+
Crates:
93+
94+
{{#each (this.listToParts token.crate_scopes) as |part|~}}
95+
{{#if (eq part.type "element")}}
96+
<strong>{{part.value}}<EmberTooltip @text={{this.patternDescription part.value}} /></strong>
97+
{{~else~}}
98+
{{part.value}}
99+
{{/if}}
100+
{{~/each}}
101+
</div>
102+
{{/if}}
103+
</div>
104+
{{/if}}
81105

82-
<div title={{token.created_at}} local-class="created-at" data-test-created-at>
83-
Created {{date-format-distance-to-now token.created_at addSuffix=true}}
106+
<div local-class="metadata">
107+
<div title={{token.last_used_at}} local-class="last-used-at" data-test-last-used-at>
108+
{{#if token.last_used_at}}
109+
Last used {{date-format-distance-to-now token.last_used_at addSuffix=true}}
110+
{{else}}
111+
Never used
112+
{{/if}}
113+
</div>
114+
115+
<div title={{token.created_at}} local-class="created-at" data-test-created-at>
116+
Created {{date-format-distance-to-now token.created_at addSuffix=true}}
117+
</div>
84118
</div>
85119

86120
{{#if token.token}}

app/components/settings/api-tokens.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,27 @@ import { tracked } from '@glimmer/tracking';
55

66
import { task } from 'ember-concurrency';
77

8+
import { patternDescription, scopeDescription } from '../../utils/token-scopes';
9+
810
export default class ApiTokens extends Component {
911
@service store;
1012
@service notifications;
1113
@service router;
1214

1315
@tracked newToken;
1416

17+
scopeDescription = scopeDescription;
18+
patternDescription = patternDescription;
19+
1520
get sortedTokens() {
1621
return this.args.tokens.filter(t => !t.isNew).sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
1722
}
1823

24+
listToParts(list) {
25+
// We hardcode `en-US` here because the rest of the interface text is also currently displayed only in English.
26+
return new Intl.ListFormat('en-US').formatToParts(list);
27+
}
28+
1929
@action startNewToken(event) {
2030
if (event.altKey) {
2131
this.router.transitionTo('settings.tokens.new');

app/components/settings/api-tokens.module.css

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,21 @@
3535
}
3636

3737
.name {
38-
margin: 0 0 12px;
38+
margin: 0 0 var(--space-s);
3939
font-weight: 500;
4040
}
4141

42-
.dates {
42+
.scopes,
43+
.metadata {
44+
composes: small from '../../styles/shared/typography.module.css';
45+
46+
> * + * {
47+
margin-top: var(--space-3xs);
48+
}
4349
}
4450

45-
.created-at,
46-
.last-used-at {
47-
composes: small from '../../styles/shared/typography.module.css';
48-
margin-top: 4px;
51+
.scopes {
52+
margin-bottom: var(--space-xs);
4953
}
5054

5155
.new-token-form {
@@ -168,11 +172,19 @@
168172
display: grid;
169173
grid-template:
170174
"name actions" auto
171-
"last-user actions" auto
172-
"created-at actions" auto
175+
"scopes actions" auto
176+
"metadata actions" auto
173177
"details details" auto
174178
/ 1fr auto;
175179

180+
.scopes {
181+
grid-area: scopes;
182+
}
183+
184+
.metadata {
185+
grid-area: metadata;
186+
}
187+
176188
.actions {
177189
grid-area: actions;
178190
align-self: start;

app/controllers/settings/tokens/new.js

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import Controller from '@ember/controller';
22
import { action } from '@ember/object';
33
import { inject as service } from '@ember/service';
4-
import { htmlSafe } from '@ember/template';
54
import { tracked } from '@glimmer/tracking';
65

76
import { task } from 'ember-concurrency';
87
import { TrackedArray } from 'tracked-built-ins';
98

9+
import { patternDescription, scopeDescription } from '../../../utils/token-scopes';
10+
1011
export default class NewTokenController extends Controller {
1112
@service notifications;
1213
@service sentry;
@@ -19,12 +20,9 @@ export default class NewTokenController extends Controller {
1920
@tracked scopesInvalid;
2021
@tracked crateScopes;
2122

22-
ENDPOINT_SCOPES = [
23-
{ id: 'change-owners', description: 'Invite new crate owners or remove existing ones' },
24-
{ id: 'publish-new', description: 'Publish new crates' },
25-
{ id: 'publish-update', description: 'Publish new versions of existing crates' },
26-
{ id: 'yank', description: 'Yank and unyank crate versions' },
27-
];
23+
ENDPOINT_SCOPES = ['change-owners', 'publish-new', 'publish-update', 'yank'];
24+
25+
scopeDescription = scopeDescription;
2826

2927
constructor() {
3028
super(...arguments);
@@ -120,14 +118,10 @@ class CratePattern {
120118
get description() {
121119
if (!this.pattern) {
122120
return 'Please enter a crate name pattern';
123-
} else if (this.pattern === '*') {
124-
return 'Matches all crates on crates.io';
125-
} else if (!this.isValid) {
126-
return 'Invalid crate name pattern';
127-
} else if (this.hasWildcard) {
128-
return htmlSafe(`Matches all crates starting with <strong>${this.pattern.slice(0, -1)}</strong>`);
121+
} else if (this.isValid) {
122+
return patternDescription(this.pattern);
129123
} else {
130-
return htmlSafe(`Matches only the <strong>${this.pattern}</strong> crate`);
124+
return 'Invalid crate name pattern';
131125
}
132126
}
133127

app/styles/shared/typography.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
strong {
66
color: var(--main-color);
77
}
8+
9+
:global(.tooltip) strong {
10+
color: inherit;
11+
}
812
}
913

1014
.small a, a.small {

app/templates/settings/tokens/new.hbs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@
4444
<ul role="list" local-class="scopes-list {{if this.scopesInvalid "invalid"}}">
4545
{{#each this.ENDPOINT_SCOPES as |scope|}}
4646
<li>
47-
<label data-test-scope={{scope.id}}>
47+
<label data-test-scope={{scope}}>
4848
<Input
4949
@type="checkbox"
50-
@checked={{this.isScopeSelected scope.id}}
50+
@checked={{this.isScopeSelected scope}}
5151
disabled={{this.saveTokenTask.isRunning}}
52-
{{on "change" (fn this.toggleScope scope.id)}}
52+
{{on "change" (fn this.toggleScope scope)}}
5353
/>
5454

55-
<span local-class="scope-id">{{scope.id}}</span>
56-
<span local-class="scope-description">{{scope.description}}</span>
55+
<span local-class="scope-id">{{scope}}</span>
56+
<span local-class="scope-description">{{this.scopeDescription scope}}</span>
5757
</label>
5858
</li>
5959
{{/each}}

app/utils/token-scopes.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { htmlSafe } from '@ember/template';
2+
3+
const DESCRIPTIONS = {
4+
'change-owners': 'Invite new crate owners or remove existing ones',
5+
'publish-new': 'Publish new crates',
6+
'publish-update': 'Publish new versions of existing crates',
7+
yank: 'Yank and unyank crate versions',
8+
};
9+
10+
export function scopeDescription(scope) {
11+
return DESCRIPTIONS[scope];
12+
}
13+
14+
export function patternDescription(pattern) {
15+
if (pattern === '*') {
16+
return 'Matches all crates on crates.io';
17+
} else if (pattern.endsWith('*')) {
18+
return htmlSafe(`Matches all crates starting with <strong>${pattern.slice(0, -1)}</strong>`);
19+
} else {
20+
return htmlSafe(`Matches only the <strong>${pattern}</strong> crate`);
21+
}
22+
}

tests/routes/settings/tokens/new-test.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ module('/settings/tokens/new', function (hooks) {
6666
assert.strictEqual(currentURL(), '/settings/tokens');
6767
assert.dom('[data-test-api-token="1"] [data-test-name]').hasText('token-name');
6868
assert.dom('[data-test-api-token="1"] [data-test-token]').hasText(token.token);
69+
assert.dom('[data-test-api-token="1"] [data-test-endpoint-scopes]').hasText('Scopes: publish-update');
70+
assert.dom('[data-test-api-token="1"] [data-test-crate-scopes]').doesNotExist();
6971
});
7072

7173
test('crate scopes', async function (assert) {
@@ -76,6 +78,7 @@ module('/settings/tokens/new', function (hooks) {
7678

7779
await fillIn('[data-test-name]', 'token-name');
7880
await click('[data-test-scope="publish-update"]');
81+
await click('[data-test-scope="yank"]');
7982

8083
assert.dom('[data-test-crates-unrestricted]').exists();
8184
assert.dom('[data-test-crate-pattern]').doesNotExist();
@@ -128,11 +131,13 @@ module('/settings/tokens/new', function (hooks) {
128131
assert.ok(Boolean(token), 'API token has been created in the backend database');
129132
assert.strictEqual(token.name, 'token-name');
130133
assert.deepEqual(token.crateScopes, ['serde-*', 'serde']);
131-
assert.deepEqual(token.endpointScopes, ['publish-update']);
134+
assert.deepEqual(token.endpointScopes, ['publish-update', 'yank']);
132135

133136
assert.strictEqual(currentURL(), '/settings/tokens');
134137
assert.dom('[data-test-api-token="1"] [data-test-name]').hasText('token-name');
135138
assert.dom('[data-test-api-token="1"] [data-test-token]').hasText(token.token);
139+
assert.dom('[data-test-api-token="1"] [data-test-endpoint-scopes]').hasText('Scopes: publish-update and yank');
140+
assert.dom('[data-test-api-token="1"] [data-test-crate-scopes]').hasText('Crates: serde-* and serde');
136141
});
137142

138143
test('loading and error state', async function (assert) {

0 commit comments

Comments
 (0)