From 90db93f223765858e37a74ca8a7d7ea0adab5dff Mon Sep 17 00:00:00 2001 From: Alexander Kaserbacher Date: Sat, 15 May 2021 13:08:31 +0200 Subject: [PATCH 1/5] do not post to errorApi in case getLastBuild or getFolder API call threw an error we do not want to show an error popup for the user here but instead move the error to the card Signed-off-by: Alexander Kaserbacher --- plugins/jenkins/src/components/useBuilds.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/jenkins/src/components/useBuilds.ts b/plugins/jenkins/src/components/useBuilds.ts index 5a6f8b9f08..e818d3f5a6 100644 --- a/plugins/jenkins/src/components/useBuilds.ts +++ b/plugins/jenkins/src/components/useBuilds.ts @@ -48,7 +48,6 @@ export function useBuilds(projectName: string, branch?: string) { return build || []; } catch (e) { - errorApi.post(e); throw e; } }, [api, errorApi, projectName, branch]); From 93068f04e7ce4505b7b88d765a58eac1ba29979f Mon Sep 17 00:00:00 2001 From: Alexander Kaserbacher Date: Sun, 16 May 2021 11:59:48 +0200 Subject: [PATCH 2/5] capture API errors in Jenkins card and show WarningPanel Signed-off-by: Alexander Kaserbacher --- plugins/jenkins/src/components/Cards/Cards.tsx | 13 +++++++++++-- plugins/jenkins/src/components/useBuilds.ts | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/jenkins/src/components/Cards/Cards.tsx b/plugins/jenkins/src/components/Cards/Cards.tsx index f19ed608ce..31df25820e 100644 --- a/plugins/jenkins/src/components/Cards/Cards.tsx +++ b/plugins/jenkins/src/components/Cards/Cards.tsx @@ -17,6 +17,7 @@ import { InfoCard, InfoCardVariants, StructuredMetadataTable, + WarningPanel, } from '@backstage/core'; import { LinearProgress, Link, makeStyles, Theme } from '@material-ui/core'; import ExternalLinkIcon from '@material-ui/icons/Launch'; @@ -83,11 +84,19 @@ 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 e818d3f5a6..f119da0838 100644 --- a/plugins/jenkins/src/components/useBuilds.ts +++ b/plugins/jenkins/src/components/useBuilds.ts @@ -25,6 +25,7 @@ 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(); const restartBuild = async (buildName: string) => { try { @@ -48,6 +49,7 @@ export function useBuilds(projectName: string, branch?: string) { return build || []; } catch (e) { + setError(e); throw e; } }, [api, errorApi, projectName, branch]); @@ -60,6 +62,7 @@ export function useBuilds(projectName: string, branch?: string) { builds, projectName, total, + error, }, { builds, From 8b13d2e0e318452964c83cf726c5bc473cb561f5 Mon Sep 17 00:00:00 2001 From: Alexander Kaserbacher Date: Wed, 19 May 2021 21:06:27 +0200 Subject: [PATCH 3/5] Show title in WarningPanel depending on the error returned by the Jenkins API Signed-off-by: Alexander Kaserbacher --- .../jenkins/src/components/Cards/Cards.tsx | 24 +++++++++++++++++-- plugins/jenkins/src/components/useBuilds.ts | 15 ++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/plugins/jenkins/src/components/Cards/Cards.tsx b/plugins/jenkins/src/components/Cards/Cards.tsx index 31df25820e..010afa8990 100644 --- a/plugins/jenkins/src/components/Cards/Cards.tsx +++ b/plugins/jenkins/src/components/Cards/Cards.tsx @@ -24,7 +24,7 @@ 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({ @@ -76,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, @@ -95,7 +112,10 @@ export const LatestRunCard = ({ latestRun={latestRun} /> ) : ( - + )} ); diff --git a/plugins/jenkins/src/components/useBuilds.ts b/plugins/jenkins/src/components/useBuilds.ts index f119da0838..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,7 +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(); + const [error, setError] = useState<{ + message: string; + errorType: ErrorType; + }>(); const restartBuild = async (buildName: string) => { try { @@ -49,7 +57,10 @@ export function useBuilds(projectName: string, branch?: string) { return build || []; } catch (e) { - setError(e); + const errorType = e.notFound + ? ErrorType.NOT_FOUND + : ErrorType.CONNECTION_ERROR; + setError({ message: e.message, errorType }); throw e; } }, [api, errorApi, projectName, branch]); From 6e829c9a4e2a56d87d3a15817323e13a914b6055 Mon Sep 17 00:00:00 2001 From: Alexander Kaserbacher Date: Sat, 22 May 2021 10:23:36 +0200 Subject: [PATCH 4/5] Add tests for WarningPanel Signed-off-by: Alexander Kaserbacher --- .../src/components/Cards/Cards.test.tsx | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 plugins/jenkins/src/components/Cards/Cards.test.tsx 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(); + }); +}); From 5baf2ff0f9b7bb569819b8e581d077190c4ed279 Mon Sep 17 00:00:00 2001 From: Alexander Kaserbacher Date: Sat, 22 May 2021 10:41:31 +0200 Subject: [PATCH 5/5] Add changeset Signed-off-by: Alexander Kaserbacher --- .changeset/smooth-wolves-sort.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/smooth-wolves-sort.md 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