diff --git a/.changeset/unlucky-snakes-turn.md b/.changeset/unlucky-snakes-turn.md new file mode 100644 index 0000000000..bcb31feb40 --- /dev/null +++ b/.changeset/unlucky-snakes-turn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins-backend': patch +--- + +Fixed possible type error if jenkins response contains null values diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts index bc7d2cb029..1d72dbf71c 100644 --- a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts +++ b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts @@ -352,6 +352,216 @@ describe('JenkinsApi', () => { const result = await jenkinsApi.getProjects(jenkinsInfo); + expect(result).toHaveLength(1); + expect(result[0].lastBuild!.tests).toEqual({ + total: 635, + passed: 632, + skipped: 1, + failed: 2, + testUrl: + 'https://jenkins.example.com/job/example-jobName/job/exampleBuild/7/testReport/', + }); + }); + }); + describe('augmented with null values', () => { + const projectWithScmActionsAndNulls: JenkinsProject = { + actions: [ + {}, + {}, + null, + {}, + { + _class: 'jenkins.scm.api.metadata.ContributorMetadataAction', + contributor: 'testuser', + contributorDisplayName: 'Mr. T User', + contributorEmail: null, + }, + {}, + { + _class: 'jenkins.scm.api.metadata.ObjectMetadataAction', + objectDescription: '', + objectDisplayName: 'Add LICENSE, CoC etc', + objectUrl: 'https://github.com/backstage/backstage/pull/1', + }, + {}, + {}, + { + _class: 'com.cloudbees.plugins.credentials.ViewCredentialsAction', + stores: {}, + }, + ], + displayName: 'Example Build', + fullDisplayName: 'Example jobName » Example Build', + fullName: 'example-jobName/exampleBuild', + inQueue: false, + lastBuild: { + actions: [ + { + _class: 'hudson.model.CauseAction', + causes: [ + { + _class: 'jenkins.branch.BranchIndexingCause', + shortDescription: 'Branch indexing', + }, + ], + }, + null, + {}, + {}, + { + _class: 'org.jenkinsci.plugins.workflow.cps.EnvActionImpl', + environment: {}, + }, + {}, + {}, + {}, + {}, + {}, + { + _class: 'hudson.plugins.git.util.BuildData', + buildsByBranchName: { + 'PR-1': { + _class: 'hudson.plugins.git.util.Build', + buildNumber: 5, + buildResult: null, + marked: { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'PR-1', + }, + ], + }, + revision: { + SHA1: '6c6b34c0fb91cf077a01fe62d3e8e996b4ea5861', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'PR-1', + }, + ], + }, + }, + }, + lastBuiltRevision: { + SHA1: '6c6b34c0fb91cf077a01fe62d3e8e996b4ea5861', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'PR-1', + }, + ], + }, + remoteUrls: ['https://github.com/backstage/backstage.git'], + scmName: '', + }, + { + _class: 'hudson.plugins.git.util.BuildData', + buildsByBranchName: { + master: { + _class: 'hudson.plugins.git.util.Build', + buildNumber: 5, + buildResult: null, + marked: { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'master', + }, + ], + }, + revision: { + SHA1: '6c6b34c0fb91cf077a01fe62d3e8e996b4ea5861', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'master', + }, + ], + }, + }, + }, + lastBuiltRevision: { + SHA1: '6c6b34c0fb91cf077a01fe62d3e8e996b4ea5861', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'master', + }, + ], + }, + remoteUrls: ['https://github.com/backstage/backstage.git'], + scmName: '', + }, + {}, + {}, + { + _class: 'hudson.tasks.junit.TestResultAction', + failCount: 2, + skipCount: 1, + totalCount: 635, + urlName: 'testReport', + }, + {}, + {}, + { + _class: + 'org.jenkinsci.plugins.pipeline.modeldefinition.actions.RestartDeclarativePipelineAction', + restartEnabled: false, + restartableStages: [], + }, + {}, + ], + 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/7/', + number: 7, + }, + }; + + it('augments project', async () => { + mockedJenkinsClient.job.get.mockResolvedValueOnce({ + jobs: [projectWithScmActionsAndNulls], + }); + + const result = await jenkinsApi.getProjects(jenkinsInfo); + + expect(result).toHaveLength(1); + expect(result[0].status).toEqual('success'); + }); + it('augments build', async () => { + mockedJenkinsClient.job.get.mockResolvedValueOnce({ + jobs: [projectWithScmActionsAndNulls], + }); + + const result = await jenkinsApi.getProjects(jenkinsInfo); + + expect(result).toHaveLength(1); + // TODO: I am really just asserting the previous behaviour with no understanding here. + // In my 2 Jenkins instances, 1 returns a lot of different and confusing BuildData sections and 1 returns none ☹️ + expect(result[0].lastBuild!.source).toEqual({ + branchName: 'master', + commit: { + hash: '14d31bde', + }, + url: 'https://github.com/backstage/backstage/pull/1', + displayName: 'Add LICENSE, CoC etc', + author: 'Mr. T User', + }); + }); + it('finds test report', async () => { + mockedJenkinsClient.job.get.mockResolvedValueOnce({ + jobs: [projectWithScmActionsAndNulls], + }); + + const result = await jenkinsApi.getProjects(jenkinsInfo); + expect(result).toHaveLength(1); expect(result[0].lastBuild!.tests).toEqual({ total: 635, diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.ts b/plugins/jenkins-backend/src/service/jenkinsApi.ts index ddc2bb0ea3..38ab20b17b 100644 --- a/plugins/jenkins-backend/src/service/jenkinsApi.ts +++ b/plugins/jenkins-backend/src/service/jenkinsApi.ts @@ -211,7 +211,7 @@ export class JenkinsApiImpl { build.actions .filter( (action: any) => - action._class === 'hudson.plugins.git.util.BuildData', + action?._class === 'hudson.plugins.git.util.BuildData', ) .map((action: any) => { const [first]: any = Object.values(action.buildsByBranchName); @@ -253,7 +253,7 @@ export class JenkinsApiImpl { const scmInfo: ScmDetails | undefined = project.actions .filter( (action: any) => - action._class === 'jenkins.scm.api.metadata.ObjectMetadataAction', + action?._class === 'jenkins.scm.api.metadata.ObjectMetadataAction', ) .map((action: any) => { return { @@ -272,7 +272,7 @@ export class JenkinsApiImpl { const author = project.actions .filter( (action: any) => - action._class === + action?._class === 'jenkins.scm.api.metadata.ContributorMetadataAction', ) .map((action: any) => { @@ -297,7 +297,7 @@ export class JenkinsApiImpl { return build.actions .filter( (action: any) => - action._class === 'hudson.tasks.junit.TestResultAction', + action?._class === 'hudson.tasks.junit.TestResultAction', ) .map((action: any) => { return {