Skip to content

Implement Trusted Publishing user interface #11398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/adapters/trustpub-github-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import ApplicationAdapter from './application';

export default class TrustpubGitHubConfigAdapter extends ApplicationAdapter {
pathForType() {
return 'trusted_publishing/github_configs';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { task } from 'ember-concurrency';

export default class CrateSettingsController extends Controller {
@service notifications;
@service store;

crate = null;
username = '';
Expand Down Expand Up @@ -64,6 +65,22 @@ export default class CrateSettingsController extends Controller {
this.notifications.error(message);
}
});

removeConfigTask = task(async config => {
try {
await config.destroyRecord();
this.notifications.success('Trusted Publishing configuration removed successfully');
} catch (error) {
let message = 'Failed to remove Trusted Publishing configuration';

let detail = error.errors?.[0]?.detail;
if (detail && !detail.startsWith('{')) {
message += `: ${detail}`;
}

this.notifications.error(message);
}
});
}

function removeOwner(owners, target) {
Expand Down
85 changes: 85 additions & 0 deletions app/controllers/crate/settings/new-trusted-publisher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

import { task } from 'ember-concurrency';

export default class NewTrustedPublisherController extends Controller {
@service notifications;
@service store;
@service router;

@tracked publisher = 'GitHub';
@tracked repositoryOwner = '';
@tracked repositoryName = '';
@tracked workflowFilename = '';
@tracked environment = '';
@tracked repositoryOwnerInvalid = false;
@tracked repositoryNameInvalid = false;
@tracked workflowFilenameInvalid = false;

get crate() {
return this.model.crate;
}

get publishers() {
return ['GitHub'];
}

saveConfigTask = task(async () => {
if (!this.validate()) return;

let config = this.store.createRecord('trustpub-github-config', {
crate: this.crate,
repository_owner: this.repositoryOwner,
repository_name: this.repositoryName,
workflow_filename: this.workflowFilename,
environment: this.environment || null,
});

try {
// Save the new config on the backend
await config.save();

this.repositoryOwner = '';
this.repositoryName = '';
this.workflowFilename = '';
this.environment = '';

// Navigate back to the crate settings page
this.notifications.success('Trusted Publishing configuration added successfully');
this.router.transitionTo('crate.settings', this.crate.id);
} catch (error) {
// Notify the user
let message = 'An error has occurred while adding the Trusted Publishing configuration';

let detail = error.errors?.[0]?.detail;
if (detail && !detail.startsWith('{')) {
message += `: ${detail}`;
}

this.notifications.error(message);
}
});

validate() {
this.repositoryOwnerInvalid = !this.repositoryOwner;
this.repositoryNameInvalid = !this.repositoryName;
this.workflowFilenameInvalid = !this.workflowFilename;

return !this.repositoryOwnerInvalid && !this.repositoryNameInvalid && !this.workflowFilenameInvalid;
}

@action resetRepositoryOwnerValidation() {
this.repositoryOwnerInvalid = false;
}

@action resetRepositoryNameValidation() {
this.repositoryNameInvalid = false;
}

@action resetWorkflowFilenameValidation() {
this.workflowFilenameInvalid = false;
}
}
10 changes: 10 additions & 0 deletions app/models/trustpub-github-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Model, { attr, belongsTo } from '@ember-data/model';

export default class TrustpubGitHubConfig extends Model {
@belongsTo('crate', { async: true, inverse: null }) crate;
@attr repository_owner;
@attr repository_name;
@attr workflow_filename;
@attr environment;
@attr('date') created_at;
}
4 changes: 3 additions & 1 deletion app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ Router.map(function () {
this.route('reverse-dependencies', { path: 'reverse_dependencies' });

this.route('owners');
this.route('settings');
this.route('settings', function () {
this.route('new-trusted-publisher');
});
this.route('delete');

// Well-known routes
Expand Down
12 changes: 4 additions & 8 deletions app/routes/crate/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ export default class SettingsRoute extends AuthenticatedRoute {
@service router;
@service session;

async afterModel(crate, transition) {
async beforeModel(transition) {
await super.beforeModel(...arguments);

let user = this.session.currentUser;
let owners = await crate.owner_user;
let owners = await this.modelFor('crate').owner_user;
let isOwner = owners.some(owner => owner.id === user.id);
if (!isOwner) {
this.router.replaceWith('catch-all', {
Expand All @@ -17,10 +19,4 @@ export default class SettingsRoute extends AuthenticatedRoute {
});
}
}

setupController(controller) {
super.setupController(...arguments);
let crate = this.modelFor('crate');
controller.set('crate', crate);
}
}
21 changes: 21 additions & 0 deletions app/routes/crate/settings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';

export default class SettingsIndexRoute extends Route {
@service store;

async model() {
let crate = this.modelFor('crate');

let githubConfigs = await this.store.query('trustpub-github-config', { crate: crate.name });

return { crate, githubConfigs };
}

setupController(controller, { crate, githubConfigs }) {
super.setupController(...arguments);

controller.set('crate', crate);
controller.set('githubConfigs', githubConfigs);
}
}
8 changes: 8 additions & 0 deletions app/routes/crate/settings/new-trusted-publisher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Route from '@ember/routing/route';

export default class NewTrustedPublisherRoute extends Route {
async model() {
let crate = this.modelFor('crate');
return { crate };
}
}
11 changes: 11 additions & 0 deletions app/serializers/trustpub-github-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ApplicationSerializer from './application';

export default class TrustpubGitHubConfigSerializer extends ApplicationSerializer {
modelNameFromPayloadKey() {
return 'trustpub-github-config';
}

payloadKeyFromModelName() {
return 'github_config';
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.owners-header {
.owners-header, .trusted-publishing-header {
display: flex;
justify-content: space-between;
align-items: center;
Expand Down Expand Up @@ -43,6 +43,30 @@
}
}

.trustpub {
background-color: light-dark(white, #141413);
border-radius: var(--space-3xs);
box-shadow: 0 1px 3px light-dark(hsla(51, 90%, 42%, .35), #232321);

tbody > tr > td {
border-top: 1px solid light-dark(hsla(51, 90%, 42%, .25), #232321);
}

th, td {
text-align: left;
padding: var(--space-s) var(--space-m);
}

.details {
font-size: 0.85em;
line-height: 1.5;
}

.actions {
text-align: right;
}
}

.email-column {
width: 25%;
color: var(--main-color-light);
Expand Down
42 changes: 42 additions & 0 deletions app/styles/crate/settings/new-trusted-publisher.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.form-group, .buttons {
margin: var(--space-m) 0;
}

.publisher-select {
max-width: 440px;
width: 100%;
padding-right: var(--space-m);
background-image: url("/assets/dropdown.svg");
background-repeat: no-repeat;
background-position: calc(100% - var(--space-2xs)) center;
background-size: 10px;
appearance: none;
}

.note {
margin-top: var(--space-2xs);
font-size: 0.85em;
}

.input {
max-width: 440px;
width: 100%;
}

.buttons {
display: flex;
gap: var(--space-2xs);
flex-wrap: wrap;
}

.add-button {
border-radius: 4px;

.spinner {
margin-left: var(--space-2xs);
}
}

.cancel-button {
border-radius: 4px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,47 @@
{{/each}}
</div>

{{! The "Trusted Publishing" section is hidden for now until we make this feature publicly available. }}
{{#if this.githubConfigs}}
<div local-class="trusted-publishing-header">
<h2>Trusted Publishing</h2>
<LinkTo @route="crate.settings.new-trusted-publisher" class="button button--small" data-test-add-trusted-publisher-button>
Add
</LinkTo>
</div>

<table local-class="trustpub" data-test-trusted-publishing>
<thead>
<tr>
<th>Publisher</th>
<th>Details</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each this.githubConfigs as |config|}}
<tr data-test-github-config={{config.id}}>
<td>GitHub</td>
<td local-class="details">
<strong>Repository:</strong> {{config.repository_owner}}/{{config.repository_name}}<br>
<strong>Workflow:</strong> {{config.workflow_filename}}<br>
{{#if config.environment}}
<strong>Environment:</strong> {{config.environment}}
{{/if}}
</td>
<td local-class="actions">
<button type="button" class="button button--small" data-test-remove-config-button {{on "click" (perform this.removeConfigTask config)}}>Remove</button>
</td>
</tr>
{{else}}
<tr data-test-no-config>
<td colspan="3">No trusted publishers configured for this crate.</td>
</tr>
{{/each}}
</tbody>
</table>
{{/if}}

<h2>Danger Zone</h2>

<div>
Expand Down
Loading
Loading