diff --git a/.changeset/seven-dolphins-tickle.md b/.changeset/seven-dolphins-tickle.md new file mode 100644 index 0000000000..cd3149792c --- /dev/null +++ b/.changeset/seven-dolphins-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins-backend': patch +--- + +added support for standalone jenkins projects diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts index 4f65e446fd..57ce2dbed4 100644 --- a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts +++ b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts @@ -75,6 +75,38 @@ describe('JenkinsApi', () => { }, }; + describe('standalone project', () => { + it('should return the only build', async () => { + mockedJenkinsClient.job.get + .mockResolvedValueOnce(project) + .mockResolvedValueOnce(project); + const result = await jenkinsApi.getProjects(jenkinsInfo); + expect(mockedJenkinsClient.job.get).toHaveBeenCalledTimes(2); + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + actions: [], + displayName: 'Example Build', + fullDisplayName: 'Example jobName » Example Build', + fullName: 'example-jobName/exampleBuild', + inQueue: false, + lastBuild: { + actions: [], + timestamp: 1, + building: false, + duration: 10, + result: 'success', + displayName: '#7', + fullDisplayName: 'Example jobName » Example Build #7', + url: 'https://jenkins.example.com/job/example-jobName/job/exampleBuild', + number: 7, + status: 'success', + source: {}, + }, + status: 'success', + }); + }); + }); + describe('unfiltered', () => { it('standard github layout', async () => { mockedJenkinsClient.job.get.mockResolvedValueOnce({ jobs: [project] }); diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.ts b/plugins/jenkins-backend/src/service/jenkinsApi.ts index 89c9a41f9c..ef4ddc47b0 100644 --- a/plugins/jenkins-backend/src/service/jenkinsApi.ts +++ b/plugins/jenkins-backend/src/service/jenkinsApi.ts @@ -75,7 +75,7 @@ export class JenkinsApiImpl { const projects: BackstageProject[] = []; if (branches) { - // Assume jenkinsInfo.jobFullName is a folder which contains one job per branch. + // Assume jenkinsInfo.jobFullName is a MultiBranch Pipeline project which contains one job per branch. // TODO: extract a strategy interface for this const job = await Promise.any( branches.map(branch => @@ -88,8 +88,10 @@ export class JenkinsApiImpl { projects.push(this.augmentProject(job)); } else { // We aren't filtering - // Assume jenkinsInfo.jobFullName is a folder which contains one job per branch. - const folder = await client.job.get({ + // Assume jenkinsInfo.jobFullName is either + // a MultiBranch Pipeline (folder with one job per branch) project + // a Pipeline (standalone) project + const project = await client.job.get({ name: jenkinsInfo.jobFullName, // Filter only be the information we need, instead of loading all fields. // Limit to only show the latest build for each job and only load 50 jobs @@ -99,15 +101,18 @@ export class JenkinsApiImpl { tree: JenkinsApiImpl.jobsTreeSpec.replace(/\s/g, ''), }); - // TODO: support this being a project itself. - for (const jobDetails of folder.jobs) { + const isStandaloneProject = !project.jobs; + if (isStandaloneProject) { + const standaloneProject = await client.job.get({ + name: jenkinsInfo.jobFullName, + tree: JenkinsApiImpl.jobTreeSpec.replace(/\s/g, ''), + }); + projects.push(this.augmentProject(standaloneProject)); + return projects; + } + for (const jobDetails of project.jobs) { // for each branch (we assume) - if (jobDetails?.jobs) { - // skipping folders inside folders for now - // TODO: recurse - } else { - projects.push(this.augmentProject(jobDetails)); - } + projects.push(this.augmentProject(jobDetails)); } } return projects;