@red-hat-developer-hub/backstage-plugin-scorecard 1.0.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +164 -0
  3. package/dist/alpha.d.ts +36 -0
  4. package/dist/alpha.esm.js +3 -0
  5. package/dist/alpha.esm.js.map +1 -0
  6. package/dist/api/index.esm.js +63 -0
  7. package/dist/api/index.esm.js.map +1 -0
  8. package/dist/components/Common/NoScorecardsState.esm.js +83 -0
  9. package/dist/components/Common/NoScorecardsState.esm.js.map +1 -0
  10. package/dist/components/Common/PermissionRequiredState.esm.js +94 -0
  11. package/dist/components/Common/PermissionRequiredState.esm.js.map +1 -0
  12. package/dist/components/Scorecard/EntityScorecardContent.esm.js +85 -0
  13. package/dist/components/Scorecard/EntityScorecardContent.esm.js.map +1 -0
  14. package/dist/components/Scorecard/Scorecard.esm.js +249 -0
  15. package/dist/components/Scorecard/Scorecard.esm.js.map +1 -0
  16. package/dist/components/Scorecard/index.esm.js +2 -0
  17. package/dist/components/Scorecard/index.esm.js.map +1 -0
  18. package/dist/hooks/useScorecardMetricsReadPermission.esm.js +16 -0
  19. package/dist/hooks/useScorecardMetricsReadPermission.esm.js.map +1 -0
  20. package/dist/hooks/useScorecards.esm.js +45 -0
  21. package/dist/hooks/useScorecards.esm.js.map +1 -0
  22. package/dist/hooks/useTranslation.esm.js +8 -0
  23. package/dist/hooks/useTranslation.esm.js.map +1 -0
  24. package/dist/images/no-scorecards.svg +9 -0
  25. package/dist/images/permission-required.svg +48 -0
  26. package/dist/index.d.ts +15 -0
  27. package/dist/index.esm.js +2 -0
  28. package/dist/index.esm.js.map +1 -0
  29. package/dist/plugin.esm.js +36 -0
  30. package/dist/plugin.esm.js.map +1 -0
  31. package/dist/routes.esm.js +8 -0
  32. package/dist/routes.esm.js.map +1 -0
  33. package/dist/translations/de.esm.js +37 -0
  34. package/dist/translations/de.esm.js.map +1 -0
  35. package/dist/translations/es.esm.js +37 -0
  36. package/dist/translations/es.esm.js.map +1 -0
  37. package/dist/translations/fr.esm.js +37 -0
  38. package/dist/translations/fr.esm.js.map +1 -0
  39. package/dist/translations/index.esm.js +14 -0
  40. package/dist/translations/index.esm.js.map +1 -0
  41. package/dist/translations/ref.esm.js +50 -0
  42. package/dist/translations/ref.esm.js.map +1 -0
  43. package/dist/utils/utils.esm.js +24 -0
  44. package/dist/utils/utils.esm.js.map +1 -0
  45. package/package.json +88 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # @red-hat-developer-hub/backstage-plugin-scorecard
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 6709132: Release the major version of Scorecard plugin
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [6709132]
12
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@1.0.0
13
+
14
+ ## 0.2.0
15
+
16
+ ### Minor Changes
17
+
18
+ - 0478b79: Unify provider IDs
19
+
20
+ ## 0.1.0
21
+
22
+ ### Minor Changes
23
+
24
+ - fe2fa2d: Add internationalization (i18n) support with German, French and Spanish translations for the Scorecard plugin
25
+ - b5ec15c: First version of Scorecard plugin
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [b5ec15c]
30
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@0.1.0
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # 📊 Scorecard Plugin for Backstage
2
+
3
+ The Scorecard plugin provides a configurable framework to visualize Key Performance Indicators (KPIs) in Backstage. This frontend plugin integrates with the Scorecard backend to deliver Scorecards.
4
+
5
+ ## Getting started
6
+
7
+ Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/scorecard](http://localhost:3000/scorecard).
8
+
9
+ You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
10
+ This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
11
+ It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
12
+
13
+ ## For Administrators
14
+
15
+ ### Prerequisites
16
+
17
+ Before installing the frontend plugin, ensure that the Scorecard backend is integrated into your Backstage instance. Follow the [Scorecard backend plugin README](https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/scorecard/plugins/scorecard-backend/README.md) for setup instructions.
18
+
19
+ ### Installation
20
+
21
+ To install the Scorecard plugin, run the following command:
22
+
23
+ ```sh
24
+ yarn workspace app add @red-hat-developer-hub/backstage-plugin-scorecard
25
+ ```
26
+
27
+ **Note**
28
+
29
+ ### Permission Framework Support
30
+
31
+ The Scorecard plugin has support for the permission framework.
32
+
33
+ - When [RBAC permission](https://github.com/backstage/community-plugins/tree/main/workspaces/rbac/plugins/rbac-backend#installation) framework is enabled, for non-admin users to access scorecard UI, the role associated with your user should have the following permission policies associated with it. Add the following in your permission policies configuration file named `rbac-policy.csv`:
34
+
35
+ ```CSV
36
+ p, role:default/team_a, scorecard.metric.read, read, allow
37
+
38
+ g, user:default/<your-user-name>, role:default/team_a
39
+ ```
40
+
41
+ You can specify the path to this configuration file in your application configuration:
42
+
43
+ ```yaml
44
+ permission:
45
+ enabled: true
46
+ rbac:
47
+ policies-csv-file: /some/path/rbac-policy.csv
48
+ policyFileReload: true
49
+ ```
50
+
51
+ ### Configuration
52
+
53
+ 1. Add the Scorecard page to you Entity overview page by modifying `packages/app/src/components/catalog/EntityPage.tsx`:
54
+
55
+ ```tsx
56
+ import { ScorecardPage } from '@red-hat-developer-hub/backstage-plugin-scorecard';
57
+
58
+ const scorecardRoute = (
59
+ <EntityLayout.Route path="/scorecard" title="Scorecard">
60
+ <ScorecardPage />
61
+ </EntityLayout.Route>
62
+ );
63
+
64
+ const serviceEntityPage = (
65
+ <EntityLayout>
66
+ ...
67
+ {scorecardRoute}
68
+ </EntityLayout>
69
+ );
70
+
71
+ const websiteEntityPage = (
72
+ <EntityLayout>
73
+ ...
74
+ {scorecardRoute}
75
+ </EntityLayout>
76
+ );
77
+
78
+ const componentPage = (
79
+ <EntitySwitch>
80
+ <EntitySwitch.Case if={isComponentType('service')}>
81
+ {serviceEntityPage}
82
+ </EntitySwitch.Case>
83
+
84
+ <EntitySwitch.Case if={isComponentType('website')}>
85
+ {websiteEntityPage}
86
+ </EntitySwitch.Case>
87
+ </EntitySwitch>
88
+ );
89
+
90
+ export const entityPage = (
91
+ <EntitySwitch>
92
+ ...
93
+ <EntitySwitch.Case if={isKind('component')} children={componentPage} />
94
+ ...
95
+ </EntitySwitch>
96
+ );
97
+ ```
98
+
99
+ ### Accessing the Plugin
100
+
101
+ 1. Open your Backstage application.
102
+ 2. Navigate to the Entity overview page from catalog.
103
+ 3. Explore and analyze scorecard metrics using the scorecards tab.
104
+
105
+ ## Adding Translations
106
+
107
+ The Scorecard plugin supports internationalization (i18n) for metric titles and descriptions. To add translations for new metrics:
108
+
109
+ ### 1. Add Translations to Translation Files
110
+
111
+ Add your translations to the appropriate language files in `src/translations/`:
112
+
113
+ **English (ref.ts):**
114
+
115
+ ```typescript
116
+ export const scorecardMessages = {
117
+ // ... existing translations
118
+ metric: {
119
+ 'your-metric-id': {
120
+ title: 'Your Translated Title',
121
+ description: 'Your translated description',
122
+ },
123
+ },
124
+ };
125
+ ```
126
+
127
+ **Other languages (de.ts, es.ts, fr.ts):**
128
+
129
+ ```typescript
130
+ const scorecardTranslationDe = createTranslationMessages({
131
+ ref: scorecardTranslationRef,
132
+ messages: {
133
+ // ... existing translations
134
+ 'metric.your-metric-id.title': 'Ihr übersetzter Titel', // German
135
+ 'metric.your-metric-id.description': 'Ihre übersetzte Beschreibung',
136
+ },
137
+ });
138
+ ```
139
+
140
+ ### 3. Translation Key Format
141
+
142
+ Translation keys follow this pattern:
143
+
144
+ - **Metric titles**: `metric.{metric-id}.title`
145
+ - **Metric descriptions**: `metric.{metric-id}.description`
146
+
147
+ ### 4. Fallback Behavior
148
+
149
+ If a translation key is not found, the plugin will automatically fall back to:
150
+
151
+ - The original `title` and `description` values from the metric definition
152
+ - For thresholds: capitalized versions of the threshold keys
153
+
154
+ ### Example: Adding GitHub Open PRs Translation
155
+
156
+ ```typescript
157
+ // In ref.ts
158
+ metric: {
159
+ 'github.open_prs': {
160
+ title: 'GitHub open PRs',
161
+ description: 'Current count of open Pull Requests for a given GitHub repository',
162
+ },
163
+ }
164
+ ```
@@ -0,0 +1,36 @@
1
+ import * as _backstage_core_plugin_api_alpha from '@backstage/core-plugin-api/alpha';
2
+
3
+ /**
4
+ * Translation reference for scorecard plugin
5
+ * @public
6
+ */
7
+ declare const scorecardTranslationRef: _backstage_core_plugin_api_alpha.TranslationRef<"plugin.scorecard", {
8
+ readonly "emptyState.button": string;
9
+ readonly "emptyState.title": string;
10
+ readonly "emptyState.description": string;
11
+ readonly "emptyState.altText": string;
12
+ readonly "permissionRequired.button": string;
13
+ readonly "permissionRequired.title": string;
14
+ readonly "permissionRequired.description": string;
15
+ readonly "permissionRequired.altText": string;
16
+ readonly "errors.entityMissingProperties": string;
17
+ readonly "errors.invalidApiResponse": string;
18
+ readonly "errors.fetchError": string;
19
+ readonly "errors.metricDataUnavailable": string;
20
+ readonly "errors.invalidThresholds": string;
21
+ readonly "metric.github.open_prs.title": string;
22
+ readonly "metric.github.open_prs.description": string;
23
+ readonly "metric.jira.open_issues.title": string;
24
+ readonly "metric.jira.open_issues.description": string;
25
+ readonly "thresholds.success": string;
26
+ readonly "thresholds.error": string;
27
+ readonly "thresholds.warning": string;
28
+ }>;
29
+
30
+ /**
31
+ * Translation Resource for scorecard
32
+ * @public
33
+ */
34
+ declare const scorecardTranslations: _backstage_core_plugin_api_alpha.TranslationResource<"plugin.scorecard">;
35
+
36
+ export { scorecardTranslationRef, scorecardTranslations };
@@ -0,0 +1,3 @@
1
+ export { scorecardTranslations } from './translations/index.esm.js';
2
+ export { scorecardTranslationRef } from './translations/ref.esm.js';
3
+ //# sourceMappingURL=alpha.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alpha.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,63 @@
1
+ import { createApiRef } from '@backstage/core-plugin-api';
2
+
3
+ const scorecardApiRef = createApiRef({
4
+ id: "plugin.scorecard.service"
5
+ });
6
+ class ScorecardApiClient {
7
+ fetchApi;
8
+ discoveryApi;
9
+ constructor(options) {
10
+ this.fetchApi = options.fetchApi;
11
+ this.discoveryApi = options.discoveryApi;
12
+ }
13
+ /**
14
+ * Gets the base URL for the scorecard backend API.
15
+ * @returns Promise resolving to the base URL
16
+ */
17
+ async getBaseUrl() {
18
+ return await this.discoveryApi.getBaseUrl("scorecard");
19
+ }
20
+ /**
21
+ * Retrieves scorecard metrics for a specific entity.
22
+ * @param entity - The Backstage entity to get metrics for
23
+ * @param metricIds - Optional array of specific metric IDs to retrieve
24
+ * @returns Promise resolving to an array of metric results
25
+ * @throws Error if the request fails or returns invalid data
26
+ */
27
+ async getScorecards(entity, metricIds) {
28
+ if (!entity?.kind || !entity?.metadata?.namespace || !entity?.metadata?.name) {
29
+ throw new Error(
30
+ "Entity missing required properties for scorecard lookup"
31
+ );
32
+ }
33
+ const baseUrl = await this.getBaseUrl();
34
+ const url = new URL(
35
+ `${baseUrl}/metrics/catalog/${entity.kind}/${entity.metadata.namespace}/${entity.metadata.name}`
36
+ );
37
+ if (metricIds) {
38
+ url.searchParams.set("metricIds", metricIds.join(","));
39
+ }
40
+ try {
41
+ const response = await this.fetchApi.fetch(url.toString());
42
+ if (!response.ok) {
43
+ const errorText = await response.text();
44
+ throw new Error(
45
+ `Failed to fetch scorecards: ${response.status} ${response.statusText}. ${errorText}`
46
+ );
47
+ }
48
+ const data = await response.json();
49
+ if (!Array.isArray(data)) {
50
+ throw new Error("Invalid response format from scorecard API");
51
+ }
52
+ return data;
53
+ } catch (error) {
54
+ if (error instanceof Error) {
55
+ throw error;
56
+ }
57
+ throw new Error(`Unexpected error fetching scorecards: ${String(error)}`);
58
+ }
59
+ }
60
+ }
61
+
62
+ export { ScorecardApiClient, scorecardApiRef };
63
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../../src/api/index.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 */\n\nimport {\n createApiRef,\n FetchApi,\n DiscoveryApi,\n} from '@backstage/core-plugin-api';\nimport type { Entity } from '@backstage/catalog-model';\nimport type { MetricResult } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport interface ScorecardApi {\n /**\n * Retrieves scorecard metrics for a specific entity.\n * @param entity - The Backstage entity to get metrics for\n * @param metricIds - Optional array of specific metric IDs to retrieve\n * @returns Promise resolving to an array of metric results\n */\n getScorecards(entity: Entity, metricIds?: string[]): Promise<MetricResult[]>;\n}\n\nexport const scorecardApiRef = createApiRef<ScorecardApi>({\n id: 'plugin.scorecard.service',\n});\n\nexport type ScorecardApiClientOptions = {\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n};\n\n/**\n * Client implementation for the Scorecard API.\n * @public\n */\nexport class ScorecardApiClient implements ScorecardApi {\n private readonly fetchApi: FetchApi;\n private readonly discoveryApi: DiscoveryApi;\n\n constructor(options: ScorecardApiClientOptions) {\n this.fetchApi = options.fetchApi;\n this.discoveryApi = options.discoveryApi;\n }\n\n /**\n * Gets the base URL for the scorecard backend API.\n * @returns Promise resolving to the base URL\n */\n async getBaseUrl(): Promise<string> {\n return await this.discoveryApi.getBaseUrl('scorecard');\n }\n\n /**\n * Retrieves scorecard metrics for a specific entity.\n * @param entity - The Backstage entity to get metrics for\n * @param metricIds - Optional array of specific metric IDs to retrieve\n * @returns Promise resolving to an array of metric results\n * @throws Error if the request fails or returns invalid data\n */\n async getScorecards(\n entity: Entity,\n metricIds?: string[],\n ): Promise<MetricResult[]> {\n if (\n !entity?.kind ||\n !entity?.metadata?.namespace ||\n !entity?.metadata?.name\n ) {\n throw new Error(\n 'Entity missing required properties for scorecard lookup',\n );\n }\n\n const baseUrl = await this.getBaseUrl();\n const url = new URL(\n `${baseUrl}/metrics/catalog/${entity.kind}/${entity.metadata.namespace}/${entity.metadata.name}`,\n );\n\n if (metricIds) {\n url.searchParams.set('metricIds', metricIds.join(','));\n }\n\n try {\n const response = await this.fetchApi.fetch(url.toString());\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to fetch scorecards: ${response.status} ${response.statusText}. ${errorText}`,\n );\n }\n\n const data = await response.json();\n\n if (!Array.isArray(data)) {\n throw new Error('Invalid response format from scorecard API');\n }\n\n return data;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Unexpected error fetching scorecards: ${String(error)}`);\n }\n }\n}\n"],"names":[],"mappings":";;AAkCO,MAAM,kBAAkB,YAA2B,CAAA;AAAA,EACxD,EAAI,EAAA;AACN,CAAC;AAWM,MAAM,kBAA2C,CAAA;AAAA,EACrC,QAAA;AAAA,EACA,YAAA;AAAA,EAEjB,YAAY,OAAoC,EAAA;AAC9C,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AACxB,IAAA,IAAA,CAAK,eAAe,OAAQ,CAAA,YAAA;AAAA;AAC9B;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAA8B,GAAA;AAClC,IAAA,OAAO,MAAM,IAAA,CAAK,YAAa,CAAA,UAAA,CAAW,WAAW,CAAA;AAAA;AACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aACJ,CAAA,MAAA,EACA,SACyB,EAAA;AACzB,IACE,IAAA,CAAC,MAAQ,EAAA,IAAA,IACT,CAAC,MAAA,EAAQ,UAAU,SACnB,IAAA,CAAC,MAAQ,EAAA,QAAA,EAAU,IACnB,EAAA;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA;AAGF,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,MAAM,MAAM,IAAI,GAAA;AAAA,MACd,CAAG,EAAA,OAAO,CAAoB,iBAAA,EAAA,MAAA,CAAO,IAAI,CAAA,CAAA,EAAI,MAAO,CAAA,QAAA,CAAS,SAAS,CAAA,CAAA,EAAI,MAAO,CAAA,QAAA,CAAS,IAAI,CAAA;AAAA,KAChG;AAEA,IAAA,IAAI,SAAW,EAAA;AACb,MAAA,GAAA,CAAI,aAAa,GAAI,CAAA,WAAA,EAAa,SAAU,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA;AAGvD,IAAI,IAAA;AACF,MAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAM,CAAA,GAAA,CAAI,UAAU,CAAA;AAEzD,MAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,QAAM,MAAA,SAAA,GAAY,MAAM,QAAA,CAAS,IAAK,EAAA;AACtC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,+BAA+B,QAAS,CAAA,MAAM,IAAI,QAAS,CAAA,UAAU,KAAK,SAAS,CAAA;AAAA,SACrF;AAAA;AAGF,MAAM,MAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AAEjC,MAAA,IAAI,CAAC,KAAA,CAAM,OAAQ,CAAA,IAAI,CAAG,EAAA;AACxB,QAAM,MAAA,IAAI,MAAM,4CAA4C,CAAA;AAAA;AAG9D,MAAO,OAAA,IAAA;AAAA,aACA,KAAO,EAAA;AACd,MAAA,IAAI,iBAAiB,KAAO,EAAA;AAC1B,QAAM,MAAA,KAAA;AAAA;AAER,MAAA,MAAM,IAAI,KAAM,CAAA,CAAA,sCAAA,EAAyC,MAAO,CAAA,KAAK,CAAC,CAAE,CAAA,CAAA;AAAA;AAC1E;AAEJ;;;;"}
@@ -0,0 +1,83 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useApi, configApiRef } from '@backstage/core-plugin-api';
3
+ import Box from '@mui/material/Box';
4
+ import Typography from '@mui/material/Typography';
5
+ import Button from '@mui/material/Button';
6
+ import Grid from '@mui/material/Grid';
7
+ import OpenInNewOutlinedIcon from '@mui/icons-material/OpenInNewOutlined';
8
+ import noScorecardsSvg from '../../images/no-scorecards.svg';
9
+ import { useTranslation } from '../../hooks/useTranslation.esm.js';
10
+
11
+ const NoScorecardsState = () => {
12
+ const { t } = useTranslation();
13
+ const configApi = useApi(configApiRef);
14
+ const supportUrl = configApi.getOptionalString("app.support.url") ?? "https://access.redhat.com/documentation/red_hat_developer_hub";
15
+ return /* @__PURE__ */ jsx(Box, { sx: { p: 4, height: "100%", maxWidth: "1592px", margin: "auto" }, children: /* @__PURE__ */ jsxs(
16
+ Grid,
17
+ {
18
+ container: true,
19
+ spacing: 4,
20
+ alignItems: "center",
21
+ justifyContent: "center",
22
+ height: "100%",
23
+ children: [
24
+ /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, md: 6, sx: { textAlign: "left" }, children: [
25
+ /* @__PURE__ */ jsx(
26
+ Typography,
27
+ {
28
+ sx: (theme) => ({
29
+ fontSize: "2.5rem",
30
+ fontWeight: 300,
31
+ color: theme.palette.text.primary,
32
+ mb: 2
33
+ }),
34
+ children: t("emptyState.title")
35
+ }
36
+ ),
37
+ /* @__PURE__ */ jsx(
38
+ Typography,
39
+ {
40
+ sx: (theme) => ({
41
+ fontSize: "1rem",
42
+ color: theme.palette.text.secondary,
43
+ mb: 3,
44
+ lineHeight: 1.5
45
+ }),
46
+ children: t("emptyState.description")
47
+ }
48
+ ),
49
+ /* @__PURE__ */ jsx(
50
+ Button,
51
+ {
52
+ color: "primary",
53
+ variant: "contained",
54
+ size: "large",
55
+ component: "a",
56
+ href: supportUrl,
57
+ target: "_blank",
58
+ rel: "noopener noreferrer",
59
+ endIcon: /* @__PURE__ */ jsx(OpenInNewOutlinedIcon, { sx: { width: 16, height: 16, ml: 1 } }),
60
+ children: t("emptyState.button")
61
+ }
62
+ )
63
+ ] }),
64
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, sx: { textAlign: "right" }, children: /* @__PURE__ */ jsx(
65
+ Box,
66
+ {
67
+ component: "img",
68
+ src: noScorecardsSvg,
69
+ alt: t("emptyState.altText"),
70
+ sx: {
71
+ width: "100%",
72
+ maxWidth: "600px",
73
+ height: "auto"
74
+ }
75
+ }
76
+ ) })
77
+ ]
78
+ }
79
+ ) });
80
+ };
81
+
82
+ export { NoScorecardsState as default };
83
+ //# sourceMappingURL=NoScorecardsState.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoScorecardsState.esm.js","sources":["../../../src/components/Common/NoScorecardsState.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 */\n\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\n\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport Button from '@mui/material/Button';\nimport Grid from '@mui/material/Grid';\nimport OpenInNewOutlinedIcon from '@mui/icons-material/OpenInNewOutlined';\n\nimport noScorecardsSvg from '../../images/no-scorecards.svg';\nimport { useTranslation } from '../../hooks/useTranslation';\n\nconst NoScorecardsState: React.FC = () => {\n const { t } = useTranslation();\n\n const configApi = useApi(configApiRef);\n const supportUrl =\n configApi.getOptionalString('app.support.url') ??\n 'https://access.redhat.com/documentation/red_hat_developer_hub';\n\n return (\n <Box sx={{ p: 4, height: '100%', maxWidth: '1592px', margin: 'auto' }}>\n <Grid\n container\n spacing={4}\n alignItems=\"center\"\n justifyContent=\"center\"\n height=\"100%\"\n >\n <Grid item xs={12} md={6} sx={{ textAlign: 'left' }}>\n <Typography\n sx={theme => ({\n fontSize: '2.5rem',\n fontWeight: 300,\n color: theme.palette.text.primary,\n mb: 2,\n })}\n >\n {t('emptyState.title')}\n </Typography>\n\n <Typography\n sx={theme => ({\n fontSize: '1rem',\n color: theme.palette.text.secondary,\n mb: 3,\n lineHeight: 1.5,\n })}\n >\n {t('emptyState.description')}\n </Typography>\n\n <Button\n color=\"primary\"\n variant=\"contained\"\n size=\"large\"\n component=\"a\"\n href={supportUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n endIcon={\n <OpenInNewOutlinedIcon sx={{ width: 16, height: 16, ml: 1 }} />\n }\n >\n {t('emptyState.button')}\n </Button>\n </Grid>\n\n <Grid item xs={12} md={6} sx={{ textAlign: 'right' }}>\n <Box\n component=\"img\"\n src={noScorecardsSvg}\n alt={t('emptyState.altText')}\n sx={{\n width: '100%',\n maxWidth: '600px',\n height: 'auto',\n }}\n />\n </Grid>\n </Grid>\n </Box>\n );\n};\n\nexport default NoScorecardsState;\n"],"names":[],"mappings":";;;;;;;;;;AA2BA,MAAM,oBAA8B,MAAM;AACxC,EAAM,MAAA,EAAE,CAAE,EAAA,GAAI,cAAe,EAAA;AAE7B,EAAM,MAAA,SAAA,GAAY,OAAO,YAAY,CAAA;AACrC,EAAA,MAAM,UACJ,GAAA,SAAA,CAAU,iBAAkB,CAAA,iBAAiB,CAC7C,IAAA,+DAAA;AAEF,EAAA,uBACG,GAAA,CAAA,GAAA,EAAA,EAAI,EAAI,EAAA,EAAE,CAAG,EAAA,CAAA,EAAG,MAAQ,EAAA,MAAA,EAAQ,QAAU,EAAA,QAAA,EAAU,MAAQ,EAAA,MAAA,EAC3D,EAAA,QAAA,kBAAA,IAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,SAAS,EAAA,IAAA;AAAA,MACT,OAAS,EAAA,CAAA;AAAA,MACT,UAAW,EAAA,QAAA;AAAA,MACX,cAAe,EAAA,QAAA;AAAA,MACf,MAAO,EAAA,MAAA;AAAA,MAEP,QAAA,EAAA;AAAA,wBAAC,IAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAI,EAAA,EAAA,EAAI,EAAI,EAAA,CAAA,EAAG,EAAI,EAAA,EAAE,SAAW,EAAA,MAAA,EACzC,EAAA,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,UAAA;AAAA,YAAA;AAAA,cACC,IAAI,CAAU,KAAA,MAAA;AAAA,gBACZ,QAAU,EAAA,QAAA;AAAA,gBACV,UAAY,EAAA,GAAA;AAAA,gBACZ,KAAA,EAAO,KAAM,CAAA,OAAA,CAAQ,IAAK,CAAA,OAAA;AAAA,gBAC1B,EAAI,EAAA;AAAA,eACN,CAAA;AAAA,cAEC,YAAE,kBAAkB;AAAA;AAAA,WACvB;AAAA,0BAEA,GAAA;AAAA,YAAC,UAAA;AAAA,YAAA;AAAA,cACC,IAAI,CAAU,KAAA,MAAA;AAAA,gBACZ,QAAU,EAAA,MAAA;AAAA,gBACV,KAAA,EAAO,KAAM,CAAA,OAAA,CAAQ,IAAK,CAAA,SAAA;AAAA,gBAC1B,EAAI,EAAA,CAAA;AAAA,gBACJ,UAAY,EAAA;AAAA,eACd,CAAA;AAAA,cAEC,YAAE,wBAAwB;AAAA;AAAA,WAC7B;AAAA,0BAEA,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,KAAM,EAAA,SAAA;AAAA,cACN,OAAQ,EAAA,WAAA;AAAA,cACR,IAAK,EAAA,OAAA;AAAA,cACL,SAAU,EAAA,GAAA;AAAA,cACV,IAAM,EAAA,UAAA;AAAA,cACN,MAAO,EAAA,QAAA;AAAA,cACP,GAAI,EAAA,qBAAA;AAAA,cACJ,OAAA,kBACG,GAAA,CAAA,qBAAA,EAAA,EAAsB,EAAI,EAAA,EAAE,KAAO,EAAA,EAAA,EAAI,MAAQ,EAAA,EAAA,EAAI,EAAI,EAAA,CAAA,EAAK,EAAA,CAAA;AAAA,cAG9D,YAAE,mBAAmB;AAAA;AAAA;AACxB,SACF,EAAA,CAAA;AAAA,wBAEC,GAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAI,EAAA,EAAA,EAAI,EAAI,EAAA,CAAA,EAAG,EAAI,EAAA,EAAE,SAAW,EAAA,OAAA,EACzC,EAAA,QAAA,kBAAA,GAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YACC,SAAU,EAAA,KAAA;AAAA,YACV,GAAK,EAAA,eAAA;AAAA,YACL,GAAA,EAAK,EAAE,oBAAoB,CAAA;AAAA,YAC3B,EAAI,EAAA;AAAA,cACF,KAAO,EAAA,MAAA;AAAA,cACP,QAAU,EAAA,OAAA;AAAA,cACV,MAAQ,EAAA;AAAA;AACV;AAAA,SAEJ,EAAA;AAAA;AAAA;AAAA,GAEJ,EAAA,CAAA;AAEJ;;;;"}
@@ -0,0 +1,94 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import Box from '@mui/material/Box';
3
+ import Grid from '@mui/material/Grid';
4
+ import Button from '@mui/material/Button';
5
+ import Typography from '@mui/material/Typography';
6
+ import OpenInNewIcon from '@mui/icons-material/OpenInNew';
7
+ import { useTranslation } from '../../hooks/useTranslation.esm.js';
8
+ import permissionRequiredSvg from '../../images/permission-required.svg';
9
+
10
+ const PermissionRequiredState = () => {
11
+ const { t } = useTranslation();
12
+ return /* @__PURE__ */ jsx(Box, { sx: { p: 4, height: "100%", maxWidth: "1592px", margin: "auto" }, children: /* @__PURE__ */ jsxs(
13
+ Grid,
14
+ {
15
+ container: true,
16
+ spacing: 4,
17
+ alignItems: "center",
18
+ justifyContent: "center",
19
+ height: "100%",
20
+ children: [
21
+ /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, md: 6, sx: { textAlign: "left" }, children: [
22
+ /* @__PURE__ */ jsx(
23
+ Typography,
24
+ {
25
+ sx: (theme) => ({
26
+ fontSize: "2.5rem",
27
+ fontWeight: 400,
28
+ color: theme.palette.text.primary,
29
+ mb: 2
30
+ }),
31
+ children: t("permissionRequired.title")
32
+ }
33
+ ),
34
+ /* @__PURE__ */ jsx(
35
+ Typography,
36
+ {
37
+ sx: (theme) => ({
38
+ fontSize: "1rem",
39
+ color: theme.palette.text.secondary,
40
+ mb: 3,
41
+ lineHeight: 1.5
42
+ }),
43
+ children: t("permissionRequired.description", {
44
+ permission: /* @__PURE__ */ jsx(
45
+ Typography,
46
+ {
47
+ component: "span",
48
+ sx: (theme) => ({
49
+ fontWeight: "bold",
50
+ color: theme.palette.text.primary
51
+ }),
52
+ children: "scorecard.metric.read"
53
+ }
54
+ )
55
+ })
56
+ }
57
+ ),
58
+ /* @__PURE__ */ jsxs(
59
+ Button,
60
+ {
61
+ variant: "outlined",
62
+ target: "_blank",
63
+ href: "https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/scorecard/plugins/scorecard/README.md#permission-framework-support",
64
+ sx: (theme) => ({
65
+ color: theme.palette.primary.main
66
+ }),
67
+ children: [
68
+ t("permissionRequired.button"),
69
+ " \xA0 ",
70
+ /* @__PURE__ */ jsx(OpenInNewIcon, {})
71
+ ]
72
+ }
73
+ )
74
+ ] }),
75
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, sx: { textAlign: "right" }, children: /* @__PURE__ */ jsx(
76
+ Box,
77
+ {
78
+ component: "img",
79
+ src: permissionRequiredSvg,
80
+ alt: t("permissionRequired.altText"),
81
+ sx: {
82
+ width: "100%",
83
+ maxWidth: "600px",
84
+ height: "auto"
85
+ }
86
+ }
87
+ ) })
88
+ ]
89
+ }
90
+ ) });
91
+ };
92
+
93
+ export { PermissionRequiredState as default };
94
+ //# sourceMappingURL=PermissionRequiredState.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionRequiredState.esm.js","sources":["../../../src/components/Common/PermissionRequiredState.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 */\n\nimport Box from '@mui/material/Box';\nimport Grid from '@mui/material/Grid';\nimport Button from '@mui/material/Button';\nimport Typography from '@mui/material/Typography';\nimport OpenInNewIcon from '@mui/icons-material/OpenInNew';\n\nimport { useTranslation } from '../../hooks/useTranslation';\nimport permissionRequiredSvg from '../../images/permission-required.svg';\n\nconst PermissionRequiredState = () => {\n const { t } = useTranslation();\n\n return (\n <Box sx={{ p: 4, height: '100%', maxWidth: '1592px', margin: 'auto' }}>\n <Grid\n container\n spacing={4}\n alignItems=\"center\"\n justifyContent=\"center\"\n height=\"100%\"\n >\n <Grid item xs={12} md={6} sx={{ textAlign: 'left' }}>\n <Typography\n sx={theme => ({\n fontSize: '2.5rem',\n fontWeight: 400,\n color: theme.palette.text.primary,\n mb: 2,\n })}\n >\n {t('permissionRequired.title')}\n </Typography>\n\n <Typography\n sx={theme => ({\n fontSize: '1rem',\n color: theme.palette.text.secondary,\n mb: 3,\n lineHeight: 1.5,\n })}\n >\n {t('permissionRequired.description' as any, {\n permission: (\n <Typography\n component=\"span\"\n sx={theme => ({\n fontWeight: 'bold',\n color: theme.palette.text.primary,\n })}\n >\n scorecard.metric.read\n </Typography>\n ),\n })}\n </Typography>\n\n <Button\n variant=\"outlined\"\n target=\"_blank\"\n href=\"https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/scorecard/plugins/scorecard/README.md#permission-framework-support\"\n sx={theme => ({\n color: theme.palette.primary.main,\n })}\n >\n {t('permissionRequired.button')} &nbsp; <OpenInNewIcon />\n </Button>\n </Grid>\n\n <Grid item xs={12} md={6} sx={{ textAlign: 'right' }}>\n <Box\n component=\"img\"\n src={permissionRequiredSvg}\n alt={t('permissionRequired.altText')}\n sx={{\n width: '100%',\n maxWidth: '600px',\n height: 'auto',\n }}\n />\n </Grid>\n </Grid>\n </Box>\n );\n};\n\nexport default PermissionRequiredState;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { ResponseErrorPanel } from '@backstage/core-components';
3
+ import Box from '@mui/material/Box';
4
+ import CircularProgress from '@mui/material/CircularProgress';
5
+ import NoScorecardsState from '../Common/NoScorecardsState.esm.js';
6
+ import Scorecard from './Scorecard.esm.js';
7
+ import { useScorecards } from '../../hooks/useScorecards.esm.js';
8
+ import { getStatusConfig } from '../../utils/utils.esm.js';
9
+ import { useScorecardMetricsReadPermission } from '../../hooks/useScorecardMetricsReadPermission.esm.js';
10
+ import PermissionRequiredState from '../Common/PermissionRequiredState.esm.js';
11
+ import { useTranslation } from '../../hooks/useTranslation.esm.js';
12
+
13
+ const EntityScorecardContent = () => {
14
+ const { scorecards, loadingData, error } = useScorecards();
15
+ const { t } = useTranslation();
16
+ const {
17
+ allowed: hasReadScorecardMetricsPermission,
18
+ loading: loadingPermission
19
+ } = useScorecardMetricsReadPermission();
20
+ if (loadingData || loadingPermission) {
21
+ return /* @__PURE__ */ jsx(
22
+ Box,
23
+ {
24
+ display: "flex",
25
+ justifyContent: "center",
26
+ alignItems: "center",
27
+ minHeight: "200px",
28
+ children: /* @__PURE__ */ jsx(CircularProgress, {})
29
+ }
30
+ );
31
+ }
32
+ if (!hasReadScorecardMetricsPermission) {
33
+ return /* @__PURE__ */ jsx(PermissionRequiredState, {});
34
+ }
35
+ if (error) {
36
+ return /* @__PURE__ */ jsx(ResponseErrorPanel, { error });
37
+ }
38
+ if (!loadingData && scorecards?.length === 0) {
39
+ return /* @__PURE__ */ jsx(NoScorecardsState, {});
40
+ }
41
+ return /* @__PURE__ */ jsx(
42
+ Box,
43
+ {
44
+ display: "flex",
45
+ flexWrap: "wrap",
46
+ gap: 2,
47
+ sx: { alignItems: "flex-start" },
48
+ children: scorecards?.map((metric) => {
49
+ const isMetricDataError = metric.status === "error" || metric.result?.value === void 0;
50
+ const isThresholdError = metric.result?.thresholdResult?.status === "error";
51
+ const statusConfig = getStatusConfig({
52
+ evaluation: metric.result?.thresholdResult?.evaluation,
53
+ thresholdStatus: metric.result?.thresholdResult?.status,
54
+ metricStatus: metric.status
55
+ });
56
+ const titleKey = `metric.${metric.id}.title`;
57
+ const descriptionKey = `metric.${metric.id}.description`;
58
+ const title = t(titleKey, {});
59
+ const description = t(descriptionKey, {});
60
+ const finalTitle = title === titleKey ? metric.metadata.title : title;
61
+ const finalDescription = description === descriptionKey ? metric.metadata.description : description;
62
+ return /* @__PURE__ */ jsx(
63
+ Scorecard,
64
+ {
65
+ cardTitle: finalTitle,
66
+ description: finalDescription,
67
+ loading: false,
68
+ statusColor: statusConfig.color,
69
+ StatusIcon: statusConfig.icon ?? (() => null),
70
+ value: metric.result?.value,
71
+ thresholds: metric.result?.thresholdResult,
72
+ isMetricDataError,
73
+ metricDataError: metric?.error,
74
+ isThresholdError,
75
+ thresholdError: metric.result?.thresholdResult?.error
76
+ },
77
+ metric.id
78
+ );
79
+ })
80
+ }
81
+ );
82
+ };
83
+
84
+ export { EntityScorecardContent };
85
+ //# sourceMappingURL=EntityScorecardContent.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityScorecardContent.esm.js","sources":["../../../src/components/Scorecard/EntityScorecardContent.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 */\n\nimport { MetricResult } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { ResponseErrorPanel } from '@backstage/core-components';\n\nimport Box from '@mui/material/Box';\nimport CircularProgress from '@mui/material/CircularProgress';\n\nimport NoScorecardsState from '../Common/NoScorecardsState';\nimport Scorecard from './Scorecard';\nimport { useScorecards } from '../../hooks/useScorecards';\nimport { getStatusConfig } from '../../utils/utils';\nimport { useScorecardMetricsReadPermission } from '../../hooks/useScorecardMetricsReadPermission';\nimport PermissionRequiredState from '../Common/PermissionRequiredState';\nimport { useTranslation } from '../../hooks/useTranslation';\n\nexport const EntityScorecardContent = () => {\n const { scorecards, loadingData, error } = useScorecards();\n const { t } = useTranslation();\n\n const {\n allowed: hasReadScorecardMetricsPermission,\n loading: loadingPermission,\n } = useScorecardMetricsReadPermission();\n\n if (loadingData || loadingPermission) {\n return (\n <Box\n display=\"flex\"\n justifyContent=\"center\"\n alignItems=\"center\"\n minHeight=\"200px\"\n >\n <CircularProgress />\n </Box>\n );\n }\n\n if (!hasReadScorecardMetricsPermission) {\n return <PermissionRequiredState />;\n }\n\n if (error) {\n return <ResponseErrorPanel error={error} />;\n }\n\n if (!loadingData && scorecards?.length === 0) {\n return <NoScorecardsState />;\n }\n\n return (\n <Box\n display=\"flex\"\n flexWrap=\"wrap\"\n gap={2}\n sx={{ alignItems: 'flex-start' }}\n >\n {scorecards?.map((metric: MetricResult) => {\n // Check if metric data unavailable\n const isMetricDataError =\n metric.status === 'error' || metric.result?.value === undefined;\n\n // Check if threshold has an error\n const isThresholdError =\n metric.result?.thresholdResult?.status === 'error';\n\n const statusConfig = getStatusConfig({\n evaluation: metric.result?.thresholdResult?.evaluation,\n thresholdStatus: metric.result?.thresholdResult?.status,\n metricStatus: metric.status,\n });\n\n // Use metric ID to construct translation keys, fallback to original title/description\n const titleKey = `metric.${metric.id}.title`;\n const descriptionKey = `metric.${metric.id}.description`;\n\n const title = t(titleKey as any, {});\n const description = t(descriptionKey as any, {});\n\n // If translation returns the key itself, fallback to original title/description\n const finalTitle = title === titleKey ? metric.metadata.title : title;\n const finalDescription =\n description === descriptionKey\n ? metric.metadata.description\n : description;\n\n return (\n <Scorecard\n key={metric.id}\n cardTitle={finalTitle}\n description={finalDescription}\n loading={false}\n statusColor={statusConfig.color}\n StatusIcon={statusConfig.icon ?? (() => null)}\n value={metric.result?.value}\n thresholds={metric.result?.thresholdResult}\n isMetricDataError={isMetricDataError}\n metricDataError={metric?.error}\n isThresholdError={isThresholdError}\n thresholdError={metric.result?.thresholdResult?.error}\n />\n );\n })}\n </Box>\n );\n};\n"],"names":[],"mappings":"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"}