diff --git a/.changeset/smooth-wolves-sort.md b/.changeset/smooth-wolves-sort.md new file mode 100644 index 0000000000..f196927073 --- /dev/null +++ b/.changeset/smooth-wolves-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins': patch +--- + +Show error in Jenkins card for errors exposed by the Jenkins API diff --git a/plugins/jenkins/src/components/Cards/Cards.test.tsx b/plugins/jenkins/src/components/Cards/Cards.test.tsx new file mode 100644 index 0000000000..67996f4dde --- /dev/null +++ b/plugins/jenkins/src/components/Cards/Cards.test.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { LatestRunCard } from './Cards'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { JenkinsApi, jenkinsApiRef } from '../../api'; + +describe('', () => { + const entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'software', + description: 'This is the description', + annotations: { JENKINS_ANNOTATION: 'jenkins' }, + }, + }; + + const jenkinsApi: Partial = { + getLastBuild: () => Promise.resolve({ timestamp: 0, result: 'success' }), + }; + + it('should show success status of latest build', async () => { + const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApi]]); + + const { getByText } = await renderInTestApp( + + + + + , + ); + + expect(getByText('Completed')).toBeInTheDocument(); + }); + + it('should show the appropriate error in case of a connection error', async () => { + const jenkinsApiWithError: Partial = { + getLastBuild: () => Promise.reject(new Error('Unauthorized')), + }; + + const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApiWithError]]); + + const { getByText } = await renderInTestApp( + + + + + , + ); + + expect(getByText("Error: Can't connect to Jenkins")).toBeInTheDocument(); + expect(getByText('Unauthorized')).toBeInTheDocument(); + }); + + it('should show the appropriate error in case Jenkins project is not found', async () => { + const jenkinsApiWithError: Partial = { + getLastBuild: () => + Promise.reject({ + notFound: true, + message: 'jenkins-project not found', + }), + }; + + const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApiWithError]]); + + const { getByText } = await renderInTestApp( + + + + + , + ); + + expect(getByText("Error: Can't find Jenkins project")).toBeInTheDocument(); + expect(getByText('jenkins-project not found')).toBeInTheDocument(); + }); +}); diff --git a/plugins/jenkins/src/components/Cards/Cards.tsx b/plugins/jenkins/src/components/Cards/Cards.tsx index f19ed608ce..010afa8990 100644 --- a/plugins/jenkins/src/components/Cards/Cards.tsx +++ b/plugins/jenkins/src/components/Cards/Cards.tsx @@ -17,13 +17,14 @@ import { InfoCard, InfoCardVariants, StructuredMetadataTable, + WarningPanel, } from '@backstage/core'; import { LinearProgress, Link, makeStyles, Theme } from '@material-ui/core'; import ExternalLinkIcon from '@material-ui/icons/Launch'; import { DateTime, Duration } from 'luxon'; import React from 'react'; import { JenkinsRunStatus } from '../BuildsPage/lib/Status'; -import { useBuilds } from '../useBuilds'; +import { ErrorType, useBuilds } from '../useBuilds'; import { useProjectSlugFromEntity } from '../useProjectSlugFromEntity'; const useStyles = makeStyles({ @@ -75,6 +76,23 @@ const WidgetContent = ({ ); }; +const JenkinsApiErrorPanel = ({ + message, + errorType, +}: { + message: string; + errorType: ErrorType; +}) => { + let title = undefined; + if (errorType === ErrorType.CONNECTION_ERROR) { + title = "Can't connect to Jenkins"; + } else if (errorType === ErrorType.NOT_FOUND) { + title = "Can't find Jenkins project"; + } + + return ; +}; + export const LatestRunCard = ({ branch = 'master', variant, @@ -83,11 +101,22 @@ export const LatestRunCard = ({ variant?: InfoCardVariants; }) => { const projectName = useProjectSlugFromEntity(); - const [{ builds, loading }] = useBuilds(projectName, branch); + const [{ builds, loading, error }] = useBuilds(projectName, branch); const latestRun = builds ?? {}; return ( - + {!error ? ( + + ) : ( + + )} ); }; diff --git a/plugins/jenkins/src/components/useBuilds.ts b/plugins/jenkins/src/components/useBuilds.ts index 5a6f8b9f08..be573ef605 100644 --- a/plugins/jenkins/src/components/useBuilds.ts +++ b/plugins/jenkins/src/components/useBuilds.ts @@ -18,6 +18,11 @@ import { useState } from 'react'; import { useAsyncRetry } from 'react-use'; import { jenkinsApiRef } from '../api'; +export enum ErrorType { + CONNECTION_ERROR, + NOT_FOUND, +} + export function useBuilds(projectName: string, branch?: string) { const api = useApi(jenkinsApiRef); const errorApi = useApi(errorApiRef); @@ -25,6 +30,10 @@ export function useBuilds(projectName: string, branch?: string) { const [total, setTotal] = useState(0); const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(5); + const [error, setError] = useState<{ + message: string; + errorType: ErrorType; + }>(); const restartBuild = async (buildName: string) => { try { @@ -48,7 +57,10 @@ export function useBuilds(projectName: string, branch?: string) { return build || []; } catch (e) { - errorApi.post(e); + const errorType = e.notFound + ? ErrorType.NOT_FOUND + : ErrorType.CONNECTION_ERROR; + setError({ message: e.message, errorType }); throw e; } }, [api, errorApi, projectName, branch]); @@ -61,6 +73,7 @@ export function useBuilds(projectName: string, branch?: string) { builds, projectName, total, + error, }, { builds,