Skip to content

Commit

Permalink
Add ability to rerun and cancel Runs
Browse files Browse the repository at this point in the history
- Standalone Run resources can be rerun from the list page
  (i.e. not child of a Pipeline)
- Any Run that is currently running (i.e. status == Unknown)
can be stopped
- Update StatusIcon to render running state to match behaviour
  described in the Runs documentation
  • Loading branch information
AlanGreene committed Aug 19, 2022
1 parent d6629e6 commit 0775b3d
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 6 deletions.
8 changes: 6 additions & 2 deletions packages/components/src/components/StatusIcon/StatusIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const typeClassNames = {
export default function StatusIcon({
DefaultIcon,
hasWarning,
isCustomTask,
reason,
status,
title,
Expand All @@ -73,8 +74,6 @@ export default function StatusIcon({
(status === 'Unknown' && reason === 'Pending')
) {
statusClass = 'pending';
} else if (isRunning(reason, status)) {
statusClass = 'running';
} else if (
status === 'True' ||
(status === 'terminated' && reason === 'Completed')
Expand All @@ -94,6 +93,11 @@ export default function StatusIcon({
(status === 'Unknown' && reason === 'PipelineRunCouldntCancel')
) {
statusClass = 'error';
} else if (
isRunning(reason, status) ||
(isCustomTask && status === 'Unknown')
) {
statusClass = 'running';
}

const Icon = icons[type]?.[statusClass] || DefaultIcon;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@ describe('StatusIcon', () => {
it('gracefully handles unsupported state', () => {
render(<StatusIcon reason="???" status="???" />);
});

// it('renders completed step', () => {
// render(<StatusIcon reason="Completed" status="terminated" />);
// });
});
35 changes: 34 additions & 1 deletion src/api/runs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { deleteRequest, get } from './comms';
import { getGenerateNamePrefixForRerun } from '@tektoncd/dashboard-utils';
import deepClone from 'lodash.clonedeep';

import { deleteRequest, get, patch, post } from './comms';
import {
getQueryParams,
getTektonAPI,
Expand Down Expand Up @@ -62,3 +65,33 @@ export function useRun(params, queryConfig) {
webSocketURL
});
}

export function cancelRun({ name, namespace }) {
const payload = [
{ op: 'replace', path: '/spec/status', value: 'RunCancelled' }
];

const uri = getTektonAPI('runs', { name, namespace, version: 'v1alpha1' });
return patch(uri, payload);
}

export function rerunRun(run) {
const { annotations, labels, name, namespace } = run.metadata;

const payload = deepClone(run);
payload.metadata = {
annotations,
generateName: getGenerateNamePrefixForRerun(name),
labels: {
...labels,
'dashboard.tekton.dev/rerunOf': name
},
namespace
};

delete payload.status;
delete payload.spec?.status;

const uri = getTektonAPI('runs', { namespace, version: 'v1alpha1' });
return post(uri, payload).then(({ body }) => body);
}
38 changes: 38 additions & 0 deletions src/api/runs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ import fetchMock from 'fetch-mock';
import * as API from './runs';
import * as utils from './utils';

it('cancelRun', () => {
const name = 'foo';
const namespace = 'foospace';
const returnedRun = { fake: 'Run' };
const payload = [
{ op: 'replace', path: '/spec/status', value: 'RunCancelled' }
];
fetchMock.patch(`end:${name}`, returnedRun);
return API.cancelRun({ name, namespace }).then(response => {
expect(fetchMock.lastOptions()).toMatchObject({
body: JSON.stringify(payload)
});
expect(response).toEqual(returnedRun);
fetchMock.restore();
});
});

it('deleteRun', () => {
const name = 'foo';
const data = { fake: 'Run' };
Expand Down Expand Up @@ -85,3 +102,24 @@ it('useRun', () => {
})
);
});

it('rerunRun', () => {
const filter = 'end:/runs/';
const originalRun = {
metadata: { name: 'fake_run' },
spec: { status: 'fake_status' },
status: 'fake_status'
};
const newRun = { metadata: { name: 'fake_run_rerun' } };
fetchMock.post(filter, { body: newRun, status: 201 });
return API.rerunRun(originalRun).then(data => {
const body = JSON.parse(fetchMock.lastCall(filter)[1].body);
expect(body.metadata.generateName).toMatch(
new RegExp(originalRun.metadata.name)
);
expect(body.status).toBeUndefined();
expect(body.spec.status).toBeUndefined();
expect(data).toEqual(newRun);
fetchMock.restore();
});
});
7 changes: 6 additions & 1 deletion src/containers/Run/Run.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ function getRunStatusIcon(run) {
}
const { reason, status } = getStatus(run);
return (
<StatusIcon DefaultIcon={UndefinedIcon} reason={reason} status={status} />
<StatusIcon
DefaultIcon={UndefinedIcon}
isCustomTask
reason={reason}
status={status}
/>
);
}

Expand Down
60 changes: 58 additions & 2 deletions src/containers/Runs/Runs.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ import {

import { ListPageLayout } from '..';
import {
cancelRun,
deleteRun,
rerunRun,
useIsReadOnly,
useRuns,
useSelectedNamespace
Expand Down Expand Up @@ -85,7 +87,12 @@ function getRunStatus(run) {
function getRunStatusIcon(run) {
const { reason, status } = getStatus(run);
return (
<StatusIcon DefaultIcon={UndefinedIcon} reason={reason} status={status} />
<StatusIcon
DefaultIcon={UndefinedIcon}
isCustomTask
reason={reason}
status={status}
/>
);
}

Expand Down Expand Up @@ -169,6 +176,17 @@ function Runs({ intl }) {
setToBeDeleted([]);
}

function cancel(run) {
cancelRun({
name: run.metadata.name,
namespace: run.metadata.namespace
});
}

function rerun(run) {
rerunRun(run);
}

function deleteResource(run) {
const { name, namespace: resourceNamespace } = run.metadata;
return deleteRun({ name, namespace: resourceNamespace }).catch(err => {
Expand Down Expand Up @@ -196,7 +214,44 @@ function Runs({ intl }) {
}

return [
// TODO: rerun?
{
action: rerun,
actionText: intl.formatMessage({
id: 'dashboard.rerun.actionText',
defaultMessage: 'Rerun'
}),
disable: resource => !!resource.metadata.labels?.['tekton.dev/pipeline']
},
{
actionText: intl.formatMessage({
id: 'dashboard.cancelTaskRun.actionText',
defaultMessage: 'Stop'
}),
action: cancel,
disable: resource => {
const { status } = getStatus(resource);
return status && status !== 'Unknown';
},
modalProperties: {
heading: intl.formatMessage({
id: 'dashboard.cancelRun.heading',
defaultMessage: 'Stop Run'
}),
primaryButtonText: intl.formatMessage({
id: 'dashboard.cancelRun.primaryText',
defaultMessage: 'Stop Run'
}),
body: resource =>
intl.formatMessage(
{
id: 'dashboard.cancelRun.body',
defaultMessage:
'Are you sure you would like to stop Run {name}?'
},
{ name: resource.metadata.name }
)
}
},
{
actionText: intl.formatMessage({
id: 'dashboard.actions.deleteButton',
Expand All @@ -208,6 +263,7 @@ function Runs({ intl }) {
const { reason, status } = getStatus(resource);
return isRunning(reason, status);
},
hasDivider: true,
modalProperties: {
danger: true,
heading: intl.formatMessage(
Expand Down

0 comments on commit 0775b3d

Please sign in to comment.