Merge pull request #2515 from ebarriosjr/cloudbuild-plugin
Cloudbuild plugin
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
* @spotify/backstage-core
|
||||
/docs/features/techdocs @spotify/techdocs-core
|
||||
/plugins/cost-insights @spotify/silver-lining
|
||||
/plugins/cloudbuild @trivago/ebarrios
|
||||
/plugins/techdocs @spotify/techdocs-core
|
||||
/plugins/techdocs-backend @spotify/techdocs-core
|
||||
/packages/techdocs-cli @spotify/techdocs-core
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"@backstage/plugin-api-docs": "^0.1.1-alpha.23",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.23",
|
||||
"@backstage/plugin-circleci": "^0.1.1-alpha.23",
|
||||
"@backstage/plugin-cloudbuild": "^0.1.1-alpha.23",
|
||||
"@backstage/plugin-cost-insights": "^0.1.1-alpha.23",
|
||||
"@backstage/plugin-explore": "^0.1.1-alpha.23",
|
||||
"@backstage/plugin-gcp-projects": "^0.1.1-alpha.23",
|
||||
|
||||
@@ -24,9 +24,13 @@ import {
|
||||
Router as GitHubActionsRouter,
|
||||
} from '@backstage/plugin-github-actions';
|
||||
import {
|
||||
Router as CloudbuildRouter,
|
||||
isPluginApplicableToEntity as isCloudbuildAvailable,
|
||||
} from '@backstage/plugin-cloudbuild';
|
||||
import {
|
||||
Router as JenkinsRouter,
|
||||
isPluginApplicableToEntity as isJenkinsAvailable,
|
||||
LatestRunCard as JenkinsLatestRunCard,
|
||||
Router as JenkinsRouter,
|
||||
} from '@backstage/plugin-jenkins';
|
||||
import {
|
||||
isPluginApplicableToEntity as isCircleCIAvailable,
|
||||
@@ -56,6 +60,8 @@ const CICDSwitcher = ({ entity }: { entity: Entity }) => {
|
||||
return <GitHubActionsRouter entity={entity} />;
|
||||
case isCircleCIAvailable(entity):
|
||||
return <CircleCIRouter entity={entity} />;
|
||||
case isCloudbuildAvailable(entity):
|
||||
return <CloudbuildRouter entity={entity} />;
|
||||
case isTravisCIAvailable(entity):
|
||||
return <TravisCIRouter entity={entity} />;
|
||||
default:
|
||||
|
||||
@@ -34,4 +34,5 @@ export { plugin as ApiDocs } from '@backstage/plugin-api-docs';
|
||||
export { plugin as GithubPullRequests } from '@roadiehq/backstage-plugin-github-pull-requests';
|
||||
export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects';
|
||||
export { plugin as Kubernetes } from '@backstage/plugin-kubernetes';
|
||||
export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild';
|
||||
export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
# Google Cloud Build
|
||||
|
||||
Welcome to the Google Cloud Build plugin!
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
|
||||
## Getting started
|
||||
|
||||
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/cloudbuild](http://localhost:3000/cloudbuild).
|
||||
|
||||
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
|
||||
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
|
||||
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(plugin).render();
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@backstage/plugin-cloudbuild",
|
||||
"version": "0.1.1-alpha.23",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"start": "backstage-cli plugin:serve",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"diff": "backstage-cli plugin:diff",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.23",
|
||||
"@backstage/core": "^0.1.1-alpha.23",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.23",
|
||||
"@backstage/theme": "^0.1.1-alpha.23",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@octokit/rest": "^18.0.0",
|
||||
"@octokit/types": "^5.4.1",
|
||||
"moment": "^2.27.0",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-lazylog": "^4.5.3",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.23",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.23",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^12.0.0",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"msw": "^0.20.5",
|
||||
"node-fetch": "^2.6.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 { createApiRef } from '@backstage/core';
|
||||
import {
|
||||
ActionsListWorkflowRunsForRepoResponseData,
|
||||
ActionsGetWorkflowResponseData,
|
||||
} from '../api/types';
|
||||
|
||||
export const cloudbuildApiRef = createApiRef<CloudbuildApi>({
|
||||
id: 'plugin.cloudbuild.service',
|
||||
description: 'Used by the Cloudbuild plugin to make requests',
|
||||
});
|
||||
|
||||
export type CloudbuildApi = {
|
||||
listWorkflowRuns: (request: {
|
||||
projectId: string;
|
||||
}) => Promise<ActionsListWorkflowRunsForRepoResponseData>;
|
||||
getWorkflow: ({
|
||||
projectId,
|
||||
id,
|
||||
}: {
|
||||
projectId: string;
|
||||
id: string;
|
||||
}) => Promise<ActionsGetWorkflowResponseData>;
|
||||
getWorkflowRun: ({
|
||||
projectId,
|
||||
id,
|
||||
}: {
|
||||
projectId: string;
|
||||
id: string;
|
||||
}) => Promise<ActionsGetWorkflowResponseData>;
|
||||
reRunWorkflow: ({
|
||||
projectId,
|
||||
runId,
|
||||
}: {
|
||||
projectId: string;
|
||||
runId: string;
|
||||
}) => Promise<any>;
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 { CloudbuildApi } from './CloudbuildApi';
|
||||
import {
|
||||
ActionsListWorkflowRunsForRepoResponseData,
|
||||
ActionsGetWorkflowResponseData,
|
||||
} from '../api/types';
|
||||
import { OAuthApi } from '@backstage/core';
|
||||
|
||||
export class CloudbuildClient implements CloudbuildApi {
|
||||
constructor(private readonly googleAuthApi: OAuthApi) {}
|
||||
|
||||
async reRunWorkflow({
|
||||
projectId,
|
||||
runId,
|
||||
}: {
|
||||
projectId: string;
|
||||
runId: string;
|
||||
}): Promise<void> {
|
||||
await fetch(
|
||||
`https://cloudbuild.googleapis.com/v1/projects/${encodeURIComponent(
|
||||
projectId,
|
||||
)}/builds/${encodeURIComponent(runId)}:retry`,
|
||||
{
|
||||
headers: new Headers({
|
||||
Accept: '*/*',
|
||||
Authorization: `Bearer ${await this.getToken()}`,
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
async listWorkflowRuns({
|
||||
projectId,
|
||||
}: {
|
||||
projectId: string;
|
||||
}): Promise<ActionsListWorkflowRunsForRepoResponseData> {
|
||||
const workflowRuns = await fetch(
|
||||
`https://cloudbuild.googleapis.com/v1/projects/${encodeURIComponent(
|
||||
projectId,
|
||||
)}/builds`,
|
||||
{
|
||||
headers: new Headers({
|
||||
Accept: '*/*',
|
||||
Authorization: `Bearer ${await this.getToken()}`,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const builds: ActionsListWorkflowRunsForRepoResponseData = await workflowRuns.json();
|
||||
|
||||
return builds;
|
||||
}
|
||||
async getWorkflow({
|
||||
projectId,
|
||||
id,
|
||||
}: {
|
||||
projectId: string;
|
||||
id: string;
|
||||
}): Promise<ActionsGetWorkflowResponseData> {
|
||||
const workflow = await fetch(
|
||||
`https://cloudbuild.googleapis.com/v1/projects/${encodeURIComponent(
|
||||
projectId,
|
||||
)}/builds/${encodeURIComponent(id)}`,
|
||||
{
|
||||
headers: new Headers({
|
||||
Accept: '*/*',
|
||||
Authorization: `Bearer ${await this.getToken()}`,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const build: ActionsGetWorkflowResponseData = await workflow.json();
|
||||
|
||||
return build;
|
||||
}
|
||||
async getWorkflowRun({
|
||||
projectId,
|
||||
id,
|
||||
}: {
|
||||
projectId: string;
|
||||
id: string;
|
||||
}): Promise<ActionsGetWorkflowResponseData> {
|
||||
const workflow = await fetch(
|
||||
`https://cloudbuild.googleapis.com/v1/projects/${encodeURIComponent(
|
||||
projectId,
|
||||
)}/builds/${encodeURIComponent(id)}`,
|
||||
{
|
||||
headers: new Headers({
|
||||
Accept: '*/*',
|
||||
Authorization: `Bearer ${await this.getToken()}`,
|
||||
}),
|
||||
},
|
||||
);
|
||||
const build: ActionsGetWorkflowResponseData = await workflow.json();
|
||||
|
||||
return build;
|
||||
}
|
||||
|
||||
async getToken(): Promise<string> {
|
||||
// NOTE(freben - gcp-projects): There's a .read-only variant of this scope that we could
|
||||
// use for readonly operations, but that means we would ask the user for a
|
||||
// second auth during creation and I decided to keep the wider scope for
|
||||
// all ops for now
|
||||
return this.googleAuthApi.getAccessToken(
|
||||
'https://www.googleapis.com/auth/cloud-platform',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 * from './CloudbuildApi';
|
||||
export * from './CloudbuildClient';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 interface ActionsListWorkflowRunsForRepoResponseData {
|
||||
builds: ActionsGetWorkflowResponseData[];
|
||||
}
|
||||
|
||||
export type ActionsGetWorkflowResponseData = {
|
||||
id: string;
|
||||
status: string;
|
||||
source: Source;
|
||||
createTime: string;
|
||||
startTime: string;
|
||||
steps: Step[];
|
||||
timeout: string;
|
||||
projectId: string;
|
||||
logsBucket: string;
|
||||
sourceProvenance: SourceProvenance;
|
||||
buildTriggerId: string;
|
||||
options: Options;
|
||||
logUrl: string;
|
||||
substitutions: Substitutions;
|
||||
tags: string[];
|
||||
queueTtl: string;
|
||||
name: string;
|
||||
finishTime: any;
|
||||
results: Results;
|
||||
timing: Timing2;
|
||||
};
|
||||
|
||||
export interface Step {
|
||||
name: string;
|
||||
args: string[];
|
||||
id: string;
|
||||
waitFor: string[];
|
||||
entrypoint: string;
|
||||
volumes: Volume[];
|
||||
dir: string;
|
||||
timing: Timing;
|
||||
status: string;
|
||||
pullTiming: PullTiming;
|
||||
}
|
||||
|
||||
export interface Timing2 {
|
||||
BUILD: BUILD;
|
||||
FETCHSOURCE: FETCHSOURCE;
|
||||
}
|
||||
|
||||
export interface SourceProvenance {
|
||||
resolvedStorageSource: {};
|
||||
fileHashes: {};
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
machineType: string;
|
||||
substitutionOption: string;
|
||||
logging: string;
|
||||
dynamicSubstitutions: boolean;
|
||||
}
|
||||
|
||||
export interface Substitutions {
|
||||
COMMIT_SHA: string;
|
||||
SHORT_SHA: string;
|
||||
BRANCH_NAME: string;
|
||||
REPO_NAME: string;
|
||||
REVISION_ID: string;
|
||||
}
|
||||
|
||||
export interface Results {
|
||||
buildStepImages: string[];
|
||||
buildStepOutputs: string[];
|
||||
}
|
||||
|
||||
export interface BUILD {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
}
|
||||
|
||||
export interface FETCHSOURCE {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
}
|
||||
|
||||
export interface StorageSource {
|
||||
bucket: string;
|
||||
object: string;
|
||||
}
|
||||
|
||||
export interface Source {
|
||||
storageSource: StorageSource;
|
||||
}
|
||||
|
||||
export interface Volume {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface Timing {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
}
|
||||
|
||||
export interface PullTiming {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
}
|
||||
|
||||
export interface ResolvedStorageSource {
|
||||
bucket: string;
|
||||
object: string;
|
||||
generation: string;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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, { useEffect } from 'react';
|
||||
import { useWorkflowRuns } from '../useWorkflowRuns';
|
||||
import { WorkflowRun, WorkflowRunsTable } from '../WorkflowRunsTable';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { WorkflowRunStatus } from '../WorkflowRunStatus';
|
||||
import { Link, Theme, makeStyles, LinearProgress } from '@material-ui/core';
|
||||
import {
|
||||
InfoCard,
|
||||
StructuredMetadataTable,
|
||||
errorApiRef,
|
||||
useApi,
|
||||
WarningPanel,
|
||||
} from '@backstage/core';
|
||||
import ExternalLinkIcon from '@material-ui/icons/Launch';
|
||||
import { CLOUDBUILD_ANNOTATION } from '../useProjectName';
|
||||
|
||||
const useStyles = makeStyles<Theme>({
|
||||
externalLinkIcon: {
|
||||
fontSize: 'inherit',
|
||||
verticalAlign: 'bottom',
|
||||
},
|
||||
});
|
||||
|
||||
const WidgetContent = ({
|
||||
error,
|
||||
loading,
|
||||
lastRun,
|
||||
branch,
|
||||
}: {
|
||||
error?: Error;
|
||||
loading?: boolean;
|
||||
lastRun: WorkflowRun;
|
||||
branch: string;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
if (error)
|
||||
return <WarningPanel>Couldn't fetch latest {branch} run</WarningPanel>;
|
||||
if (loading) return <LinearProgress />;
|
||||
return (
|
||||
<StructuredMetadataTable
|
||||
metadata={{
|
||||
status: (
|
||||
<>
|
||||
<WorkflowRunStatus status={lastRun.status} />
|
||||
</>
|
||||
),
|
||||
message: lastRun.message,
|
||||
url: (
|
||||
<Link href={lastRun.googleUrl} target="_blank">
|
||||
See more on Google{' '}
|
||||
<ExternalLinkIcon className={classes.externalLinkIcon} />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const LatestWorkflowRunCard = ({
|
||||
entity,
|
||||
branch = 'master',
|
||||
}: {
|
||||
entity: Entity;
|
||||
branch: string;
|
||||
}) => {
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const projectId = entity?.metadata.annotations?.[CLOUDBUILD_ANNOTATION] || '';
|
||||
|
||||
const [{ runs, loading, error }] = useWorkflowRuns({
|
||||
projectId,
|
||||
});
|
||||
const lastRun = runs?.[0] ?? ({} as WorkflowRun);
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
errorApi.post(error);
|
||||
}
|
||||
}, [error, errorApi]);
|
||||
|
||||
return (
|
||||
<InfoCard title={`Last ${branch} build`}>
|
||||
<WidgetContent
|
||||
error={error}
|
||||
loading={loading}
|
||||
branch={branch}
|
||||
lastRun={lastRun}
|
||||
/>
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
|
||||
export const LatestWorkflowsForBranchCard = ({
|
||||
entity,
|
||||
branch = 'master',
|
||||
}: {
|
||||
entity: Entity;
|
||||
branch: string;
|
||||
}) => (
|
||||
<InfoCard title={`Last ${branch} build`}>
|
||||
<WorkflowRunsTable entity={entity} />
|
||||
</InfoCard>
|
||||
);
|
||||
@@ -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 { LatestWorkflowRunCard, LatestWorkflowsForBranchCard } from './Cards';
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { Routes, Route } from 'react-router';
|
||||
import { rootRouteRef, buildRouteRef } from '../plugin';
|
||||
import { WorkflowRunDetails } from './WorkflowRunDetails';
|
||||
import { WorkflowRunsTable } from './WorkflowRunsTable';
|
||||
import { CLOUDBUILD_ANNOTATION } from './useProjectName';
|
||||
import { WarningPanel } from '@backstage/core';
|
||||
|
||||
export const isPluginApplicableToEntity = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[CLOUDBUILD_ANNOTATION]);
|
||||
|
||||
export const Router = ({ entity }: { entity: Entity }) =>
|
||||
// TODO(shmidt-i): move warning to a separate standardized component
|
||||
!isPluginApplicableToEntity(entity) ? (
|
||||
<WarningPanel title="Cloudbuild plugin:">
|
||||
<pre>{CLOUDBUILD_ANNOTATION}</pre> annotation is missing on the entity.
|
||||
</WarningPanel>
|
||||
) : (
|
||||
<Routes>
|
||||
<Route
|
||||
path={`/${rootRouteRef.path}`}
|
||||
element={<WorkflowRunsTable entity={entity} />}
|
||||
/>
|
||||
<Route
|
||||
path={`/${buildRouteRef.path}`}
|
||||
element={<WorkflowRunDetails entity={entity} />}
|
||||
/>
|
||||
)
|
||||
</Routes>
|
||||
);
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { Link, WarningPanel } from '@backstage/core';
|
||||
import {
|
||||
Breadcrumbs,
|
||||
LinearProgress,
|
||||
Link as MaterialLink,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import ExternalLinkIcon from '@material-ui/icons/Launch';
|
||||
import qs from 'qs';
|
||||
import React from 'react';
|
||||
import { useProjectName } from '../useProjectName';
|
||||
import { WorkflowRunStatus } from '../WorkflowRunStatus';
|
||||
import { useWorkflowRunsDetails } from './useWorkflowRunsDetails';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
root: {
|
||||
maxWidth: 720,
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
title: {
|
||||
padding: theme.spacing(1, 0, 2, 0),
|
||||
},
|
||||
table: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
accordionDetails: {
|
||||
padding: 0,
|
||||
},
|
||||
button: {
|
||||
order: -1,
|
||||
marginRight: 0,
|
||||
marginLeft: '-20px',
|
||||
},
|
||||
externalLinkIcon: {
|
||||
fontSize: 'inherit',
|
||||
verticalAlign: 'bottom',
|
||||
},
|
||||
}));
|
||||
|
||||
export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
|
||||
const { value: projectName, loading, error } = useProjectName(entity);
|
||||
const [projectId] = (projectName ?? '/').split('/');
|
||||
|
||||
const details = useWorkflowRunsDetails(projectId);
|
||||
|
||||
const classes = useStyles();
|
||||
if (error) {
|
||||
return (
|
||||
<WarningPanel title="Error:">
|
||||
Failed to load build, {error.message}.
|
||||
</WarningPanel>
|
||||
);
|
||||
} else if (loading) {
|
||||
return <LinearProgress />;
|
||||
} else if (details.value?.logUrl === undefined) {
|
||||
return <LinearProgress />;
|
||||
}
|
||||
|
||||
const serviceAccount = qs.parse(new URL(details.value?.logUrl).search, {
|
||||
ignoreQueryPrefix: true,
|
||||
}).project;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Breadcrumbs aria-label="breadcrumb">
|
||||
<Link to="..">Workflow runs</Link>
|
||||
<Typography>Workflow run details</Typography>
|
||||
</Breadcrumbs>
|
||||
<TableContainer component={Paper} className={classes.table}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Branch</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{details.value?.substitutions.BRANCH_NAME}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Message</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{details.value?.substitutions.REPO_NAME}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Commit ID</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{details.value?.substitutions.COMMIT_SHA}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Status</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<WorkflowRunStatus status={details.value?.status} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Service Account</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{`${serviceAccount}`}@cloudbuild.gserviceaccount.com
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Links</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{details.value?.logUrl && (
|
||||
<MaterialLink target="_blank" href={details.value.logUrl}>
|
||||
Workflow runs on Google{' '}
|
||||
<ExternalLinkIcon className={classes.externalLinkIcon} />
|
||||
</MaterialLink>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 (
|
||||
<>
|
||||
<StatusPending /> Queued
|
||||
</>
|
||||
);
|
||||
case 'working':
|
||||
return (
|
||||
<>
|
||||
<StatusRunning /> In progress
|
||||
</>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<>
|
||||
<StatusOK /> Completed
|
||||
</>
|
||||
);
|
||||
case 'cancelled':
|
||||
return (
|
||||
<>
|
||||
<StatusAborted /> Cancelled
|
||||
</>
|
||||
);
|
||||
case 'failure':
|
||||
return (
|
||||
<>
|
||||
<StatusError /> Failed
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
<StatusPending /> Pending
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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<WorkflowRun>) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
<WorkflowRunStatus status={row.status} />
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Build',
|
||||
field: 'id',
|
||||
type: 'numeric',
|
||||
width: '150px',
|
||||
render: (row: Partial<WorkflowRun>) => (
|
||||
<Typography variant="body2" noWrap>
|
||||
<p>{row.id?.substring(0, 8)}</p>
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Source',
|
||||
field: 'source',
|
||||
highlight: true,
|
||||
width: '200px',
|
||||
render: (row: Partial<WorkflowRun>) => (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={generatePath(buildRouteRef.path, { id: row.id! })}
|
||||
>
|
||||
{row.message}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Ref',
|
||||
render: (row: Partial<WorkflowRun>) => (
|
||||
<Typography variant="body2" noWrap>
|
||||
<p>{row.substitutions?.BRANCH_NAME}</p>
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Commit',
|
||||
render: (row: Partial<WorkflowRun>) => (
|
||||
<Typography variant="body2" noWrap>
|
||||
<p>{row.substitutions?.SHORT_SHA}</p>
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Created',
|
||||
render: (row: Partial<WorkflowRun>) => (
|
||||
<Typography variant="body2" noWrap>
|
||||
<p>{moment(row.createTime).format('DD-MM-YYYY hh:mm:ss')}</p>
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
render: (row: Partial<WorkflowRun>) => (
|
||||
<Tooltip title="Rerun workflow">
|
||||
<IconButton onClick={row.rerun}>
|
||||
<RetryIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
),
|
||||
width: '10%',
|
||||
},
|
||||
];
|
||||
|
||||
type Props = {
|
||||
loading: boolean;
|
||||
retry: () => void;
|
||||
runs?: WorkflowRun[];
|
||||
projectName: string;
|
||||
page: number;
|
||||
onChangePage: (page: number) => void;
|
||||
total: number;
|
||||
pageSize: number;
|
||||
onChangePageSize: (pageSize: number) => void;
|
||||
};
|
||||
|
||||
export const WorkflowRunsTableView: FC<Props> = ({
|
||||
projectName,
|
||||
loading,
|
||||
pageSize,
|
||||
page,
|
||||
retry,
|
||||
runs,
|
||||
onChangePage,
|
||||
onChangePageSize,
|
||||
total,
|
||||
}) => {
|
||||
return (
|
||||
<Table
|
||||
isLoading={loading}
|
||||
options={{ paging: true, pageSize, padding: 'dense' }}
|
||||
totalCount={total}
|
||||
page={page}
|
||||
actions={[
|
||||
{
|
||||
icon: () => <SyncIcon />,
|
||||
tooltip: 'Reload workflow runs',
|
||||
isFreeAction: true,
|
||||
onClick: () => retry(),
|
||||
},
|
||||
]}
|
||||
data={runs ?? []}
|
||||
onChangePage={onChangePage}
|
||||
onChangeRowsPerPage={onChangePageSize}
|
||||
style={{ width: '100%' }}
|
||||
title={
|
||||
<Box display="flex" alignItems="center">
|
||||
<GoogleIcon />
|
||||
<Box mr={1} />
|
||||
<Typography variant="h6">{projectName}</Typography>
|
||||
</Box>
|
||||
}
|
||||
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 (
|
||||
<WorkflowRunsTableView
|
||||
{...tableProps}
|
||||
loading={loading || tableProps.loading}
|
||||
retry={retry}
|
||||
onChangePageSize={setPageSize}
|
||||
onChangePage={setPage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user