Update Jenkins plugin to new routing
This commit is contained in:
@@ -17,6 +17,11 @@ import {
|
||||
Router as GitHubActionsRouter,
|
||||
isPluginApplicableToEntity as isGitHubActionsAvailable,
|
||||
} from '@backstage/plugin-github-actions';
|
||||
import {
|
||||
Router as JenkinsRouter,
|
||||
isPluginApplicableToEntity as isJenkinsAvailable,
|
||||
LatestRunCard as JenkinsLatestRunCard,
|
||||
} from '@backstage/plugin-jenkins';
|
||||
import {
|
||||
Router as CircleCIRouter,
|
||||
isPluginApplicableToEntity as isCircleCIAvailable,
|
||||
@@ -38,6 +43,8 @@ const CICDSwitcher = ({ entity }: { entity: Entity }) => {
|
||||
// This component is just an example of how you can implement your company's logic in entity page.
|
||||
// You can for example enforce that all components of type 'service' should use GitHubActions
|
||||
switch (true) {
|
||||
case isJenkinsAvailable(entity):
|
||||
return <JenkinsRouter entity={entity} />;
|
||||
case isGitHubActionsAvailable(entity):
|
||||
return <GitHubActionsRouter entity={entity} />;
|
||||
case isCircleCIAvailable(entity):
|
||||
@@ -57,6 +64,11 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
|
||||
<Grid item>
|
||||
<AboutCard entity={entity} />
|
||||
</Grid>
|
||||
{isJenkinsAvailable(entity) && (
|
||||
<Grid item sm={4}>
|
||||
<JenkinsLatestRunCard branch="master" />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
|
||||
|
||||
@@ -21,10 +21,6 @@ import {
|
||||
LatestWorkflowRunCard,
|
||||
GITHUB_ACTIONS_ANNOTATION,
|
||||
} from '@backstage/plugin-github-actions';
|
||||
import {
|
||||
JenkinsBuildsWidget,
|
||||
JenkinsLastBuildWidget,
|
||||
} from '@backstage/plugin-jenkins';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React, { FC } from 'react';
|
||||
import { AboutCard } from '../AboutCard';
|
||||
@@ -36,16 +32,6 @@ export const EntityPageOverview: FC<{ entity: Entity }> = ({ entity }) => {
|
||||
<Grid item sm={4}>
|
||||
<AboutCard entity={entity} />
|
||||
</Grid>
|
||||
{entity.metadata?.annotations?.['jenkins.io/github-folder'] && (
|
||||
<Grid item sm={4}>
|
||||
<JenkinsLastBuildWidget entity={entity} branch="master" />
|
||||
</Grid>
|
||||
)}
|
||||
{entity.metadata?.annotations?.['jenkins.io/github-folder'] && (
|
||||
<Grid item sm={8}>
|
||||
<JenkinsBuildsWidget entity={entity} />
|
||||
</Grid>
|
||||
)}
|
||||
{entity.metadata?.annotations?.[GITHUB_ACTIONS_ANNOTATION] && (
|
||||
<Grid item sm={3}>
|
||||
<LatestWorkflowRunCard entity={entity} branch="master" />
|
||||
|
||||
@@ -14,17 +14,7 @@ Website: [https://jenkins.io/](https://jenkins.io/)
|
||||
yarn add @backstage/plugin-jenkins
|
||||
```
|
||||
|
||||
2. Add plugin API to your Backstage instance:
|
||||
|
||||
```js
|
||||
// packages/app/src/api.ts
|
||||
import { JenkinsApi, jenkinsApiRef } from '@backstage/plugin-jenkins';
|
||||
|
||||
const builder = ApiRegistry.builder();
|
||||
builder.add(jenkinsApiRef, new JenkinsApi(`${backendUrl}/proxy/jenkins/api`));
|
||||
```
|
||||
|
||||
2. Add plugin itself:
|
||||
2. Add plugin:
|
||||
|
||||
```js
|
||||
// packages/app/src/plugins.ts
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.21",
|
||||
"@backstage/core": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
|
||||
"@backstage/theme": "^0.1.1-alpha.21",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { createApiRef } from '@backstage/core';
|
||||
import { CITableBuildInfo } from '../pages/BuildsPage/lib/CITable';
|
||||
import { CITableBuildInfo } from '../components/BuildsPage/lib/CITable';
|
||||
|
||||
const jenkins = require('jenkins');
|
||||
|
||||
@@ -65,6 +65,21 @@ export class JenkinsApi {
|
||||
})
|
||||
.pop();
|
||||
|
||||
const author = jobDetails.actions
|
||||
.filter(
|
||||
(action: any) =>
|
||||
action._class ===
|
||||
'jenkins.scm.api.metadata.ContributorMetadataAction',
|
||||
)
|
||||
.map((action: any) => {
|
||||
return action.contributorDisplayName;
|
||||
})
|
||||
.pop();
|
||||
|
||||
if (author) {
|
||||
scmInfo.author = author;
|
||||
}
|
||||
|
||||
return scmInfo;
|
||||
}
|
||||
|
||||
@@ -154,12 +169,15 @@ export class JenkinsApi {
|
||||
if (jobScmInfo) {
|
||||
source.url = jobScmInfo?.url;
|
||||
source.displayName = jobScmInfo?.displayName;
|
||||
source.author = jobScmInfo?.author;
|
||||
}
|
||||
|
||||
const path = new URL(jenkinsResult.url).pathname;
|
||||
|
||||
return {
|
||||
id: path,
|
||||
buildNumber: jenkinsResult.number,
|
||||
buildUrl: jenkinsResult.url,
|
||||
buildName: jenkinsResult.fullDisplayName,
|
||||
status: jenkinsResult.building ? 'running' : jenkinsResult.result,
|
||||
onRestartClick: () => {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
import { Content, Link } from '@backstage/core';
|
||||
import {
|
||||
Typography,
|
||||
Breadcrumbs,
|
||||
Paper,
|
||||
TableContainer,
|
||||
Table,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
Link as MaterialLink,
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { useBuildWithSteps } from '../useBuildWithSteps';
|
||||
import { useProjectSlugFromEntity } from '../useProjectSlugFromEntity';
|
||||
import { JenkinsRunStatus } from '../BuildsPage/lib/Status';
|
||||
import ExternalLinkIcon from '@material-ui/icons/Launch';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
maxWidth: 720,
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
table: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
externalLinkIcon: {
|
||||
fontSize: 'inherit',
|
||||
verticalAlign: 'bottom',
|
||||
},
|
||||
}));
|
||||
|
||||
const Page = () => (
|
||||
<Content>
|
||||
<BuildWithStepsView />
|
||||
</Content>
|
||||
);
|
||||
|
||||
const BuildWithStepsView = () => {
|
||||
const { owner, repo } = useProjectSlugFromEntity();
|
||||
const { branch, buildNumber } = useParams();
|
||||
const classes = useStyles();
|
||||
const buildPath = `${owner}/${repo}/${branch}/${buildNumber}`;
|
||||
const [{ value }] = useBuildWithSteps(buildPath);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Breadcrumbs aria-label="breadcrumb">
|
||||
<Link to="../../..">Jobs</Link>
|
||||
<Typography>Run</Typography>
|
||||
</Breadcrumbs>
|
||||
<TableContainer component={Paper} className={classes.table}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Branch</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{value?.source?.branchName}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Message</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{value?.source?.displayName}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Commit ID</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{value?.source?.commit?.hash}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Status</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<JenkinsRunStatus status={value?.status} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Author</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{value?.source?.author}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Jenkins</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MaterialLink target="_blank" href={value?.buildUrl}>
|
||||
View on Jenkins{' '}
|
||||
<ExternalLinkIcon className={classes.externalLinkIcon} />
|
||||
</MaterialLink>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>GitHub</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MaterialLink target="_blank" href={value?.source.url}>
|
||||
View on GitHub{' '}
|
||||
<ExternalLinkIcon className={classes.externalLinkIcon} />
|
||||
</MaterialLink>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export { BuildWithStepsView as BuildWithSteps };
|
||||
+34
-7
@@ -14,21 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { FC } from 'react';
|
||||
import { Link, Typography, Box, IconButton } from '@material-ui/core';
|
||||
import { Box, IconButton, Link, Typography } from '@material-ui/core';
|
||||
import RetryIcon from '@material-ui/icons/Replay';
|
||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { generatePath, Link as RouterLink } from 'react-router-dom';
|
||||
import { Table, TableColumn } from '@backstage/core';
|
||||
import { JenkinsRunStatus } from '../Status';
|
||||
import { useBuilds } from '../../../useBuilds';
|
||||
import { useProjectSlugFromEntity } from '../../../useProjectSlugFromEntity';
|
||||
import { buildRouteRef } from '../../../../plugin';
|
||||
|
||||
export type CITableBuildInfo = {
|
||||
id: string;
|
||||
buildName: string;
|
||||
buildUrl?: string;
|
||||
buildNumber: number;
|
||||
buildUrl: string;
|
||||
source: {
|
||||
branchName: string;
|
||||
url: string;
|
||||
displayName: string;
|
||||
author?: string;
|
||||
commit: {
|
||||
hash: string;
|
||||
};
|
||||
@@ -105,7 +110,13 @@ const generatedColumns: TableColumn[] = [
|
||||
field: 'buildName',
|
||||
highlight: true,
|
||||
render: (row: Partial<CITableBuildInfo>) => (
|
||||
<Link component={RouterLink} to={`/jenkins/job?url=${row.id}`}>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={generatePath(buildRouteRef.path, {
|
||||
branch: row.source?.branchName!,
|
||||
buildNumber: row.buildNumber?.toString()!,
|
||||
})}
|
||||
>
|
||||
{row.buildName}
|
||||
</Link>
|
||||
),
|
||||
@@ -177,7 +188,8 @@ type Props = {
|
||||
pageSize: number;
|
||||
onChangePageSize: (pageSize: number) => void;
|
||||
};
|
||||
export const CITable: FC<Props> = ({
|
||||
|
||||
export const CITableView: FC<Props> = ({
|
||||
projectName,
|
||||
loading,
|
||||
pageSize,
|
||||
@@ -191,7 +203,7 @@ export const CITable: FC<Props> = ({
|
||||
return (
|
||||
<Table
|
||||
isLoading={loading}
|
||||
options={{ paging: true, pageSize }}
|
||||
options={{ paging: true, pageSize, padding: 'dense' }}
|
||||
totalCount={total}
|
||||
page={page}
|
||||
actions={[
|
||||
@@ -202,7 +214,7 @@ export const CITable: FC<Props> = ({
|
||||
onClick: () => retry(),
|
||||
},
|
||||
]}
|
||||
data={builds}
|
||||
data={builds ?? []}
|
||||
onChangePage={onChangePage}
|
||||
onChangeRowsPerPage={onChangePageSize}
|
||||
title={
|
||||
@@ -216,3 +228,18 @@ export const CITable: FC<Props> = ({
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const CITable = () => {
|
||||
const { owner, repo } = useProjectSlugFromEntity();
|
||||
|
||||
const [tableProps, { setPage, retry, setPageSize }] = useBuilds(owner, repo);
|
||||
|
||||
return (
|
||||
<CITableView
|
||||
{...tableProps}
|
||||
retry={retry}
|
||||
onChangePageSize={setPageSize}
|
||||
onChangePage={setPage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+8
-17
@@ -14,12 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Link, Theme, makeStyles, LinearProgress } from '@material-ui/core';
|
||||
import { InfoCard, StructuredMetadataTable } from '@backstage/core';
|
||||
import ExternalLinkIcon from '@material-ui/icons/Launch';
|
||||
import { useBuilds } from '../../state';
|
||||
import { JenkinsRunStatus } from '../../pages/BuildsPage/lib/Status';
|
||||
import { useBuilds } from '../useBuilds';
|
||||
import { JenkinsRunStatus } from '../BuildsPage/lib/Status';
|
||||
import { useProjectSlugFromEntity } from '../useProjectSlugFromEntity';
|
||||
|
||||
const useStyles = makeStyles<Theme>({
|
||||
externalLinkIcon: {
|
||||
@@ -38,6 +38,7 @@ const WidgetContent = ({
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
if (loading || !lastRun) return <LinearProgress />;
|
||||
|
||||
return (
|
||||
<StructuredMetadataTable
|
||||
metadata={{
|
||||
@@ -60,20 +61,10 @@ const WidgetContent = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const JenkinsLastBuildWidget = ({
|
||||
entity,
|
||||
branch = 'master',
|
||||
}: {
|
||||
entity: Entity;
|
||||
branch: string;
|
||||
}) => {
|
||||
const [owner, repo] = (
|
||||
entity?.metadata.annotations?.['jenkins.io/github-folder'] ?? '/'
|
||||
).split('/');
|
||||
const [{ loading, value }] = useBuilds(owner, repo, branch);
|
||||
|
||||
const lastRun = value ?? {};
|
||||
|
||||
export const LatestRunCard = ({ branch = 'master' }: { branch: string }) => {
|
||||
const { owner, repo } = useProjectSlugFromEntity();
|
||||
const [{ builds, loading }] = useBuilds(owner, repo, branch);
|
||||
const lastRun = builds ?? {};
|
||||
return (
|
||||
<InfoCard title={`Last ${branch} build`}>
|
||||
<WidgetContent loading={loading} branch={branch} lastRun={lastRun} />
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './PluginHeader';
|
||||
export { LatestRunCard } from './Cards';
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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 { Header, Page, pageTheme, HeaderLabel } from '@backstage/core';
|
||||
|
||||
export const Layout: React.FC = ({ children }) => {
|
||||
return (
|
||||
<Page theme={pageTheme.tool}>
|
||||
<Header title="Jenkins" subtitle="See recent builds and their status">
|
||||
<HeaderLabel label="Owner" value="Spotify" />
|
||||
<HeaderLabel label="Lifecycle" value="Alpha" />
|
||||
</Header>
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* 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 './Layout';
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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 { ContentHeader, SupportButton } from '@backstage/core';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
|
||||
export type Props = { title?: string };
|
||||
export const PluginHeader = ({ title = 'Jenkins' }) => {
|
||||
return (
|
||||
<ContentHeader
|
||||
title={title}
|
||||
titleComponent={() => (
|
||||
<Box alignItems="center" display="flex">
|
||||
<Typography variant="h4">{title}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
<SupportButton>
|
||||
This plugin allows you to view and interact with your builds in Jenkins.
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 { Route, Routes } from 'react-router';
|
||||
import { buildRouteRef, rootRouteRef } from '../plugin';
|
||||
import { DetailedViewPage } from './BuildWithStepsPage/';
|
||||
import { JENKINS_ANNOTATION } from '../constants';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { WarningPanel } from '@backstage/core';
|
||||
import { CITable } from './BuildsPage/lib/CITable';
|
||||
|
||||
export const isPluginApplicableToEntity = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[JENKINS_ANNOTATION]) &&
|
||||
entity.metadata.annotations?.[JENKINS_ANNOTATION] !== '';
|
||||
|
||||
export const Router = ({ entity }: { entity: Entity }) => {
|
||||
return !isPluginApplicableToEntity(entity) ? (
|
||||
<WarningPanel title="Jenkins plugin:">
|
||||
<pre>entity.metadata.annotations['{JENKINS_ANNOTATION}']</pre>
|
||||
key is missing on the entity.
|
||||
</WarningPanel>
|
||||
) : (
|
||||
<Routes>
|
||||
<Route path={`/${rootRouteRef.path}`} element={<CITable />} />
|
||||
<Route path={`/${buildRouteRef.path}`} element={<DetailedViewPage />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
import { errorApiRef, useApi } from '@backstage/core';
|
||||
import { useCallback } from 'react';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { jenkinsApiRef } from '../api/index';
|
||||
import { jenkinsApiRef } from '../api';
|
||||
import { useAsyncPolling } from './useAsyncPolling';
|
||||
|
||||
const INTERVAL_AMOUNT = 1500;
|
||||
+5
-4
@@ -56,8 +56,9 @@ export function useBuilds(owner: string, repo: string, branch?: string) {
|
||||
});
|
||||
}, [repo, getBuilds]);
|
||||
|
||||
const { loading, value, retry } = useAsyncRetry(
|
||||
() => getBuilds().then(builds => builds ?? [], restartBuild),
|
||||
const { loading, value: builds, retry } = useAsyncRetry(
|
||||
() =>
|
||||
getBuilds().then(retrievedBuilds => retrievedBuilds ?? [], restartBuild),
|
||||
[page, pageSize, getBuilds],
|
||||
);
|
||||
|
||||
@@ -67,12 +68,12 @@ export function useBuilds(owner: string, repo: string, branch?: string) {
|
||||
page,
|
||||
pageSize,
|
||||
loading,
|
||||
value,
|
||||
builds,
|
||||
projectName,
|
||||
total,
|
||||
},
|
||||
{
|
||||
getBuilds,
|
||||
builds,
|
||||
setPage,
|
||||
setPageSize,
|
||||
restartBuild,
|
||||
+6
-7
@@ -13,15 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useEntity } from '@backstage/plugin-catalog';
|
||||
import { JENKINS_ANNOTATION } from '../constants';
|
||||
|
||||
import React from 'react';
|
||||
import { Builds } from '../../pages/BuildsPage/lib/Builds';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
export const useProjectSlugFromEntity = () => {
|
||||
const { entity } = useEntity();
|
||||
|
||||
export const JenkinsBuildsWidget = ({ entity }: { entity: Entity }) => {
|
||||
const [owner, repo] = (
|
||||
entity?.metadata.annotations?.['jenkins.io/github-folder'] ?? '/'
|
||||
entity.metadata.annotations?.[JENKINS_ANNOTATION] ?? ''
|
||||
).split('/');
|
||||
|
||||
return <Builds owner={owner} repo={repo} />;
|
||||
return { owner, repo };
|
||||
};
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { Builds } from './Builds';
|
||||
export const JENKINS_ANNOTATION = 'jenkins.io/github-folder';
|
||||
@@ -14,5 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { plugin, JenkinsBuildsWidget, JenkinsLastBuildWidget } from './plugin';
|
||||
export { plugin } from './plugin';
|
||||
export { LatestRunCard } from './components/Cards';
|
||||
export { Router, isPluginApplicableToEntity } from './components/Router';
|
||||
export { JENKINS_ANNOTATION } from './constants';
|
||||
export * from './api';
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* 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, useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Content, InfoCard, Progress } from '@backstage/core';
|
||||
import { Grid, Box, Link, IconButton } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { PluginHeader } from '../../components/PluginHeader';
|
||||
import { ActionOutput } from './lib/ActionOutput/ActionOutput';
|
||||
import { Layout } from '../../components/Layout';
|
||||
import LaunchIcon from '@material-ui/icons/Launch';
|
||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import { useBuildWithSteps } from '../../state/useBuildWithSteps';
|
||||
|
||||
const IconLink = IconButton as typeof Link;
|
||||
const BuildName: FC<{ build?: any }> = ({ build }) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
{build?.buildName}
|
||||
<IconLink href={build?.url} target="_blank" title="View on Jenkins">
|
||||
<LaunchIcon /> {/* TODO use Jenkins logo*/}
|
||||
</IconLink>
|
||||
<IconLink href={build?.source.url} target="_blank" title="View on GitHub">
|
||||
<GitHubIcon />
|
||||
</IconLink>
|
||||
</Box>
|
||||
);
|
||||
const useStyles = makeStyles(theme => ({
|
||||
neutral: {},
|
||||
failed: {
|
||||
position: 'relative',
|
||||
'&:after': {
|
||||
pointerEvents: 'none',
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
boxShadow: `inset 4px 0px 0px ${theme.palette.error.main}`,
|
||||
},
|
||||
},
|
||||
running: {
|
||||
position: 'relative',
|
||||
'&:after': {
|
||||
pointerEvents: 'none',
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
boxShadow: `inset 4px 0px 0px ${theme.palette.info.main}`,
|
||||
},
|
||||
},
|
||||
cardContent: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
success: {
|
||||
position: 'relative',
|
||||
'&:after': {
|
||||
pointerEvents: 'none',
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
boxShadow: `inset 4px 0px 0px ${theme.palette.success.main}`,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const pickClassName = (
|
||||
classes: ReturnType<typeof useStyles>,
|
||||
build: any = {},
|
||||
) => {
|
||||
if (build.result === 'UNSTABLE') return classes.failed;
|
||||
if (build.result === 'FAILURE') return classes.failed;
|
||||
if (build.building) return classes.running;
|
||||
if (build.status === 'SUCCESS') return classes.success;
|
||||
|
||||
return classes.neutral;
|
||||
};
|
||||
|
||||
const Page = () => (
|
||||
<Layout>
|
||||
<Content>
|
||||
<BuildWithStepsView />
|
||||
</Content>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
const BuildWithStepsView = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const buildPath = searchParams.get('url') || '';
|
||||
const classes = useStyles();
|
||||
const [{ loading, value }, { startPolling, stopPolling }] = useBuildWithSteps(
|
||||
buildPath,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
startPolling();
|
||||
return () => stopPolling();
|
||||
}, [buildPath, startPolling, stopPolling]);
|
||||
return (
|
||||
<>
|
||||
<PluginHeader title={value?.source.displayName || 'Build details'} />
|
||||
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item>
|
||||
<InfoCard
|
||||
className={pickClassName(classes, value)}
|
||||
title={<BuildName build={value} />}
|
||||
cardClassName={classes.cardContent}
|
||||
>
|
||||
{loading ? <Progress /> : <BuildsList build={value} />}
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const BuildsList: FC<{ build?: any }> = ({ build }) => (
|
||||
<Box>
|
||||
{build &&
|
||||
build.steps &&
|
||||
build.steps.map(({ name, actions }: { name: string; actions: any[] }) => (
|
||||
<ActionsList name={name} actions={actions} />
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
|
||||
const ActionsList: FC<{ actions: any[]; name: string }> = ({ actions }) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<>
|
||||
{actions.map((action: any) => (
|
||||
<ActionOutput
|
||||
className={action.failed ? classes.failed : classes.success}
|
||||
action={action}
|
||||
name={action.name}
|
||||
url={action.output_url || ''}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export { BuildWithStepsView as BuildWithSteps };
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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 { CITable } from '../CITable';
|
||||
import { useBuilds } from '../../../../state';
|
||||
|
||||
export const Builds = ({ owner, repo }: { owner: string; repo: string }) => {
|
||||
const [
|
||||
{ total, loading, value, projectName, page, pageSize },
|
||||
{ setPage, retry, setPageSize },
|
||||
] = useBuilds(owner, repo);
|
||||
return (
|
||||
<CITable
|
||||
total={total}
|
||||
loading={loading}
|
||||
retry={retry}
|
||||
builds={value ?? []}
|
||||
projectName={projectName}
|
||||
page={page}
|
||||
onChangePage={setPage}
|
||||
pageSize={pageSize}
|
||||
onChangePageSize={setPageSize}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -20,11 +20,15 @@ import {
|
||||
createApiFactory,
|
||||
configApiRef,
|
||||
} from '@backstage/core';
|
||||
import { DetailedViewPage } from './pages/BuildWithStepsPage';
|
||||
import { jenkinsApiRef, JenkinsApi } from './api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '',
|
||||
title: 'Jenkins',
|
||||
});
|
||||
|
||||
export const buildRouteRef = createRouteRef({
|
||||
path: '/jenkins/job',
|
||||
path: 'run/:branch/:buildNumber',
|
||||
title: 'Jenkins run',
|
||||
});
|
||||
|
||||
@@ -40,10 +44,4 @@ export const plugin = createPlugin({
|
||||
),
|
||||
}),
|
||||
],
|
||||
register({ router }) {
|
||||
router.addRoute(buildRouteRef, DetailedViewPage);
|
||||
},
|
||||
});
|
||||
|
||||
export { JenkinsBuildsWidget } from './components/JenkinsPluginWidget/JenkinsBuildsWidget';
|
||||
export { JenkinsLastBuildWidget } from './components/JenkinsPluginWidget/JenkinsLastBuildWidget';
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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 './useBuilds';
|
||||
export * from './useBuildWithSteps';
|
||||
Reference in New Issue
Block a user