+ );
+};
diff --git a/plugins/cloudbuild/src/components/WorkflowRunDetails/index.ts b/plugins/cloudbuild/src/components/WorkflowRunDetails/index.ts
new file mode 100644
index 0000000000..2886a26740
--- /dev/null
+++ b/plugins/cloudbuild/src/components/WorkflowRunDetails/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+export { WorkflowRunDetails } from './WorkflowRunDetails';
diff --git a/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts b/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts
new file mode 100644
index 0000000000..2150e01d98
--- /dev/null
+++ b/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { useAsync } from 'react-use';
+import { ActionsListWorkflowRunsForRepoResponseData } from '../../api/types';
+
+export const useWorkflowRunJobs = (jobsUrl?: string) => {
+ const jobs = useAsync(async (): Promise<
+ ActionsListWorkflowRunsForRepoResponseData
+ > => {
+ if (jobsUrl === undefined) {
+ return {
+ builds: [],
+ };
+ }
+
+ const data = await fetch(jobsUrl).then(d => d.json());
+ return data;
+ }, [jobsUrl]);
+ return jobs;
+};
diff --git a/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts b/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts
new file mode 100644
index 0000000000..ae5e750d15
--- /dev/null
+++ b/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { useApi } from '@backstage/core';
+import { useParams } from 'react-router-dom';
+import { useAsync } from 'react-use';
+import { cloudbuildApiRef } from '../../api';
+
+export const useWorkflowRunsDetails = (projectId: string) => {
+ const api = useApi(cloudbuildApiRef);
+ const { id } = useParams();
+ const details = useAsync(async () => {
+ return projectId
+ ? api.getWorkflowRun({
+ projectId,
+ id: id,
+ })
+ : Promise.reject('No projectId provided');
+ }, [projectId, id]);
+ return details;
+};
diff --git a/plugins/cloudbuild/src/components/WorkflowRunStatus/WorkflowRunStatus.tsx b/plugins/cloudbuild/src/components/WorkflowRunStatus/WorkflowRunStatus.tsx
new file mode 100644
index 0000000000..45a8fc662c
--- /dev/null
+++ b/plugins/cloudbuild/src/components/WorkflowRunStatus/WorkflowRunStatus.tsx
@@ -0,0 +1,70 @@
+/*
+ * 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 {
+ StatusPending,
+ StatusRunning,
+ StatusOK,
+ StatusAborted,
+ StatusError,
+} from '@backstage/core';
+import React from 'react';
+
+export const WorkflowRunStatus = ({
+ status,
+}: {
+ status: string | undefined;
+}) => {
+ if (status === undefined) return null;
+ switch (status.toLowerCase()) {
+ case 'queued':
+ return (
+ <>
+ Queued
+ >
+ );
+ case 'working':
+ return (
+ <>
+ In progress
+ >
+ );
+ case 'success':
+ return (
+ <>
+ Completed
+ >
+ );
+ case 'cancelled':
+ return (
+ <>
+ Cancelled
+ >
+ );
+ case 'failure':
+ return (
+ <>
+ Failed
+ >
+ );
+ default:
+ return (
+ <>
+ Pending
+ >
+ );
+ }
+};
diff --git a/plugins/cloudbuild/src/components/WorkflowRunStatus/index.ts b/plugins/cloudbuild/src/components/WorkflowRunStatus/index.ts
new file mode 100644
index 0000000000..8ebca32cbd
--- /dev/null
+++ b/plugins/cloudbuild/src/components/WorkflowRunStatus/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+export { WorkflowRunStatus } from './WorkflowRunStatus';
diff --git a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx
new file mode 100644
index 0000000000..254e6f89ed
--- /dev/null
+++ b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx
@@ -0,0 +1,185 @@
+/*
+ * 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, { FC } from 'react';
+import { Link, Typography, Box, IconButton, Tooltip } from '@material-ui/core';
+import RetryIcon from '@material-ui/icons/Replay';
+import GoogleIcon from '@material-ui/icons/CloudCircle';
+import { Link as RouterLink, generatePath } from 'react-router-dom';
+import { Table, TableColumn } from '@backstage/core';
+import { useWorkflowRuns } from '../useWorkflowRuns';
+import { WorkflowRunStatus } from '../WorkflowRunStatus';
+import SyncIcon from '@material-ui/icons/Sync';
+import { useProjectName } from '../useProjectName';
+import { Entity } from '@backstage/catalog-model';
+import { Substitutions } from '../../api/types';
+import { buildRouteRef } from '../../plugin';
+import moment from 'moment';
+
+export type WorkflowRun = {
+ id: string;
+ message: string;
+ url?: string;
+ googleUrl?: string;
+ status: string;
+ substitutions: Substitutions;
+ createTime: string;
+ rerun: () => void;
+};
+
+const generatedColumns: TableColumn[] = [
+ {
+ title: 'Status',
+ width: '150px',
+
+ render: (row: Partial) => (
+
+
+
+ ),
+ },
+ {
+ title: 'Build',
+ field: 'id',
+ type: 'numeric',
+ width: '150px',
+ render: (row: Partial) => (
+
+
,
+ tooltip: 'Reload workflow runs',
+ isFreeAction: true,
+ onClick: () => retry(),
+ },
+ ]}
+ data={runs ?? []}
+ onChangePage={onChangePage}
+ onChangeRowsPerPage={onChangePageSize}
+ style={{ width: '100%' }}
+ title={
+
+
+
+ {projectName}
+
+ }
+ columns={generatedColumns}
+ />
+ );
+};
+
+export const WorkflowRunsTable = ({ entity }: { entity: Entity }) => {
+ const { value: projectName, loading } = useProjectName(entity);
+ const [projectId] = (projectName ?? '/').split('/');
+
+ const [tableProps, { retry, setPage, setPageSize }] = useWorkflowRuns({
+ projectId,
+ });
+
+ return (
+
+ );
+};
diff --git a/plugins/cloudbuild/src/components/WorkflowRunsTable/index.ts b/plugins/cloudbuild/src/components/WorkflowRunsTable/index.ts
new file mode 100644
index 0000000000..4e21cc67c2
--- /dev/null
+++ b/plugins/cloudbuild/src/components/WorkflowRunsTable/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+export { WorkflowRunsTable, WorkflowRunsTableView } from './WorkflowRunsTable';
+export type { WorkflowRun } from './WorkflowRunsTable';
diff --git a/plugins/cloudbuild/src/components/useProjectName.ts b/plugins/cloudbuild/src/components/useProjectName.ts
new file mode 100644
index 0000000000..0c227f690d
--- /dev/null
+++ b/plugins/cloudbuild/src/components/useProjectName.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 { useAsync } from 'react-use';
+import { Entity } from '@backstage/catalog-model';
+
+export const CLOUDBUILD_ANNOTATION = 'google.com/cloudbuild-project-slug';
+
+export const useProjectName = (entity: Entity) => {
+ const { value, loading, error } = useAsync(async () => {
+ return entity?.metadata.annotations?.[CLOUDBUILD_ANNOTATION] ?? '';
+ });
+ return { value, loading, error };
+};
diff --git a/plugins/cloudbuild/src/components/useWorkflowRuns.ts b/plugins/cloudbuild/src/components/useWorkflowRuns.ts
new file mode 100644
index 0000000000..b6d6c2c620
--- /dev/null
+++ b/plugins/cloudbuild/src/components/useWorkflowRuns.ts
@@ -0,0 +1,91 @@
+/*
+ * 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 { useState } from 'react';
+import { useAsyncRetry } from 'react-use';
+import { WorkflowRun } from './WorkflowRunsTable/WorkflowRunsTable';
+import { cloudbuildApiRef } from '../api/CloudbuildApi';
+import { useApi, errorApiRef } from '@backstage/core';
+import { ActionsListWorkflowRunsForRepoResponseData } from '../api/types';
+
+export function useWorkflowRuns({ projectId }: { projectId: string }) {
+ const api = useApi(cloudbuildApiRef);
+ const errorApi = useApi(errorApiRef);
+
+ const [total, setTotal] = useState(0);
+ const [page, setPage] = useState(0);
+ const [pageSize, setPageSize] = useState(5);
+
+ const { loading, value: runs, retry, error } = useAsyncRetry<
+ WorkflowRun[]
+ >(async () => {
+ return api
+ .listWorkflowRuns({
+ projectId,
+ })
+ .then(
+ (
+ workflowRunsData: ActionsListWorkflowRunsForRepoResponseData,
+ ): WorkflowRun[] => {
+ setTotal(workflowRunsData.builds.length);
+ // Transformation here
+ return workflowRunsData.builds.map(run => ({
+ message: run.substitutions.REPO_NAME,
+ id: run.id,
+ rerun: async () => {
+ try {
+ await api.reRunWorkflow({
+ projectId,
+ runId: run.id,
+ });
+ } catch (e) {
+ errorApi.post(e);
+ }
+ },
+ substitutions: run.substitutions,
+ source: {
+ branchName: run.substitutions.REPO_NAME,
+ commit: {
+ hash: run.substitutions.COMMIT_SHA,
+ url: run.substitutions.REPO_NAME,
+ },
+ },
+ status: run.status,
+ url: run.logUrl,
+ googleUrl: run.logUrl,
+ createTime: run.createTime,
+ }));
+ },
+ );
+ }, [page, pageSize, projectId]);
+
+ return [
+ {
+ page,
+ pageSize,
+ loading,
+ runs,
+ projectName: `${projectId}`,
+ total,
+ error,
+ },
+ {
+ runs,
+ setPage,
+ setPageSize,
+ retry,
+ },
+ ] as const;
+}
diff --git a/plugins/cloudbuild/src/index.ts b/plugins/cloudbuild/src/index.ts
new file mode 100644
index 0000000000..616bb6b4e7
--- /dev/null
+++ b/plugins/cloudbuild/src/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+export { plugin } from './plugin';
+export * from './api';
+export { Router, isPluginApplicableToEntity } from './components/Router';
+export * from './components/Cards';
+export { CLOUDBUILD_ANNOTATION } from './components/useProjectName';
diff --git a/plugins/cloudbuild/src/plugin.test.ts b/plugins/cloudbuild/src/plugin.test.ts
new file mode 100644
index 0000000000..dae16f1d1b
--- /dev/null
+++ b/plugins/cloudbuild/src/plugin.test.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 { plugin } from './plugin';
+
+describe('cloudbuild', () => {
+ it('should export plugin', () => {
+ expect(plugin).toBeDefined();
+ });
+});
diff --git a/plugins/cloudbuild/src/plugin.ts b/plugins/cloudbuild/src/plugin.ts
new file mode 100644
index 0000000000..ee997cc7ec
--- /dev/null
+++ b/plugins/cloudbuild/src/plugin.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 {
+ createPlugin,
+ createRouteRef,
+ createApiFactory,
+ googleAuthApiRef,
+} from '@backstage/core';
+import { cloudbuildApiRef, CloudbuildClient } from './api';
+
+export const rootRouteRef = createRouteRef({
+ path: '',
+ title: 'Google Cloudbuild',
+});
+
+export const buildRouteRef = createRouteRef({
+ path: ':id',
+ title: 'Cloudbuild Run',
+});
+
+export const plugin = createPlugin({
+ id: 'cloudbuild',
+ apis: [
+ createApiFactory({
+ api: cloudbuildApiRef,
+ deps: { googleAuthApi: googleAuthApiRef },
+ factory({ googleAuthApi }) {
+ return new CloudbuildClient(googleAuthApi);
+ },
+ }),
+ ],
+});
diff --git a/plugins/cloudbuild/src/setupTests.ts b/plugins/cloudbuild/src/setupTests.ts
new file mode 100644
index 0000000000..4b4cdbdaaf
--- /dev/null
+++ b/plugins/cloudbuild/src/setupTests.ts
@@ -0,0 +1,18 @@
+/*
+ * 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 '@testing-library/jest-dom';
+
+require('jest-fetch-mock').enableMocks();