Skip to content

Commit 6d4006d

Browse files
committed
feat: add support for label colors
1 parent 2b2e7d0 commit 6d4006d

File tree

5 files changed

+118
-15
lines changed

5 files changed

+118
-15
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,31 @@ From a boolean logic perspective, top-level match objects are `OR`-ed together a
4545
> You need to set `dot: true` to change this behavior.
4646
> See [Inputs](#inputs) table below for details.
4747

48+
#### Advanced configuration
49+
50+
In order to define label colors, the `.github/labeler.yml` can be extended as follow:
51+
```yml
52+
# Add 'label1' to any changes within 'example' folder or any subfolders
53+
label1:
54+
pattern:
55+
- example/**
56+
color:
57+
'#FFFF00'
58+
59+
60+
# Add 'label2' to any file changes within 'example2' folder
61+
label2: example2/*
62+
63+
# Add label3 to any change to .txt files within the entire repository. Quotation marks are required for the leading asterisk
64+
label3:
65+
pattern:
66+
- '**/*.txt'
67+
color:
68+
'#ECECEC'
69+
70+
```
71+
72+
4873
#### Basic Examples
4974

5075
```yml

__mocks__/@actions/github.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export const context = {
1313
const mockApi = {
1414
rest: {
1515
issues: {
16-
setLabels: jest.fn()
16+
setLabels: jest.fn(),
17+
updateLabel: jest.fn()
1718
},
1819
pulls: {
1920
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+
pattern:
3+
- any: ['*.pdf']
4+
color: '#FF0011'

__tests__/main.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ jest.mock('@actions/github');
99

1010
const gh = github.getOctokit('_');
1111
const setLabelsMock = jest.spyOn(gh.rest.issues, 'setLabels');
12+
const updateLabelMock = jest.spyOn(gh.rest.issues, 'updateLabel');
1213
const reposMock = jest.spyOn(gh.rest.repos, 'getContent');
1314
const paginateMock = jest.spyOn(gh, 'paginate');
1415
const getPullMock = jest.spyOn(gh.rest.pulls, 'get');
@@ -34,7 +35,10 @@ class NotFound extends Error {
3435
}
3536

3637
const yamlFixtures = {
37-
'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml')
38+
'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml'),
39+
'only_pdfs_with_color.yml': fs.readFileSync(
40+
'__tests__/fixtures/only_pdfs_with_color.yml'
41+
)
3842
};
3943

4044
const configureInput = (
@@ -352,6 +356,31 @@ describe('run', () => {
352356
expect(reposMock).toHaveBeenCalled();
353357
});
354358

359+
it('does update label color when defined in the configuration', async () => {
360+
setLabelsMock.mockClear();
361+
362+
usingLabelerConfigYaml('only_pdfs_with_color.yml');
363+
mockGitHubResponseChangedFiles('foo.pdf');
364+
365+
await run();
366+
367+
console.log(setLabelsMock.mock.calls);
368+
expect(setLabelsMock).toHaveBeenCalledTimes(1);
369+
expect(setLabelsMock).toHaveBeenCalledWith({
370+
owner: 'monalisa',
371+
repo: 'helloworld',
372+
issue_number: 123,
373+
labels: ['manually-added', 'touched-a-pdf-file']
374+
});
375+
expect(updateLabelMock).toHaveBeenCalledTimes(1);
376+
expect(updateLabelMock).toHaveBeenCalledWith({
377+
owner: 'monalisa',
378+
repo: 'helloworld',
379+
name: 'touched-a-pdf-file',
380+
color: '#FF0011'
381+
});
382+
});
383+
355384
test.each([
356385
[new HttpError('Error message')],
357386
[new NotFound('Error message')]

src/labeler.ts

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ interface MatchConfig {
1111
}
1212

1313
type StringOrMatchConfig = string | MatchConfig;
14+
type LabelsConfig = Map<
15+
string,
16+
{stringOrMatch: StringOrMatchConfig[]; color?: string}
17+
>;
1418
type ClientType = ReturnType<typeof github.getOctokit>;
1519

1620
// GitHub Issues cannot have more than 100 labels
@@ -55,13 +59,15 @@ export async function run() {
5559
continue;
5660
}
5761

58-
const labelGlobs: Map<string, StringOrMatchConfig[]> =
59-
await getLabelGlobs(client, configPath);
62+
const labelsConfig: LabelsConfig = await getLabelGlobs(
63+
client,
64+
configPath
65+
);
6066

6167
const preexistingLabels = pullRequest.labels.map(l => l.name);
6268
const allLabels: Set<string> = new Set<string>(preexistingLabels);
6369

64-
for (const [label, globs] of labelGlobs.entries()) {
70+
for (const [label, {stringOrMatch: globs}] of labelsConfig.entries()) {
6571
core.debug(`processing ${label}`);
6672
if (checkGlobs(changedFiles, globs, dot)) {
6773
allLabels.add(label);
@@ -77,7 +83,12 @@ export async function run() {
7783
let newLabels: string[] = [];
7884

7985
if (!isListEqual(labelsToAdd, preexistingLabels)) {
80-
await setLabels(client, prNumber, labelsToAdd);
86+
await setLabels(
87+
client,
88+
prNumber,
89+
labelsToAdd,
90+
getLabelsColor(labelsConfig)
91+
);
8192
newLabels = labelsToAdd.filter(l => !preexistingLabels.includes(l));
8293
}
8394

@@ -164,7 +175,7 @@ async function getChangedFiles(
164175
async function getLabelGlobs(
165176
client: ClientType,
166177
configurationPath: string
167-
): Promise<Map<string, StringOrMatchConfig[]>> {
178+
): Promise<LabelsConfig> {
168179
let configurationContent: string;
169180
try {
170181
if (!fs.existsSync(configurationPath)) {
@@ -196,6 +207,16 @@ async function getLabelGlobs(
196207
return getLabelGlobMapFromObject(configObject);
197208
}
198209

210+
function getLabelsColor(labelsConfig: LabelsConfig): Map<string, string> {
211+
const labelsColor: Map<string, string> = new Map();
212+
for (const [label, {color}] of labelsConfig.entries()) {
213+
if (color) {
214+
labelsColor.set(label, color);
215+
}
216+
}
217+
return labelsColor;
218+
}
219+
199220
async function fetchContent(
200221
client: ClientType,
201222
repoPath: string
@@ -210,15 +231,24 @@ async function fetchContent(
210231
return Buffer.from(response.data.content, response.data.encoding).toString();
211232
}
212233

213-
function getLabelGlobMapFromObject(
214-
configObject: any
215-
): Map<string, StringOrMatchConfig[]> {
216-
const labelGlobs: Map<string, StringOrMatchConfig[]> = new Map();
234+
function getLabelGlobMapFromObject(configObject: any): LabelsConfig {
235+
const labelGlobs: Map<
236+
string,
237+
{stringOrMatch: StringOrMatchConfig[]; color?: string}
238+
> = new Map();
217239
for (const label in configObject) {
218240
if (typeof configObject[label] === 'string') {
219-
labelGlobs.set(label, [configObject[label]]);
241+
labelGlobs.set(label, {stringOrMatch: [configObject[label]]});
220242
} else if (configObject[label] instanceof Array) {
221-
labelGlobs.set(label, configObject[label]);
243+
labelGlobs.set(label, {stringOrMatch: configObject[label]});
244+
} else if (
245+
typeof configObject[label] === 'object' &&
246+
configObject[label]?.pattern
247+
) {
248+
labelGlobs.set(label, {
249+
stringOrMatch: configObject[label].pattern,
250+
color: configObject[label].color
251+
});
222252
} else {
223253
throw Error(
224254
`found unexpected type for label ${label} (should be string or array of globs)`
@@ -337,12 +367,26 @@ function isListEqual(listA: string[], listB: string[]): boolean {
337367
async function setLabels(
338368
client: ClientType,
339369
prNumber: number,
340-
labels: string[]
370+
labels: string[],
371+
labelsColour: Map<string, string>
341372
) {
373+
// remove previous labels
342374
await client.rest.issues.setLabels({
343375
owner: github.context.repo.owner,
344376
repo: github.context.repo.repo,
345377
issue_number: prNumber,
346-
labels: labels
378+
labels
347379
});
380+
381+
for (const label of labels) {
382+
const color = labelsColour.get(label);
383+
if (color) {
384+
await client.rest.issues.updateLabel({
385+
owner: github.context.repo.owner,
386+
repo: github.context.repo.repo,
387+
name: label,
388+
color: color ?? '#EDEDED'
389+
});
390+
}
391+
}
348392
}

0 commit comments

Comments
 (0)