Skip to content

Commit 5a04e9c

Browse files
committed
feat: add support for labels with color
1 parent 3629d55 commit 5a04e9c

File tree

9 files changed

+107
-21
lines changed

9 files changed

+107
-21
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,14 @@ source:
137137
- any-glob-to-any-file: 'src/**/*'
138138
- all-globs-to-all-files: '!src/docs/*'
139139
140+
# Add 'source' label with color #F3F3F3 to any change to src files within the source dir EXCEPT for the docs sub-folder
141+
source:
142+
- all:
143+
- color: '#F3F3F3'
144+
- changed-files:
145+
- any-glob-to-any-file: 'src/**/*'
146+
- all-globs-to-all-files: '!src/docs/*'
147+
140148
# Add 'feature' label to any PR where the head branch name starts with `feature` or has a `feature` section in the name
141149
feature:
142150
- head-branch: ['^feature', 'feature']

__mocks__/@actions/github.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export const context = {
1919
const mockApi = {
2020
rest: {
2121
issues: {
22-
setLabels: jest.fn()
22+
setLabels: jest.fn(),
23+
updateLabel: jest.fn()
2324
},
2425
pulls: {
2526
get: jest.fn().mockResolvedValue({
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
touched-a-pdf-file:
2+
- color: '#FF0011'
3+
- changed-files:
4+
- any-glob-to-any-file: ['*.pdf']

__tests__/main.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import * as github from '@actions/github';
33
import * as core from '@actions/core';
44
import path from 'path';
55
import fs from 'fs';
6+
import {PullRequest} from '../src/api/types';
67

78
jest.mock('@actions/core');
89
jest.mock('@actions/github');
910

1011
const gh = github.getOctokit('_');
1112
const setLabelsMock = jest.spyOn(gh.rest.issues, 'setLabels');
13+
const updateLabelMock = jest.spyOn(gh.rest.issues, 'updateLabel');
1214
const reposMock = jest.spyOn(gh.rest.repos, 'getContent');
1315
const paginateMock = jest.spyOn(gh, 'paginate');
1416
const getPullMock = jest.spyOn(gh.rest.pulls, 'get');
@@ -36,6 +38,9 @@ class NotFound extends Error {
3638
const yamlFixtures = {
3739
'branches.yml': fs.readFileSync('__tests__/fixtures/branches.yml'),
3840
'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml'),
41+
'only_pdfs_with_color.yml': fs.readFileSync(
42+
'__tests__/fixtures/only_pdfs_with_color.yml'
43+
),
3944
'not_supported.yml': fs.readFileSync('__tests__/fixtures/not_supported.yml'),
4045
'any_and_all.yml': fs.readFileSync('__tests__/fixtures/any_and_all.yml')
4146
};
@@ -471,6 +476,37 @@ describe('run', () => {
471476
expect(reposMock).toHaveBeenCalled();
472477
});
473478

479+
it('does update label color when defined in the configuration', async () => {
480+
setLabelsMock.mockClear();
481+
482+
usingLabelerConfigYaml('only_pdfs_with_color.yml');
483+
mockGitHubResponseChangedFiles('foo.pdf');
484+
485+
getPullMock.mockResolvedValueOnce(<any>{
486+
data: {
487+
labels: [{name: 'manually-added'}]
488+
}
489+
});
490+
491+
await run();
492+
493+
console.log(setLabelsMock.mock.calls);
494+
expect(setLabelsMock).toHaveBeenCalledTimes(1);
495+
expect(setLabelsMock).toHaveBeenCalledWith({
496+
owner: 'monalisa',
497+
repo: 'helloworld',
498+
issue_number: 123,
499+
labels: ['manually-added', 'touched-a-pdf-file']
500+
});
501+
expect(updateLabelMock).toHaveBeenCalledTimes(1);
502+
expect(updateLabelMock).toHaveBeenCalledWith({
503+
owner: 'monalisa',
504+
repo: 'helloworld',
505+
name: 'touched-a-pdf-file',
506+
color: 'FF0011'
507+
});
508+
});
509+
474510
test.each([
475511
[new HttpError('Error message')],
476512
[new NotFound('Error message')]

src/api/get-changed-pull-requests.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import * as core from '@actions/core';
22
import * as github from '@actions/github';
33
import {getChangedFiles} from './get-changed-files';
4-
import {ClientType} from './types';
4+
import {ClientType, PullRequest} from './types';
55

66
export async function* getPullRequests(
77
client: ClientType,
88
prNumbers: number[]
99
) {
1010
for (const prNumber of prNumbers) {
1111
core.debug(`looking for pr #${prNumber}`);
12-
let prData: any;
12+
let prData: PullRequest;
1313
try {
1414
const result = await client.rest.pulls.get({
1515
owner: github.context.repo.owner,

src/api/get-label-configs.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import {toBranchMatchConfig, BranchMatchConfig} from '../branch';
1313

1414
export interface MatchConfig {
15+
color?: string;
1516
all?: BaseMatchConfig[];
1617
any?: BaseMatchConfig[];
1718
}
@@ -63,7 +64,13 @@ export function getLabelConfigMapFromObject(
6364
): Map<string, MatchConfig[]> {
6465
const labelMap: Map<string, MatchConfig[]> = new Map();
6566
for (const label in configObject) {
66-
const configOptions = configObject[label];
67+
const configOptions: [] = configObject[label];
68+
69+
// Get the color from the label if it exists.
70+
const color = configOptions.find(x => Object.keys(x).includes('color'))?.[
71+
'color'
72+
];
73+
6774
if (
6875
!Array.isArray(configOptions) ||
6976
!configOptions.every(opts => typeof opts === 'object')
@@ -84,17 +91,26 @@ export function getLabelConfigMapFromObject(
8491
if (key === 'any' || key === 'all') {
8592
if (Array.isArray(value)) {
8693
const newConfigs = value.map(toMatchConfig);
87-
updatedConfig.push({[key]: newConfigs});
94+
updatedConfig.push({
95+
color,
96+
[key]: newConfigs
97+
});
8898
}
8999
} else if (ALLOWED_CONFIG_KEYS.includes(key)) {
90-
const newMatchConfig = toMatchConfig({[key]: value});
100+
const newMatchConfig = toMatchConfig({
101+
color,
102+
[key]: value
103+
});
91104
// Find or set the `any` key so that we can add these properties to that rule,
92105
// Or create a new `any` key and add that to our array of configs.
93106
const indexOfAny = updatedConfig.findIndex(mc => !!mc['any']);
94107
if (indexOfAny >= 0) {
95108
updatedConfig[indexOfAny].any?.push(newMatchConfig);
96109
} else {
97-
updatedConfig.push({any: [newMatchConfig]});
110+
updatedConfig.push({
111+
color,
112+
any: [newMatchConfig]
113+
});
98114
}
99115
} else {
100116
// Log the key that we don't know what to do with.

src/api/set-labels.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,23 @@ import {ClientType} from './types';
44
export const setLabels = async (
55
client: ClientType,
66
prNumber: number,
7-
labels: string[]
7+
labels: [string, string][]
88
) => {
99
await client.rest.issues.setLabels({
1010
owner: github.context.repo.owner,
1111
repo: github.context.repo.repo,
1212
issue_number: prNumber,
13-
labels: labels
13+
labels: labels.map(([label]) => label)
1414
});
15+
16+
for (const [label, color] of labels) {
17+
if (color) {
18+
await client.rest.issues.updateLabel({
19+
owner: github.context.repo.owner,
20+
repo: github.context.repo.repo,
21+
name: label,
22+
color: color?.replace('#', '')
23+
});
24+
}
25+
}
1526
};

src/api/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
import * as github from '@actions/github';
2+
import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types';
3+
24
export type ClientType = ReturnType<typeof github.getOctokit>;
5+
6+
export type PullRequest =
7+
RestEndpointMethodTypes['pulls']['get']['response']['data'];

src/labeler.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import {BaseMatchConfig, MatchConfig} from './api/get-label-configs';
1010
import {checkAllChangedFiles, checkAnyChangedFiles} from './changedFiles';
1111

1212
import {checkAnyBranch, checkAllBranch} from './branch';
13-
14-
type ClientType = ReturnType<typeof github.getOctokit>;
13+
import {ClientType} from './api';
1514

1615
// GitHub Issues cannot have more than 100 labels
1716
const GITHUB_MAX_LABELS = 100;
@@ -39,13 +38,16 @@ async function labeler() {
3938
client,
4039
configPath
4140
);
42-
const preexistingLabels = pullRequest.data.labels.map(l => l.name);
43-
const allLabels: Set<string> = new Set<string>(preexistingLabels);
41+
const preexistingLabels: [string, string][] = pullRequest.data.labels.map(
42+
(l: {name: string; color: string}) => [l.name, l.color]
43+
);
44+
const allLabels = new Map<string, string>();
45+
preexistingLabels.forEach(([label, color]) => allLabels.set(label, color));
4446

4547
for (const [label, configs] of labelConfigs.entries()) {
4648
core.debug(`processing ${label}`);
4749
if (checkMatchConfigs(pullRequest.changedFiles, configs, dot)) {
48-
allLabels.add(label);
50+
allLabels.set(label, configs[0]?.color || '');
4951
} else if (syncLabels) {
5052
allLabels.delete(label);
5153
}
@@ -54,13 +56,16 @@ async function labeler() {
5456
const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS);
5557
const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS);
5658

57-
let newLabels: string[] = [];
59+
let newLabels: [string, string][] = [];
5860

5961
try {
6062
if (!isEqual(labelsToAdd, preexistingLabels)) {
6163
await api.setLabels(client, pullRequest.number, labelsToAdd);
6264
newLabels = labelsToAdd.filter(
63-
label => !preexistingLabels.includes(label)
65+
([label]) =>
66+
!preexistingLabels.some(
67+
existingsLabel => existingsLabel[0] === label
68+
)
6469
);
6570
}
6671
} catch (error: any) {
@@ -83,14 +88,14 @@ async function labeler() {
8388
return;
8489
}
8590

86-
core.setOutput('new-labels', newLabels.join(','));
87-
core.setOutput('all-labels', labelsToAdd.join(','));
91+
core.setOutput('new-labels', newLabels.map(([label]) => label).join(','));
92+
core.setOutput('all-labels', labelsToAdd.map(([label]) => label).join(','));
8893

8994
if (excessLabels.length) {
9095
core.warning(
91-
`Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels.join(
92-
', '
93-
)}`,
96+
`Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels
97+
.map(([label]) => [label])
98+
.join(', ')}`,
9499
{title: 'Label limit for a PR exceeded'}
95100
);
96101
}

0 commit comments

Comments
 (0)