Signed-off-by: Peiman Jafari <pjafari@skillz.com>
This commit is contained in:
Peiman Jafari
2022-03-15 08:50:53 -07:00
parent 12693d5f87
commit 765639f98c
8 changed files with 231 additions and 5 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
GitHub issues label action:'github:issues:label' for Backstage plugin Scaffolder has been added.
'pullRequestNumber' output has been added to 'publish:github:pull-request' action.
+1
View File
@@ -368,6 +368,7 @@ export interface OctokitWithPullRequestPluginClient {
createPullRequest(options: createPullRequest.Options): Promise<{
data: {
html_url: string;
number: number;
};
} | null>;
}
@@ -45,6 +45,7 @@ import {
import {
createGithubActionsDispatchAction,
createGithubWebhookAction,
createGithubIssuesLabelAction,
} from './github';
import { TemplateFilter } from '../../../lib';
import { TemplateAction } from '../types';
@@ -145,6 +146,10 @@ export const createBuiltinActions = (
integrations,
githubCredentialsProvider,
}),
createGithubIssuesLabelAction({
integrations,
githubCredentialsProvider,
}),
];
return actions as TemplateAction<JsonObject>[];
@@ -0,0 +1,89 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createGithubIssuesLabelAction } from './githubIssuesLabel';
import {
ScmIntegrations,
DefaultGithubCredentialsProvider,
GithubCredentialsProvider,
} from '@backstage/integration';
import { ConfigReader } from '@backstage/config';
import { getVoidLogger } from '@backstage/backend-common';
import { PassThrough } from 'stream';
import { TemplateAction } from '../..';
const mockOctokit = {
rest: {
issues: {
addLabels: jest.fn(),
},
},
};
jest.mock('octokit', () => ({
Octokit: class {
constructor() {
return mockOctokit;
}
},
}));
describe('github:issues:label', () => {
const config = new ConfigReader({
integrations: {
github: [
{ host: 'github.com', token: 'tokenlols' },
{ host: 'ghe.github.com' },
],
},
});
const integrations = ScmIntegrations.fromConfig(config);
let githubCredentialsProvider: GithubCredentialsProvider;
let action: TemplateAction<any>;
const mockContext = {
input: {
repoUrl: 'github.com?repo=repo&owner=owner',
number: '1',
labels: ['label1', 'label2'],
},
workspacePath: 'lol',
logger: getVoidLogger(),
logStream: new PassThrough(),
output: jest.fn(),
createTemporaryDirectory: jest.fn(),
};
beforeEach(() => {
jest.resetAllMocks();
githubCredentialsProvider =
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
action = createGithubIssuesLabelAction({
integrations,
githubCredentialsProvider,
});
});
it('should call the githubApi for adding labels', async () => {
await action.handler(mockContext);
expect(mockOctokit.rest.issues.addLabels).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
issue_number: '1',
labels: ['label1', 'label2'],
});
});
});
@@ -0,0 +1,110 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
GithubCredentialsProvider,
ScmIntegrationRegistry,
} from '@backstage/integration';
import { createTemplateAction } from '../../createTemplateAction';
import { assertError, InputError } from '@backstage/errors';
import { Octokit } from 'octokit';
import { getOctokitOptions } from './helpers';
import { parseRepoUrl } from '../publish/util';
/**
* Adds labels to a pull request or issue on GitHub
* @public
*/
export function createGithubIssuesLabelAction(options: {
integrations: ScmIntegrationRegistry;
defaultWebhookSecret?: string;
githubCredentialsProvider?: GithubCredentialsProvider;
}) {
const { integrations, githubCredentialsProvider } = options;
return createTemplateAction<{
repoUrl: string;
number: number;
labels: string[];
token?: string;
}>({
id: 'github:issues:label',
description: 'Adds labels to a pull request or issue on GitHub.',
schema: {
input: {
type: 'object',
required: ['repoUrl', 'number', 'labels'],
properties: {
repoUrl: {
title: 'Repository Location',
description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
type: 'string',
},
number: {
title: 'Pull Request or issue number',
description: 'The pull request or issue number to add labels to',
type: 'integer',
},
labels: {
title: 'Labels',
description: 'The labels to add to the pull request or issue',
type: 'array',
items: {
type: 'string',
},
},
token: {
title: 'Authentication Token',
type: 'string',
description: 'The GITHUB_TOKEN to use for authorization to GitHub',
},
},
},
},
async handler(ctx) {
const { repoUrl, number, labels, token: providedToken } = ctx.input;
const { owner, repo } = parseRepoUrl(repoUrl, integrations);
ctx.logger.info(`Adding labels to ${number} issue on repo ${repo}`);
if (!owner) {
throw new InputError('Invalid repository owner provided in repoUrl');
}
const client = new Octokit(
await getOctokitOptions({
integrations,
credentialsProvider: githubCredentialsProvider,
repoUrl: repoUrl,
token: providedToken,
}),
);
try {
await client.rest.issues.addLabels({
owner,
repo,
issue_number: number,
labels,
});
} catch (e) {
assertError(e);
ctx.logger.warn(
`Failed: adding labels to issue: '${number}' on repo: '${repo}', ${e.message}`,
);
}
},
});
}
@@ -16,3 +16,4 @@
export { createGithubActionsDispatchAction } from './githubActionsDispatch';
export { createGithubWebhookAction } from './githubWebhook';
export { createGithubIssuesLabelAction } from './githubIssuesLabel';
@@ -58,6 +58,7 @@ describe('createPublishGithubPullRequestAction', () => {
status: 201,
data: {
html_url: 'https://github.com/myorg/myrepo/pull/123',
number: 123,
},
};
}),
@@ -123,13 +124,14 @@ describe('createPublishGithubPullRequestAction', () => {
});
});
it('creates outputs for the url', async () => {
it('creates outputs for the pull request url and number', async () => {
await instance.handler(ctx);
expect(ctx.output).toHaveBeenCalledWith(
'remoteUrl',
'https://github.com/myorg/myrepo/pull/123',
);
expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123);
});
afterEach(() => {
mockFs.restore();
@@ -254,13 +256,14 @@ describe('createPublishGithubPullRequestAction', () => {
});
});
it('creates outputs for the url', async () => {
it('creates outputs for the pull request url and number', async () => {
await instance.handler(ctx);
expect(ctx.output).toHaveBeenCalledWith(
'remoteUrl',
'https://github.com/myorg/myrepo/pull/123',
);
expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123);
});
afterEach(() => {
mockFs.restore();
@@ -322,13 +325,14 @@ describe('createPublishGithubPullRequestAction', () => {
});
});
it('creates outputs for the url', async () => {
it('creates outputs for the pull request url and number', async () => {
await instance.handler(ctx);
expect(ctx.output).toHaveBeenCalledWith(
'remoteUrl',
'https://github.com/myorg/myrepo/pull/123',
);
expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123);
});
afterEach(() => {
mockFs.restore();
@@ -390,13 +394,14 @@ describe('createPublishGithubPullRequestAction', () => {
});
});
it('creates outputs for the url', async () => {
it('creates outputs for the pull request url and number', async () => {
await instance.handler(ctx);
expect(ctx.output).toHaveBeenCalledWith(
'remoteUrl',
'https://github.com/myorg/myrepo/pull/123',
);
expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123);
});
afterEach(() => {
mockFs.restore();
@@ -37,7 +37,10 @@ class GithubResponseError extends CustomErrorBase {}
/** @public */
export interface OctokitWithPullRequestPluginClient {
createPullRequest(options: createPullRequest.Options): Promise<{
data: { html_url: string };
data: {
html_url: string;
number: number;
};
} | null>;
}
@@ -169,6 +172,11 @@ export const createPublishGithubPullRequestAction = ({
title: 'Pull Request URL',
description: 'Link to the pull request in Github',
},
pullRequestNumber: {
type: 'number',
title: 'Pull Request Number',
description: 'The pull request number',
},
},
},
},
@@ -263,6 +271,7 @@ export const createPublishGithubPullRequestAction = ({
}
ctx.output('remoteUrl', response.data.html_url);
ctx.output('pullRequestNumber', response.data.number);
} catch (e) {
throw new GithubResponseError('Pull request creation failed', e);
}