TimeSaver helps you measure real time saved in Backstage. It reads simple metadata in your software templates, sums the hours after each run, then turns it into clear charts. You can view totals across your catalog. You can drill into teams and templates. The result is a plain view of where templates remove busywork.
Use TimeSaver to answer practical questions. Which templates deliver the most value. Which teams benefit the most. How savings change over time. Use it to back platform decisions with numbers. Share progress with leaders. Find gaps where a new template could help.
Under the hood the backend collects and stores stats on a schedule. The frontend shows trends and summaries in one place. You can include older runs through a simple migration so the story starts before adoption. No guesswork. Just data you can trust.
Teams in the field report good results. Rackspace Technology writes that it offers comprehensive insights into time efficiencies gained from Backstage templates.
Installation Instructions
These instructions apply to self-hosted Backstage only.
Install the frontend
- Add the package to the app
yarn add --cwd packages/app @tduniec/backstage-plugin-time-saver
- Open packages app src App.tsx
Add the import
import { TimeSaverPage } from '@tduniec/backstage-plugin-time-saver';
- In the same file add the page route inside FlatRoutes
import { Route } from 'react-router-dom';
// inside <FlatRoutes>
<Route path="/time-saver" element={<TimeSaverPage />} />
- Open packages app src components Root Root.tsx
Import the icon
import Timelapse from '@material-ui/icons/Timelapse';
- Add the sidebar item near the other SidebarItem entries
<SidebarItem icon={Timelapse} to="time-saver" text="timeSaver" />
Install the backend on the old backend system
- Add the package to the backend
yarn add --cwd packages/backend @tduniec/backstage-plugin-time-saver-backend
- Create packages backend src plugins timeSaver.ts
import { createRouter } from '@tduniec/backstage-plugin-time-saver-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
database: env.database,
config: env.config,
scheduler: env.scheduler,
});
}
- Wire it in packages backend src index.ts
import timeSaver from './plugins/timeSaver';
// inside the main function where apiRouter is created
const timeSaverEnv = useHotMemoize(module, () => createEnv('timesaver'));
// mount the router
apiRouter.use('/time-saver', await timeSaver(timeSaverEnv));
// add your auth middleware here if you use one
Install the backend on the new backend system
- Add the backend module in packages backend src index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@tduniec/backstage-plugin-time-saver-backend'));
backend.start();
Optional install of the catalog processor
This updates catalog entities with a time saved annotation based on template metadata.
- Add the package
yarn add --cwd packages/backend @tduniec/backstage-plugin-catalog-backend-module-time-saver-processor
- Old backend system
Open packages backend src catalog.ts
Add the processor
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
import {
TimeSaverProcessor,
} from '@tduniec/backstage-plugin-catalog-backend-module-time-saver-processor';
export default async function createPlugin(env: PluginEnvironment): Promise<Router> {
const builder = CatalogBuilder.create(env);
builder.addProcessor(new TimeSaverProcessor(env.logger));
// the rest of your catalog setup
}
- New backend system
Open packages backend src index.ts
Add the module
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@tduniec/backstage-plugin-catalog-backend-module-time-saver-processor'));
backend.start();
Add permissions with the common package
You can gate the page and the sidebar with a permission.
- Install in the app
yarn add --cwd packages/app @tduniec/backstage-plugin-time-saver-common
- Wrap the route in packages app src App.tsx
import { RequirePermission } from '@backstage/plugin-permission-react';
import { timeSaverPermission } from '@tduniec/backstage-plugin-time-saver-common';
<Route
path="/time-saver"
element={
<RequirePermission permission={timeSaverPermission}>
<TimeSaverPage />
</RequirePermission>
}
/>
- Wrap the sidebar item in packages app src components Root Root.tsx
import { RequirePermission } from '@backstage/plugin-permission-react';
import { timeSaverPermission } from '@tduniec/backstage-plugin-time-saver-common';
<RequirePermission permission={timeSaverPermission} errorPage={<></>}>
<SidebarItem icon={Timelapse} to="time-saver" text="TimeSaver" />
</RequirePermission>
- If you use the old permissions backend add a rule in packages backend src plugins permission.ts
import { isPermission, AuthorizeResult } from '@backstage/plugin-permission-common';
import { timeSaverPermission } from '@tduniec/backstage-plugin-time-saver-common';
// inside your permission policy handler
if (isPermission(request.permission, timeSaverPermission)) {
if (isAdmin) {
return { result: AuthorizeResult.ALLOW };
}
return { result: AuthorizeResult.DENY };
}
If you need the permission symbol on the backend as well
yarn add --cwd packages/backend @tduniec/backstage-plugin-time-saver-common
Configure the backend
Add optional settings in app config if you need them.
- Backend URL override for split deployments
ts:
backendUrl: https://my-awesome-backstage.com
- Scheduler settings
ts:
scheduler:
parallelProcessing: 100
handler:
frequency: 'PT1H'
timeout: 'PT10M'
initialDelay: 'PT1M'
Default scheduler runs every five minutes with a thirty minute timeout and a thirty second initial delay.
Add template metadata for time savings
Add a substitute object under metadata in your templates. Values are hours saved per run.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: example-template
title: create-github-project
description: Creates Github project
+ substitute:
+ engineering:
+ devops: 1
+ security: 4
+ development_team: 2
spec:
owner: group:default/backstage-admins
type: service
The backend processes scaffolder tasks on a schedule and aggregates stats.
Migration support
You can backfill time savings for past runs.
- Configure in app config
ts:
backward:
config: |
[
{
"entityRef": "template:default/create-github-project",
"engineering": {
"devops": 8,
"development_team": 8,
"security": 3
}
}
]
- Or post a JSON body to the migrate endpoint
curl --header "Content-Type: application/json" \
--request POST \
--data '[{"entityRef":"template:default/create-github-project","engineering":{"devsecops":3,"developer":1}},{"entityRef":"template:default/create-golang-project","engineering":{"devsecops":1,"developer":2}}]' \
http://localhost:7007/api/time-saver/migrate
You can also generate a sample classification file.
- Get a sample with a GET request
curl "http://localhost:7007/api/time-saver/generate-sample-classification?useScaffolderTasksEntries=true"
- Post a custom sample template
curl --header "Content-Type: application/json" \
--request POST \
--data '{"customClassificationRequest":{"engineering":{"architecture":1}},"options":{"useScaffolderTasksEntries":true}}' \
http://localhost:7007/api/time-saver/generate-sample-classification
To exclude specific scaffolder tasks from all calculations put their task id values into the ts_exclude_tasks_everywhere table in your database.
Optional page customization
- Set a custom header in App.tsx
<Route
path="/time-saver"
element={<TimeSaverPage
title="Backstage TS plugin!"
subtitle="Check saved time with TS plugin!"
headerLabel={{ Owner: 'Sample Company', Lifecycle: 'production' }}
/>}
/>
- Show table values in days
ts:
frontend:
table:
showInDays: true
hoursPerDay: 8
Changelog
This changelog is produced from commits made to the TimeSaver plugin since a year ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.
Breaking changes
- Upgrade UI properties to align with REST API responses. Includes backend ROLE column migration. Plan a migration before update #38 11 months ago
Features
- Add frontend customization options #46 9 months ago
- Add units to the table view #43 11 months ago
- Add configurable scheduler #42 11 months ago
- Add date filters panel. Add SQL builder. Support light mode and dark mode. Keep selected dates visible. Panel is expandable #40 11 months ago
Performance
- Improve memory use and CPU use #50 5 months ago
Bug fixes
- Fix SQL issues. Improve API responses to return empty arrays to avoid iteration errors #36 11 months ago
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.