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,