Skip to content

Commit

Permalink
feat: add support for label colors
Browse files Browse the repository at this point in the history
  • Loading branch information
overbit committed Sep 22, 2023
1 parent 2b2e7d0 commit 6d4006d
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 15 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ From a boolean logic perspective, top-level match objects are `OR`-ed together a
> You need to set `dot: true` to change this behavior.
> See [Inputs](#inputs) table below for details.

#### Advanced configuration

In order to define label colors, the `.github/labeler.yml` can be extended as follow:
```yml
# Add 'label1' to any changes within 'example' folder or any subfolders
label1:
pattern:
- example/**
color:
'#FFFF00'
# Add 'label2' to any file changes within 'example2' folder
label2: example2/*
# Add label3 to any change to .txt files within the entire repository. Quotation marks are required for the leading asterisk
label3:
pattern:
- '**/*.txt'
color:
'#ECECEC'
```


#### Basic Examples

```yml
Expand Down
3 changes: 2 additions & 1 deletion __mocks__/@actions/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const context = {
const mockApi = {
rest: {
issues: {
setLabels: jest.fn()
setLabels: jest.fn(),
updateLabel: jest.fn()
},
pulls: {
get: jest.fn().mockResolvedValue({
Expand Down
4 changes: 4 additions & 0 deletions __tests__/fixtures/only_pdfs_with_color.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
touched-a-pdf-file:
pattern:
- any: ['*.pdf']
color: '#FF0011'
31 changes: 30 additions & 1 deletion __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jest.mock('@actions/github');

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

const yamlFixtures = {
'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml')
'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml'),
'only_pdfs_with_color.yml': fs.readFileSync(
'__tests__/fixtures/only_pdfs_with_color.yml'
)
};

const configureInput = (
Expand Down Expand Up @@ -352,6 +356,31 @@ describe('run', () => {
expect(reposMock).toHaveBeenCalled();
});

it('does update label color when defined in the configuration', async () => {
setLabelsMock.mockClear();

usingLabelerConfigYaml('only_pdfs_with_color.yml');
mockGitHubResponseChangedFiles('foo.pdf');

await run();

console.log(setLabelsMock.mock.calls);
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['manually-added', 'touched-a-pdf-file']
});
expect(updateLabelMock).toHaveBeenCalledTimes(1);
expect(updateLabelMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
name: 'touched-a-pdf-file',
color: '#FF0011'
});
});

test.each([
[new HttpError('Error message')],
[new NotFound('Error message')]
Expand Down
70 changes: 57 additions & 13 deletions src/labeler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ interface MatchConfig {
}

type StringOrMatchConfig = string | MatchConfig;
type LabelsConfig = Map<
string,
{stringOrMatch: StringOrMatchConfig[]; color?: string}
>;
type ClientType = ReturnType<typeof github.getOctokit>;

// GitHub Issues cannot have more than 100 labels
Expand Down Expand Up @@ -55,13 +59,15 @@ export async function run() {
continue;
}

const labelGlobs: Map<string, StringOrMatchConfig[]> =
await getLabelGlobs(client, configPath);
const labelsConfig: LabelsConfig = await getLabelGlobs(
client,
configPath
);

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

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

if (!isListEqual(labelsToAdd, preexistingLabels)) {
await setLabels(client, prNumber, labelsToAdd);
await setLabels(
client,
prNumber,
labelsToAdd,
getLabelsColor(labelsConfig)
);
newLabels = labelsToAdd.filter(l => !preexistingLabels.includes(l));
}

Expand Down Expand Up @@ -164,7 +175,7 @@ async function getChangedFiles(
async function getLabelGlobs(
client: ClientType,
configurationPath: string
): Promise<Map<string, StringOrMatchConfig[]>> {
): Promise<LabelsConfig> {
let configurationContent: string;
try {
if (!fs.existsSync(configurationPath)) {
Expand Down Expand Up @@ -196,6 +207,16 @@ async function getLabelGlobs(
return getLabelGlobMapFromObject(configObject);
}

function getLabelsColor(labelsConfig: LabelsConfig): Map<string, string> {
const labelsColor: Map<string, string> = new Map();
for (const [label, {color}] of labelsConfig.entries()) {
if (color) {
labelsColor.set(label, color);
}
}
return labelsColor;
}

async function fetchContent(
client: ClientType,
repoPath: string
Expand All @@ -210,15 +231,24 @@ async function fetchContent(
return Buffer.from(response.data.content, response.data.encoding).toString();
}

function getLabelGlobMapFromObject(
configObject: any
): Map<string, StringOrMatchConfig[]> {
const labelGlobs: Map<string, StringOrMatchConfig[]> = new Map();
function getLabelGlobMapFromObject(configObject: any): LabelsConfig {
const labelGlobs: Map<
string,
{stringOrMatch: StringOrMatchConfig[]; color?: string}
> = new Map();
for (const label in configObject) {
if (typeof configObject[label] === 'string') {
labelGlobs.set(label, [configObject[label]]);
labelGlobs.set(label, {stringOrMatch: [configObject[label]]});
} else if (configObject[label] instanceof Array) {
labelGlobs.set(label, configObject[label]);
labelGlobs.set(label, {stringOrMatch: configObject[label]});
} else if (
typeof configObject[label] === 'object' &&
configObject[label]?.pattern
) {
labelGlobs.set(label, {
stringOrMatch: configObject[label].pattern,
color: configObject[label].color
});
} else {
throw Error(
`found unexpected type for label ${label} (should be string or array of globs)`
Expand Down Expand Up @@ -337,12 +367,26 @@ function isListEqual(listA: string[], listB: string[]): boolean {
async function setLabels(
client: ClientType,
prNumber: number,
labels: string[]
labels: string[],
labelsColour: Map<string, string>
) {
// remove previous labels
await client.rest.issues.setLabels({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber,
labels: labels
labels
});

for (const label of labels) {
const color = labelsColour.get(label);
if (color) {
await client.rest.issues.updateLabel({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
name: label,
color: color ?? '#EDEDED'
});
}
}
}

0 comments on commit 6d4006d

Please sign in to comment.