@c2l2c/backstage-plugin-dora-metrics 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,17 +1,27 @@
1
1
  {
2
2
  "name": "@c2l2c/backstage-plugin-dora-metrics",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "A Backstage frontend plugin that displays DORA metrics (Deployment Frequency, Lead Time, Change Failure Rate, MTTR) for catalog entities via the GitHub API.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "backstage": {
9
- "role": "frontend-plugin"
9
+ "pluginId": "dora-metrics",
10
+ "role": "frontend-plugin",
11
+ "pluginPackages": [
12
+ "@c2l2c/backstage-plugin-dora-metrics"
13
+ ]
10
14
  },
11
15
  "configSchema": "config.d.ts",
12
16
  "exports": {
13
- ".": "./src/index.ts",
14
- "./alpha": "./src/alpha.ts",
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.esm.js"
20
+ },
21
+ "./alpha": {
22
+ "types": "./dist/alpha.d.ts",
23
+ "default": "./dist/alpha.esm.js"
24
+ },
15
25
  "./package.json": "./package.json"
16
26
  },
17
27
  "publishConfig": {
@@ -38,7 +48,8 @@
38
48
  "lint": "backstage-cli package lint",
39
49
  "test": "backstage-cli package test",
40
50
  "clean": "backstage-cli package clean",
41
- "prepack": "yarn build"
51
+ "prepack": "backstage-cli package prepack",
52
+ "postpack": "backstage-cli package postpack"
42
53
  },
43
54
  "keywords": [
44
55
  "backstage",
@@ -67,7 +78,7 @@
67
78
  "@backstage/config": "^1.3.2",
68
79
  "@backstage/core-components": "^0.18.7",
69
80
  "@backstage/core-plugin-api": "^1.12.3",
70
- "@backstage/frontend-plugin-api": "^0.15.1",
81
+ "@backstage/frontend-plugin-api": "^0.16.2",
71
82
  "@backstage/plugin-catalog-react": "^2.0.0",
72
83
  "@material-ui/core": "^4.12.4",
73
84
  "@radix-ui/react-select": "^2.2.6",
package/dist/alpha.d.ts DELETED
@@ -1,60 +0,0 @@
1
- import * as _backstage_catalog_model from '@backstage/catalog-model';
2
- import * as react from 'react';
3
- import * as _backstage_filter_predicates from '@backstage/filter-predicates';
4
- import * as _backstage_frontend_plugin_api from '@backstage/frontend-plugin-api';
5
-
6
- declare const doraMetricsPlugin: _backstage_frontend_plugin_api.OverridableFrontendPlugin<{}, {}, {
7
- "api:dora-metrics/dora-metrics": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
8
- kind: "api";
9
- name: "dora-metrics";
10
- config: {};
11
- configInput: {};
12
- output: _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.AnyApiFactory, "core.api.factory", {}>;
13
- inputs: {};
14
- params: <TApi, TImpl extends TApi, TDeps extends { [name in string]: unknown; }>(params: _backstage_frontend_plugin_api.ApiFactory<TApi, TImpl, TDeps>) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<_backstage_frontend_plugin_api.AnyApiFactory>;
15
- }>;
16
- "entity-content:dora-metrics": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
17
- kind: "entity-content";
18
- name: undefined;
19
- config: {
20
- path: string | undefined;
21
- title: string | undefined;
22
- filter: _backstage_filter_predicates.FilterPredicate | undefined;
23
- group: string | false | undefined;
24
- icon: string | undefined;
25
- };
26
- configInput: {
27
- filter?: _backstage_filter_predicates.FilterPredicate | undefined;
28
- title?: string | undefined;
29
- path?: string | undefined;
30
- group?: string | false | undefined;
31
- icon?: string | undefined;
32
- };
33
- output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
34
- optional: true;
35
- }> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<(entity: _backstage_catalog_model.Entity) => boolean, "catalog.entity-filter-function", {
36
- optional: true;
37
- }> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "catalog.entity-filter-expression", {
38
- optional: true;
39
- }> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "catalog.entity-content-title", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "catalog.entity-content-group", {
40
- optional: true;
41
- }> | _backstage_frontend_plugin_api.ExtensionDataRef<string | react.ReactElement<any, string | react.JSXElementConstructor<any>>, "catalog.entity-content-icon", {
42
- optional: true;
43
- }>;
44
- inputs: {};
45
- params: {
46
- defaultPath?: [Error: `Use the 'path' param instead`];
47
- path: string;
48
- defaultTitle?: [Error: `Use the 'title' param instead`];
49
- title: string;
50
- defaultGroup?: [Error: `Use the 'group' param instead`];
51
- group?: ("overview" | "documentation" | "development" | "deployment" | "operation" | "observability") | (string & {});
52
- icon?: string | react.ReactElement;
53
- loader: () => Promise<JSX.Element>;
54
- routeRef?: _backstage_frontend_plugin_api.RouteRef;
55
- filter?: string | _backstage_filter_predicates.FilterPredicate | ((entity: _backstage_catalog_model.Entity) => boolean);
56
- };
57
- }>;
58
- }>;
59
-
60
- export { doraMetricsPlugin as default };
package/dist/alpha.esm.js DELETED
@@ -1,2 +0,0 @@
1
- export { doraMetricsPlugin as default } from './plugin.esm.js';
2
- //# sourceMappingURL=alpha.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"alpha.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,253 +0,0 @@
1
- function computeRating(value, target, lowerIsBetter) {
2
- if (lowerIsBetter) {
3
- if (target === 0) {
4
- if (value === 0) return "elite";
5
- if (value <= 2) return "high";
6
- if (value <= 5) return "medium";
7
- return "low";
8
- }
9
- if (value <= target) return "elite";
10
- if (value <= target * 2) return "high";
11
- if (value <= target * 4) return "medium";
12
- return "low";
13
- }
14
- if (value >= target) return "elite";
15
- if (value >= target * 0.7) return "high";
16
- if (value >= target * 0.4) return "medium";
17
- return "low";
18
- }
19
- function toPrDetail(pr, durationHours) {
20
- return {
21
- number: pr.number,
22
- title: pr.title,
23
- url: pr.html_url,
24
- durationHours: Math.round(durationHours * 10) / 10,
25
- mergedAt: pr.merged_at,
26
- author: pr.user?.login,
27
- authorAvatar: pr.user?.avatar_url
28
- };
29
- }
30
- function isRegexPattern(pattern) {
31
- return /[|^$[\](){}?+*\\]/.test(pattern);
32
- }
33
- function matchesLabel(prLabels, labelPattern) {
34
- if (!labelPattern) return false;
35
- const names = prLabels.map((l) => l.name);
36
- if (isRegexPattern(labelPattern)) {
37
- const regex = new RegExp(labelPattern);
38
- return names.some((n) => regex.test(n));
39
- }
40
- const patterns = labelPattern.split(",").map((l) => l.trim()).filter(Boolean);
41
- return names.some((n) => patterns.includes(n));
42
- }
43
- async function resolveBranches(owner, repo, branchPattern, fetchFn) {
44
- if (isRegexPattern(branchPattern)) {
45
- const repoBranches = await fetchFn(
46
- `https://api.github.com/repos/${owner}/${repo}/branches?per_page=100`
47
- );
48
- const regex = new RegExp(branchPattern);
49
- return repoBranches.map((b) => b.name).filter((name) => regex.test(name));
50
- }
51
- return branchPattern.split(",").map((b) => b.trim()).filter(Boolean);
52
- }
53
- class DoraMetricsClient {
54
- githubAuthApi;
55
- configApi;
56
- constructor(githubAuthApi, configApi) {
57
- this.githubAuthApi = githubAuthApi;
58
- this.configApi = configApi;
59
- }
60
- getConfig() {
61
- const appCfg = this.configApi.getConfig("app");
62
- const cfg = appCfg.getOptionalConfig("doraMetrics");
63
- const rawEnvs = cfg?.getOptionalConfigArray("environments") ?? [];
64
- const environments = rawEnvs.map((e) => ({
65
- name: e.getString("name"),
66
- branch: e.getString("branch"),
67
- isProduction: e.getOptionalBoolean("isProduction") ?? false,
68
- label: e.getOptionalString("label") ?? "hotfix"
69
- }));
70
- const prodEnvs = environments.filter((e) => e.isProduction);
71
- if (prodEnvs.length > 1) {
72
- throw new Error(
73
- `DORA metrics config error: only one environment may have isProduction: true, but found ${prodEnvs.length}: ${prodEnvs.map((e) => e.name).join(", ")}`
74
- );
75
- }
76
- const initialDays = cfg?.getOptionalConfig("collection")?.getOptionalNumber("initialDays") ?? 30;
77
- const targetsCfg = cfg?.getOptionalConfig("targets");
78
- const targets = {
79
- deploymentFrequency: targetsCfg?.getOptionalNumber("deploymentFrequency") ?? 7,
80
- leadTime: targetsCfg?.getOptionalNumber("leadTime") ?? 24,
81
- changeFailureRate: targetsCfg?.getOptionalNumber("changeFailureRate") ?? 15,
82
- mttr: targetsCfg?.getOptionalNumber("mttr") ?? 1
83
- };
84
- return { environments, initialDays, targets };
85
- }
86
- getEnvironments() {
87
- return this.getConfig().environments;
88
- }
89
- getDefaultDays() {
90
- return this.getConfig().initialDays;
91
- }
92
- getTargets() {
93
- return this.getConfig().targets;
94
- }
95
- async fetchWithAuth(url, token) {
96
- const response = await fetch(url, {
97
- headers: {
98
- Authorization: `token ${token}`,
99
- Accept: "application/vnd.github.v3+json"
100
- }
101
- });
102
- if (!response.ok) {
103
- throw new Error(
104
- `GitHub API request failed: ${response.status} ${response.statusText} \u2014 ${url}`
105
- );
106
- }
107
- return response.json();
108
- }
109
- /**
110
- * Fetch all merged PRs for an environment, resolving branch patterns and
111
- * deduplicating across multiple branches by PR number.
112
- */
113
- async fetchMergedPRs(owner, repo, env, cutoff, token) {
114
- const boundFetch = (url) => this.fetchWithAuth(url, token);
115
- const branches = await resolveBranches(owner, repo, env.branch, boundFetch);
116
- const allByNumber = /* @__PURE__ */ new Map();
117
- await Promise.all(
118
- branches.map(async (branch) => {
119
- const url = `https://api.github.com/repos/${owner}/${repo}/pulls?state=closed&base=${branch}&per_page=100`;
120
- const prs = await boundFetch(url);
121
- for (const pr of prs) {
122
- if (pr.merged_at && new Date(pr.merged_at) >= cutoff) {
123
- if (!allByNumber.has(pr.number)) {
124
- allByNumber.set(pr.number, pr);
125
- }
126
- }
127
- }
128
- })
129
- );
130
- return Array.from(allByNumber.values());
131
- }
132
- async getMetrics(projectSlug, env, days, targetsOverride) {
133
- const token = await this.githubAuthApi.getAccessToken("repo");
134
- const { targets: configTargets } = this.getConfig();
135
- const targets = { ...configTargets, ...targetsOverride };
136
- const [owner, repo] = projectSlug.split("/");
137
- if (!owner || !repo) {
138
- throw new Error(`Invalid project slug "${projectSlug}". Expected "owner/repo".`);
139
- }
140
- const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
141
- const mergedPRs = await this.fetchMergedPRs(owner, repo, env, cutoff, token);
142
- const deploymentsPerWeek = days > 0 ? mergedPRs.length / days * 7 : 0;
143
- const deploymentFrequency = {
144
- value: Math.round(deploymentsPerWeek * 100) / 100,
145
- unit: "per week",
146
- rating: computeRating(deploymentsPerWeek, targets.deploymentFrequency, false),
147
- target: targets.deploymentFrequency,
148
- // All merged PRs sorted newest-first — used in expanded view date table
149
- slowestPRs: [...mergedPRs].sort((a, b) => new Date(b.merged_at).getTime() - new Date(a.merged_at).getTime()).map((pr) => toPrDetail(
150
- pr,
151
- (new Date(pr.merged_at).getTime() - new Date(pr.created_at).getTime()) / (1e3 * 60 * 60)
152
- ))
153
- };
154
- const prsWithLeadTime = mergedPRs.map((pr) => ({
155
- pr,
156
- durationHours: (new Date(pr.merged_at).getTime() - new Date(pr.created_at).getTime()) / (1e3 * 60 * 60)
157
- })).sort((a, b) => b.durationHours - a.durationHours);
158
- const avgLeadTimeHours = prsWithLeadTime.length > 0 ? prsWithLeadTime.reduce((sum, p) => sum + p.durationHours, 0) / prsWithLeadTime.length : 0;
159
- const leadTime = {
160
- value: Math.round(avgLeadTimeHours * 10) / 10,
161
- unit: "hours",
162
- rating: computeRating(avgLeadTimeHours, targets.leadTime, true),
163
- target: targets.leadTime,
164
- slowestPRs: prsWithLeadTime.slice(0, 10).map((p) => toPrDetail(p.pr, p.durationHours))
165
- };
166
- if (!env.isProduction) {
167
- return {
168
- deploymentFrequency,
169
- leadTime,
170
- changeFailureRate: null,
171
- mttr: null,
172
- numberOfHotfixes: null
173
- };
174
- }
175
- const hotfixPRs = mergedPRs.filter(
176
- (pr) => matchesLabel(pr.labels ?? [], env.label)
177
- );
178
- const numberOfHotfixes = {
179
- value: hotfixPRs.length,
180
- unit: "PRs",
181
- rating: computeRating(hotfixPRs.length, 0, true),
182
- target: 0,
183
- slowestPRs: [...hotfixPRs].sort((a, b) => new Date(b.merged_at).getTime() - new Date(a.merged_at).getTime()).map((pr) => toPrDetail(
184
- pr,
185
- (new Date(pr.merged_at).getTime() - new Date(pr.created_at).getTime()) / (1e3 * 60 * 60)
186
- ))
187
- };
188
- const changeFailureRatePct = mergedPRs.length > 0 ? hotfixPRs.length / mergedPRs.length * 100 : 0;
189
- const changeFailureRate = {
190
- value: Math.round(changeFailureRatePct * 10) / 10,
191
- unit: "%",
192
- rating: computeRating(changeFailureRatePct, targets.changeFailureRate, true),
193
- target: targets.changeFailureRate
194
- };
195
- const hotfixWithDuration = hotfixPRs.map((pr) => ({
196
- pr,
197
- durationHours: (new Date(pr.merged_at).getTime() - new Date(pr.created_at).getTime()) / (1e3 * 60 * 60)
198
- })).sort((a, b) => b.durationHours - a.durationHours);
199
- const avgMttrHours = hotfixWithDuration.length > 0 ? hotfixWithDuration.reduce((sum, p) => sum + p.durationHours, 0) / hotfixWithDuration.length : 0;
200
- const mttr = {
201
- value: Math.round(avgMttrHours * 10) / 10,
202
- unit: "hours",
203
- rating: computeRating(avgMttrHours, targets.mttr, true),
204
- target: targets.mttr,
205
- slowestPRs: hotfixWithDuration.slice(0, 10).map((p) => toPrDetail(p.pr, p.durationHours))
206
- };
207
- return { deploymentFrequency, leadTime, changeFailureRate, mttr, numberOfHotfixes };
208
- }
209
- async getHistory(projectSlug, env, days) {
210
- const token = await this.githubAuthApi.getAccessToken("repo");
211
- const [owner, repo] = projectSlug.split("/");
212
- if (!owner || !repo) throw new Error(`Invalid project slug "${projectSlug}".`);
213
- const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
214
- const mergedPRs = await this.fetchMergedPRs(owner, repo, env, cutoff, token);
215
- const bucketDays = Math.max(1, Math.round(days / 7));
216
- const numBuckets = Math.max(2, Math.ceil(days / bucketDays));
217
- const buckets = [];
218
- for (let w = numBuckets - 1; w >= 0; w--) {
219
- const weekEnd = new Date(Date.now() - w * bucketDays * 24 * 60 * 60 * 1e3);
220
- const weekStart = new Date(weekEnd.getTime() - bucketDays * 24 * 60 * 60 * 1e3);
221
- const weekPRs = mergedPRs.filter((pr) => {
222
- const t = new Date(pr.merged_at).getTime();
223
- return t >= weekStart.getTime() && t < weekEnd.getTime();
224
- });
225
- const leadTimes = weekPRs.map(
226
- (pr) => (new Date(pr.merged_at).getTime() - new Date(pr.created_at).getTime()) / (1e3 * 60 * 60)
227
- );
228
- const avgLeadTime = leadTimes.length > 0 ? leadTimes.reduce((a, b) => a + b, 0) / leadTimes.length : 0;
229
- let changeFailureRate;
230
- let mttrHours;
231
- if (env.isProduction) {
232
- const hotfixPRs = weekPRs.filter((pr) => matchesLabel(pr.labels, env.label));
233
- changeFailureRate = weekPRs.length > 0 ? Math.round(hotfixPRs.length / weekPRs.length * 1e3) / 10 : 0;
234
- const hotfixDurations = hotfixPRs.map(
235
- (pr) => (new Date(pr.merged_at).getTime() - new Date(pr.created_at).getTime()) / (1e3 * 60 * 60)
236
- );
237
- mttrHours = hotfixDurations.length > 0 ? Math.round(hotfixDurations.reduce((a, b) => a + b, 0) / hotfixDurations.length * 10) / 10 : 0;
238
- }
239
- buckets.push({
240
- weekLabel: weekStart.toLocaleDateString("en-US", { month: "short", day: "numeric" }),
241
- bucketMidMs: Math.round((weekStart.getTime() + weekEnd.getTime()) / 2),
242
- deploymentCount: weekPRs.length,
243
- leadTimeHours: Math.round(avgLeadTime * 10) / 10,
244
- changeFailureRate,
245
- mttrHours
246
- });
247
- }
248
- return buckets;
249
- }
250
- }
251
-
252
- export { DoraMetricsClient };
253
- //# sourceMappingURL=DoraMetricsClient.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DoraMetricsClient.esm.js","sources":["../../src/api/DoraMetricsClient.ts"],"sourcesContent":["import { ConfigApi, OAuthApi } from '@backstage/core-plugin-api';\nimport {\n DoraEnvironment,\n DoraHistoryPoint,\n DoraMetrics,\n DoraMetricValue,\n DoraMetricsApi,\n DoraRating,\n DoraTargets,\n PrDetail,\n} from './types';\n\ninterface GitHubPR {\n number: number;\n title: string;\n html_url: string;\n created_at: string;\n merged_at: string | null;\n closed_at: string | null;\n labels: Array<{ name: string }>;\n user: { login: string; avatar_url: string } | null;\n}\n\n/**\n * Compute a DORA rating based on the configured target.\n *\n * lowerIsBetter (lead time, CFR, MTTR, hotfix count):\n * elite = value <= target\n * high = value <= target * 2\n * medium = value <= target * 4\n * low = value > target * 4\n *\n * higherIsBetter (deployment frequency):\n * elite = value >= target\n * high = value >= target * 0.7\n * medium = value >= target * 0.4\n * low = value < target * 0.4\n *\n * Special case — target = 0 (hotfix count ideal = 0):\n * elite=0, high≤2, medium≤5, low>5\n */\nfunction computeRating(value: number, target: number, lowerIsBetter: boolean): DoraRating {\n if (lowerIsBetter) {\n if (target === 0) {\n if (value === 0) return 'elite';\n if (value <= 2) return 'high';\n if (value <= 5) return 'medium';\n return 'low';\n }\n if (value <= target) return 'elite';\n if (value <= target * 2) return 'high';\n if (value <= target * 4) return 'medium';\n return 'low';\n }\n if (value >= target) return 'elite';\n if (value >= target * 0.7) return 'high';\n if (value >= target * 0.4) return 'medium';\n return 'low';\n}\n\nfunction toPrDetail(pr: GitHubPR, durationHours: number): PrDetail {\n return {\n number: pr.number,\n title: pr.title,\n url: pr.html_url,\n durationHours: Math.round(durationHours * 10) / 10,\n mergedAt: pr.merged_at!,\n author: pr.user?.login,\n authorAvatar: pr.user?.avatar_url,\n };\n}\n\n/**\n * Determine whether a pattern string is a regex (vs a plain name or\n * comma-separated list of names). A string is treated as a regex if it\n * contains any regex meta-character: | ^ $ [ ] ( ) { } ? + * \\\n */\nfunction isRegexPattern(pattern: string): boolean {\n return /[|^$[\\](){}?+*\\\\]/.test(pattern);\n}\n\n/**\n * Return true if any of the PR's labels match the label pattern.\n *\n * Supports the same three formats as branch patterns:\n * - Single name: \"hotfix\"\n * - Comma-separated: \"hotfix,fix,bug\"\n * - JavaScript regex: \"^(hotfix|fix)$\"\n */\nfunction matchesLabel(prLabels: Array<{ name: string }>, labelPattern: string): boolean {\n if (!labelPattern) return false;\n const names = prLabels.map(l => l.name);\n if (isRegexPattern(labelPattern)) {\n const regex = new RegExp(labelPattern);\n return names.some(n => regex.test(n));\n }\n const patterns = labelPattern.split(',').map(l => l.trim()).filter(Boolean);\n return names.some(n => patterns.includes(n));\n}\n\n/**\n * Resolve a branch pattern to a concrete list of branch names.\n *\n * - Single name \"main\" → [\"main\"]\n * - Comma-sep \"main,master\" → [\"main\", \"master\"]\n * - Regex \"^(main|master)$\"→ fetched from the repo's branch list and filtered\n */\nasync function resolveBranches(\n owner: string,\n repo: string,\n branchPattern: string,\n fetchFn: <T>(url: string) => Promise<T>,\n): Promise<string[]> {\n if (isRegexPattern(branchPattern)) {\n const repoBranches = await fetchFn<Array<{ name: string }>>(\n `https://api.github.com/repos/${owner}/${repo}/branches?per_page=100`,\n );\n const regex = new RegExp(branchPattern);\n return repoBranches.map(b => b.name).filter(name => regex.test(name));\n }\n // Comma-separated or single branch name\n return branchPattern.split(',').map(b => b.trim()).filter(Boolean);\n}\n\nexport class DoraMetricsClient implements DoraMetricsApi {\n private readonly githubAuthApi: OAuthApi;\n private readonly configApi: ConfigApi;\n\n constructor(githubAuthApi: OAuthApi, configApi: ConfigApi) {\n this.githubAuthApi = githubAuthApi;\n this.configApi = configApi;\n }\n\n private getConfig() {\n const appCfg = this.configApi.getConfig('app');\n const cfg = appCfg.getOptionalConfig('doraMetrics');\n\n const rawEnvs = cfg?.getOptionalConfigArray('environments') ?? [];\n const environments: DoraEnvironment[] = rawEnvs.map(e => ({\n name: e.getString('name'),\n branch: e.getString('branch'),\n isProduction: e.getOptionalBoolean('isProduction') ?? false,\n label: e.getOptionalString('label') ?? 'hotfix',\n }));\n\n // Validate: at most one isProduction environment\n const prodEnvs = environments.filter(e => e.isProduction);\n if (prodEnvs.length > 1) {\n throw new Error(\n `DORA metrics config error: only one environment may have isProduction: true, ` +\n `but found ${prodEnvs.length}: ${prodEnvs.map(e => e.name).join(', ')}`,\n );\n }\n\n const initialDays =\n cfg?.getOptionalConfig('collection')?.getOptionalNumber('initialDays') ?? 30;\n\n const targetsCfg = cfg?.getOptionalConfig('targets');\n const targets: DoraTargets = {\n deploymentFrequency: targetsCfg?.getOptionalNumber('deploymentFrequency') ?? 7,\n leadTime: targetsCfg?.getOptionalNumber('leadTime') ?? 24,\n changeFailureRate: targetsCfg?.getOptionalNumber('changeFailureRate') ?? 15,\n mttr: targetsCfg?.getOptionalNumber('mttr') ?? 1,\n };\n\n return { environments, initialDays, targets };\n }\n\n getEnvironments(): DoraEnvironment[] {\n return this.getConfig().environments;\n }\n\n getDefaultDays(): number {\n return this.getConfig().initialDays;\n }\n\n getTargets(): DoraTargets {\n return this.getConfig().targets;\n }\n\n private async fetchWithAuth<T>(url: string, token: string): Promise<T> {\n const response = await fetch(url, {\n headers: {\n Authorization: `token ${token}`,\n Accept: 'application/vnd.github.v3+json',\n },\n });\n\n if (!response.ok) {\n throw new Error(\n `GitHub API request failed: ${response.status} ${response.statusText} — ${url}`,\n );\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * Fetch all merged PRs for an environment, resolving branch patterns and\n * deduplicating across multiple branches by PR number.\n */\n private async fetchMergedPRs(\n owner: string,\n repo: string,\n env: DoraEnvironment,\n cutoff: Date,\n token: string,\n ): Promise<GitHubPR[]> {\n const boundFetch = <T>(url: string) => this.fetchWithAuth<T>(url, token);\n\n const branches = await resolveBranches(owner, repo, env.branch, boundFetch);\n\n const allByNumber = new Map<number, GitHubPR>();\n\n await Promise.all(\n branches.map(async branch => {\n const url =\n `https://api.github.com/repos/${owner}/${repo}/pulls` +\n `?state=closed&base=${branch}&per_page=100`;\n const prs = await boundFetch<GitHubPR[]>(url);\n for (const pr of prs) {\n if (pr.merged_at && new Date(pr.merged_at) >= cutoff) {\n // Keep first occurrence (branches may share PRs — shouldn't happen\n // for base-branch-filtered queries, but guard anyway)\n if (!allByNumber.has(pr.number)) {\n allByNumber.set(pr.number, pr);\n }\n }\n }\n }),\n );\n\n return Array.from(allByNumber.values());\n }\n\n async getMetrics(\n projectSlug: string,\n env: DoraEnvironment,\n days: number,\n targetsOverride?: Partial<DoraTargets>,\n ): Promise<DoraMetrics> {\n const token = await this.githubAuthApi.getAccessToken('repo');\n const { targets: configTargets } = this.getConfig();\n const targets: DoraTargets = { ...configTargets, ...targetsOverride };\n\n const [owner, repo] = projectSlug.split('/');\n if (!owner || !repo) {\n throw new Error(`Invalid project slug \"${projectSlug}\". Expected \"owner/repo\".`);\n }\n\n const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);\n const mergedPRs = await this.fetchMergedPRs(owner, repo, env, cutoff, token);\n\n // --- Deployment Frequency ---\n const deploymentsPerWeek = days > 0 ? (mergedPRs.length / days) * 7 : 0;\n\n const deploymentFrequency: DoraMetricValue = {\n value: Math.round(deploymentsPerWeek * 100) / 100,\n unit: 'per week',\n rating: computeRating(deploymentsPerWeek, targets.deploymentFrequency, false),\n target: targets.deploymentFrequency,\n // All merged PRs sorted newest-first — used in expanded view date table\n slowestPRs: [...mergedPRs]\n .sort((a, b) => new Date(b.merged_at!).getTime() - new Date(a.merged_at!).getTime())\n .map(pr => toPrDetail(pr,\n (new Date(pr.merged_at!).getTime() - new Date(pr.created_at).getTime()) / (1000 * 60 * 60)\n )),\n };\n\n // --- Lead Time ---\n const prsWithLeadTime = mergedPRs\n .map(pr => ({\n pr,\n durationHours:\n (new Date(pr.merged_at!).getTime() - new Date(pr.created_at).getTime()) /\n (1000 * 60 * 60),\n }))\n .sort((a, b) => b.durationHours - a.durationHours);\n\n const avgLeadTimeHours =\n prsWithLeadTime.length > 0\n ? prsWithLeadTime.reduce((sum, p) => sum + p.durationHours, 0) / prsWithLeadTime.length\n : 0;\n\n const leadTime: DoraMetricValue = {\n value: Math.round(avgLeadTimeHours * 10) / 10,\n unit: 'hours',\n rating: computeRating(avgLeadTimeHours, targets.leadTime, true),\n target: targets.leadTime,\n slowestPRs: prsWithLeadTime\n .slice(0, 10)\n .map(p => toPrDetail(p.pr, p.durationHours)),\n };\n\n // --- Production-only metrics ---\n if (!env.isProduction) {\n return {\n deploymentFrequency,\n leadTime,\n changeFailureRate: null,\n mttr: null,\n numberOfHotfixes: null,\n };\n }\n\n const hotfixPRs = mergedPRs.filter(pr =>\n matchesLabel(pr.labels ?? [], env.label),\n );\n\n // Number of Hotfixes — sorted newest-first for the expanded list\n const numberOfHotfixes: DoraMetricValue = {\n value: hotfixPRs.length,\n unit: 'PRs',\n rating: computeRating(hotfixPRs.length, 0, true),\n target: 0,\n slowestPRs: [...hotfixPRs]\n .sort((a, b) => new Date(b.merged_at!).getTime() - new Date(a.merged_at!).getTime())\n .map(pr => toPrDetail(pr,\n (new Date(pr.merged_at!).getTime() - new Date(pr.created_at).getTime()) / (1000 * 60 * 60)\n )),\n };\n\n // Change Failure Rate = fix PRs / all merged PRs (DORA standard: always 0–100%)\n const changeFailureRatePct =\n mergedPRs.length > 0 ? (hotfixPRs.length / mergedPRs.length) * 100 : 0;\n\n const changeFailureRate: DoraMetricValue = {\n value: Math.round(changeFailureRatePct * 10) / 10,\n unit: '%',\n rating: computeRating(changeFailureRatePct, targets.changeFailureRate, true),\n target: targets.changeFailureRate,\n };\n\n // MTTR\n const hotfixWithDuration = hotfixPRs\n .map(pr => ({\n pr,\n durationHours:\n (new Date(pr.merged_at!).getTime() - new Date(pr.created_at).getTime()) /\n (1000 * 60 * 60),\n }))\n .sort((a, b) => b.durationHours - a.durationHours);\n\n const avgMttrHours =\n hotfixWithDuration.length > 0\n ? hotfixWithDuration.reduce((sum, p) => sum + p.durationHours, 0) /\n hotfixWithDuration.length\n : 0;\n\n const mttr: DoraMetricValue = {\n value: Math.round(avgMttrHours * 10) / 10,\n unit: 'hours',\n rating: computeRating(avgMttrHours, targets.mttr, true),\n target: targets.mttr,\n slowestPRs: hotfixWithDuration\n .slice(0, 10)\n .map(p => toPrDetail(p.pr, p.durationHours)),\n };\n\n return { deploymentFrequency, leadTime, changeFailureRate, mttr, numberOfHotfixes };\n }\n\n async getHistory(\n projectSlug: string,\n env: DoraEnvironment,\n days: number,\n ): Promise<DoraHistoryPoint[]> {\n const token = await this.githubAuthApi.getAccessToken('repo');\n const [owner, repo] = projectSlug.split('/');\n if (!owner || !repo) throw new Error(`Invalid project slug \"${projectSlug}\".`);\n\n const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);\n const mergedPRs = await this.fetchMergedPRs(owner, repo, env, cutoff, token);\n\n // Scale bucket size so we always get ~7 data points regardless of date range\n const bucketDays = Math.max(1, Math.round(days / 7));\n const numBuckets = Math.max(2, Math.ceil(days / bucketDays));\n const buckets: DoraHistoryPoint[] = [];\n\n for (let w = numBuckets - 1; w >= 0; w--) {\n const weekEnd = new Date(Date.now() - w * bucketDays * 24 * 60 * 60 * 1000);\n const weekStart = new Date(weekEnd.getTime() - bucketDays * 24 * 60 * 60 * 1000);\n\n const weekPRs = mergedPRs.filter(pr => {\n const t = new Date(pr.merged_at!).getTime();\n return t >= weekStart.getTime() && t < weekEnd.getTime();\n });\n\n const leadTimes = weekPRs.map(pr =>\n (new Date(pr.merged_at!).getTime() - new Date(pr.created_at).getTime()) / (1000 * 60 * 60),\n );\n const avgLeadTime =\n leadTimes.length > 0 ? leadTimes.reduce((a, b) => a + b, 0) / leadTimes.length : 0;\n\n let changeFailureRate: number | undefined;\n let mttrHours: number | undefined;\n\n if (env.isProduction) {\n const hotfixPRs = weekPRs.filter(pr => matchesLabel(pr.labels, env.label));\n changeFailureRate =\n weekPRs.length > 0 ? Math.round((hotfixPRs.length / weekPRs.length) * 1000) / 10 : 0;\n const hotfixDurations = hotfixPRs.map(pr =>\n (new Date(pr.merged_at!).getTime() - new Date(pr.created_at).getTime()) / (1000 * 60 * 60),\n );\n mttrHours =\n hotfixDurations.length > 0\n ? Math.round((hotfixDurations.reduce((a, b) => a + b, 0) / hotfixDurations.length) * 10) / 10\n : 0;\n }\n\n buckets.push({\n weekLabel: weekStart.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),\n bucketMidMs: Math.round((weekStart.getTime() + weekEnd.getTime()) / 2),\n deploymentCount: weekPRs.length,\n leadTimeHours: Math.round(avgLeadTime * 10) / 10,\n changeFailureRate,\n mttrHours,\n });\n }\n\n return buckets;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,72 +0,0 @@
1
- const MOCK_ENVIRONMENTS = [
2
- { name: "Production", branch: "main", isProduction: true, label: "hotfix" },
3
- { name: "Staging", branch: "staging", isProduction: false, label: "" }
4
- ];
5
- const MOCK_TARGETS = {
6
- deploymentFrequency: 7,
7
- leadTime: 24,
8
- changeFailureRate: 15,
9
- mttr: 1
10
- };
11
- const MOCK_PRS = [
12
- { number: 142, title: "feat: redesign dashboard layout", url: "https://github.com/example/repo/pull/142", durationHours: 31.2, mergedAt: "2026-03-15T14:22:00Z", author: "alice", authorAvatar: "https://avatars.githubusercontent.com/u/1?v=4" },
13
- { number: 138, title: "fix: resolve memory leak in worker thread", url: "https://github.com/example/repo/pull/138", durationHours: 22.5, mergedAt: "2026-03-12T09:10:00Z", author: "bob", authorAvatar: "https://avatars.githubusercontent.com/u/2?v=4" },
14
- { number: 135, title: "feat: add export to CSV functionality", url: "https://github.com/example/repo/pull/135", durationHours: 18.8, mergedAt: "2026-03-10T16:45:00Z", author: "carol", authorAvatar: "https://avatars.githubusercontent.com/u/3?v=4" },
15
- { number: 131, title: "chore: upgrade dependencies to latest", url: "https://github.com/example/repo/pull/131", durationHours: 14.1, mergedAt: "2026-03-08T11:30:00Z", author: "dave", authorAvatar: "https://avatars.githubusercontent.com/u/4?v=4" },
16
- { number: 128, title: "feat: implement rate limiting middleware", url: "https://github.com/example/repo/pull/128", durationHours: 9.6, mergedAt: "2026-03-05T13:20:00Z", author: "alice", authorAvatar: "https://avatars.githubusercontent.com/u/1?v=4" }
17
- ];
18
- const MOCK_HOTFIX_PRS = [
19
- { number: 140, title: "hotfix: fix broken auth token refresh", url: "https://github.com/example/repo/pull/140", durationHours: 1.2, mergedAt: "2026-03-14T03:45:00Z", author: "bob", authorAvatar: "https://avatars.githubusercontent.com/u/2?v=4" },
20
- { number: 133, title: "hotfix: patch SQL injection in search query", url: "https://github.com/example/repo/pull/133", durationHours: 0.8, mergedAt: "2026-03-09T22:10:00Z", author: "carol", authorAvatar: "https://avatars.githubusercontent.com/u/3?v=4" }
21
- ];
22
- const MOCK_HISTORY = [
23
- { weekLabel: "Feb 17", bucketMidMs: (/* @__PURE__ */ new Date("2026-02-20")).getTime(), deploymentCount: 3, leadTimeHours: 24.1, changeFailureRate: 0, mttrHours: 0 },
24
- { weekLabel: "Feb 24", bucketMidMs: (/* @__PURE__ */ new Date("2026-02-27")).getTime(), deploymentCount: 5, leadTimeHours: 19.8, changeFailureRate: 20, mttrHours: 1.2 },
25
- { weekLabel: "Mar 3", bucketMidMs: (/* @__PURE__ */ new Date("2026-03-06")).getTime(), deploymentCount: 4, leadTimeHours: 22.3, changeFailureRate: 0, mttrHours: 0 },
26
- { weekLabel: "Mar 10", bucketMidMs: (/* @__PURE__ */ new Date("2026-03-13")).getTime(), deploymentCount: 6, leadTimeHours: 16.5, changeFailureRate: 16.7, mttrHours: 0.9 },
27
- { weekLabel: "Mar 17", bucketMidMs: (/* @__PURE__ */ new Date("2026-03-20")).getTime(), deploymentCount: 5, leadTimeHours: 18.4, changeFailureRate: 0, mttrHours: 0 }
28
- ];
29
- class MockDoraMetricsClient {
30
- getEnvironments() {
31
- return MOCK_ENVIRONMENTS;
32
- }
33
- getDefaultDays() {
34
- return 30;
35
- }
36
- getTargets() {
37
- return MOCK_TARGETS;
38
- }
39
- async getMetrics(_projectSlug, env, _days, _targetsOverride) {
40
- await new Promise((r) => setTimeout(r, 600));
41
- return {
42
- deploymentFrequency: {
43
- value: 4.2,
44
- unit: "per week",
45
- rating: "high",
46
- target: 7,
47
- slowestPRs: MOCK_PRS
48
- },
49
- leadTime: {
50
- value: 18.4,
51
- unit: "hours",
52
- rating: "elite",
53
- target: 24,
54
- slowestPRs: MOCK_PRS
55
- },
56
- changeFailureRate: env.isProduction ? { value: 8.3, unit: "%", rating: "high", target: 15 } : null,
57
- mttr: env.isProduction ? { value: 0.8, unit: "hours", rating: "elite", target: 1, slowestPRs: MOCK_HOTFIX_PRS } : null,
58
- numberOfHotfixes: env.isProduction ? { value: 2, unit: "PRs", rating: "high", target: 0, slowestPRs: MOCK_HOTFIX_PRS } : null
59
- };
60
- }
61
- async getHistory(_projectSlug, env, _days) {
62
- await new Promise((r) => setTimeout(r, 400));
63
- return MOCK_HISTORY.map((point) => ({
64
- ...point,
65
- changeFailureRate: env.isProduction ? point.changeFailureRate : void 0,
66
- mttrHours: env.isProduction ? point.mttrHours : void 0
67
- }));
68
- }
69
- }
70
-
71
- export { MockDoraMetricsClient };
72
- //# sourceMappingURL=MockDoraMetricsClient.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MockDoraMetricsClient.esm.js","sources":["../../src/api/MockDoraMetricsClient.ts"],"sourcesContent":["import { DoraEnvironment, DoraHistoryPoint, DoraMetrics, DoraMetricsApi, DoraTargets } from './types';\n\nconst MOCK_ENVIRONMENTS: DoraEnvironment[] = [\n { name: 'Production', branch: 'main', isProduction: true, label: 'hotfix' },\n { name: 'Staging', branch: 'staging', isProduction: false, label: '' },\n];\n\nconst MOCK_TARGETS: DoraTargets = {\n deploymentFrequency: 7,\n leadTime: 24,\n changeFailureRate: 15,\n mttr: 1,\n};\n\nconst MOCK_PRS = [\n { number: 142, title: 'feat: redesign dashboard layout', url: 'https://github.com/example/repo/pull/142', durationHours: 31.2, mergedAt: '2026-03-15T14:22:00Z', author: 'alice', authorAvatar: 'https://avatars.githubusercontent.com/u/1?v=4' },\n { number: 138, title: 'fix: resolve memory leak in worker thread', url: 'https://github.com/example/repo/pull/138', durationHours: 22.5, mergedAt: '2026-03-12T09:10:00Z', author: 'bob', authorAvatar: 'https://avatars.githubusercontent.com/u/2?v=4' },\n { number: 135, title: 'feat: add export to CSV functionality', url: 'https://github.com/example/repo/pull/135', durationHours: 18.8, mergedAt: '2026-03-10T16:45:00Z', author: 'carol', authorAvatar: 'https://avatars.githubusercontent.com/u/3?v=4' },\n { number: 131, title: 'chore: upgrade dependencies to latest', url: 'https://github.com/example/repo/pull/131', durationHours: 14.1, mergedAt: '2026-03-08T11:30:00Z', author: 'dave', authorAvatar: 'https://avatars.githubusercontent.com/u/4?v=4' },\n { number: 128, title: 'feat: implement rate limiting middleware', url: 'https://github.com/example/repo/pull/128', durationHours: 9.6, mergedAt: '2026-03-05T13:20:00Z', author: 'alice', authorAvatar: 'https://avatars.githubusercontent.com/u/1?v=4' },\n];\n\nconst MOCK_HOTFIX_PRS = [\n { number: 140, title: 'hotfix: fix broken auth token refresh', url: 'https://github.com/example/repo/pull/140', durationHours: 1.2, mergedAt: '2026-03-14T03:45:00Z', author: 'bob', authorAvatar: 'https://avatars.githubusercontent.com/u/2?v=4' },\n { number: 133, title: 'hotfix: patch SQL injection in search query', url: 'https://github.com/example/repo/pull/133', durationHours: 0.8, mergedAt: '2026-03-09T22:10:00Z', author: 'carol', authorAvatar: 'https://avatars.githubusercontent.com/u/3?v=4' },\n];\n\nconst MOCK_HISTORY: DoraHistoryPoint[] = [\n { weekLabel: 'Feb 17', bucketMidMs: new Date('2026-02-20').getTime(), deploymentCount: 3, leadTimeHours: 24.1, changeFailureRate: 0, mttrHours: 0 },\n { weekLabel: 'Feb 24', bucketMidMs: new Date('2026-02-27').getTime(), deploymentCount: 5, leadTimeHours: 19.8, changeFailureRate: 20, mttrHours: 1.2 },\n { weekLabel: 'Mar 3', bucketMidMs: new Date('2026-03-06').getTime(), deploymentCount: 4, leadTimeHours: 22.3, changeFailureRate: 0, mttrHours: 0 },\n { weekLabel: 'Mar 10', bucketMidMs: new Date('2026-03-13').getTime(), deploymentCount: 6, leadTimeHours: 16.5, changeFailureRate: 16.7, mttrHours: 0.9 },\n { weekLabel: 'Mar 17', bucketMidMs: new Date('2026-03-20').getTime(), deploymentCount: 5, leadTimeHours: 18.4, changeFailureRate: 0, mttrHours: 0 },\n];\n\nexport class MockDoraMetricsClient implements DoraMetricsApi {\n getEnvironments(): DoraEnvironment[] {\n return MOCK_ENVIRONMENTS;\n }\n\n getDefaultDays(): number {\n return 30;\n }\n\n getTargets(): DoraTargets {\n return MOCK_TARGETS;\n }\n\n async getMetrics(\n _projectSlug: string,\n env: DoraEnvironment,\n _days: number,\n _targetsOverride?: Partial<DoraTargets>,\n ): Promise<DoraMetrics> {\n await new Promise(r => setTimeout(r, 600)); // simulate network delay\n\n return {\n deploymentFrequency: {\n value: 4.2,\n unit: 'per week',\n rating: 'high',\n target: 7,\n slowestPRs: MOCK_PRS,\n },\n leadTime: {\n value: 18.4,\n unit: 'hours',\n rating: 'elite',\n target: 24,\n slowestPRs: MOCK_PRS,\n },\n changeFailureRate: env.isProduction\n ? { value: 8.3, unit: '%', rating: 'high', target: 15 }\n : null,\n mttr: env.isProduction\n ? { value: 0.8, unit: 'hours', rating: 'elite', target: 1, slowestPRs: MOCK_HOTFIX_PRS }\n : null,\n numberOfHotfixes: env.isProduction\n ? { value: 2, unit: 'PRs', rating: 'high', target: 0, slowestPRs: MOCK_HOTFIX_PRS }\n : null,\n };\n }\n\n async getHistory(\n _projectSlug: string,\n env: DoraEnvironment,\n _days: number,\n ): Promise<DoraHistoryPoint[]> {\n await new Promise(r => setTimeout(r, 400));\n\n return MOCK_HISTORY.map(point => ({\n ...point,\n changeFailureRate: env.isProduction ? point.changeFailureRate : undefined,\n mttrHours: env.isProduction ? point.mttrHours : undefined,\n }));\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,8 +0,0 @@
1
- import { createApiRef } from '@backstage/core-plugin-api';
2
-
3
- const doraMetricsApiRef = createApiRef({
4
- id: "plugin.dora-metrics.service"
5
- });
6
-
7
- export { doraMetricsApiRef };
8
- //# sourceMappingURL=types.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.esm.js","sources":["../../src/api/types.ts"],"sourcesContent":["import { createApiRef } from '@backstage/core-plugin-api';\n\nexport type DoraRating = 'elite' | 'high' | 'medium' | 'low';\n\nexport interface PrDetail {\n number: number;\n title: string;\n url: string;\n durationHours: number;\n mergedAt: string;\n author?: string;\n authorAvatar?: string;\n}\n\nexport interface DoraMetricValue {\n value: number;\n unit: string;\n rating: DoraRating;\n target: number;\n slowestPRs?: PrDetail[];\n}\n\nexport interface DoraEnvironment {\n name: string;\n /**\n * Raw branch pattern as configured — a single branch name, a comma-separated\n * list (\"main,master\"), or a JavaScript regex string (\"^(main|master)$\").\n * The client resolves this to a concrete list of branches at query time.\n */\n branch: string;\n isProduction: boolean;\n label: string;\n}\n\nexport interface DoraTargets {\n deploymentFrequency: number;\n leadTime: number;\n changeFailureRate: number;\n mttr: number;\n}\n\nexport interface DoraMetrics {\n deploymentFrequency: DoraMetricValue;\n leadTime: DoraMetricValue;\n /** null for non-production environments */\n changeFailureRate: DoraMetricValue | null;\n /** null for non-production environments */\n mttr: DoraMetricValue | null;\n /** null for non-production environments */\n numberOfHotfixes: DoraMetricValue | null;\n}\n\nexport interface DoraHistoryPoint {\n weekLabel: string; // e.g. \"Mar 10\"\n bucketMidMs: number; // timestamp (ms) of bucket midpoint — for chart x-axis alignment\n deploymentCount: number; // total PRs merged that week\n leadTimeHours: number; // avg lead time that week (0 if no PRs)\n changeFailureRate?: number; // % (prod only)\n mttrHours?: number; // avg (prod only)\n}\n\nexport interface DoraMetricsApi {\n getEnvironments(): DoraEnvironment[];\n getDefaultDays(): number;\n getTargets(): DoraTargets;\n getMetrics(\n projectSlug: string,\n env: DoraEnvironment,\n days: number,\n targetsOverride?: Partial<DoraTargets>,\n ): Promise<DoraMetrics>;\n getHistory(\n projectSlug: string,\n env: DoraEnvironment,\n days: number,\n ): Promise<DoraHistoryPoint[]>;\n}\n\nexport const doraMetricsApiRef = createApiRef<DoraMetricsApi>({\n id: 'plugin.dora-metrics.service',\n});\n"],"names":[],"mappings":";;AA8EO,MAAM,oBAAoB,YAAA,CAA6B;AAAA,EAC5D,EAAA,EAAI;AACN,CAAC;;;;"}