Skip to content

feat: add DevLake DORA Metrics plugin#8364

Open
mikilior wants to merge 11 commits intobackstage:mainfrom
mikilior:feat/devlake-dora-metrics-plugin
Open

feat: add DevLake DORA Metrics plugin#8364
mikilior wants to merge 11 commits intobackstage:mainfrom
mikilior:feat/devlake-dora-metrics-plugin

Conversation

@mikilior
Copy link
Copy Markdown

DevLake DORA Metrics Plugin

Closes #8189

Why this plugin matters

Engineering teams today face a fundamental challenge: measuring what actually matters. Lines of code, story points, and velocity tell you how busy a team is — not how effective it is. The DORA research program, backed by over a decade of data from thousands of engineering organizations, identified four metrics that reliably predict both software delivery performance and organizational outcomes:

Metric What it measures
Deployment Frequency How often code reaches production
Lead Time for Changes Time from commit to production
Change Failure Rate Percentage of deployments causing incidents
Mean Time to Recovery How fast teams recover from failures

Elite-performing teams don't just ship faster — they ship with higher quality, lower burnout, and better business outcomes. DORA metrics give engineering leadership an objective, evidence-based language for that conversation.

The gap, however, is access. Most teams collect the raw data — in CI/CD systems, Git providers, and incident trackers — but it lives in silos. Making sense of it requires either expensive tooling or significant engineering investment.

Apache DevLake solves the collection and normalization problem: it ingests data from GitHub, GitLab, Jenkins, PagerDuty, Jira, and more into a unified domain model, and computes DORA metrics out of the box. It is 100% open-source, CNCF-incubating, and production-ready.

This plugin brings those metrics directly into Backstage — the developer portal where engineers already spend their time. Instead of opening a separate Grafana dashboard, teams can see DORA performance at a glance: on a dedicated metrics page, and inline on every service's entity page. It closes the feedback loop between the software catalog and engineering effectiveness data, making DORA metrics a first-class citizen in the inner loop of development.


Packages

Package Role
@backstage-community/plugin-devlake Frontend plugin
@backstage-community/plugin-devlake-backend Backend plugin
@backstage-community/plugin-devlake-common Shared types and DORA classification logic

Features

DoraMetricsPage — a full standalone dashboard page:

  • Team selector (driven by app-config.yaml)
  • Time range presets: 7D, 30D, 90D, Quarter, 1Y + custom date picker
  • Four metric cards: Deployment Frequency, Lead Time for Changes, Change Failure Rate, MTTR
  • DORA level badges (Elite / High / Medium / Low) with color coding
  • Trend line charts per metric
  • "All teams" aggregate view

EntityDoraCard — a compact card for entity pages:

  • Annotate any entity with devlake.io/project-name to show its DORA metrics inline
  • 2×2 grid of metrics with DORA level chips
  • Optional Grafana deep-link per entity (configurable via devlake.grafana.baseUrl)
  • Falls back to MissingAnnotationEmptyState when the annotation is absent

Both components support the New Frontend System extension points via the /alpha entry point (EntityCardBlueprint, PageBlueprint, ApiBlueprint) — no manual route registration in App.tsx is required.


Technical Design

Why direct MySQL instead of the DevLake REST API?

DevLake v1.x exposes a Grafana-style dashboard API but no DORA REST endpoint suitable for programmatic consumption. The backend plugin queries DevLake's MySQL domain-layer tables directly with read-only credentials. This is the same data source Grafana uses.

Backend data flow

Backstage backend
  └─ devlake-backend plugin
       └─ DevlakeDbClient (mysql2/promise, read-only pool)
            └─ DevLake MySQL DB (domain layer tables)

The backend exposes three endpoints under /api/devlake:

Endpoint Description
GET /health Liveness check
GET /teams Returns configured teams + synthetic "All" entry
GET /dora/metrics?team=&preset= Returns all 4 DORA metrics for the given team and time range
GET /dora/metrics/trend?team=&preset= Returns daily trend data points for charting

Time range can be specified as a preset (7d, 30d, 90d, quarter, 1y) or explicit from/to date strings. team=All queries across all projects.

Key domain tables queried

Table Used for
cicd_deployment_commits Deployment Frequency, Change Failure Rate
project_mapping Joining scopes/repos to projects
pull_requests + project_pr_metrics Lead Time for Changes (median PR cycle time)
incidents + project_incident_deployment_relationships Change Failure Rate, MTTR

DORA level thresholds

Metric Elite High Medium Low
Deployment Frequency ≥1/day ≥1/week ≥1/month <1/month
Lead Time <1h <1 day <1 week ≥1 week
Change Failure Rate <5% <10% <15% ≥15%
MTTR <1h <1 day <1 week ≥1 week

Frontend architecture

DoraMetricsPage / EntityDoraCard
  └─ DevlakeApi (ApiRef)
       └─ DevlakeClientImpl
            └─ Backstage discovery API → /api/devlake/*

Installation

1. Add the packages

yarn --cwd packages/app add @backstage-community/plugin-devlake
yarn --cwd packages/backend add @backstage-community/plugin-devlake-backend

2. Register the backend plugin

// packages/backend/src/index.ts
backend.add(import('@backstage-community/plugin-devlake-backend'));

3. Configure app-config.yaml

devlake:
  baseUrl: http://localhost:4000

  db:
    host: ${DEVLAKE_DB_HOST}
    port: 3306
    user: ${DEVLAKE_DB_USER}
    password: ${DEVLAKE_DB_PASSWORD}
    database: devlake
    ssl: true

  teams:
    - name: Platform
      devlakeProjectName: platform-backend   # must match the project name in DevLake
    - name: Mobile
      devlakeProjectName: mobile-app

  # Optional: deep-link entity cards to a Grafana DORA dashboard
  grafana:
    baseUrl: https://grafana.example.com
    doraDashboardPath: /d/qNo8_0M4z/dora    # default, can be omitted

Note: devlakeProjectName must exactly match the project name in DevLake (visible under Projects in the DevLake UI). It is separate from the display name shown in Backstage.

4a. Legacy system — add the page route

// packages/app/src/App.tsx
import { DoraMetricsPage } from '@backstage-community/plugin-devlake';

<Route path="/devlake" element={<DoraMetricsPage />} />

4b. New Frontend System — no route registration needed

import devlakePlugin from '@backstage-community/plugin-devlake/alpha';
// add to your app's features array

5. Add EntityDoraCard to entity pages

Legacy system:

import { EntityDoraCard, isDevlakeAvailable } from '@backstage-community/plugin-devlake';

<EntitySwitch>
  <EntitySwitch.Case if={isDevlakeAvailable}>
    <Grid item md={4}>
      <EntityDoraCard />
    </Grid>
  </EntitySwitch.Case>
</EntitySwitch>

New Frontend System: the entityDoraCard extension from /alpha is auto-registered — no entity page changes required.

6. Annotate entities

# catalog-info.yaml
metadata:
  annotations:
    devlake.io/project-name: platform-backend

Test plan

  • yarn tsc passes with no errors
  • 81 tests across 12 suites — all passing
    • Backend: 17 tests (router, DevlakeDbClient, config parsing)
    • Frontend: 60 tests (API client, all components, EntityDoraCard, DoraMetricsPage)
    • Common: 4 tests (DORA classification thresholds)
  • DoraMetricsPage renders at /devlake with a running DevLake instance
  • EntityDoraCard renders on an annotated entity
  • EntityDoraCard shows MissingAnnotationEmptyState when annotation is absent

mikilior and others added 6 commits March 23, 2026 23:29
Add new workspace with three packages:
- @backstage-community/plugin-devlake: Frontend dashboard with DORA metric cards and trend charts
- @backstage-community/plugin-devlake-backend: Backend plugin proxying Apache DevLake API
- @backstage-community/plugin-devlake-common: Shared types and DORA level classifiers

Closes backstage#8189

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: mikilior <mikilior@gmail.com>
- Replace REST API client with direct MySQL queries via mysql2
- Query DevLake domain layer tables (cicd_deployment_commits, project_pr_metrics, incidents)
- Add "All" team option to aggregate across all projects
- Add 1Y time range preset
- Remove PRODUCTION environment filter (environments are not standardized)
- Use pr.merged_date for lead time (not deployment date)
- Add helpful no-data messages per metric type
- Export MetricCard, MetricChart, TeamSelector, TimeRangeSelector components
- Update API reports and tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: mikilior <mikilior@gmail.com>
Add alpha.ts and src/alpha/ to support the Backstage New Frontend System:
- PageBlueprint wraps DoraMetricsPage via compatWrapper (no App.tsx route needed)
- ApiBlueprint registers devlakeApiRef via the NFS DI system
- Plugin is auto-discoverable and path is configurable via app-config

Adopters on the legacy system are unaffected (src/index.ts unchanged).

Signed-off-by: mikilior <mikilior@gmail.com>
Adds a compact DORA metrics card that renders on entity pages annotated
with `devlake.io/project-name`. Supports both the legacy and New Frontend
System extension points.

- EntityDoraCard component: 2x2 grid of DORA metrics with level chips
- MissingAnnotationEmptyState when annotation is absent
- Optional Grafana deep-link per project via app-config
  (devlake.grafana.baseUrl / devlake.grafana.doraDashboardPath)
- EntityCardBlueprint NFS wiring (entityDoraCard)
- Legacy createComponentExtension wiring in plugin.ts
- Exports: EntityDoraCard, EntityDoraCardProps, isDevlakeAvailable,
  DEVLAKE_PROJECT_NAME_ANNOTATION
- New deps: @backstage/catalog-model, @backstage/plugin-catalog-react
- config.d.ts updated with grafana config schema

Signed-off-by: mikilior <mikilior@gmail.com>
Adds 9 new test files covering the frontend plugin, backend plugin,
and common utilities. Total: 81 tests across 12 suites.

Frontend:
- constants.test.ts: isDevlakeAvailable predicate + annotation constant
- api/DevlakeClient.test.ts: all 3 API methods (success + error paths)
- components/TeamSelector: render with teams and selected value
- components/TimeRangeSelector: preset buttons, date inputs, callbacks
- components/MetricChart: render with mocked recharts
- components/EntityDoraCard: missing annotation, loading, metrics,
  N/A state, Grafana link, error panel, API call params
- pages/DoraMetricsPage: title, metric cards, error handling

Backend:
- service/DevlakeDbClient.test.ts: pool creation, getDoraMetrics
  (4 parallel queries, null rows, level classification),
  getDoraTrend, close()
- types/config.test.ts: readDevlakeConfig (db fields, defaults,
  teams, missing config errors)

Signed-off-by: mikilior <mikilior@gmail.com>
Signed-off-by: mikilior <mikilior@gmail.com>
@mikilior mikilior requested review from a team and backstage-service as code owners March 28, 2026 18:52
@mikilior mikilior requested a review from vinzscam March 28, 2026 18:52
@backstage-goalie
Copy link
Copy Markdown
Contributor

backstage-goalie bot commented Mar 28, 2026

Changed Packages

Package Name Package Path Changeset Bump Current Version
@backstage-community/plugin-devlake-backend workspaces/devlake/plugins/devlake-backend minor v0.1.0
@backstage-community/plugin-devlake-common workspaces/devlake/plugins/devlake-common minor v0.1.0
@backstage-community/plugin-devlake workspaces/devlake/plugins/devlake minor v0.1.0

Signed-off-by: mikilior <mikilior@gmail.com>
Signed-off-by: mikilior <mikilior@gmail.com>
- Remove unused renderCard variable (TS6133 noUnusedLocals)
- Add @types/lodash devDependency to fix recharts type resolution (TS7016)
- Add NoInfer<T> polyfill in types/global.d.ts for TS 5.3 compat (TS2304)
- Override knex resolution via tsconfig paths to CJS types so
  @backstage/backend-plugin-api's { Knex } import resolves correctly
  with moduleResolution: bundler (TS2724)
- Exclude types/ dir from ESLint (declaration files need no linting)

Signed-off-by: mikilior <mikilior@gmail.com>
- Remove @material-ui/lab (unused — only @material-ui/core and icons used)
- Remove msw (unused — API is mocked via jest.fn() in all tests)
- Replace @types/lodash devDep with a minimal ambient module stub in
  types/global.d.ts, satisfying recharts' DebouncedFunc import under
  tsc --skipLibCheck false without an unnecessary direct dependency
- Commit generated knip-report.md (now clean — no unused deps)

Signed-off-by: mikilior <mikilior@gmail.com>
- Replace deprecated MissingAnnotationEmptyState from @backstage/core-components
  with the current version from @backstage/plugin-catalog-react
- Explicitly type EntityDoraCard as ComponentType<EntityDoraCardProps> to
  eliminate ae-forgotten-export warning from API Extractor (was inferring
  internal EntityDoraCard_2 type that API Extractor couldn't name)
- Add report-alpha.api.md for /alpha entry point (auto-generated)
- Update report.api.md with corrected EntityDoraCard signature

Signed-off-by: mikilior <mikilior@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🔌 Plugin: DORA Metrics Data Provider for Apache DevLake

1 participant