Skip to content

Commit ed33e39

Browse files
authored
feat: add TemplateTaskRunsCard component to display task runs for templates (#9)
- Implemented TemplateTaskRunsCard component that fetches and displays task runs associated with a template. - Integrated loading and error handling states. - Created a table with columns for ID, Status, Created At, Duration, and Created By. - Added links to task details and user profiles where applicable. Signed-off-by: Gaurav Pandey <grvpandey11@gmail.com>
1 parent 5e3b5ff commit ed33e39

9 files changed

Lines changed: 197 additions & 8 deletions

File tree

packages/app/src/components/catalog/EntityPage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
import {
6161
TemplateUsageSummaryCard,
6262
TemplateMonthlyStatsCard,
63+
TemplateTaskRunsCard,
6364
} from '@codeverse-gp/plugin-usage-statistics';
6465

6566
const techdocsContent = (
@@ -424,6 +425,9 @@ const templatePage = (
424425
<Grid item md={12} xs={12}>
425426
<TemplateMonthlyStatsCard />
426427
</Grid>
428+
<Grid item md={12} xs={12}>
429+
<TemplateTaskRunsCard />
430+
</Grid>
427431
</Grid>
428432
</EntityLayout.Route>
429433
</EntityLayout>

plugins/usage-statistics/README.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
# usage-statistics
22

3-
Welcome to the `usage-statistics` plugin. This Backstage plugin helps you understand how templates are being used across your organization by surfacing metrics and trends from scaffolder task history.
3+
Welcome to the `usage-statistics` plugin. This Backstage plugin helps you understand how templates are being used across your organization by surfacing metrics and trends from scaffolder task history, directly within the template entity view.
44

55
## Components
66

77
### TemplateUsageSummaryCard
88

9-
Provides a quick overview of template usage statistics in an easy-to-read card format.
9+
Provides a quick overview of template usage statistics in an easy-to-read card format, directly within the template entity view.
1010

1111
**Features:**
1212

1313
- Total template runs count
1414
- Successful runs with success rate percentage
1515
- Failed runs count
1616
- Color-coded indicators for quick assessment
17-
- Responsive grid layout
17+
- Average duration for completed tasks
1818

1919
![Example of TemplateUsageSummaryCard](./assets/usage-summary.png)
2020

2121
### TemplateMonthlyStatsCard
2222

23-
Displays monthly usage statistics through interactive bar charts.
23+
Displays monthly usage statistics through interactive bar charts, directly within the template entity view.
2424

2525
**Features:**
2626

@@ -33,9 +33,22 @@ Displays monthly usage statistics through interactive bar charts.
3333

3434
![Example of TemplateMonthlyStatsCard](./assets/monthy-stats.png)
3535

36+
### TemplateTaskRunsCard
37+
38+
Displays a list of task runs specific to this template, directly within the template entity view.
39+
40+
Features:
41+
42+
- Table view showing Task ID, Status, Created At, Duration, and Triggered By
43+
- Filter and search by status, user, or task ID
44+
- Clickable rows to view detailed run information
45+
- Pagination support for large numbers of runs
46+
47+
![Example of Task Runs Table](./assets/task-runs.png)
48+
3649
## Setup
3750

38-
The following section will help you get the pusage-statistics plugin setup and running
51+
The following section will help you get the usage-statistics plugin setup and running
3952

4053
### Backend
4154

@@ -49,7 +62,7 @@ To setup the usage-statistics frondend plugin you'll need to do the following st
4962

5063
```sh
5164
# From your Backstage root directory
52-
yarn -cwd packages/app add @codeverse-gp/plugin-usage-statistics
65+
yarn --cwd packages/app add @codeverse-gp/plugin-usage-statistics
5366
```
5467

5568
2. Now open `packages/app/src/catalog/EntityPage.tsx` file
@@ -60,6 +73,7 @@ yarn -cwd packages/app add @codeverse-gp/plugin-usage-statistics
6073
import {
6174
TemplateUsageSummaryCard,
6275
TemplateMonthlyStatsCard,
76+
TemplateTaskRunsCard,
6377
} from '@codeverse-gp/plugin-usage-statistics';
6478
```
6579

@@ -90,6 +104,9 @@ const templatePage = (
90104
<Grid item xs={12}>
91105
<TemplateMonthlyStatsCard />
92106
</Grid>
107+
<Grid item md={12} xs={12}>
108+
<TemplateTaskRunsCard />
109+
</Grid>
93110
</Grid>
94111
</EntityLayout.Route>
95112
</EntityLayout>
249 KB
Loading

plugins/usage-statistics/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codeverse-gp/plugin-usage-statistics",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"description": "A plugin for tracking and displaying usage statistics in Backstage.",
55
"license": "MIT",
66
"main": "src/index.ts",
@@ -51,6 +51,7 @@
5151
"@material-ui/core": "^4.9.13",
5252
"@material-ui/icons": "^4.9.1",
5353
"@material-ui/lab": "^4.0.0-alpha.61",
54+
"luxon": "^3.7.1",
5455
"react-use": "^17.2.4",
5556
"recharts": "^3.1.0"
5657
},
@@ -65,6 +66,7 @@
6566
"@testing-library/jest-dom": "^6.0.0",
6667
"@testing-library/react": "^14.0.0",
6768
"@testing-library/user-event": "^14.0.0",
69+
"@types/luxon": "^3",
6870
"@types/recharts": "^2.0.1",
6971
"msw": "^1.0.0",
7072
"react": "^18.0.0 || ^20.0.0 || ^22.0.0"
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import {
2+
InfoCard,
3+
Progress,
4+
Table,
5+
type TableColumn,
6+
StatusOK,
7+
StatusError,
8+
Link,
9+
} from '@backstage/core-components';
10+
import Alert from '@material-ui/lab/Alert';
11+
import { useEntity } from '@backstage/plugin-catalog-react';
12+
import { useTemplateTaskRuns } from '../../../hooks/useTemplateTaskRuns';
13+
import { DateTime } from 'luxon';
14+
import { useApi, configApiRef } from '@backstage/core-plugin-api';
15+
import { TaskRun } from '../../../types';
16+
17+
export const TemplateTaskRunsCard = () => {
18+
const { entity } = useEntity();
19+
const templateName = entity.metadata.name;
20+
const { taskRuns, loading, error } = useTemplateTaskRuns(templateName);
21+
const config = useApi(configApiRef);
22+
const backendUrl = config.getOptionalString('app.baseUrl');
23+
if (!backendUrl) {
24+
throw new Error("app.baseUrl is not configured in Backstage config.");
25+
}
26+
if (loading) return <Progress />;
27+
if (error) {
28+
return (
29+
<InfoCard title="Template Task Runs">
30+
<Alert severity="info">No task runs found for this template.</Alert>
31+
</InfoCard>
32+
);
33+
}
34+
if (!taskRuns || taskRuns.length === 0) {
35+
return (
36+
<InfoCard title="Template Task Runs">
37+
<Alert severity="info">No task runs found for this template.</Alert>
38+
</InfoCard>
39+
);
40+
}
41+
const columns: TableColumn<TaskRun>[] = [
42+
{
43+
title: 'ID',
44+
field: 'id',
45+
render: (row: TaskRun) => {
46+
const taskUrl = `${backendUrl}/create/tasks/${row.id}`;
47+
return <Link to={taskUrl}>{row.id}</Link>;
48+
},
49+
},
50+
{
51+
title: 'Status',
52+
field: 'status',
53+
render: (row: TaskRun) => {
54+
if (row.status === 'completed') {
55+
return (
56+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
57+
<StatusOK />
58+
<span>completed</span>
59+
</div>
60+
);
61+
}
62+
if (row.status === 'failed') {
63+
return (
64+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
65+
<StatusError />
66+
<span>failed</span>
67+
</div>
68+
);
69+
}
70+
return row.status;
71+
},
72+
},
73+
{
74+
title: 'Created At',
75+
field: 'created_at',
76+
render: (row: TaskRun) =>
77+
DateTime.fromISO(row.created_at).toLocaleString(
78+
DateTime.DATETIME_SHORT,
79+
),
80+
},
81+
{
82+
title: 'Duration',
83+
field: 'duration',
84+
render: (row: TaskRun) => {
85+
if (row.status === 'failed') {
86+
return <StatusError />;
87+
}
88+
89+
if (!row.created_at || !row.last_heartbeat_at) {
90+
return 'N/A';
91+
}
92+
93+
const start = new Date(row.created_at).getTime();
94+
const end = new Date(row.last_heartbeat_at).getTime();
95+
const durationMs = end - start;
96+
97+
if (durationMs <= 0) {
98+
return 'N/A';
99+
}
100+
101+
const minutes = Math.floor(durationMs / (1000 * 60));
102+
const seconds = Math.floor((durationMs % (1000 * 60)) / 1000);
103+
104+
if (minutes > 0) {
105+
return `${minutes}m ${seconds}s`;
106+
}
107+
return `${seconds}s`;
108+
},
109+
},
110+
{
111+
title: 'Created By',
112+
field: 'created_by',
113+
render: (row: TaskRun) => {
114+
if (!row.created_by) return 'N/A';
115+
116+
// Parse user:development/guest format
117+
const userMatch = row.created_by.match(/^user:(.+)\/(.+)$/);
118+
if (userMatch) {
119+
const [, namespace, username] = userMatch;
120+
const userUrl = `${backendUrl}/catalog/${namespace}/user/${username}`;
121+
return (
122+
<Link to={userUrl}>
123+
user:{namespace}/{username}
124+
</Link>
125+
);
126+
}
127+
128+
// Fallback for other formats
129+
return row.created_by;
130+
},
131+
},
132+
];
133+
return (
134+
<InfoCard title="Task Runs">
135+
<Table
136+
options={{ paging: true, search: true }}
137+
columns={columns}
138+
data={taskRuns}
139+
/>
140+
</InfoCard>
141+
);
142+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { TemplateTaskRunsCard } from './TemplateTaskRunsCard';

plugins/usage-statistics/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export {
22
usageStatisticsPlugin,
33
TemplateUsageSummaryCard,
44
TemplateMonthlyStatsCard,
5+
TemplateTaskRunsCard,
56
} from './plugin';

plugins/usage-statistics/src/plugin.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,16 @@ export const TemplateMonthlyStatsCard = usageStatisticsPlugin.provide(
5454
},
5555
}),
5656
);
57+
58+
/** @public */
59+
export const TemplateTaskRunsCard = usageStatisticsPlugin.provide(
60+
createComponentExtension({
61+
name: 'TemplateTaskRunsCard',
62+
component: {
63+
lazy: () =>
64+
import('./components/Templates/TemplateTaskRunsCard').then(
65+
m => m.TemplateTaskRunsCard,
66+
),
67+
},
68+
}),
69+
);

yarn.lock

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5414,7 +5414,9 @@ __metadata:
54145414
"@testing-library/jest-dom": "npm:^6.0.0"
54155415
"@testing-library/react": "npm:^14.0.0"
54165416
"@testing-library/user-event": "npm:^14.0.0"
5417+
"@types/luxon": "npm:^3"
54175418
"@types/recharts": "npm:^2.0.1"
5419+
luxon: "npm:^3.7.1"
54185420
msw: "npm:^1.0.0"
54195421
react: "npm:^18.0.0 || ^20.0.0 || ^22.0.0"
54205422
react-use: "npm:^17.2.4"
@@ -14161,6 +14163,13 @@ __metadata:
1416114163
languageName: node
1416214164
linkType: hard
1416314165

14166+
"@types/luxon@npm:^3":
14167+
version: 3.7.1
14168+
resolution: "@types/luxon@npm:3.7.1"
14169+
checksum: 10c0/2db30c13b58adcd86daa447faa3ba59515fe907ead8ee3e6bb716d662812af0619d712f6c1eb190cdd7f9d2c00444c3ecd80af0f36e8143eb0c5e7339d6b2aca
14170+
languageName: node
14171+
linkType: hard
14172+
1416414173
"@types/luxon@npm:^3.0.0":
1416514174
version: 3.6.2
1416614175
resolution: "@types/luxon@npm:3.6.2"
@@ -25301,7 +25310,7 @@ __metadata:
2530125310
languageName: node
2530225311
linkType: hard
2530325312

25304-
"luxon@npm:^3.0.0, luxon@npm:^3.2.1":
25313+
"luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.7.1":
2530525314
version: 3.7.1
2530625315
resolution: "luxon@npm:3.7.1"
2530725316
checksum: 10c0/f83bc23a4c09da9111bc2510d2f5346e1ced4938379ebff13e308fece2ea852eb6e8b9ed8053b8d82e0fce05d5dd46e4cd64d8831ca04cfe32c0954b6f087258

0 commit comments

Comments
 (0)