Update Jenkins plugin to new routing

This commit is contained in:
Tim Jacomb
2020-09-08 11:05:31 +01:00
parent 8daab3651f
commit e5ae7a029d
31 changed files with 274 additions and 373 deletions
@@ -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" />
+1 -11
View File
@@ -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
+1
View File
@@ -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",
+19 -1
View File
@@ -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 };
@@ -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}
/>
);
};
@@ -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} />
@@ -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>
);
};
+41
View File
@@ -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>
);
};
@@ -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;
@@ -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,
@@ -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 };
};
@@ -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';
+4 -1
View File
@@ -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}
/>
);
};
+6 -8
View File
@@ -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';
-17
View File
@@ -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';