diff --git a/plugins/jenkins-backend/src/service/router.ts b/plugins/jenkins-backend/src/service/router.ts index 95bbde4bb3..6ba7f9e574 100644 --- a/plugins/jenkins-backend/src/service/router.ts +++ b/plugins/jenkins-backend/src/service/router.ts @@ -52,8 +52,10 @@ const jobTreeSpec = `actions[*], ${lastBuildTreeSpec} jobs{0,1}, name, + fullName, displayName, - fullDisplayName`; + fullDisplayName, + inQueue`; const jobsTreeSpec = `jobs[ ${jobTreeSpec} @@ -67,8 +69,7 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - // @ts-ignore keeping unused logger for future use - const { logger, jenkinsInfoProvider } = options; + const { jenkinsInfoProvider } = options; const router = Router(); router.use(express.json()); @@ -169,6 +170,34 @@ export async function createRouter( }, ); + router.post( + '/v1/entity/:namespace/:kind/:name/job/:jobName/:buildNumber::rebuild', + async (request, response) => { + const { namespace, kind, name, jobName } = request.params; + + const jenkinsInfo = await jenkinsInfoProvider.getInstance({ + entityRef: { + kind, + namespace, + name, + }, + jobName, + }); + + const client = await getClient(jenkinsInfo); + + // looks like the current SDK only supports triggering a new build + // can't see any support for replay (re-running the specific build with the same SCM info) + + // Note Jenkins itself has concepts of rebuild and replay on a job. + // The latter should be possible to trigger with a POST to /replay/rebuild + await client.job.build(jobName); + + // TODO: return the buildNumber which was started. + response.send({}); + }, + ); + router.use(errorHandler()); return router; } diff --git a/plugins/jenkins/src/api/JenkinsApi.ts b/plugins/jenkins/src/api/JenkinsApi.ts index a816cc3afd..68f712b09b 100644 --- a/plugins/jenkins/src/api/JenkinsApi.ts +++ b/plugins/jenkins/src/api/JenkinsApi.ts @@ -62,7 +62,7 @@ export interface Project { inQueue: string; // added by us status: string; // == inQueue ? 'queued' : lastBuild.building ? 'running' : lastBuild.result, - onRestartClick: () => void; // TODO handle.* ? + onRestartClick: () => Promise; // TODO rename to handle.* ? also, should this be on lastBuild? } export interface JenkinsApi { @@ -98,7 +98,11 @@ export interface JenkinsApi { buildNumber: string, ): Promise; - retry(entity: EntityName, jobName: string, buildNumber: string): Promise; + retry( + entity: EntityName, + jobName: string, + buildNumber: string, + ): Promise; } export class JenkinsApiImpl implements JenkinsApi { @@ -135,7 +139,14 @@ export class JenkinsApiImpl implements JenkinsApi { }, }); - return (await response.json()).projects; + return ( + (await response.json()).projects?.map((p: Project) => ({ + ...p, + onRestartClick: async () => { + await this.retry(entity, p.fullName, String(p.lastBuild.number)); + }, + })) || [] + ); } async getBuild( @@ -160,14 +171,23 @@ export class JenkinsApiImpl implements JenkinsApi { return (await response.json()).build; } - retry( - // @ts-ignore unused because unimplemented + async retry( entity: EntityName, - // @ts-ignore unused because unimplemented jobName: string, - // @ts-ignore unused because unimplemented buildNumber: string, - ): Promise { - return Promise.resolve(undefined); + ): Promise { + const url = `${await this.discoveryApi.getBaseUrl('jenkins')}/v1/entity/${ + entity.namespace + }/${entity.kind}/${entity.name}/job/${encodeURIComponent( + jobName, + )}/${buildNumber}:rebuild`; + + const idToken = await this.identityApi.getIdToken(); + await fetch(url, { + method: 'POST', + headers: { + ...(idToken && { Authorization: `Bearer ${idToken}` }), + }, + }); } } diff --git a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index 0b466f1f2e..9d8a4c11e4 100644 --- a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -48,6 +48,7 @@ const useStyles = makeStyles(theme => ({ })); const BuildWithStepsView = () => { + // TODO: Add a test that react-router decodes this (even though `generatePath` doesn't encode it for you!) const { jobName, buildNumber } = useRouteRefParams(buildRouteRef); const classes = useStyles(); diff --git a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx index f4c3e26e92..7c1e7db8b1 100644 --- a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx +++ b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx @@ -100,11 +100,11 @@ const generatedColumns: TableColumn[] = [ - {row.fullName} + {row.fullDisplayName} ); }, diff --git a/plugins/jenkins/src/components/useBuildWithSteps.ts b/plugins/jenkins/src/components/useBuildWithSteps.ts index cf2719d1e8..82f3634941 100644 --- a/plugins/jenkins/src/components/useBuildWithSteps.ts +++ b/plugins/jenkins/src/components/useBuildWithSteps.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { useAsyncRetry } from 'react-use'; import { jenkinsApiRef } from '../api'; import { useAsyncPolling } from './useAsyncPolling'; @@ -32,7 +32,7 @@ export function useBuildWithSteps(jobName: string, buildNumber: string) { const api = useApi(jenkinsApiRef); const errorApi = useApi(errorApiRef); const { entity } = useEntity(); - const entityName = getEntityName(entity); + const entityName = useMemo(() => getEntityName(entity), [entity]); const getBuildWithSteps = useCallback(async () => { try { @@ -43,14 +43,6 @@ export function useBuildWithSteps(jobName: string, buildNumber: string) { } }, [buildNumber, jobName, entityName, api, errorApi]); - const restartBuild = async () => { - try { - await api.retry(entityName, jobName, buildNumber); - } catch (e) { - errorApi.post(e); - } - }; - const { loading, value, retry } = useAsyncRetry(() => getBuildWithSteps(), [ getBuildWithSteps, ]); @@ -63,7 +55,6 @@ export function useBuildWithSteps(jobName: string, buildNumber: string) { return [ { loading, value, retry }, { - restartBuild, getBuildWithSteps, startPolling, stopPolling,