TimeSaver logo

Backstage TimeSaver Plugin

Created by tduniec

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

  1. Add the package to the app
Copy
yarn add --cwd packages/app @tduniec/backstage-plugin-time-saver
  1. Open packages app src App.tsx
    Add the import
Copy
import { TimeSaverPage } from '@tduniec/backstage-plugin-time-saver';
  1. In the same file add the page route inside FlatRoutes
Copy
import { Route } from 'react-router-dom';

// inside <FlatRoutes>
<Route path="/time-saver" element={<TimeSaverPage />} />
  1. Open packages app src components Root Root.tsx
    Import the icon
Copy
import Timelapse from '@material-ui/icons/Timelapse';
  1. Add the sidebar item near the other SidebarItem entries
Copy
<SidebarItem icon={Timelapse} to="time-saver" text="timeSaver" />

Install the backend on the old backend system

  1. Add the package to the backend
Copy
yarn add --cwd packages/backend @tduniec/backstage-plugin-time-saver-backend
  1. Create packages backend src plugins timeSaver.ts
Copy
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,
  });
}
  1. Wire it in packages backend src index.ts
Copy
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

  1. Add the backend module in packages backend src index.ts
Copy
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.

  1. Add the package
Copy
yarn add --cwd packages/backend @tduniec/backstage-plugin-catalog-backend-module-time-saver-processor
  1. Old backend system
    Open packages backend src catalog.ts
    Add the processor
Copy
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
}
  1. New backend system
    Open packages backend src index.ts
    Add the module
Copy
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.

  1. Install in the app
Copy
yarn add --cwd packages/app @tduniec/backstage-plugin-time-saver-common
  1. Wrap the route in packages app src App.tsx
Copy
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>
  }
/>
  1. Wrap the sidebar item in packages app src components Root Root.tsx
Copy
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>
  1. If you use the old permissions backend add a rule in packages backend src plugins permission.ts
Copy
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

Copy
yarn add --cwd packages/backend @tduniec/backstage-plugin-time-saver-common

Configure the backend

Add optional settings in app config if you need them.

  1. Backend URL override for split deployments
Copy
ts:
  backendUrl: https://my-awesome-backstage.com
  1. Scheduler settings
Copy
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.

Copy
 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.

  1. Configure in app config
Copy
ts:
  backward:
    config: |
      [
        {
          "entityRef": "template:default/create-github-project",
          "engineering": {
            "devops": 8,
            "development_team": 8,
            "security": 3
          }
        }
      ]
  1. Or post a JSON body to the migrate endpoint
Copy
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.

  1. Get a sample with a GET request
Copy
curl "http://localhost:7007/api/time-saver/generate-sample-classification?useScaffolderTasksEntries=true"
  1. Post a custom sample template
Copy
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

  1. Set a custom header in App.tsx
Copy
<Route
  path="/time-saver"
  element={<TimeSaverPage
    title="Backstage TS plugin!"
    subtitle="Check saved time with TS plugin!"
    headerLabel={{ Owner: 'Sample Company', Lifecycle: 'production' }}
  />}
/>
  1. Show table values in days
Copy
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