Merge pull request #19881 from Abhay-soni-developer/jenkins/jobrun-table
jenkins JobRunTable added
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-jenkins-backend': minor
|
||||
'@backstage/plugin-jenkins': minor
|
||||
---
|
||||
|
||||
Added JobRunTable Component.
|
||||
Added new Route and extended Api to get buildJobs.
|
||||
Actions column has a new icon button, clicking on which takes us to page where we
|
||||
can see all the job runs.
|
||||
@@ -54,6 +54,7 @@ export class JenkinsApiImpl {
|
||||
private static readonly jobTreeSpec = `actions[*],
|
||||
${JenkinsApiImpl.lastBuildTreeSpec}
|
||||
jobs{0,1},
|
||||
url,
|
||||
name,
|
||||
fullName,
|
||||
displayName,
|
||||
@@ -64,6 +65,16 @@ export class JenkinsApiImpl {
|
||||
${JenkinsApiImpl.jobTreeSpec}
|
||||
]{0,50}`;
|
||||
|
||||
private static readonly jobBuildsTreeSpec = `
|
||||
name,
|
||||
description,
|
||||
url,
|
||||
fullName,
|
||||
displayName,
|
||||
fullDisplayName,
|
||||
inQueue,
|
||||
builds[*]`;
|
||||
|
||||
constructor(private readonly permissionApi?: PermissionEvaluator) {}
|
||||
|
||||
/**
|
||||
@@ -329,4 +340,35 @@ export class JenkinsApiImpl {
|
||||
const jobs = jobFullName.split('/');
|
||||
return `${jenkinsInfo.baseUrl}/job/${jobs.join('/job/')}/${buildId}`;
|
||||
}
|
||||
|
||||
async getJobBuilds(jenkinsInfo: JenkinsInfo, jobFullName: string) {
|
||||
let jobName = jobFullName;
|
||||
|
||||
if (jobFullName.includes('/')) {
|
||||
const arr = jobFullName.split('/');
|
||||
const multibranchJobName = arr.shift();
|
||||
jobName = [
|
||||
multibranchJobName,
|
||||
'job',
|
||||
encodeURIComponent(arr.join('/')),
|
||||
].join('/');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${
|
||||
jenkinsInfo.baseUrl
|
||||
}/job/${jobName}/api/json?tree=${JenkinsApiImpl.jobBuildsTreeSpec.replace(
|
||||
/\s/g,
|
||||
'',
|
||||
)}`,
|
||||
{
|
||||
method: 'get',
|
||||
headers: jenkinsInfo.headers as HeaderInit,
|
||||
},
|
||||
);
|
||||
|
||||
const jobBuilds = await response.json();
|
||||
|
||||
return jobBuilds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,32 @@ export async function createRouter(
|
||||
},
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/v1/entity/:namespace/:kind/:name/job/:jobFullName',
|
||||
async (request, response) => {
|
||||
const token = getBearerTokenFromAuthorizationHeader(
|
||||
request.header('authorization'),
|
||||
);
|
||||
const { namespace, kind, name, jobFullName } = request.params;
|
||||
|
||||
const jenkinsInfo = await jenkinsInfoProvider.getInstance({
|
||||
entityRef: {
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
},
|
||||
jobFullName,
|
||||
backstageToken: token,
|
||||
});
|
||||
|
||||
const build = await jenkinsApi.getJobBuilds(jenkinsInfo, jobFullName);
|
||||
|
||||
response.json({
|
||||
build: build,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/v1/entity/:namespace/:kind/:name/job/:jobFullName/:buildNumber',
|
||||
async (request, response) => {
|
||||
|
||||
@@ -5,7 +5,9 @@ Website: [https://jenkins.io/](https://jenkins.io/)
|
||||
<img src="./src/assets/last-master-build.png" alt="Last master build"/>
|
||||
<img src="./src/assets/folder-results.png" alt="Folder results"/>
|
||||
<img src="./src/assets/build-details.png" alt="Build details"/>
|
||||
<img src="./src/assets/jobrun-table.png" alt="Job builds records"/>
|
||||
<img src="./src/assets/dynamic-columns.png" alt="Modify Table Columns"/>
|
||||
|
||||
## Setup
|
||||
|
||||
1. If you have a standalone app (you didn't clone this repo), then do
|
||||
@@ -74,7 +76,9 @@ metadata:
|
||||
name: 'your-component'
|
||||
description: 'a description'
|
||||
annotations:
|
||||
jenkins.io/github-folder: 'folder-name/project-name'
|
||||
jenkins.io/github-folder: 'folder-name/project-name' # deprecated
|
||||
jenkins.io/job-full-name: 'folder-name/project-name' # use this instead
|
||||
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: experimental
|
||||
@@ -98,6 +102,11 @@ spec:
|
||||
- No pagination support currently, limited to 50 projects - don't run this on a
|
||||
Jenkins instance with lots of builds
|
||||
|
||||
## EntityJobRunsTable
|
||||
|
||||
- View all builds of a particular job
|
||||
- shows average build time for successful builds
|
||||
|
||||
## Modify Columns of EntityJenkinsContent
|
||||
|
||||
- now you can pass down column props to show the columns/metadata as per your use case.
|
||||
|
||||
@@ -22,6 +22,9 @@ export const EntityJenkinsContent: (props: {
|
||||
columns?: TableColumn<Project>[] | undefined;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityJobRunsTable: () => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityLatestJenkinsRunCard: (props: {
|
||||
branch: string;
|
||||
@@ -48,6 +51,13 @@ export interface JenkinsApi {
|
||||
jobFullName: string;
|
||||
buildNumber: string;
|
||||
}): Promise<Build>;
|
||||
// Warning: (ae-forgotten-export) The symbol "Job" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
getJobBuilds(options: {
|
||||
entity: CompoundEntityRef;
|
||||
jobFullName: string;
|
||||
}): Promise<Job>;
|
||||
getProjects(options: {
|
||||
entity: CompoundEntityRef;
|
||||
filter: {
|
||||
@@ -82,6 +92,11 @@ export class JenkinsClient implements JenkinsApi {
|
||||
buildNumber: string;
|
||||
}): Promise<Build>;
|
||||
// (undocumented)
|
||||
getJobBuilds(options: {
|
||||
entity: CompoundEntityRef;
|
||||
jobFullName: string;
|
||||
}): Promise<Job>;
|
||||
// (undocumented)
|
||||
getProjects(options: {
|
||||
entity: CompoundEntityRef;
|
||||
filter: {
|
||||
|
||||
@@ -56,6 +56,30 @@ export interface Build {
|
||||
};
|
||||
status: string; // == building ? 'running' : result,
|
||||
}
|
||||
export interface JobBuild {
|
||||
timestamp: number;
|
||||
building: boolean;
|
||||
duration: number;
|
||||
result?: string;
|
||||
fullDisplayName: string;
|
||||
displayName: string;
|
||||
url: string;
|
||||
number: number;
|
||||
inProgress: boolean;
|
||||
queueId: number;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
fullDisplayName: string;
|
||||
inQueue: boolean;
|
||||
fullName: string;
|
||||
url: string;
|
||||
builds: JobBuild[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface Project {
|
||||
@@ -99,6 +123,11 @@ export interface JenkinsApi {
|
||||
buildNumber: string;
|
||||
}): Promise<Build>;
|
||||
|
||||
getJobBuilds(options: {
|
||||
entity: CompoundEntityRef;
|
||||
jobFullName: string;
|
||||
}): Promise<Job>;
|
||||
|
||||
retry(options: {
|
||||
entity: CompoundEntityRef;
|
||||
jobFullName: string;
|
||||
@@ -213,4 +242,28 @@ export class JenkinsClient implements JenkinsApi {
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
return token;
|
||||
}
|
||||
|
||||
async getJobBuilds(options: {
|
||||
entity: CompoundEntityRef;
|
||||
jobFullName: string;
|
||||
}): Promise<Job> {
|
||||
const { entity, jobFullName } = options;
|
||||
const url = `${await this.discoveryApi.getBaseUrl(
|
||||
'jenkins',
|
||||
)}/v1/entity/${encodeURIComponent(entity.namespace)}/${encodeURIComponent(
|
||||
entity.kind,
|
||||
)}/${encodeURIComponent(entity.name)}/job/${encodeURIComponent(
|
||||
jobFullName,
|
||||
)}`;
|
||||
|
||||
const idToken = await this.getToken();
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...(idToken && { Authorization: `Bearer ${idToken}` }),
|
||||
},
|
||||
});
|
||||
|
||||
return (await response.json()).build;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
@@ -19,9 +19,10 @@ import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { Box, IconButton, Tooltip, Typography } from '@material-ui/core';
|
||||
import RetryIcon from '@material-ui/icons/Replay';
|
||||
import VisibilityIcon from '@material-ui/icons/Visibility';
|
||||
import HistoryIcon from '@material-ui/icons/History';
|
||||
import { default as React, useState } from 'react';
|
||||
import { Project } from '../../../../api/JenkinsApi';
|
||||
import { buildRouteRef } from '../../../../plugin';
|
||||
import { buildRouteRef, jobRunsRouteRef } from '../../../../plugin';
|
||||
import { JenkinsRunStatus } from '../Status';
|
||||
import { jenkinsExecutePermission } from '@backstage/plugin-jenkins-common';
|
||||
|
||||
@@ -186,6 +187,25 @@ export const columnFactories = Object.freeze({
|
||||
};
|
||||
},
|
||||
|
||||
createLastRunDuration(): TableColumn<Project> {
|
||||
return {
|
||||
title: 'Last Run Duration',
|
||||
align: 'left',
|
||||
render: (row: Partial<Project>) => (
|
||||
<>
|
||||
<Typography>
|
||||
{row?.lastBuild?.duration
|
||||
? (row?.lastBuild?.duration / 1000)
|
||||
.toFixed(1)
|
||||
.toString()
|
||||
.concat(' s')
|
||||
: ''}{' '}
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
createActionsColumn(): TableColumn<Project> {
|
||||
return {
|
||||
title: 'Actions',
|
||||
@@ -198,6 +218,7 @@ export const columnFactories = Object.freeze({
|
||||
);
|
||||
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const jobRunsLink = useRouteRef(jobRunsRouteRef);
|
||||
|
||||
const onRebuild = async () => {
|
||||
if (row.onRestartClick) {
|
||||
@@ -221,7 +242,7 @@ export const columnFactories = Object.freeze({
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: '98px' }}>
|
||||
<div style={{ width: '148px' }}>
|
||||
{row.lastBuild?.url && (
|
||||
<Tooltip title="View build">
|
||||
<IconButton href={row.lastBuild.url} target="_blank">
|
||||
@@ -240,6 +261,17 @@ export const columnFactories = Object.freeze({
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Link
|
||||
to={jobRunsLink({
|
||||
jobFullName: encodeURIComponent(row.fullName || ''),
|
||||
})}
|
||||
>
|
||||
<Tooltip title="View Runs">
|
||||
<IconButton>
|
||||
<HistoryIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,5 +24,6 @@ export const defaultCITableColumns: TableColumn<Project>[] = [
|
||||
columnFactories.createBuildColumn(),
|
||||
columnFactories.createTestColumn(),
|
||||
columnFactories.createStatusColumn(),
|
||||
columnFactories.createLastRunDuration(),
|
||||
columnFactories.createActionsColumn(),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 { Link, Table, TableColumn } from '@backstage/core-components';
|
||||
import { Box, IconButton, Tooltip, Typography } from '@material-ui/core';
|
||||
import { default as React } from 'react';
|
||||
import JenkinsLogo from './../../assets/JenkinsLogo.svg';
|
||||
import { useJobRuns } from './../useJobRuns';
|
||||
import { Job, JobBuild } from './../../api/JenkinsApi';
|
||||
import { JenkinsRunStatus } from './../BuildsPage/lib/Status';
|
||||
import VisibilityIcon from '@material-ui/icons/Visibility';
|
||||
import { jobRunsRouteRef } from '../../plugin';
|
||||
import { useRouteRefParams } from '@backstage/core-plugin-api';
|
||||
|
||||
const generatedColumns: TableColumn[] = [
|
||||
{
|
||||
title: 'Number',
|
||||
field: 'number',
|
||||
render: (row: Partial<JobBuild>) => {
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography paragraph>
|
||||
<Link to={row.url ?? ''}>{row.number}</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Timestamp',
|
||||
field: 'timestamp',
|
||||
render: (row: Partial<JobBuild>) => {
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography>
|
||||
{row?.timestamp ? new Date(row?.timestamp).toLocaleString() : ' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Result',
|
||||
field: 'result',
|
||||
render: (row: Partial<JobBuild>) => {
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
{row.inProgress ? (
|
||||
<Typography>In Progress</Typography>
|
||||
) : (
|
||||
<JenkinsRunStatus status={row?.result} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
field: 'duration',
|
||||
render: (row: Partial<JobBuild>) => {
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography>
|
||||
{row?.duration
|
||||
? (row.duration / 1000).toFixed(1).toString().concat(' s')
|
||||
: ''}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Actions',
|
||||
render: (row: Partial<JobBuild>) => {
|
||||
const ActionWrapper = () => {
|
||||
return (
|
||||
<div style={{ width: '98px' }}>
|
||||
{row?.url && (
|
||||
<Tooltip title="View build">
|
||||
<Link component={IconButton} to={row.url}>
|
||||
<VisibilityIcon />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return <ActionWrapper />;
|
||||
},
|
||||
width: '10%',
|
||||
},
|
||||
];
|
||||
|
||||
type Props = {
|
||||
loading: boolean;
|
||||
jobRuns?: Job;
|
||||
page: number;
|
||||
onChangePage: (page: number) => void;
|
||||
pageSize: number;
|
||||
onChangePageSize: (pageSize: number) => void;
|
||||
};
|
||||
|
||||
export const JobRunsTableView = ({
|
||||
loading,
|
||||
pageSize,
|
||||
page,
|
||||
jobRuns,
|
||||
onChangePage,
|
||||
onChangePageSize,
|
||||
}: Props) => {
|
||||
const builds = jobRuns?.builds.slice(
|
||||
page * pageSize,
|
||||
page * pageSize + pageSize,
|
||||
);
|
||||
let sumOfAllSuccessfulJobDuration = 0;
|
||||
|
||||
const successfulJobCount =
|
||||
builds?.reduce((count, build) => {
|
||||
if (!build.inProgress && build.result === 'SUCCESS') {
|
||||
sumOfAllSuccessfulJobDuration += build.duration;
|
||||
return count + 1;
|
||||
}
|
||||
return count;
|
||||
}, 0) || 0;
|
||||
|
||||
let avgTime;
|
||||
|
||||
if (successfulJobCount > 0) {
|
||||
avgTime = (sumOfAllSuccessfulJobDuration / successfulJobCount / 1000)
|
||||
.toFixed(1)
|
||||
.toString();
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
isLoading={loading}
|
||||
options={{ paging: true, pageSize, padding: 'dense' }}
|
||||
totalCount={jobRuns?.builds.length || 0}
|
||||
page={page}
|
||||
data={builds ?? []}
|
||||
onPageChange={onChangePage}
|
||||
onRowsPerPageChange={onChangePageSize}
|
||||
title={
|
||||
<Box>
|
||||
<Box display="flex" alignItems="center">
|
||||
<img src={JenkinsLogo} alt="Jenkins logo" height="50px" />
|
||||
<Box mr={2} />
|
||||
<Typography variant="h6">{`${jobRuns?.displayName} Runs`}</Typography>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" mt={2}>
|
||||
<Typography variant="h6">
|
||||
Average Build Time For Last {successfulJobCount} Successful jobs :{' '}
|
||||
{avgTime || 0}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
columns={generatedColumns}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const JobRunsTable = () => {
|
||||
const { jobFullName } = useRouteRefParams(jobRunsRouteRef);
|
||||
const [tableProps, { setPage, setPageSize }] = useJobRuns(jobFullName);
|
||||
|
||||
return (
|
||||
<JobRunsTableView
|
||||
{...tableProps}
|
||||
onChangePageSize={setPageSize}
|
||||
onChangePage={setPage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* 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 { JobRunsTable } from './JobRunsTable';
|
||||
@@ -23,9 +23,10 @@ import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { JENKINS_ANNOTATION, LEGACY_JENKINS_ANNOTATION } from '../constants';
|
||||
import { buildRouteRef } from '../plugin';
|
||||
import { buildRouteRef, jobRunsRouteRef } from '../plugin';
|
||||
import { CITable } from './BuildsPage/lib/CITable';
|
||||
import { DetailedViewPage } from './BuildWithStepsPage/';
|
||||
import { JobRunsTable } from './JobRunsTable';
|
||||
import { Project } from '../api';
|
||||
|
||||
/** @public */
|
||||
@@ -46,6 +47,7 @@ export const Router = (props: { columns?: TableColumn<Project>[] }) => {
|
||||
<Routes>
|
||||
<Route path="/" element={<CITable columns={columns} />} />
|
||||
<Route path={`/${buildRouteRef.path}`} element={<DetailedViewPage />} />
|
||||
<Route path={`/${jobRunsRouteRef.path}`} element={<JobRunsTable />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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/lib/useAsyncRetry';
|
||||
import { jenkinsApiRef } from '../api';
|
||||
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { getCompoundEntityRef } from '@backstage/catalog-model';
|
||||
|
||||
export enum ErrorType {
|
||||
CONNECTION_ERROR,
|
||||
NOT_FOUND,
|
||||
}
|
||||
|
||||
export function useJobRuns(jobFullName: string) {
|
||||
const { entity } = useEntity();
|
||||
const api = useApi(jenkinsApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
|
||||
const [error, setError] = useState<{
|
||||
message: string;
|
||||
errorType: ErrorType;
|
||||
}>();
|
||||
|
||||
const { loading, value: jobRuns } = useAsyncRetry(async () => {
|
||||
try {
|
||||
const jobBuilds = await api.getJobBuilds({
|
||||
entity: getCompoundEntityRef(entity),
|
||||
jobFullName,
|
||||
});
|
||||
return jobBuilds;
|
||||
} catch (e) {
|
||||
const errorType = e.notFound
|
||||
? ErrorType.NOT_FOUND
|
||||
: ErrorType.CONNECTION_ERROR;
|
||||
setError({ message: e.message, errorType });
|
||||
throw e;
|
||||
}
|
||||
}, [api, errorApi, entity]);
|
||||
|
||||
return [
|
||||
{
|
||||
page,
|
||||
pageSize,
|
||||
loading,
|
||||
jobRuns,
|
||||
error,
|
||||
},
|
||||
{
|
||||
setPage,
|
||||
setPageSize,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export {
|
||||
jenkinsPlugin as plugin,
|
||||
EntityJenkinsContent,
|
||||
EntityLatestJenkinsRunCard,
|
||||
EntityJobRunsTable,
|
||||
} from './plugin';
|
||||
export { LatestRunCard } from './components/Cards';
|
||||
export {
|
||||
|
||||
@@ -38,6 +38,13 @@ export const buildRouteRef = createSubRouteRef({
|
||||
parent: rootRouteRef,
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const jobRunsRouteRef = createSubRouteRef({
|
||||
id: 'jenkins/job/runs',
|
||||
path: '/builds/:jobFullName/runs',
|
||||
parent: rootRouteRef,
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const jenkinsPlugin = createPlugin({
|
||||
id: 'jenkins',
|
||||
@@ -72,3 +79,13 @@ export const EntityLatestJenkinsRunCard = jenkinsPlugin.provide(
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const EntityJobRunsTable = jenkinsPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityJobRunsTable',
|
||||
component: {
|
||||
lazy: () => import('./components/JobRunsTable').then(m => m.JobRunsTable),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user