@backstage-community/plugin-tech-insights-maturity 0.2.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/CHANGELOG.md +21 -0
- package/README.md +177 -0
- package/dist/ScoreRouter.esm.js +8 -0
- package/dist/ScoreRouter.esm.js.map +1 -0
- package/dist/SummaryRouter.esm.js +8 -0
- package/dist/SummaryRouter.esm.js.map +1 -0
- package/dist/api/MaturityApi.esm.js +8 -0
- package/dist/api/MaturityApi.esm.js.map +1 -0
- package/dist/api/MaturityClient.esm.js +128 -0
- package/dist/api/MaturityClient.esm.js.map +1 -0
- package/dist/api/ScoringDataFormatter.esm.js +142 -0
- package/dist/api/ScoringDataFormatter.esm.js.map +1 -0
- package/dist/components/MaturityChartCard/MaturityChartCard.esm.js +79 -0
- package/dist/components/MaturityChartCard/MaturityChartCard.esm.js.map +1 -0
- package/dist/components/MaturityChartCard/index.esm.js +2 -0
- package/dist/components/MaturityChartCard/index.esm.js.map +1 -0
- package/dist/components/MaturityPage/MaturityPage.esm.js +21 -0
- package/dist/components/MaturityPage/MaturityPage.esm.js.map +1 -0
- package/dist/components/MaturityPage/index.esm.js +2 -0
- package/dist/components/MaturityPage/index.esm.js.map +1 -0
- package/dist/components/MaturityRankAvatar/MaturityRankAvatar.esm.js +100 -0
- package/dist/components/MaturityRankAvatar/MaturityRankAvatar.esm.js.map +1 -0
- package/dist/components/MaturityRankInfoCard/MaturityRankInfoCard.esm.js +80 -0
- package/dist/components/MaturityRankInfoCard/MaturityRankInfoCard.esm.js.map +1 -0
- package/dist/components/MaturityRankWidget/MaturityRankWidget.esm.js +22 -0
- package/dist/components/MaturityRankWidget/MaturityRankWidget.esm.js.map +1 -0
- package/dist/components/MaturityRankWidget/index.esm.js +2 -0
- package/dist/components/MaturityRankWidget/index.esm.js.map +1 -0
- package/dist/components/MaturityScorePage/MaturityScorePage.esm.js +85 -0
- package/dist/components/MaturityScorePage/MaturityScorePage.esm.js.map +1 -0
- package/dist/components/MaturityScorePage/maturityTableRows.esm.js +124 -0
- package/dist/components/MaturityScorePage/maturityTableRows.esm.js.map +1 -0
- package/dist/components/MaturitySummaryInfoCard/MaturitySummaryCardContent.esm.js +17 -0
- package/dist/components/MaturitySummaryInfoCard/MaturitySummaryCardContent.esm.js.map +1 -0
- package/dist/components/MaturitySummaryInfoCard/MaturitySummaryInfoCard.esm.js +34 -0
- package/dist/components/MaturitySummaryInfoCard/MaturitySummaryInfoCard.esm.js.map +1 -0
- package/dist/components/MaturitySummaryInfoCard/index.esm.js +2 -0
- package/dist/components/MaturitySummaryInfoCard/index.esm.js.map +1 -0
- package/dist/components/MaturitySummaryPage/MaturitySummaryPage.esm.js +35 -0
- package/dist/components/MaturitySummaryPage/MaturitySummaryPage.esm.js.map +1 -0
- package/dist/components/MaturitySummaryTable/MaturitySummaryTable.esm.js +206 -0
- package/dist/components/MaturitySummaryTable/MaturitySummaryTable.esm.js.map +1 -0
- package/dist/helpers/AreaProgress.esm.js +47 -0
- package/dist/helpers/AreaProgress.esm.js.map +1 -0
- package/dist/helpers/MaturityHelp.esm.js +9 -0
- package/dist/helpers/MaturityHelp.esm.js.map +1 -0
- package/dist/helpers/MaturityLink.esm.js +25 -0
- package/dist/helpers/MaturityLink.esm.js.map +1 -0
- package/dist/helpers/Rank.esm.js +33 -0
- package/dist/helpers/Rank.esm.js.map +1 -0
- package/dist/helpers/utils.esm.js +41 -0
- package/dist/helpers/utils.esm.js.map +1 -0
- package/dist/img/bronze.png +0 -0
- package/dist/img/gold.png +0 -0
- package/dist/img/silver.png +0 -0
- package/dist/img/stone.png +0 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.esm.js +4 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/plugin.esm.js +80 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +104 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @backstage-community/plugin-tech-insights-maturity
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e919e53: Backstage version bump to v1.35.1
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- f015469: Introducing a new tech insights react plugin for reusuable frontend utilities. All migrated components and APIs have been marked as deprecated. Please update your imports to come from `@backstage-community/plugin-tech-insights-react`
|
|
12
|
+
|
|
13
|
+
Package json files for each plugin have been updated to reflect the new plugin in the Backstage `pluginPackages` metadata.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [f015469]
|
|
16
|
+
- Updated dependencies [f015469]
|
|
17
|
+
- Updated dependencies [e919e53]
|
|
18
|
+
- Updated dependencies [c107e0f]
|
|
19
|
+
- @backstage-community/plugin-tech-insights-maturity-common@0.2.0
|
|
20
|
+
- @backstage-community/plugin-tech-insights-common@0.5.0
|
|
21
|
+
- @backstage-community/plugin-tech-insights@0.5.0
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Tech Insights Maturity
|
|
2
|
+
|
|
3
|
+
Tech Insights Maturity helps teams see where their services stand and get actionable steps for improving them. This plugin introduces maturity rankings and categories into your checks.
|
|
4
|
+
|
|
5
|
+
## Maturity Ranks
|
|
6
|
+
|
|
7
|
+
Maturity Ranks are assigned to components based on their fulfillment of their required Maturity Checks associated with each Rank.
|
|
8
|
+
|
|
9
|
+
These are the currently available Maturity Ranks:
|
|
10
|
+
|
|
11
|
+
<img src="./src/img/stone.png" width="15" height="15" /> **Stone**: Default, base-level rank. Components with this Rank are failing one or more Bronze-level Check.
|
|
12
|
+
|
|
13
|
+
<img src="./src/img/bronze.png" width="15" height="15" /> **Bronze**: Components with this Rank have fulfilled all Bronze-level Checks.
|
|
14
|
+
|
|
15
|
+
<img src="./src/img/silver.png" width="15" height="15" /> **Silver**: Components with this Rank have fulfilled all Bronze- and Silver-level Checks.
|
|
16
|
+
|
|
17
|
+
<img src="./src/img/gold.png" width="15" height="15" /> **Gold**: Components with this Rank have fulfilled all Bronze-, Silver-, and Gold-level Checks.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
### Install the plugin
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# From your Backstage root directory
|
|
25
|
+
yarn --cwd packages/app add @backstage-community/plugin-tech-insights-maturity
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Add Maturity Summary Card to the entity overview page:
|
|
29
|
+
|
|
30
|
+
If you want to show Maturity Summary in the overview of any entity use `EntityMaturitySummaryCard`.
|
|
31
|
+
Note: This applies to all types of entity.
|
|
32
|
+
|
|
33
|
+
<img src="./img/maturitySummaryCard.png" width="700" height="350" />
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// packages/app/src/components/catalog/EntityPage.tsx
|
|
37
|
+
|
|
38
|
+
import { EntityMaturitySummaryCard } from '@backstage-community/plugin-tech-insights-maturity';
|
|
39
|
+
|
|
40
|
+
const overviewContent = (
|
|
41
|
+
<Grid container spacing={3} alignItems="stretch">
|
|
42
|
+
{entityWarningContent}
|
|
43
|
+
<Grid item md={3} xs={6}>
|
|
44
|
+
<EntityAboutCard variant="gridItem" />
|
|
45
|
+
</Grid>
|
|
46
|
+
<Grid item md={3} xs={6}>
|
|
47
|
+
<EntityCatalogGraphCard variant="gridItem" height={400} />
|
|
48
|
+
</Grid>
|
|
49
|
+
...
|
|
50
|
+
<Grid item md={3} xs={6}>
|
|
51
|
+
<EntityMaturitySummaryCard />
|
|
52
|
+
</Grid>
|
|
53
|
+
</Grid>
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Add Maturity Checks overview (Maturity Scorecards) page to the any Component EntityPage:
|
|
58
|
+
|
|
59
|
+
Note: This only applies to entities of Kind: 'Component'
|
|
60
|
+
|
|
61
|
+
<img src="./img/maturityScorecardContent.png" width="700" height="350" />
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// packages/app/src/components/catalog/EntityPage.tsx
|
|
65
|
+
|
|
66
|
+
import { EntityMaturityScorecardContent } from '@backstage-community/plugin-tech-insight-maturity';
|
|
67
|
+
|
|
68
|
+
const componentEntityPage = (
|
|
69
|
+
<EntityLayoutWrapper>
|
|
70
|
+
<EntityLayout.Route path="/" title="Overview">
|
|
71
|
+
{overviewContent}
|
|
72
|
+
</EntityLayout.Route>
|
|
73
|
+
<EntityLayout.Route path="/ci-cd" title="CI/CD">
|
|
74
|
+
{cicdContent}
|
|
75
|
+
</EntityLayout.Route>
|
|
76
|
+
...
|
|
77
|
+
<EntityLayout.Route path="/maturity" title="Maturity">
|
|
78
|
+
<EntityMaturityScorecardContent />
|
|
79
|
+
</EntityLayout.Route>
|
|
80
|
+
...
|
|
81
|
+
</EntityLayoutWrapper>
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Add Maturity Summary overview page to System/Domain/Group:
|
|
86
|
+
|
|
87
|
+
Note: This only applies to entities of Kind: 'System', 'Domain', or 'Group'
|
|
88
|
+
|
|
89
|
+
<img src="./img/maturitySummaryCardContent.png" width="700" height="350" />
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// packages/app/src/components/catalog/EntityPage.tsx
|
|
93
|
+
|
|
94
|
+
import { EntityMaturitySummaryCardContent } from '@backstage-community/plugin-tech-insight-maturity';
|
|
95
|
+
|
|
96
|
+
const systemEntityPage = (
|
|
97
|
+
<EntityLayoutWrapper>
|
|
98
|
+
<EntityLayout.Route path="/" title="Overview">
|
|
99
|
+
{overviewContent}
|
|
100
|
+
</EntityLayout.Route>
|
|
101
|
+
...
|
|
102
|
+
<EntityLayout.Route path="/maturity" title="Maturity">
|
|
103
|
+
<EntityMaturitySummaryCardContent />
|
|
104
|
+
</EntityLayout.Route>
|
|
105
|
+
...
|
|
106
|
+
</EntityLayoutWrapper>
|
|
107
|
+
);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Add Maturity page:
|
|
111
|
+
|
|
112
|
+
<img src="./img/maturityPage.png" width="700" height="350" />
|
|
113
|
+
|
|
114
|
+
First make the Maturity page available as route
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// packages/app/src/App.tsx
|
|
118
|
+
|
|
119
|
+
import { MaturityPage } from '@backstage-community/plugin-tech-insights-maturity';
|
|
120
|
+
|
|
121
|
+
const routes = (
|
|
122
|
+
<FlatRoutes>
|
|
123
|
+
...
|
|
124
|
+
<Route path="/maturity" element={<MaturityPage />} />
|
|
125
|
+
</FlatRoutes>
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Then add it to the navigation menu
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
// packages/app/src/components/Root/Root.tsx
|
|
133
|
+
|
|
134
|
+
import EmojiObjectsIcon from '@material-ui/icons/EmojiObjects';
|
|
135
|
+
|
|
136
|
+
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
|
137
|
+
...
|
|
138
|
+
<SidebarItem icon={EmojiObjectsIcon} to="maturity" text="Maturity" />
|
|
139
|
+
...
|
|
140
|
+
);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Compatibility!!
|
|
144
|
+
|
|
145
|
+
This module is based on the existing Tech Insights Plugin and should be compatible with any existing implementations of [tech-insights-backend](../../../tech-insights/plugins/tech-insights-backend/README.md) or [tech-insights-backend-module-jsonfc](../../../tech-insights/plugins/tech-insights-backend-module-jsonfc/README.md).
|
|
146
|
+
|
|
147
|
+
If you don't have any existing Tech Insights implementation, make sure to set it up first before you can use this frontend plugin.
|
|
148
|
+
|
|
149
|
+
After configuring your backend, add Maturity Metadata to all of your checks!
|
|
150
|
+
|
|
151
|
+
Sample check written in app-config:
|
|
152
|
+
|
|
153
|
+
```yaml title="app-config.yaml"
|
|
154
|
+
techInsights:
|
|
155
|
+
factRetrievers: ...
|
|
156
|
+
factChecker:
|
|
157
|
+
checks:
|
|
158
|
+
groupOwnerCheck:
|
|
159
|
+
...
|
|
160
|
+
metadata:
|
|
161
|
+
category: 'Ownership',
|
|
162
|
+
rank: 1, // rank values: Bronze - 1, Silver - 2, Gold - 3
|
|
163
|
+
solution: 'Add a group owner to spec.owner field in the factbook',
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Sample check written in TS
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
export const groupOwnerCheck = {
|
|
170
|
+
...,
|
|
171
|
+
metadata: {
|
|
172
|
+
category: 'Ownership',
|
|
173
|
+
rank: 1, // rank values: Bronze - 1, Silver - 2, Gold - 3
|
|
174
|
+
solution: 'Add a group owner to spec.owner field in the factbook',
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Routes, Route } from 'react-router-dom';
|
|
3
|
+
import { MaturityScorePage } from './components/MaturityScorePage/MaturityScorePage.esm.js';
|
|
4
|
+
|
|
5
|
+
const ScoreRouter = () => /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: "/", element: /* @__PURE__ */ React.createElement(MaturityScorePage, null) }));
|
|
6
|
+
|
|
7
|
+
export { ScoreRouter };
|
|
8
|
+
//# sourceMappingURL=ScoreRouter.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScoreRouter.esm.js","sources":["../src/ScoreRouter.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\nimport { Route, Routes } from 'react-router-dom';\nimport { MaturityScorePage } from './components/MaturityScorePage';\n\nexport const ScoreRouter = () => (\n <Routes>\n <Route path=\"/\" element={<MaturityScorePage />} />\n </Routes>\n);\n"],"names":[],"mappings":";;;;AAmBO,MAAM,WAAc,GAAA,sBACxB,KAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAM,IAAK,EAAA,GAAA,EAAI,OAAS,kBAAA,KAAA,CAAA,aAAA,CAAC,iBAAkB,EAAA,IAAA,CAAA,EAAI,CAClD;;;;"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Routes, Route } from 'react-router-dom';
|
|
3
|
+
import { MaturitySummaryPage } from './components/MaturitySummaryPage/MaturitySummaryPage.esm.js';
|
|
4
|
+
|
|
5
|
+
const SummaryRouter = () => /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: "/", element: /* @__PURE__ */ React.createElement(MaturitySummaryPage, null) }));
|
|
6
|
+
|
|
7
|
+
export { SummaryRouter };
|
|
8
|
+
//# sourceMappingURL=SummaryRouter.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SummaryRouter.esm.js","sources":["../src/SummaryRouter.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\nimport { Route, Routes } from 'react-router-dom';\nimport { MaturitySummaryPage } from './components/MaturitySummaryPage';\n\nexport const SummaryRouter = () => (\n <Routes>\n <Route path=\"/\" element={<MaturitySummaryPage />} />\n </Routes>\n);\n"],"names":[],"mappings":";;;;AAmBO,MAAM,aAAgB,GAAA,sBAC1B,KAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAM,IAAK,EAAA,GAAA,EAAI,OAAS,kBAAA,KAAA,CAAA,aAAA,CAAC,mBAAoB,EAAA,IAAA,CAAA,EAAI,CACpD;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MaturityApi.esm.js","sources":["../../src/api/MaturityApi.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { TechInsightsApi } from '@backstage-community/plugin-tech-insights';\nimport { Entity } from '@backstage/catalog-model';\nimport { createApiRef } from '@backstage/core-plugin-api';\nimport {\n BulkMaturityCheckResponse,\n BulkMaturitySummary,\n MaturityRank,\n MaturityScore,\n MaturitySummary,\n} from '@backstage-community/plugin-tech-insights-maturity-common';\n\n/**\n * {@link @backstage/core-plugin-api#ApiRef} for the {@link MaturityApi}\n *\n * @public\n */\nexport const maturityApiRef = createApiRef<MaturityApi>({\n id: 'plugin.maturity.service',\n});\n\n/**\n * Maturity API client interface extention of TechInsightsApi\n *\n * @public\n */\nexport type MaturityApi = TechInsightsApi & {\n getMaturityRank(entity: Entity): Promise<MaturityRank>;\n getMaturityScore(entity: Entity): Promise<MaturityScore>;\n getBulkMaturityCheckResults(\n entities: Entity[],\n ): Promise<BulkMaturityCheckResponse>;\n getChildMaturityCheckResults(\n entity: Entity,\n ): Promise<BulkMaturityCheckResponse>;\n getMaturitySummary(entity: Entity): Promise<MaturitySummary>;\n getBulkMaturitySummary(entities: Entity[]): Promise<BulkMaturitySummary>;\n};\n"],"names":[],"mappings":";;AA+BO,MAAM,iBAAiB,YAA0B,CAAA;AAAA,EACtD,EAAI,EAAA;AACN,CAAC;;;;"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { getCompoundEntityRef, stringifyEntityRef, isComponentEntity, RELATION_HAS_PART, RELATION_PARENT_OF, RELATION_OWNER_OF } from '@backstage/catalog-model';
|
|
2
|
+
import { getEntityRelations } from '@backstage/plugin-catalog-react';
|
|
3
|
+
import { TechInsightsClient } from '@backstage-community/plugin-tech-insights';
|
|
4
|
+
import { ScoringDataFormatter } from './ScoringDataFormatter.esm.js';
|
|
5
|
+
|
|
6
|
+
const SDF = new ScoringDataFormatter();
|
|
7
|
+
class MaturityClient extends TechInsightsClient {
|
|
8
|
+
catalogApi;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.catalogApi = options.catalogApi;
|
|
12
|
+
}
|
|
13
|
+
async getMaturityRank(entity) {
|
|
14
|
+
const checksResult = await this.getCheckResults(entity);
|
|
15
|
+
return SDF.getMaturityRank(checksResult);
|
|
16
|
+
}
|
|
17
|
+
async getBulkMaturityCheckResults(entities) {
|
|
18
|
+
return await this.getBulkCheckResults(
|
|
19
|
+
entities.map((x) => getCompoundEntityRef(x))
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
async getMaturityScore(entity) {
|
|
23
|
+
const checks = await this.getCheckResults(entity);
|
|
24
|
+
const rank = SDF.getMaturityRank(checks);
|
|
25
|
+
const summary = SDF.getMaturitySummary(checks);
|
|
26
|
+
return {
|
|
27
|
+
checks,
|
|
28
|
+
summary,
|
|
29
|
+
rank
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async getChildMaturityCheckResults(entity) {
|
|
33
|
+
const entities = await this.getRelatedComponents(entity);
|
|
34
|
+
return await this.getBulkCheckResults(entities);
|
|
35
|
+
}
|
|
36
|
+
async getMaturitySummary(entity) {
|
|
37
|
+
const checksResult = await this.getCheckResults(entity);
|
|
38
|
+
return SDF.getMaturitySummary(checksResult);
|
|
39
|
+
}
|
|
40
|
+
async getBulkMaturitySummary(entities) {
|
|
41
|
+
return Promise.all(
|
|
42
|
+
entities.map(async (x) => {
|
|
43
|
+
const checks = await this.getCheckResults(x);
|
|
44
|
+
const summary = SDF.getMaturitySummary(checks);
|
|
45
|
+
const rank = SDF.getMaturityRank(checks);
|
|
46
|
+
return {
|
|
47
|
+
entity: stringifyEntityRef(x),
|
|
48
|
+
rank: rank.rank,
|
|
49
|
+
isMaxRank: rank.isMaxRank,
|
|
50
|
+
summary
|
|
51
|
+
};
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
async getCheckResults(entity) {
|
|
56
|
+
if (isComponentEntity(entity)) {
|
|
57
|
+
return await this.runChecks(
|
|
58
|
+
getCompoundEntityRef(entity)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const entities = await this.getRelatedComponents(entity);
|
|
62
|
+
return await this.getGroupCheckResults(entities);
|
|
63
|
+
}
|
|
64
|
+
async getBulkCheckResults(entities) {
|
|
65
|
+
const bulkResponse = await this.runBulkChecks(entities);
|
|
66
|
+
return Promise.all(
|
|
67
|
+
bulkResponse.map(async (x) => {
|
|
68
|
+
const checks = x.results;
|
|
69
|
+
const rank = SDF.getMaturityRank(checks);
|
|
70
|
+
return {
|
|
71
|
+
entity: x.entity,
|
|
72
|
+
rank: rank.rank,
|
|
73
|
+
isMaxRank: rank.isMaxRank,
|
|
74
|
+
checks
|
|
75
|
+
};
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
async getGroupCheckResults(entities) {
|
|
80
|
+
if (entities.length === 0) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const results = [];
|
|
84
|
+
const bulkResponse = await this.runBulkChecks(entities);
|
|
85
|
+
for (const response of bulkResponse) {
|
|
86
|
+
Array.prototype.push.apply(results, response.results);
|
|
87
|
+
}
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
async getRelatedComponents(entity) {
|
|
91
|
+
switch (entity.kind) {
|
|
92
|
+
case "System":
|
|
93
|
+
return getEntityRelations(entity, RELATION_HAS_PART);
|
|
94
|
+
case "Domain":
|
|
95
|
+
return await this.getRelatedComponentsByRefs(
|
|
96
|
+
getEntityRelations(entity, RELATION_HAS_PART)
|
|
97
|
+
);
|
|
98
|
+
case "Group":
|
|
99
|
+
return this.getComponentsForGroup(entity);
|
|
100
|
+
default:
|
|
101
|
+
return [getCompoundEntityRef(entity)];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async getComponentsForGroup(entity) {
|
|
105
|
+
const childEntities = getEntityRelations(entity, RELATION_PARENT_OF);
|
|
106
|
+
if (childEntities.length > 0) {
|
|
107
|
+
return await this.getRelatedComponentsByRefs(childEntities);
|
|
108
|
+
}
|
|
109
|
+
const entityPartsRef = getEntityRelations(entity, RELATION_OWNER_OF);
|
|
110
|
+
return await this.getRelatedComponentsByRefs(entityPartsRef);
|
|
111
|
+
}
|
|
112
|
+
async getRelatedComponentsByRefs(refs) {
|
|
113
|
+
const { items } = await this.catalogApi.getEntitiesByRefs({
|
|
114
|
+
entityRefs: refs.map((x) => stringifyEntityRef(x))
|
|
115
|
+
});
|
|
116
|
+
const entityParts = [];
|
|
117
|
+
for (const item of items) {
|
|
118
|
+
Array.prototype.push.apply(
|
|
119
|
+
entityParts,
|
|
120
|
+
await this.getRelatedComponents(item)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return entityParts;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { MaturityClient };
|
|
128
|
+
//# sourceMappingURL=MaturityClient.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MaturityClient.esm.js","sources":["../../src/api/MaturityClient.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { CatalogApi } from '@backstage/catalog-client';\nimport {\n CompoundEntityRef,\n Entity,\n getCompoundEntityRef,\n isComponentEntity,\n RELATION_HAS_PART,\n RELATION_OWNER_OF,\n RELATION_PARENT_OF,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';\nimport { getEntityRelations } from '@backstage/plugin-catalog-react';\nimport { TechInsightsClient } from '@backstage-community/plugin-tech-insights';\nimport {\n BulkMaturityCheckResponse,\n BulkMaturitySummary,\n MaturityCheckResult,\n MaturityRank,\n MaturityScore,\n MaturitySummary,\n} from '@backstage-community/plugin-tech-insights-maturity-common';\nimport { MaturityApi } from './MaturityApi';\nimport { ScoringDataFormatter } from './ScoringDataFormatter';\n\nconst SDF = new ScoringDataFormatter();\n\n/**\n * MaturityClient extension of TechInsightsClient\n *\n * @public\n */\nexport class MaturityClient extends TechInsightsClient implements MaturityApi {\n readonly catalogApi: CatalogApi;\n\n constructor(options: {\n discoveryApi: DiscoveryApi;\n identityApi: IdentityApi;\n catalogApi: CatalogApi;\n }) {\n super(options);\n this.catalogApi = options.catalogApi;\n }\n\n public async getMaturityRank(entity: Entity): Promise<MaturityRank> {\n const checksResult = await this.getCheckResults(entity);\n return SDF.getMaturityRank(checksResult);\n }\n\n public async getBulkMaturityCheckResults(\n entities: Entity[],\n ): Promise<BulkMaturityCheckResponse> {\n return await this.getBulkCheckResults(\n entities.map(x => getCompoundEntityRef(x)),\n );\n }\n\n public async getMaturityScore(entity: Entity): Promise<MaturityScore> {\n const checks = await this.getCheckResults(entity);\n const rank = SDF.getMaturityRank(checks);\n const summary = SDF.getMaturitySummary(checks);\n return {\n checks,\n summary,\n rank,\n };\n }\n\n public async getChildMaturityCheckResults(\n entity: Entity,\n ): Promise<BulkMaturityCheckResponse> {\n const entities = await this.getRelatedComponents(entity);\n return await this.getBulkCheckResults(entities);\n }\n\n public async getMaturitySummary(entity: Entity): Promise<MaturitySummary> {\n const checksResult = await this.getCheckResults(entity);\n return SDF.getMaturitySummary(checksResult);\n }\n\n public async getBulkMaturitySummary(\n entities: Entity[],\n ): Promise<BulkMaturitySummary> {\n return Promise.all(\n entities.map(async x => {\n const checks = await this.getCheckResults(x);\n const summary = SDF.getMaturitySummary(checks);\n const rank = SDF.getMaturityRank(checks);\n\n return {\n entity: stringifyEntityRef(x),\n rank: rank.rank,\n isMaxRank: rank.isMaxRank,\n summary,\n };\n }),\n );\n }\n\n private async getCheckResults(\n entity: Entity,\n ): Promise<MaturityCheckResult[]> {\n if (isComponentEntity(entity)) {\n return (await this.runChecks(\n getCompoundEntityRef(entity),\n )) as MaturityCheckResult[];\n }\n\n const entities = await this.getRelatedComponents(entity);\n return await this.getGroupCheckResults(entities);\n }\n\n private async getBulkCheckResults(entities: CompoundEntityRef[]) {\n const bulkResponse = await this.runBulkChecks(entities);\n return Promise.all(\n bulkResponse.map(async x => {\n const checks = x.results as MaturityCheckResult[];\n const rank = SDF.getMaturityRank(checks);\n\n return {\n entity: x.entity,\n rank: rank.rank,\n isMaxRank: rank.isMaxRank,\n checks,\n };\n }),\n );\n }\n\n private async getGroupCheckResults(\n entities: CompoundEntityRef[],\n ): Promise<MaturityCheckResult[]> {\n /**\n * Passing an empty array into techInsightsClient.runBulkChecks()\n * now causes checks to run across the entire catalog.\n * We don't want to run checks here if no entities were provided.\n */\n if (entities.length === 0) {\n return [];\n }\n\n const results: MaturityCheckResult[] = [];\n const bulkResponse = await this.runBulkChecks(entities);\n for (const response of bulkResponse) {\n Array.prototype.push.apply(results, response.results);\n }\n\n return results;\n }\n\n private async getRelatedComponents(\n entity: Entity,\n ): Promise<CompoundEntityRef[]> {\n switch (entity.kind) {\n case 'System':\n return getEntityRelations(entity, RELATION_HAS_PART);\n case 'Domain':\n return await this.getRelatedComponentsByRefs(\n getEntityRelations(entity, RELATION_HAS_PART),\n );\n case 'Group':\n return this.getComponentsForGroup(entity);\n default:\n return [getCompoundEntityRef(entity)];\n }\n }\n\n private async getComponentsForGroup(\n entity: Entity,\n ): Promise<CompoundEntityRef[]> {\n const childEntities = getEntityRelations(entity, RELATION_PARENT_OF);\n if (childEntities.length > 0) {\n return await this.getRelatedComponentsByRefs(childEntities);\n }\n const entityPartsRef = getEntityRelations(entity, RELATION_OWNER_OF);\n return await this.getRelatedComponentsByRefs(entityPartsRef);\n }\n\n private async getRelatedComponentsByRefs(refs: CompoundEntityRef[]) {\n const { items } = await this.catalogApi.getEntitiesByRefs({\n entityRefs: refs.map(x => stringifyEntityRef(x)),\n });\n\n const entityParts: CompoundEntityRef[] = [];\n for (const item of items) {\n Array.prototype.push.apply(\n entityParts,\n await this.getRelatedComponents(item!),\n );\n }\n return entityParts;\n }\n}\n"],"names":[],"mappings":";;;;;AAwCA,MAAM,GAAA,GAAM,IAAI,oBAAqB,EAAA;AAO9B,MAAM,uBAAuB,kBAA0C,CAAA;AAAA,EACnE,UAAA;AAAA,EAET,YAAY,OAIT,EAAA;AACD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,aAAa,OAAQ,CAAA,UAAA;AAAA;AAC5B,EAEA,MAAa,gBAAgB,MAAuC,EAAA;AAClE,IAAA,MAAM,YAAe,GAAA,MAAM,IAAK,CAAA,eAAA,CAAgB,MAAM,CAAA;AACtD,IAAO,OAAA,GAAA,CAAI,gBAAgB,YAAY,CAAA;AAAA;AACzC,EAEA,MAAa,4BACX,QACoC,EAAA;AACpC,IAAA,OAAO,MAAM,IAAK,CAAA,mBAAA;AAAA,MAChB,QAAS,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,oBAAA,CAAqB,CAAC,CAAC;AAAA,KAC3C;AAAA;AACF,EAEA,MAAa,iBAAiB,MAAwC,EAAA;AACpE,IAAA,MAAM,MAAS,GAAA,MAAM,IAAK,CAAA,eAAA,CAAgB,MAAM,CAAA;AAChD,IAAM,MAAA,IAAA,GAAO,GAAI,CAAA,eAAA,CAAgB,MAAM,CAAA;AACvC,IAAM,MAAA,OAAA,GAAU,GAAI,CAAA,kBAAA,CAAmB,MAAM,CAAA;AAC7C,IAAO,OAAA;AAAA,MACL,MAAA;AAAA,MACA,OAAA;AAAA,MACA;AAAA,KACF;AAAA;AACF,EAEA,MAAa,6BACX,MACoC,EAAA;AACpC,IAAA,MAAM,QAAW,GAAA,MAAM,IAAK,CAAA,oBAAA,CAAqB,MAAM,CAAA;AACvD,IAAO,OAAA,MAAM,IAAK,CAAA,mBAAA,CAAoB,QAAQ,CAAA;AAAA;AAChD,EAEA,MAAa,mBAAmB,MAA0C,EAAA;AACxE,IAAA,MAAM,YAAe,GAAA,MAAM,IAAK,CAAA,eAAA,CAAgB,MAAM,CAAA;AACtD,IAAO,OAAA,GAAA,CAAI,mBAAmB,YAAY,CAAA;AAAA;AAC5C,EAEA,MAAa,uBACX,QAC8B,EAAA;AAC9B,IAAA,OAAO,OAAQ,CAAA,GAAA;AAAA,MACb,QAAA,CAAS,GAAI,CAAA,OAAM,CAAK,KAAA;AACtB,QAAA,MAAM,MAAS,GAAA,MAAM,IAAK,CAAA,eAAA,CAAgB,CAAC,CAAA;AAC3C,QAAM,MAAA,OAAA,GAAU,GAAI,CAAA,kBAAA,CAAmB,MAAM,CAAA;AAC7C,QAAM,MAAA,IAAA,GAAO,GAAI,CAAA,eAAA,CAAgB,MAAM,CAAA;AAEvC,QAAO,OAAA;AAAA,UACL,MAAA,EAAQ,mBAAmB,CAAC,CAAA;AAAA,UAC5B,MAAM,IAAK,CAAA,IAAA;AAAA,UACX,WAAW,IAAK,CAAA,SAAA;AAAA,UAChB;AAAA,SACF;AAAA,OACD;AAAA,KACH;AAAA;AACF,EAEA,MAAc,gBACZ,MACgC,EAAA;AAChC,IAAI,IAAA,iBAAA,CAAkB,MAAM,CAAG,EAAA;AAC7B,MAAA,OAAQ,MAAM,IAAK,CAAA,SAAA;AAAA,QACjB,qBAAqB,MAAM;AAAA,OAC7B;AAAA;AAGF,IAAA,MAAM,QAAW,GAAA,MAAM,IAAK,CAAA,oBAAA,CAAqB,MAAM,CAAA;AACvD,IAAO,OAAA,MAAM,IAAK,CAAA,oBAAA,CAAqB,QAAQ,CAAA;AAAA;AACjD,EAEA,MAAc,oBAAoB,QAA+B,EAAA;AAC/D,IAAA,MAAM,YAAe,GAAA,MAAM,IAAK,CAAA,aAAA,CAAc,QAAQ,CAAA;AACtD,IAAA,OAAO,OAAQ,CAAA,GAAA;AAAA,MACb,YAAA,CAAa,GAAI,CAAA,OAAM,CAAK,KAAA;AAC1B,QAAA,MAAM,SAAS,CAAE,CAAA,OAAA;AACjB,QAAM,MAAA,IAAA,GAAO,GAAI,CAAA,eAAA,CAAgB,MAAM,CAAA;AAEvC,QAAO,OAAA;AAAA,UACL,QAAQ,CAAE,CAAA,MAAA;AAAA,UACV,MAAM,IAAK,CAAA,IAAA;AAAA,UACX,WAAW,IAAK,CAAA,SAAA;AAAA,UAChB;AAAA,SACF;AAAA,OACD;AAAA,KACH;AAAA;AACF,EAEA,MAAc,qBACZ,QACgC,EAAA;AAMhC,IAAI,IAAA,QAAA,CAAS,WAAW,CAAG,EAAA;AACzB,MAAA,OAAO,EAAC;AAAA;AAGV,IAAA,MAAM,UAAiC,EAAC;AACxC,IAAA,MAAM,YAAe,GAAA,MAAM,IAAK,CAAA,aAAA,CAAc,QAAQ,CAAA;AACtD,IAAA,KAAA,MAAW,YAAY,YAAc,EAAA;AACnC,MAAA,KAAA,CAAM,SAAU,CAAA,IAAA,CAAK,KAAM,CAAA,OAAA,EAAS,SAAS,OAAO,CAAA;AAAA;AAGtD,IAAO,OAAA,OAAA;AAAA;AACT,EAEA,MAAc,qBACZ,MAC8B,EAAA;AAC9B,IAAA,QAAQ,OAAO,IAAM;AAAA,MACnB,KAAK,QAAA;AACH,QAAO,OAAA,kBAAA,CAAmB,QAAQ,iBAAiB,CAAA;AAAA,MACrD,KAAK,QAAA;AACH,QAAA,OAAO,MAAM,IAAK,CAAA,0BAAA;AAAA,UAChB,kBAAA,CAAmB,QAAQ,iBAAiB;AAAA,SAC9C;AAAA,MACF,KAAK,OAAA;AACH,QAAO,OAAA,IAAA,CAAK,sBAAsB,MAAM,CAAA;AAAA,MAC1C;AACE,QAAO,OAAA,CAAC,oBAAqB,CAAA,MAAM,CAAC,CAAA;AAAA;AACxC;AACF,EAEA,MAAc,sBACZ,MAC8B,EAAA;AAC9B,IAAM,MAAA,aAAA,GAAgB,kBAAmB,CAAA,MAAA,EAAQ,kBAAkB,CAAA;AACnE,IAAI,IAAA,aAAA,CAAc,SAAS,CAAG,EAAA;AAC5B,MAAO,OAAA,MAAM,IAAK,CAAA,0BAAA,CAA2B,aAAa,CAAA;AAAA;AAE5D,IAAM,MAAA,cAAA,GAAiB,kBAAmB,CAAA,MAAA,EAAQ,iBAAiB,CAAA;AACnE,IAAO,OAAA,MAAM,IAAK,CAAA,0BAAA,CAA2B,cAAc,CAAA;AAAA;AAC7D,EAEA,MAAc,2BAA2B,IAA2B,EAAA;AAClE,IAAA,MAAM,EAAE,KAAM,EAAA,GAAI,MAAM,IAAA,CAAK,WAAW,iBAAkB,CAAA;AAAA,MACxD,YAAY,IAAK,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,kBAAA,CAAmB,CAAC,CAAC;AAAA,KAChD,CAAA;AAED,IAAA,MAAM,cAAmC,EAAC;AAC1C,IAAA,KAAA,MAAW,QAAQ,KAAO,EAAA;AACxB,MAAA,KAAA,CAAM,UAAU,IAAK,CAAA,KAAA;AAAA,QACnB,WAAA;AAAA,QACA,MAAM,IAAK,CAAA,oBAAA,CAAqB,IAAK;AAAA,OACvC;AAAA;AAEF,IAAO,OAAA,WAAA;AAAA;AAEX;;;;"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Rank } from '@backstage-community/plugin-tech-insights-maturity-common';
|
|
2
|
+
|
|
3
|
+
class ScoringDataFormatter {
|
|
4
|
+
getMaturityRank(results) {
|
|
5
|
+
return this.calculateRank(results);
|
|
6
|
+
}
|
|
7
|
+
getMaturitySummary(results) {
|
|
8
|
+
const { rank, isMaxRank } = this.calculateRank(results);
|
|
9
|
+
const maxRank = this.calculateMaxRank(results);
|
|
10
|
+
const nextRank = isMaxRank ? maxRank : rank + 1;
|
|
11
|
+
const scoresByArea = this.groupCheckResultByCategory(results);
|
|
12
|
+
let totalChecks = 0;
|
|
13
|
+
let passedChecks = 0;
|
|
14
|
+
let rankTotalChecks = 0;
|
|
15
|
+
let rankPassedChecks = 0;
|
|
16
|
+
const areaSummaries = Object.entries(scoresByArea).map(([areaName, areaResults]) => {
|
|
17
|
+
const { rank: areaRank, isMaxRank: areaIsMaxRank } = this.calculateRank(areaResults);
|
|
18
|
+
const areaMaxRank = this.calculateMaxRank(areaResults);
|
|
19
|
+
const areaNextRank = areaIsMaxRank ? areaMaxRank : areaRank + 1;
|
|
20
|
+
let areaTotalChecks = 0;
|
|
21
|
+
let areaPassedChecks = 0;
|
|
22
|
+
const areaRankTotalChecks = {
|
|
23
|
+
[Rank.Stone]: 0,
|
|
24
|
+
[Rank.Bronze]: 0,
|
|
25
|
+
[Rank.Silver]: 0,
|
|
26
|
+
[Rank.Gold]: 0
|
|
27
|
+
};
|
|
28
|
+
const areaRankPassedChecks = {
|
|
29
|
+
[Rank.Stone]: 0,
|
|
30
|
+
[Rank.Bronze]: 0,
|
|
31
|
+
[Rank.Silver]: 0,
|
|
32
|
+
[Rank.Gold]: 0
|
|
33
|
+
};
|
|
34
|
+
areaResults.forEach((checkResult) => {
|
|
35
|
+
areaTotalChecks++;
|
|
36
|
+
if (checkResult.result) {
|
|
37
|
+
areaPassedChecks++;
|
|
38
|
+
}
|
|
39
|
+
areaRankTotalChecks[checkResult.check.metadata.rank]++;
|
|
40
|
+
if (checkResult.result) {
|
|
41
|
+
areaRankPassedChecks[checkResult.check.metadata.rank]++;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
totalChecks += areaTotalChecks;
|
|
45
|
+
passedChecks += areaPassedChecks;
|
|
46
|
+
rankTotalChecks += areaRankTotalChecks[nextRank];
|
|
47
|
+
rankPassedChecks += areaRankPassedChecks[nextRank];
|
|
48
|
+
const areaProgPercent = this.calculatePercent(
|
|
49
|
+
areaPassedChecks,
|
|
50
|
+
areaTotalChecks
|
|
51
|
+
);
|
|
52
|
+
const areaRankProgPercent = this.calculatePercent(
|
|
53
|
+
areaRankPassedChecks[areaNextRank],
|
|
54
|
+
areaRankTotalChecks[areaNextRank]
|
|
55
|
+
);
|
|
56
|
+
return {
|
|
57
|
+
area: areaName,
|
|
58
|
+
progress: {
|
|
59
|
+
passedChecks: areaPassedChecks,
|
|
60
|
+
totalChecks: areaTotalChecks,
|
|
61
|
+
percentage: areaProgPercent
|
|
62
|
+
},
|
|
63
|
+
rankProgress: {
|
|
64
|
+
passedChecks: areaRankPassedChecks[areaNextRank],
|
|
65
|
+
totalChecks: areaRankTotalChecks[areaNextRank],
|
|
66
|
+
percentage: areaRankProgPercent
|
|
67
|
+
},
|
|
68
|
+
rank: areaRank,
|
|
69
|
+
maxRank: areaMaxRank,
|
|
70
|
+
isMaxRank: areaIsMaxRank
|
|
71
|
+
};
|
|
72
|
+
}).sort((a, b) => a.area > b.area ? -1 : 1);
|
|
73
|
+
const progPercent = this.calculatePercent(passedChecks, totalChecks);
|
|
74
|
+
const rankProgPercent = this.calculatePercent(
|
|
75
|
+
rankPassedChecks,
|
|
76
|
+
rankTotalChecks
|
|
77
|
+
);
|
|
78
|
+
return {
|
|
79
|
+
points: this.calculatePoints(results),
|
|
80
|
+
progress: {
|
|
81
|
+
passedChecks,
|
|
82
|
+
totalChecks,
|
|
83
|
+
percentage: progPercent
|
|
84
|
+
},
|
|
85
|
+
rankProgress: {
|
|
86
|
+
passedChecks: rankPassedChecks,
|
|
87
|
+
totalChecks: rankTotalChecks,
|
|
88
|
+
percentage: rankProgPercent
|
|
89
|
+
},
|
|
90
|
+
rank,
|
|
91
|
+
maxRank,
|
|
92
|
+
isMaxRank,
|
|
93
|
+
areaSummaries
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Some checks within one Area are divided as they should be applied for certain types of Components only
|
|
98
|
+
* When displaying Area scores in the UI they should be concatenated again for total Area score calculation
|
|
99
|
+
*/
|
|
100
|
+
groupCheckResultByCategory(results) {
|
|
101
|
+
const checksByCategory = {};
|
|
102
|
+
results.forEach((checkResult) => {
|
|
103
|
+
checksByCategory[checkResult.check.metadata.category] ??= [];
|
|
104
|
+
checksByCategory[checkResult.check.metadata.category].push(checkResult);
|
|
105
|
+
});
|
|
106
|
+
return checksByCategory;
|
|
107
|
+
}
|
|
108
|
+
/** Calculated rank based on the total performance of all areas */
|
|
109
|
+
calculateRank(results) {
|
|
110
|
+
const maxRank = this.calculateMaxRank(results);
|
|
111
|
+
let rank = maxRank;
|
|
112
|
+
results.forEach((checkResult) => {
|
|
113
|
+
if (!checkResult.result && checkResult.check.metadata.rank <= rank) {
|
|
114
|
+
rank = checkResult.check.metadata.rank - 1;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
const isMaxRank = rank === maxRank;
|
|
118
|
+
return { rank, isMaxRank };
|
|
119
|
+
}
|
|
120
|
+
calculateMaxRank(results) {
|
|
121
|
+
let rank = Rank.Stone;
|
|
122
|
+
results.forEach((checkResult) => {
|
|
123
|
+
if (checkResult.check.metadata.rank > rank) {
|
|
124
|
+
rank = checkResult.check.metadata.rank;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return rank;
|
|
128
|
+
}
|
|
129
|
+
calculatePoints(results) {
|
|
130
|
+
let points = 0;
|
|
131
|
+
results.filter((checkResult) => checkResult.result).forEach((checkResult) => {
|
|
132
|
+
points += checkResult.check.metadata.rank * 100;
|
|
133
|
+
});
|
|
134
|
+
return points;
|
|
135
|
+
}
|
|
136
|
+
calculatePercent(passedChecks, totalChecks) {
|
|
137
|
+
return totalChecks === 0 ? 0 : Math.round(passedChecks / totalChecks * 100);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { ScoringDataFormatter };
|
|
142
|
+
//# sourceMappingURL=ScoringDataFormatter.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScoringDataFormatter.esm.js","sources":["../../src/api/ScoringDataFormatter.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n MaturityCheckResult,\n MaturityRank,\n MaturitySummary,\n MaturitySummaryByArea,\n Rank,\n} from '@backstage-community/plugin-tech-insights-maturity-common';\n\n/**\n * Formats scoring data received from TechInsightsPlugin checks run\n */\nexport class ScoringDataFormatter {\n getMaturityRank(results: MaturityCheckResult[]): MaturityRank {\n return this.calculateRank(results);\n }\n\n getMaturitySummary(results: MaturityCheckResult[]): MaturitySummary {\n const { rank, isMaxRank } = this.calculateRank(results);\n const maxRank = this.calculateMaxRank(results);\n const nextRank = isMaxRank ? maxRank : rank + 1;\n const scoresByArea = this.groupCheckResultByCategory(results);\n let totalChecks = 0;\n let passedChecks = 0;\n let rankTotalChecks = 0;\n let rankPassedChecks = 0;\n\n const areaSummaries: MaturitySummaryByArea[] = Object.entries(scoresByArea)\n .map(([areaName, areaResults]) => {\n const { rank: areaRank, isMaxRank: areaIsMaxRank } =\n this.calculateRank(areaResults);\n const areaMaxRank = this.calculateMaxRank(areaResults);\n const areaNextRank = areaIsMaxRank ? areaMaxRank : areaRank + 1;\n let areaTotalChecks = 0;\n let areaPassedChecks = 0;\n\n // Count checks for each rank because overall next rank may be different from area next rank\n const areaRankTotalChecks: { [key: number]: number } = {\n [Rank.Stone]: 0,\n [Rank.Bronze]: 0,\n [Rank.Silver]: 0,\n [Rank.Gold]: 0,\n };\n const areaRankPassedChecks: { [key: number]: number } = {\n [Rank.Stone]: 0,\n [Rank.Bronze]: 0,\n [Rank.Silver]: 0,\n [Rank.Gold]: 0,\n };\n\n areaResults.forEach(checkResult => {\n areaTotalChecks++;\n if (checkResult.result) {\n areaPassedChecks++;\n }\n\n // Count check progress in each rank\n areaRankTotalChecks[checkResult.check.metadata.rank]++;\n if (checkResult.result) {\n areaRankPassedChecks[checkResult.check.metadata.rank]++;\n }\n });\n\n // Aggregate overall check progress\n totalChecks += areaTotalChecks;\n passedChecks += areaPassedChecks;\n // Aggregate check progress from checks in next overall rank\n rankTotalChecks += areaRankTotalChecks[nextRank];\n rankPassedChecks += areaRankPassedChecks[nextRank];\n\n const areaProgPercent = this.calculatePercent(\n areaPassedChecks,\n areaTotalChecks,\n );\n const areaRankProgPercent = this.calculatePercent(\n areaRankPassedChecks[areaNextRank],\n areaRankTotalChecks[areaNextRank],\n );\n\n return {\n area: areaName,\n progress: {\n passedChecks: areaPassedChecks,\n totalChecks: areaTotalChecks,\n percentage: areaProgPercent,\n },\n rankProgress: {\n passedChecks: areaRankPassedChecks[areaNextRank],\n totalChecks: areaRankTotalChecks[areaNextRank],\n percentage: areaRankProgPercent,\n },\n rank: areaRank,\n maxRank: areaMaxRank,\n isMaxRank: areaIsMaxRank,\n };\n })\n .sort((a, b) => (a.area > b.area ? -1 : 1)); // Sort according to area name\n\n const progPercent = this.calculatePercent(passedChecks, totalChecks);\n const rankProgPercent = this.calculatePercent(\n rankPassedChecks,\n rankTotalChecks,\n );\n\n return {\n points: this.calculatePoints(results),\n progress: {\n passedChecks,\n totalChecks,\n percentage: progPercent,\n },\n rankProgress: {\n passedChecks: rankPassedChecks,\n totalChecks: rankTotalChecks,\n percentage: rankProgPercent,\n },\n rank,\n maxRank,\n isMaxRank,\n areaSummaries,\n };\n }\n\n /**\n * Some checks within one Area are divided as they should be applied for certain types of Components only\n * When displaying Area scores in the UI they should be concatenated again for total Area score calculation\n */\n private groupCheckResultByCategory(results: MaturityCheckResult[]) {\n const checksByCategory: {\n [key: string]: MaturityCheckResult[];\n } = {};\n\n results.forEach(checkResult => {\n checksByCategory[checkResult.check.metadata.category] ??= [];\n checksByCategory[checkResult.check.metadata.category].push(checkResult);\n });\n\n return checksByCategory;\n }\n\n /** Calculated rank based on the total performance of all areas */\n private calculateRank(results: MaturityCheckResult[]): MaturityRank {\n const maxRank = this.calculateMaxRank(results);\n let rank = maxRank; // Assume maximum rank\n\n // Reduce rank if a failing check for a lower rank exists\n results.forEach(checkResult => {\n if (!checkResult.result && checkResult.check.metadata.rank <= rank) {\n rank = checkResult.check.metadata.rank - 1;\n }\n });\n\n const isMaxRank = rank === maxRank;\n\n return { rank, isMaxRank };\n }\n\n private calculateMaxRank(results: MaturityCheckResult[]): Rank {\n let rank: Rank = Rank.Stone;\n\n // Get the highest rank achievable for this area\n results.forEach(checkResult => {\n if (checkResult.check.metadata.rank > rank) {\n rank = checkResult.check.metadata.rank;\n }\n });\n\n return rank;\n }\n\n private calculatePoints(results: MaturityCheckResult[]): number {\n let points = 0;\n\n // Incremental of 100 points for each succeeding rank\n results\n .filter(checkResult => checkResult.result)\n .forEach(checkResult => {\n points += checkResult.check.metadata.rank * 100;\n });\n\n return points;\n }\n\n calculatePercent(passedChecks: number, totalChecks: number): number {\n // Prevent dividing by zero\n return totalChecks === 0\n ? 0\n : Math.round((passedChecks / totalChecks) * 100);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
2
|
+
import { Rank } from '@backstage-community/plugin-tech-insights-maturity-common';
|
|
3
|
+
import Card from '@mui/material/Card';
|
|
4
|
+
import CardContent from '@mui/material/CardContent';
|
|
5
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
|
6
|
+
import { styled } from '@mui/material/styles';
|
|
7
|
+
import { useDrawingArea } from '@mui/x-charts/hooks';
|
|
8
|
+
import { PieChart } from '@mui/x-charts/PieChart';
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import useAsyncRetry from 'react-use/lib/useAsync';
|
|
11
|
+
import { maturityApiRef } from '../../api/MaturityApi.esm.js';
|
|
12
|
+
import '@backstage/catalog-model';
|
|
13
|
+
import '@backstage/plugin-catalog-react';
|
|
14
|
+
import '@backstage-community/plugin-tech-insights';
|
|
15
|
+
import { getRankColor } from '../../helpers/utils.esm.js';
|
|
16
|
+
|
|
17
|
+
const size = {
|
|
18
|
+
width: 380,
|
|
19
|
+
height: 300
|
|
20
|
+
};
|
|
21
|
+
const StyledText = styled("text")(({ theme }) => ({
|
|
22
|
+
fill: theme.palette.text.primary,
|
|
23
|
+
textAnchor: "middle",
|
|
24
|
+
dominantBaseline: "central",
|
|
25
|
+
fontSize: 20
|
|
26
|
+
}));
|
|
27
|
+
function PieCenterLabel({ children }) {
|
|
28
|
+
const { width, height, left, top } = useDrawingArea();
|
|
29
|
+
return /* @__PURE__ */ React.createElement(StyledText, { x: left + width / 2, y: top + height / 2 }, children);
|
|
30
|
+
}
|
|
31
|
+
const MaturityChartCard = ({ entities }) => {
|
|
32
|
+
const api = useApi(maturityApiRef);
|
|
33
|
+
const { loading, value } = useAsyncRetry(
|
|
34
|
+
async () => api.getBulkMaturityCheckResults(entities),
|
|
35
|
+
[api]
|
|
36
|
+
);
|
|
37
|
+
return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardContent, null, loading && /* @__PURE__ */ React.createElement(CircularProgress, { size: 100 }), value && value.length > 0 && /* @__PURE__ */ React.createElement(
|
|
38
|
+
PieChart,
|
|
39
|
+
{
|
|
40
|
+
colors: [
|
|
41
|
+
getRankColor(Rank.Stone),
|
|
42
|
+
getRankColor(Rank.Bronze),
|
|
43
|
+
getRankColor(Rank.Silver),
|
|
44
|
+
getRankColor(Rank.Gold)
|
|
45
|
+
],
|
|
46
|
+
series: [
|
|
47
|
+
{
|
|
48
|
+
arcLabel: (item) => item.value > 0 ? `${(item.value / value.length * 100).toFixed()}%` : "",
|
|
49
|
+
data: [
|
|
50
|
+
{
|
|
51
|
+
value: value.filter((x) => x.rank === Rank.Stone).length,
|
|
52
|
+
label: Rank[Rank.Stone]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
value: value.filter((x) => x.rank === Rank.Bronze).length,
|
|
56
|
+
label: Rank[Rank.Bronze]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: value.filter((x) => x.rank === Rank.Silver).length,
|
|
60
|
+
label: Rank[Rank.Silver]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
value: value.filter((x) => x.rank === Rank.Gold).length,
|
|
64
|
+
label: Rank[Rank.Gold]
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
highlightScope: { faded: "global", highlighted: "item" },
|
|
68
|
+
faded: { color: "gray" },
|
|
69
|
+
innerRadius: 90
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
...size
|
|
73
|
+
},
|
|
74
|
+
/* @__PURE__ */ React.createElement(PieCenterLabel, null, `${value.length} services`)
|
|
75
|
+
)));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export { MaturityChartCard };
|
|
79
|
+
//# sourceMappingURL=MaturityChartCard.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MaturityChartCard.esm.js","sources":["../../../src/components/MaturityChartCard/MaturityChartCard.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Entity } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Rank } from '@backstage-community/plugin-tech-insights-maturity-common';\nimport Card from '@mui/material/Card';\nimport CardContent from '@mui/material/CardContent';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport { styled } from '@mui/material/styles';\nimport { useDrawingArea } from '@mui/x-charts/hooks';\nimport { PieChart } from '@mui/x-charts/PieChart';\nimport React from 'react';\nimport useAsyncRetry from 'react-use/lib/useAsync';\nimport { maturityApiRef } from '../../api';\nimport { getRankColor } from '../../helpers/utils';\n\ntype Props = {\n entities: Entity[];\n};\n\nconst size = {\n width: 380,\n height: 300,\n};\n\nconst StyledText = styled('text')(({ theme }) => ({\n fill: theme.palette.text.primary,\n textAnchor: 'middle',\n dominantBaseline: 'central',\n fontSize: 20,\n}));\n\nfunction PieCenterLabel({ children }: { children: React.ReactNode }) {\n const { width, height, left, top } = useDrawingArea();\n return (\n <StyledText x={left + width / 2} y={top + height / 2}>\n {children}\n </StyledText>\n );\n}\n\nexport const MaturityChartCard = ({ entities }: Props) => {\n const api = useApi(maturityApiRef);\n const { loading, value } = useAsyncRetry(\n async () => api.getBulkMaturityCheckResults(entities),\n [api],\n );\n\n return (\n <Card>\n <CardContent>\n {loading && <CircularProgress size={100} />}\n {value && value.length > 0 && (\n <PieChart\n colors={[\n getRankColor(Rank.Stone),\n getRankColor(Rank.Bronze),\n getRankColor(Rank.Silver),\n getRankColor(Rank.Gold),\n ]}\n series={[\n {\n arcLabel: item =>\n item.value > 0\n ? `${((item.value / value.length) * 100).toFixed()}%`\n : '',\n data: [\n {\n value: value.filter(x => x.rank === Rank.Stone).length,\n label: Rank[Rank.Stone],\n },\n {\n value: value.filter(x => x.rank === Rank.Bronze).length,\n label: Rank[Rank.Bronze],\n },\n {\n value: value.filter(x => x.rank === Rank.Silver).length,\n label: Rank[Rank.Silver],\n },\n {\n value: value.filter(x => x.rank === Rank.Gold).length,\n label: Rank[Rank.Gold],\n },\n ],\n highlightScope: { faded: 'global', highlighted: 'item' },\n faded: { color: 'gray' },\n innerRadius: 90,\n },\n ]}\n {...size}\n >\n <PieCenterLabel>{`${value.length} services`}</PieCenterLabel>\n </PieChart>\n )}\n </CardContent>\n </Card>\n );\n};\n"],"names":[],"mappings":"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"}
|