Skip to content
This repository was archived by the owner on Sep 11, 2023. It is now read-only.

Commit 0614bce

Browse files
authored
Add option to conditionally update status (#8)
1 parent cca7627 commit 0614bce

17 files changed

Lines changed: 378 additions & 28 deletions

File tree

.azext/changelog-cache.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
{
2-
"issues": [],
2+
"issues": [
3+
{
4+
"id": 1175102696,
5+
"number": 7,
6+
"submitter": "knoxi",
7+
"title": "Update PullRequestStatus only if the status is fulfilled",
8+
"url": "https://github.com/joachimdalen/azdevops-pull-request-utils/issues/7"
9+
}
10+
],
311
"pullRequests": [
12+
{
13+
"id": 886324202,
14+
"number": 8,
15+
"submitter": "joachimdalen",
16+
"title": "Add option to conditionally update status",
17+
"url": "https://github.com/joachimdalen/azdevops-pull-request-utils/pull/8"
18+
},
419
{
520
"id": 872397756,
621
"number": 2,

.azext/changelog.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
11
[
2+
{
3+
"publishDate": "2022-03-25",
4+
"version": "1.1.0",
5+
"modules": [
6+
{
7+
"name": "PullRequestStatus",
8+
"version": "0.5.0",
9+
"changes": [
10+
{
11+
"type": "fix",
12+
"description": "Fixed an issue where action `Delete` did not load the correct status",
13+
"pullRequest": 8
14+
},
15+
{
16+
"type": "feature",
17+
"description": "Added new argument `whenState` to control when a status update should be applied",
18+
"pullRequest": 8,
19+
"issue": 7
20+
}
21+
]
22+
}
23+
]
24+
},
225
{
326
"publishDate": "2022-03-12",
427
"version": "1.0.0",

.azext/readme.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"file": "../tasks/pull-request-description/docs/examples/set-variable.md"
2020
},
2121
"status-create": { "file": "../tasks/pull-request-status/docs/examples/create-status.yml" },
22+
"status-conditional-update": { "file": "../tasks/pull-request-status/docs/examples/conditional-update.yml" },
2223
"status-access-warning": { "file": "../tasks/pull-request-status/docs/access-warning.md" },
2324
"tags-assign": { "file": "../tasks/pull-request-tags/docs/examples/assign-tag.md" },
2425
"tags-remove": { "file": "../tasks/pull-request-tags/docs/examples/remove-tag.md" },

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Changelog
22

3+
## 1.1.0 (2022-03-25)
4+
5+
### 🐛 Fixes (1)
6+
7+
#### `PullRequestStatus@0.5.0`
8+
9+
- Fixed an issue where action `Delete` did not load the correct status
10+
- Fixed in [PR#8 - Add option to conditionally update status](https://github.com/joachimdalen/azdevops-pull-request-utils/pull/8)
11+
12+
### 🚀 Features (1)
13+
14+
#### `PullRequestStatus@0.5.0`
15+
16+
- Added new argument `whenState` to control when a status update should be applied
17+
- Suggested in [GH#7 - Update PullRequestStatus only if the status is fulfilled](https://github.com/joachimdalen/azdevops-pull-request-utils/issues/7)
18+
- Added in [PR#8 - Add option to conditionally update status](https://github.com/joachimdalen/azdevops-pull-request-utils/pull/8)
19+
20+
## 🌟 Contributors
21+
22+
Thank you to the following for contributing to the latest release
23+
24+
- [@knoxi](https://github.com/knoxi)
25+
26+
---
27+
328
## 1.0.0 (2022-03-12)
429

530
### 🛠️ Maintenance (1)

docs/tasks/pull-request-status.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,15 @@
2121
### Create and update status
2222

2323
{{ #include-partial[file=status-create;wrap=yml] }}
24+
25+
### Conditionally update status
26+
27+
A status can be conditionally updated by using the `whenState` argument. This will first check that the current status is in one of the given states before it updates.
28+
29+
For the example below, it will only update the status if it is in one of the following states:
30+
31+
- Error
32+
- Failed
33+
- Pending
34+
35+
{{ #include-partial[file=status-conditional-update;wrap=yml] }}

tasks/pull-request-status/README.md

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,21 @@
1717
state: notSet
1818
useDefined: false #If set, overrides the value from `System.PullRequest.PullRequestId`
1919
pullRequestId: $(System.PullRequest.PullRequestId) #If no id is given, the value from `System.PullRequest.PullRequestId` is taken. If a value is given, this overrides the value from `System.PullRequest.PullRequestId`
20+
whenState: #Only update the state of the status when the existing state is one of these
2021

2122
```
2223

2324
## Arguments
2425

25-
| Argument | Description |
26-
| :------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27-
| `action` <br />Action | **(Required)** <br /> Options: `Create`, `Update`, `Delete` <br /> Default value: `Create` |
28-
| `name` <br />Name | **(Required)** Name of the status. Full status will be `pull-request-utils/<name>` <br /> Default value: `my-custom-gate` |
29-
| `description` <br />Description | **(Optional)** Status description. Normally describes the current state of the status. <br /> |
30-
| `state` <br />State | **(Required)** <br /> Options: `notSet`, `error`, `failed`, `notApplicable`, `pending`, `succeeded` <br /> Default value: `notSet` |
31-
| `useDefined` <br />Use defined id | **(Optional)** If set, overrides the value from `System.PullRequest.PullRequestId` <br /> |
32-
| `pullRequestId` <br />Pull Request Id | **(Optional)** If no id is given, the value from `System.PullRequest.PullRequestId` is taken. If a value is given, this overrides the value from `System.PullRequest.PullRequestId` <br /> Default value: `$(System.PullRequest.PullRequestId)` |
26+
| Argument | Description |
27+
| :------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28+
| `action` <br />Action | **(Required)** <br /> Options: `Create`, `Update`, `Delete` <br /> Default value: `Create` |
29+
| `name` <br />Name | **(Required)** Name of the status. Full status will be `pull-request-utils/<name>` <br /> Default value: `my-custom-gate` |
30+
| `description` <br />Description | **(Optional)** Status description. Normally describes the current state of the status. <br /> |
31+
| `state` <br />State | **(Required)** <br /> Options: `notSet`, `error`, `failed`, `notApplicable`, `pending`, `succeeded` <br /> Default value: `notSet` |
32+
| `useDefined` <br />Use defined id | **(Optional)** If set, overrides the value from `System.PullRequest.PullRequestId` <br /> |
33+
| `pullRequestId` <br />Pull Request Id | **(Optional)** If no id is given, the value from `System.PullRequest.PullRequestId` is taken. If a value is given, this overrides the value from `System.PullRequest.PullRequestId` <br /> Default value: `$(System.PullRequest.PullRequestId)` |
34+
| `whenState` <br />When current status is in state | **(Optional)** Only update the state of the status when the existing state is one of these <br /> Options: `notSet`, `error`, `failed`, `notApplicable`, `pending`, `succeeded` <br /> |
3335

3436

3537
## Examples
@@ -56,10 +58,40 @@ steps:
5658

5759
```
5860

61+
### Conditionally update status
62+
63+
A status can be conditionally updated by using the `whenState` argument. This will first check that the current status is in one of the given states before it updates.
64+
65+
For the example below, it will only update the status if it is in one of the following states:
66+
67+
- Error
68+
- Failed
69+
- Pending
70+
71+
```yml
72+
- task: PullRequestStatus@0
73+
displayName: 'Initialize status'
74+
inputs:
75+
name: 'my-custom-check'
76+
action: 'Create'
77+
state: 'pending'
78+
description: 'Awaiting my custom check'
79+
- script: 'echo Do something. It might even update the status'
80+
- task: PullRequestStatus@0
81+
displayName: 'Update status'
82+
inputs:
83+
name: 'my-custom-check'
84+
action: 'Update'
85+
state: 'succeeded'
86+
description: 'Check passed'
87+
whenState: 'error, failed, pending'
88+
```
89+
5990
# 🐞 Known issues
6091
6192
If using classic pipelines and you get the error:
6293
6394
> TF400813: The user '' is not authorized to access this resource.
6495
6596
Ensure `Allow scripts to access the OAuth token` is checked under options. See the [docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/build/options?view=azure-devops#allow-scripts-to-access-the-oauth-token) for more info.
97+

tasks/pull-request-status/docs/README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@
2020

2121
{{ #include-partial[file=status-create;wrap=yml] }}
2222

23+
### Conditionally update status
24+
25+
A status can be conditionally updated by using the `whenState` argument. This will first check that the current status is in one of the given states before it updates.
26+
27+
For the example below, it will only update the status if it is in one of the following states:
28+
29+
- Error
30+
- Failed
31+
- Pending
32+
33+
{{ #include-partial[file=status-conditional-update;wrap=yml] }}
34+
2335
# 🐞 Known issues
2436

25-
{{ #include-partial[file=status-access-warning] }}
37+
{{ #include-partial[file=status-access-warning] }}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- task: PullRequestStatus@0
2+
displayName: 'Initialize status'
3+
inputs:
4+
name: 'my-custom-check'
5+
action: 'Create'
6+
state: 'pending'
7+
description: 'Awaiting my custom check'
8+
- script: 'echo Do something. It might even update the status'
9+
- task: PullRequestStatus@0
10+
displayName: 'Update status'
11+
inputs:
12+
name: 'my-custom-check'
13+
action: 'Update'
14+
state: 'succeeded'
15+
description: 'Check passed'
16+
whenState: 'error, failed, pending'

tasks/pull-request-status/src/operations/PullRequestManager.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
GitStatusState
55
} from 'azure-devops-node-api/interfaces/GitInterfaces';
66
import * as tl from 'azure-pipelines-task-lib/task';
7-
import { getPullRequestId,getWebApi } from 'pull-request-core';
7+
import { getPullRequestId, getWebApi } from 'pull-request-core';
88
export class PullRequestManager {
99
public async manageStatus(): Promise<void> {
1010
const gitApi = await getWebApi().getGitApi();
@@ -22,19 +22,16 @@ export class PullRequestManager {
2222
switch (action) {
2323
case 'Create':
2424
case 'Update':
25-
await this.createStatus(gitApi, repositoryId, pullRequestId);
25+
await this.createStatus(gitApi, repositoryId, pullRequestId, action === 'Update');
2626
break;
2727
case 'Delete':
2828
await this.deleteStatus(gitApi, repositoryId, pullRequestId);
2929
break;
3030
}
3131
}
3232

33-
private createPayload(): GitPullRequestStatus {
33+
private mapState(state: string): GitStatusState {
3434
let status: GitStatusState = GitStatusState.NotSet;
35-
const state = tl.getInput('state', true);
36-
const name = tl.getInput('name', true);
37-
const description = tl.getInput('description');
3835

3936
switch (state) {
4037
case 'notSet':
@@ -57,6 +54,14 @@ export class PullRequestManager {
5754
break;
5855
}
5956

57+
return status;
58+
}
59+
60+
private createPayload(): GitPullRequestStatus {
61+
const state = tl.getInput('state', true);
62+
const name = tl.getInput('name', true);
63+
const description = tl.getInput('description');
64+
const status = this.mapState(state);
6065
const payload: GitPullRequestStatus = {
6166
state: status,
6267
context: {
@@ -69,9 +74,39 @@ export class PullRequestManager {
6974
return payload;
7075
}
7176

72-
private async createStatus(gitApi: IGitApi, repositoryId: string, pullRequestId: number) {
77+
private async createStatus(
78+
gitApi: IGitApi,
79+
repositoryId: string,
80+
pullRequestId: number,
81+
isUpdate: boolean
82+
) {
7383
try {
7484
const action = tl.getInput('action');
85+
if (isUpdate) {
86+
const whenState = tl.getDelimitedInput('whenState', ',')?.map(x => x.trim());
87+
if (whenState !== undefined && whenState.length > 0) {
88+
const existingStatuses = await gitApi.getPullRequestStatuses(repositoryId, pullRequestId);
89+
const name = tl.getInput('name', true);
90+
const currentStatus = existingStatuses?.find(
91+
x => x.context?.genre === 'pull-request-utils' && x.context?.name === name
92+
);
93+
if (currentStatus !== undefined) {
94+
const mappedStaus = whenState.map(this.mapState);
95+
if (!mappedStaus.includes(currentStatus.state)) {
96+
tl.setResult(
97+
tl.TaskResult.Succeeded,
98+
`Skipping updating state. ${
99+
GitStatusState[currentStatus.state]
100+
} is not in any of the updatable states ${mappedStaus
101+
.map(x => GitStatusState[x])
102+
.join(',')}`
103+
);
104+
return;
105+
}
106+
}
107+
}
108+
}
109+
75110
const createdStatus = await gitApi.createPullRequestStatus(
76111
this.createPayload(),
77112
repositoryId,
@@ -87,10 +122,11 @@ export class PullRequestManager {
87122
}
88123
}
89124
private async deleteStatus(gitApi: IGitApi, repositoryId: string, pullRequestId: number) {
125+
const name = tl.getInput('name', true);
90126
const existingStatuses = await gitApi.getPullRequestStatuses(repositoryId, pullRequestId);
91127

92128
const thisStatus = existingStatuses?.filter(
93-
x => x.context.genre === '' && x.context.name === ''
129+
x => x.context.genre === 'pull-request-utils' && x.context.name === name
94130
);
95131

96132
if (thisStatus?.length === 0) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as tmrm from 'azure-pipelines-task-lib/mock-run';
2+
import * as path from 'path';
3+
4+
import { getMock } from './utils/apiMock';
5+
import mockery = require('mockery');
6+
import { GitStatusState } from 'azure-devops-node-api/interfaces/GitInterfaces';
7+
8+
const taskPath = path.join(__dirname, '..', 'index.js');
9+
const tr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath);
10+
const mock = getMock({ id: 12345 }, [
11+
{
12+
id: 12345,
13+
state: GitStatusState.Pending,
14+
context: {
15+
genre: 'pull-request-utils',
16+
name: 'my-custom-status'
17+
}
18+
}
19+
]);
20+
21+
tr.setInput('action', 'Update');
22+
tr.setInput('name', 'my-custom-status');
23+
tr.setInput('state', 'success');
24+
tr.setInput('whenState', 'failed, pending');
25+
26+
27+
tr.registerMock('azure-devops-node-api', mock);
28+
29+
tr.run();
30+
31+
mockery.deregisterMock('azure-devops-node-api');

0 commit comments

Comments
 (0)