Merge pull request #12099 from planeiii-te/jenkins-branches
feat(jenkins-backend): add multiple branch support to the JenkinsApi
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-jenkins': patch
|
||||
'@backstage/plugin-jenkins-backend': patch
|
||||
---
|
||||
|
||||
feature: added support for multiple branches to the `JenkinsApi`
|
||||
@@ -37,6 +37,7 @@
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"jenkins": "^0.28.1",
|
||||
"promise-any-polyfill": "^1.0.1",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -118,10 +118,9 @@ describe('JenkinsApi', () => {
|
||||
it('standard github layout', async () => {
|
||||
mockedJenkinsClient.job.get.mockResolvedValueOnce(project);
|
||||
|
||||
const result = await jenkinsApi.getProjects(
|
||||
jenkinsInfo,
|
||||
const result = await jenkinsApi.getProjects(jenkinsInfo, [
|
||||
'testBranchName',
|
||||
);
|
||||
]);
|
||||
|
||||
expect(mockedJenkins).toHaveBeenCalledWith({
|
||||
baseUrl: jenkinsInfo.baseUrl,
|
||||
@@ -134,6 +133,35 @@ describe('JenkinsApi', () => {
|
||||
});
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('supports multiple branches', async () => {
|
||||
mockedJenkinsClient.job.get.mockResolvedValue(project);
|
||||
|
||||
const result = await jenkinsApi.getProjects(jenkinsInfo, [
|
||||
'foo',
|
||||
'bar',
|
||||
'catpants',
|
||||
]);
|
||||
|
||||
expect(mockedJenkins).toHaveBeenCalledWith({
|
||||
baseUrl: jenkinsInfo.baseUrl,
|
||||
headers: jenkinsInfo.headers,
|
||||
promisify: true,
|
||||
});
|
||||
expect(mockedJenkinsClient.job.get).toBeCalledWith({
|
||||
name: `${jenkinsInfo.jobFullName}/foo`,
|
||||
tree: expect.anything(),
|
||||
});
|
||||
expect(mockedJenkinsClient.job.get).toBeCalledWith({
|
||||
name: `${jenkinsInfo.jobFullName}/bar`,
|
||||
tree: expect.anything(),
|
||||
});
|
||||
expect(mockedJenkinsClient.job.get).toBeCalledWith({
|
||||
name: `${jenkinsInfo.jobFullName}/catpants`,
|
||||
tree: expect.anything(),
|
||||
});
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
describe('augmented', () => {
|
||||
const projectWithScmActions: JenkinsProject = {
|
||||
|
||||
@@ -30,6 +30,12 @@ import {
|
||||
import { jenkinsExecutePermission } from '@backstage/plugin-jenkins-common';
|
||||
import { NotAllowedError } from '@backstage/errors';
|
||||
|
||||
if (!Promise.any) {
|
||||
(async () => {
|
||||
await import('promise-any-polyfill');
|
||||
})();
|
||||
}
|
||||
|
||||
export class JenkinsApiImpl {
|
||||
private static readonly lastBuildTreeSpec = `lastBuild[
|
||||
number,
|
||||
@@ -70,18 +76,21 @@ export class JenkinsApiImpl {
|
||||
* Get a list of projects for the given JenkinsInfo.
|
||||
* @see ../../../jenkins/src/api/JenkinsApi.ts#getProjects
|
||||
*/
|
||||
async getProjects(jenkinsInfo: JenkinsInfo, branch?: string) {
|
||||
async getProjects(jenkinsInfo: JenkinsInfo, branches?: string[]) {
|
||||
const client = await JenkinsApiImpl.getClient(jenkinsInfo);
|
||||
const projects: BackstageProject[] = [];
|
||||
|
||||
if (branch) {
|
||||
// we have been asked to filter to a single branch.
|
||||
if (branches) {
|
||||
// Assume jenkinsInfo.jobFullName is a folder which contains one job per branch.
|
||||
// TODO: extract a strategy interface for this
|
||||
const job = await client.job.get({
|
||||
name: `${jenkinsInfo.jobFullName}/${branch}`,
|
||||
tree: JenkinsApiImpl.jobTreeSpec.replace(/\s/g, ''),
|
||||
});
|
||||
const job = await Promise.any(
|
||||
branches.map(branch =>
|
||||
client.job.get({
|
||||
name: `${jenkinsInfo.jobFullName}/${branch}`,
|
||||
tree: JenkinsApiImpl.jobTreeSpec.replace(/\s/g, ''),
|
||||
}),
|
||||
),
|
||||
);
|
||||
projects.push(this.augmentProject(job));
|
||||
} else {
|
||||
// We aren't filtering
|
||||
|
||||
@@ -64,12 +64,12 @@ export async function createRouter(
|
||||
request.header('authorization'),
|
||||
);
|
||||
const branch = request.query.branch;
|
||||
let branchStr: string | undefined;
|
||||
let branches: string[] | undefined;
|
||||
|
||||
if (branch === undefined) {
|
||||
branchStr = undefined;
|
||||
branches = undefined;
|
||||
} else if (typeof branch === 'string') {
|
||||
branchStr = branch;
|
||||
branches = branch.split(/,/g);
|
||||
} else {
|
||||
// this was passed in as something weird -> 400
|
||||
// https://evanhahn.com/gotchas-with-express-query-parsing-and-how-to-avoid-them/
|
||||
@@ -88,7 +88,7 @@ export async function createRouter(
|
||||
},
|
||||
backstageToken: token,
|
||||
});
|
||||
const projects = await jenkinsApi.getProjects(jenkinsInfo, branchStr);
|
||||
const projects = await jenkinsApi.getProjects(jenkinsInfo, branches);
|
||||
|
||||
response.json({
|
||||
projects: projects,
|
||||
|
||||
@@ -19,7 +19,7 @@ yarn add --cwd packages/app @backstage/plugin-jenkins
|
||||
|
||||
3. Add the `EntityJenkinsContent` extension to the `CI/CD` page and `EntityLatestJenkinsRunCard` to the `overview` page in the app (or wherever you'd prefer):
|
||||
|
||||
Note that if you configured a custom JenkinsInfoProvider in step 2, you may need a custom isJenkinsAvailable.
|
||||
Note that if you configured a custom JenkinsInfoProvider in step 2, you may need a custom isJenkinsAvailable. Also if you're transitioning to a new default branch name, you can pass multiple branch names as a comma-separated list and it will check for each branch name.
|
||||
|
||||
```tsx
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
@@ -38,7 +38,10 @@ const serviceEntityPage = (
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isJenkinsAvailable}>
|
||||
<Grid item sm={6}>
|
||||
<EntityLatestJenkinsRunCard branch="master" variant="gridItem" />
|
||||
<EntityLatestJenkinsRunCard
|
||||
branch="main,master"
|
||||
variant="gridItem"
|
||||
/>
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
{/* ... */}
|
||||
|
||||
@@ -6787,7 +6787,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
|
||||
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
|
||||
|
||||
"@types/react-dom@*", "@types/react-dom@<18.0.0", "@types/react-dom@^17":
|
||||
"@types/react-dom@*", "@types/react-dom@<18.0.0":
|
||||
version "17.0.17"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz#2e3743277a793a96a99f1bf87614598289da68a1"
|
||||
integrity sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==
|
||||
@@ -21223,6 +21223,11 @@ promise-all-reject-late@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2"
|
||||
integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==
|
||||
|
||||
promise-any-polyfill@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/promise-any-polyfill/-/promise-any-polyfill-1.0.1.tgz#563872dd0abd370bfa8039e63980469b4b8b0d53"
|
||||
integrity sha512-51Tj+hOoV3LOeCHItmWBklN9FYkyV53uIIjjzgIchitGneTvl6+ivOxWXhxraeUzzS3YFRpXRPPbv7+KD3NzYQ==
|
||||
|
||||
promise-call-limit@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz#4bdee03aeb85674385ca934da7114e9bcd3c6e24"
|
||||
@@ -26308,7 +26313,7 @@ ws@^7.3.1, ws@^7.4.6:
|
||||
resolved "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67"
|
||||
integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==
|
||||
|
||||
ws@^8.0.0, ws@^8.3.0:
|
||||
ws@^8.3.0:
|
||||
version "8.8.0"
|
||||
resolved "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769"
|
||||
integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==
|
||||
|
||||
Reference in New Issue
Block a user