Skip to content
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

feat: New confirm action modal UI #1075

Merged
merged 3 commits into from
Jun 28, 2024
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
124 changes: 124 additions & 0 deletions app/components/pipeline/modal/confirm-action/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { service } from '@ember/service';
import {
buildPostBody,
capitalizeFirstLetter,
initializeParameters,
truncateMessage
} from './util';

export default class PipelineModalConfirmActionComponent extends Component {
@service shuttle;

@service session;

@tracked isAwaitingResponse = false;

@tracked wasActionSuccessful = false;

@tracked reason = '';

parameters;

constructor() {
super(...arguments);

this.parameters = initializeParameters(this.args.event);
}

get action() {
return this.args.job.status ? 'restart' : 'start';
}

get truncatedMessage() {
return truncateMessage(this.args.event.commit.message);
}

get isLatestCommitEvent() {
return this.args.event.sha === this.args.latestCommitEvent.sha;
}

get commitUrl() {
return this.args.event.commit.url;
}

get truncatedSha() {
return this.args.event.sha.substring(0, 7);
}

get isLatestNonPrCommitEvent() {
return this.args.event.type === 'pr' ? true : this.isLatestCommitEvent;
}

get isFrozen() {
return this.args.job.status === 'FROZEN';
}

get isParameterized() {
if (this.args.pipeline.parameters) {
return Object.keys(this.args.pipeline.parameters).length > 0;
}

return false;
}

get isSubmitButtonDisabled() {
if (this.wasActionSuccessful || this.isAwaitingResponse) {
return true;
}

if (this.isFrozen) {
return this.reason.length === 0;
}

return false;
}

get pendingAction() {
return `${capitalizeFirstLetter(this.action)}ing...`;
}

get completedAction() {
return this.wasActionSuccessful
? `${capitalizeFirstLetter(this.action)}ed`
: 'Yes';
}

@action
onUpdateParameters(parameters) {
this.parameters = parameters;
}

@action
async startBuild() {
this.isAwaitingResponse = true;

const data = buildPostBody(
this.session.data.authenticated.username,
this.args.pipeline.id,
this.args.job,
this.args.event,
this.parameters,
this.isFrozen,
this.reason
);

return new Promise((resolve, reject) => {
this.shuttle
.fetchFromApi('POST', '/events', data)
.then(() => {
this.wasActionSuccessful = true;
resolve();
})
.catch(err => {
this.wasActionSuccessful = false;
reject(err);
})
.finally(() => {
this.isAwaitingResponse = false;
});
});
}
}
73 changes: 73 additions & 0 deletions app/components/pipeline/modal/confirm-action/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
@use 'screwdriver-colors' as colors;

@mixin styles {
#ember-bootstrap-wormhole {
.modal.confirm-action {
.modal-dialog {
max-width: 50%;

.modal-body {
display: flex;
flex-direction: column;

.modal-title {
font-size: 1.75rem;
padding-bottom: 0.75rem;
}

.alert {
margin-bottom: 0.5rem;
}

.frozen-reason {
display: flex;
justify-content: space-between;

label {
margin: auto;
padding-right: 0.25rem;
}

input {
flex: 1;
border-radius: 4px;
border: 1px solid colors.$sd-text-med;
padding-left: 8px;
}
}

.pipeline-parameters {
padding-top: 2.5rem;
}
}

.modal-footer {
display: flex;
justify-content: space-between;

button {
width: 10rem;

color: colors.$sd-link;
border-color: colors.$sd-link;

&:hover {
background-color: colors.$sd-info-bg;
}

&.confirm {
color: colors.$sd-white;
background-color: colors.$sd-running;
border-color: colors.$sd-running;

&:hover {
background-color: colors.$sd-link;
border-color: colors.$sd-link;
}
}
}
}
}
}
}
}
66 changes: 66 additions & 0 deletions app/components/pipeline/modal/confirm-action/template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<BsModal
class="confirm-action"
@onHide={{fn @closeModal}}
as |modal|
>
<modal.body>
<div class="modal-title">Are you sure you want to {{this.action}}?</div>
<div id="confirm-action-job">Job: <code>{{@job.name}}</code></div>
<div id="confirm-action-commit">
Commit: <code>{{this.truncatedMessage}}</code>
<a
id="confirm-action-commit-link"
class={{if this.isLatestCommitEvent "latest-commit"}}
href={{this.commitUrl}}
>
#{{this.truncatedSha}}
</a>
{{#unless this.isLatestNonPrCommitEvent}}
<div class="alert alert-warning">
<FaIcon @icon="exclamation-triangle" />
This is <strong>NOT</strong> the latest commit.
</div>
{{/unless}}
</div>

{{#if this.isFrozen}}
<div class="frozen-reason">
<label>
Reason:
</label>
<Input @type="text" @value={{this.reason}} placeholder="Please enter a reason"/>
</div>
{{/if}}

{{#if this.isParameterized}}
<Pipeline::Parameters
@action={{this.action}}
@job={{@job}}
@event={{@event}}
@pipeline={{@pipeline}}
@jobs={{@jobs}}
@onUpdateParameters={{this.onUpdateParameters}}
/>
{{/if}}
</modal.body>
<modal.footer>
<BsButton
class="confirm"
disabled={{this.isSubmitButtonDisabled}}
@defaultText="Yes"
@pendingText={{this.pendingAction}}
@fulfilledText={{this.completedAction}}
@onClick={{this.startBuild}}
/>
<BsButton
@outline={{true}}
@onClick={{modal.close}}
>
{{#if this.wasActionSuccessful}}
Close
{{else}}
No
{{/if}}
</BsButton>
</modal.footer>
</BsModal>
80 changes: 80 additions & 0 deletions app/components/pipeline/modal/confirm-action/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { flattenParameters } from 'screwdriver-ui/utils/pipeline/parameters';

/**
* Initialize parameters from an event API response object
* @param event
* @returns {undefined|*}
*/
export function initializeParameters(event) {
if (event.meta?.parameters && event.meta.parameters.length > 0) {
return flattenParameters(event.meta.parameters);
}

return undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It intentionally returns undefined, rather than {}. Either one should be fine, and I'm curious to know the reason for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly to keep things in line with the original API response. The meta field returned on the event from the API would resolve to undefined if there are no parameters set.

}

/**
* Truncate commit message to 150 characters
* @param commitMessage
* @returns {string|*}
*/
export function truncateMessage(commitMessage) {
const cutOff = 150;

return commitMessage.length > cutOff
? `${commitMessage.substring(0, cutOff)}...`
: commitMessage;
}

/**
* Capitalize first letter of string
* @param string
* @returns {string}
*/
export function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have a default string = '' ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It already returns '' if the input string is empty.

}

/**
* Build post body for starting a job
* @param username
* @param pipeline
* @param job
* @param event
* @param parameters
* @param isFrozen
* @param reason
* @returns {{causeMessage: string, pipelineId}}
*/
export function buildPostBody(
username,
pipelineId,
job,
event,
parameters,
isFrozen,
reason
) {
const data = {
pipelineId,
causeMessage: `Manually started by ${username}`
};

if (job.status) {
data.startFrom = job.name;
data.groupEventId = event.groupEventId;
data.parentEventId = event.id;
} else {
data.startFrom = '~commit';
}

if (parameters) {
data.meta = { parameters };
}

if (isFrozen) {
data.causeMessage = `[force start]${reason}`;
}

return data;
}
2 changes: 2 additions & 0 deletions app/components/pipeline/styles.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
@use 'nav/styles' as nav;
@use 'modal/confirm-action/styles' as confirm-action-modal;
@use 'modal/toggle-job/styles' as toggle-job;
@use 'parameters/styles' as parameters;

@mixin styles {
@include nav.styles;
@include confirm-action-modal.styles;
@include toggle-job.styles;
@include parameters.styles;
}
2 changes: 2 additions & 0 deletions app/styles/app.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@use 'variables';
@use 'bootstrap-modal' as bootstrapModal;
@use 'components/styles' as components;
@use 'v2/pipeline/styles' as pipeline;

Expand Down Expand Up @@ -204,6 +205,7 @@ html {
font-family: Helvetica, Arial, sans-serif;
font-weight: variables.$weight-normal;

@include bootstrapModal.styles;
@include components.styles;
@include pipeline.styles;
}
Expand Down
15 changes: 15 additions & 0 deletions app/styles/bootstrap-modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@use 'screwdriver-colors' as colors;

@mixin styles {
#ember-bootstrap-wormhole {
.modal-backdrop.fade.show {
background-color: rgba(128, 128, 128, 0.77);
opacity: 1;
}

.modal-content {
border-radius: 8px;
box-shadow: 0 0 10px colors.$sd-black;
}
}
}
Loading