CI CD Statistics gives you a clear view of your build pipelines. It pulls build data for a single service in the Software Catalog. Then it shows trends over time. You see build durations. You see counts by status. You can switch between main branch and feature branches. You can focus on a single stage or drill into nested stages. This helps you spot bottlenecks and track fixes over weeks or months. It also helps teams discuss outcomes with real numbers, not guesses.The plugin renders charts on the entity page. It relies on your own CI source through a small API. That keeps the UI generic while you decide where builds come from. Common uses include finding slow steps, comparing branches, and watching success rates during migrations or refactors.
Spotify publishes the plugin to the Backstage ecosystem. Their page describes it as “Effortlessly track and visualize CI CD pipeline statistics such as build time or success and error rates.” This matches the goal of bringing pipeline facts into one place for each service.
Teams often connect it to providers like GitLab or Buildkite through community modules. That lets you reuse existing CI data without changing your pipelines. If you run Backstage in your own stack, this plugin adds pipeline insight where your developers already work.
Installation Instructions
These instructions apply to self-hosted Backstage only.
Install the frontend package
- From your repository root, add the plugin to the app package
yarn workspace app add @backstage-community/plugin-cicd-statistics
Provide a CicdStatisticsApi implementation
This plugin does not fetch builds by itself. You must provide an API client that implements CicdStatisticsApi and bind it to cicdStatisticsApiRef.
-
Create a client in your app
- File path
packages/app/src/plugins/cicdStatisticsApi.ts
- Code
import { CicdStatisticsApi, cicdStatisticsApiRef, statusTypes, ChartTypes, FetchBuildsOptions, CicdState, } from '@backstage-community/plugin-cicd-statistics'; import { FetchApi, fetchApiRef, } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; // Replace the fetch logic with calls to your CI system export class MyCicdStatisticsClient implements CicdStatisticsApi { constructor(private readonly fetchApi: FetchApi) {} async getConfiguration(_opts: { entity: Entity }) { const chartTypesAllStatuses: Record<string, ChartTypes> = { succeeded: ['duration', 'count'], failed: ['duration', 'count'], running: ['count'], aborted: ['count'], unknown: ['count'], scheduled: ['count'], enqueued: ['count'], stalled: ['count'], expired: ['count'], }; return { availableStatuses: statusTypes, defaults: { normalizeTimeRange: true, lowercaseNames: true, hideLimit: 6, collapsedLimit: 10, chartTypes: chartTypesAllStatuses as any, filterStatus: ['succeeded', 'failed'], filterType: 'all', }, formatStageName: (_parents: string[], name: string) => name, }; } async fetchBuilds(options: FetchBuildsOptions): Promise<CicdState> { const { entity, timeFrom, timeTo, filterStatus, filterType, abortSignal, updateProgress, } = options; updateProgress(0, 1); // Example stub // Replace this with a call to your CI provider or an internal service const response = await this.fetchApi.fetch('/api/cicd-stats', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entityRef: `${entity.kind}:${entity.metadata.namespace ?? 'default'}/${entity.metadata.name}`, timeFrom, timeTo, filterStatus, filterType, }), signal: abortSignal, }); // Expected structure // { builds: Array<Build> } const data = await response.json(); updateProgress(1, 1); return { builds: data.builds ?? [] }; } } // Helper for factory wiring export const cicdStatisticsApiFactory = { api: cicdStatisticsApiRef, deps: { fetchApi: fetchApiRef }, factory: ({ fetchApi }: { fetchApi: FetchApi }) => new MyCicdStatisticsClient(fetchApi), };
- File path
-
Register the API in your app API registry
- File path
packages/app/src/apis.ts
- Code
import { createApiFactory } from '@backstage/core-app-api'; import { cicdStatisticsApiFactory } from './plugins/cicdStatisticsApi'; export const apis = [ createApiFactory(cicdStatisticsApiFactory), ];
- File path
-
Ensure your app uses the apis export
- File path
packages/app/src/App.tsx
- Code
import React from 'react'; import { createApp } from '@backstage/core-app-api'; import { apis } from './apis'; import { FlatRoutes } from '@backstage/core-app-api'; import { Route } from 'react-router'; import { AppRouter } from './components/AppRouter'; const app = createApp({ apis, }); const AppProvider = app.getProvider(); const AppRoutes = app.getRoutes(); export default function App() { return ( <AppProvider> <AppRouter> <FlatRoutes> {AppRoutes} <Route path="/" element={null} /> </FlatRoutes> </AppRouter> </AppProvider> ); }
- File path
Add the UI to the Catalog entity page
The component reads the current entity via useEntity. Add it to the Software Catalog entity page so users can see charts for each entity.
- Import the component
- File path
packages/app/src/components/catalog/EntityPage.tsx
- Code
import React from 'react'; import { EntityLayout, EntitySwitch, isKind, hasCatalogProcessingErrors, EntityProcessingErrorsPanel, } from '@backstage/plugin-catalog'; import { EntityAboutCard } from '@backstage/plugin-catalog'; import { EntityCicdStatisticsContent } from '@backstage-community/plugin-cicd-statistics'; const isComponent = isKind('Component'); export const EntityPage = () => ( <EntitySwitch> <EntitySwitch.Case if={hasCatalogProcessingErrors}> <EntityProcessingErrorsPanel /> </EntitySwitch.Case> <EntitySwitch.Case if={isComponent}> <EntityLayout> <EntityLayout.Route path="overview" title="Overview"> <EntityAboutCard /> </EntityLayout.Route> {/* New tab for CI CD statistics */} <EntityLayout.Route path="ci-cd" title="CI CD"> <EntityCicdStatisticsContent /> </EntityLayout.Route> </EntityLayout> </EntitySwitch.Case> <EntitySwitch.DefaultCase> <EntityLayout> <EntityLayout.Route path="overview" title="Overview"> <EntityAboutCard /> </EntityLayout.Route> </EntityLayout> </EntitySwitch.DefaultCase> </EntitySwitch> );
- File path
Wire the plugin object in legacy apps
This step is optional for most apps, but safe. It helps keep plugin features discoverable.
- Add the plugin object to the app setup
- File path
packages/app/src/App.tsx
- Code
import { createApp } from '@backstage/core-app-api'; import { cicdStatisticsPlugin } from '@backstage-community/plugin-cicd-statistics'; import { apis } from './apis'; const app = createApp({ apis, plugins: [ cicdStatisticsPlugin, ], });
- File path
Backend setup
There is no backend package for this plugin. You do not need to install anything for the legacy backend system. You do not need to install anything for the new backend system.
Your CicdStatisticsApi implementation can call your own backend service or your CI provider. The plugin only needs the data. How you fetch it is up to you.
Changelog
This changelog is produced from commits made to the CI/CD Statistics 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.
Features
- Add GitHub support for the CI CD statistics plugin #3227 merged 6 months ago
Maintenance
- Reduce false positives in knip reports by updating Backstage repo tools to 0.13.0 with a single workspace based config #3018 merged 6 months ago
- Remove unused dependencies in CI CD statistics packages. Also remove a few in the buildkite module and the gitlab module. Add a script to rebuild knip reports #2434 merged 9 months ago
Breaking changes
- None
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.