@red-hat-developer-hub/backstage-plugin-adoption-insights 0.0.2

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 (104) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +88 -0
  3. package/app-config.dynamic.yaml +16 -0
  4. package/dist/api/index.esm.js +106 -0
  5. package/dist/api/index.esm.js.map +1 -0
  6. package/dist/components/ActiveUsers/ActiveUsers.esm.js +125 -0
  7. package/dist/components/ActiveUsers/ActiveUsers.esm.js.map +1 -0
  8. package/dist/components/ActiveUsers/CustomLegend.esm.js +58 -0
  9. package/dist/components/ActiveUsers/CustomLegend.esm.js.map +1 -0
  10. package/dist/components/ActiveUsers/CustomTooltip.esm.js +87 -0
  11. package/dist/components/ActiveUsers/CustomTooltip.esm.js.map +1 -0
  12. package/dist/components/ActiveUsers/ExportCSVButton.esm.js +75 -0
  13. package/dist/components/ActiveUsers/ExportCSVButton.esm.js.map +1 -0
  14. package/dist/components/ActiveUsers/index.esm.js +6 -0
  15. package/dist/components/ActiveUsers/index.esm.js.map +1 -0
  16. package/dist/components/AdoptionInsightsPage/AdoptionInsightsPage.esm.js +29 -0
  17. package/dist/components/AdoptionInsightsPage/AdoptionInsightsPage.esm.js.map +1 -0
  18. package/dist/components/AdoptionInsightsPage/index.esm.js +2 -0
  19. package/dist/components/AdoptionInsightsPage/index.esm.js.map +1 -0
  20. package/dist/components/CardFooter/TableFooterPagination.esm.js +70 -0
  21. package/dist/components/CardFooter/TableFooterPagination.esm.js.map +1 -0
  22. package/dist/components/CardFooter/index.esm.js +6 -0
  23. package/dist/components/CardFooter/index.esm.js.map +1 -0
  24. package/dist/components/CardWrapper/CardWrapper.esm.js +42 -0
  25. package/dist/components/CardWrapper/CardWrapper.esm.js.map +1 -0
  26. package/dist/components/CardWrapper/index.esm.js +6 -0
  27. package/dist/components/CardWrapper/index.esm.js.map +1 -0
  28. package/dist/components/CatalogEntities/CatalogEntities.esm.js +158 -0
  29. package/dist/components/CatalogEntities/CatalogEntities.esm.js.map +1 -0
  30. package/dist/components/CatalogEntities/FilterDropdown.esm.js +33 -0
  31. package/dist/components/CatalogEntities/FilterDropdown.esm.js.map +1 -0
  32. package/dist/components/CatalogEntities/index.esm.js +6 -0
  33. package/dist/components/CatalogEntities/index.esm.js.map +1 -0
  34. package/dist/components/Common/CustomCursor.esm.js +22 -0
  35. package/dist/components/Common/CustomCursor.esm.js.map +1 -0
  36. package/dist/components/Common/EmptyChartState.esm.js +9 -0
  37. package/dist/components/Common/EmptyChartState.esm.js.map +1 -0
  38. package/dist/components/Common/PermissionRequiredIcon.esm.js +9 -0
  39. package/dist/components/Common/PermissionRequiredIcon.esm.js.map +1 -0
  40. package/dist/components/Common/PermissionRequiredState.esm.js +43 -0
  41. package/dist/components/Common/PermissionRequiredState.esm.js.map +1 -0
  42. package/dist/components/Header/DateRangeContext.esm.js +34 -0
  43. package/dist/components/Header/DateRangeContext.esm.js.map +1 -0
  44. package/dist/components/Header/DateRangePicker.esm.js +160 -0
  45. package/dist/components/Header/DateRangePicker.esm.js.map +1 -0
  46. package/dist/components/Header/Header.esm.js +195 -0
  47. package/dist/components/Header/Header.esm.js.map +1 -0
  48. package/dist/components/Header/index.esm.js +6 -0
  49. package/dist/components/Header/index.esm.js.map +1 -0
  50. package/dist/components/Plugins/Plugins.esm.js +113 -0
  51. package/dist/components/Plugins/Plugins.esm.js.map +1 -0
  52. package/dist/components/Plugins/index.esm.js +6 -0
  53. package/dist/components/Plugins/index.esm.js.map +1 -0
  54. package/dist/components/Searches/CustomTooltip.esm.js +66 -0
  55. package/dist/components/Searches/CustomTooltip.esm.js.map +1 -0
  56. package/dist/components/Searches/Searches.esm.js +106 -0
  57. package/dist/components/Searches/Searches.esm.js.map +1 -0
  58. package/dist/components/Searches/index.esm.js +6 -0
  59. package/dist/components/Searches/index.esm.js.map +1 -0
  60. package/dist/components/Techdocs/Techdocs.esm.js +129 -0
  61. package/dist/components/Techdocs/Techdocs.esm.js.map +1 -0
  62. package/dist/components/Techdocs/index.esm.js +6 -0
  63. package/dist/components/Techdocs/index.esm.js.map +1 -0
  64. package/dist/components/Templates/Templates.esm.js +118 -0
  65. package/dist/components/Templates/Templates.esm.js.map +1 -0
  66. package/dist/components/Templates/index.esm.js +6 -0
  67. package/dist/components/Templates/index.esm.js.map +1 -0
  68. package/dist/components/Users/Info.esm.js +39 -0
  69. package/dist/components/Users/Info.esm.js.map +1 -0
  70. package/dist/components/Users/Tooltip.esm.js +48 -0
  71. package/dist/components/Users/Tooltip.esm.js.map +1 -0
  72. package/dist/components/Users/Users.esm.js +143 -0
  73. package/dist/components/Users/Users.esm.js.map +1 -0
  74. package/dist/components/Users/index.esm.js +6 -0
  75. package/dist/components/Users/index.esm.js.map +1 -0
  76. package/dist/hooks/useActiveUsers.esm.js +44 -0
  77. package/dist/hooks/useActiveUsers.esm.js.map +1 -0
  78. package/dist/hooks/useAdoptionInsightsEventsReadPermission.esm.js +15 -0
  79. package/dist/hooks/useAdoptionInsightsEventsReadPermission.esm.js.map +1 -0
  80. package/dist/hooks/useCatalogEntities.esm.js +45 -0
  81. package/dist/hooks/useCatalogEntities.esm.js.map +1 -0
  82. package/dist/hooks/usePlugins.esm.js +44 -0
  83. package/dist/hooks/usePlugins.esm.js.map +1 -0
  84. package/dist/hooks/useSearches.esm.js +43 -0
  85. package/dist/hooks/useSearches.esm.js.map +1 -0
  86. package/dist/hooks/useTechdocs.esm.js +41 -0
  87. package/dist/hooks/useTechdocs.esm.js.map +1 -0
  88. package/dist/hooks/useTemplates.esm.js +43 -0
  89. package/dist/hooks/useTemplates.esm.js.map +1 -0
  90. package/dist/hooks/useUsers.esm.js +36 -0
  91. package/dist/hooks/useUsers.esm.js.map +1 -0
  92. package/dist/images/permission-required.svg +48 -0
  93. package/dist/index.d.ts +23 -0
  94. package/dist/index.esm.js +7 -0
  95. package/dist/index.esm.js.map +1 -0
  96. package/dist/plugin.esm.js +34 -0
  97. package/dist/plugin.esm.js.map +1 -0
  98. package/dist/routes.esm.js +8 -0
  99. package/dist/routes.esm.js.map +1 -0
  100. package/dist/utils/constants.esm.js +33 -0
  101. package/dist/utils/constants.esm.js.map +1 -0
  102. package/dist/utils/utils.esm.js +148 -0
  103. package/dist/utils/utils.esm.js.map +1 -0
  104. package/package.json +83 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # @red-hat-developer-hub/backstage-plugin-adoption-insights
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 480aa8e: Release Adoption insights plugin
8
+ - Updated dependencies [480aa8e]
9
+ - @red-hat-developer-hub/backstage-plugin-adoption-insights-common@0.1.1
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Adoption Insights Plugin for Backstage
2
+
3
+ The Adoption Insights plugin provides an interactive dashboard to visualize analytics data in Backstage. This frontend plugin integrates with the Adoption Insights backend to deliver insights into adoption trends and usage statistics.
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 [/adoption-insights](http://localhost:3000/adoption-insights).
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 Adoption Insights backend is integrated into your Backstage instance. Follow the [Adoption Insights backend plugin README](https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/adoption-insights/plugins/adoption-insights-backend/README.md) for setup instructions.
18
+
19
+ ### Installation
20
+
21
+ To install the Adoption Insights plugin, run the following command:
22
+
23
+ ```sh
24
+ yarn workspace app add @red-hat-developer-hub/backstage-plugin-adoption-insights
25
+ ```
26
+
27
+ **Note**
28
+
29
+ ### Permission Framework Support
30
+
31
+ The Adoption Insights 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 adoption insights 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, adoption-insights.events.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 **Adoption Insights** page to your Backstage application by modifying `packages/app/src/App.tsx`:
54
+
55
+ ```tsx
56
+ import { AdoptionInsightsPage } from '@red-hat-developer-hub/backstage-plugin-adoption-insights';
57
+
58
+ <Route path="/adoption-insights" element={<AdoptionInsightsPage />} />;
59
+ ```
60
+
61
+ 2. Add a navigation item to the Backstage sidebar by updating `packages/app/src/components/Root/Root.tsx`:
62
+
63
+ ```tsx
64
+ import QueryStatsIcon from '@mui/icons-material/QueryStats';
65
+
66
+ <SidebarItem
67
+ icon={QueryStatsIcon}
68
+ to="adoption-insights"
69
+ text="Adoption Insights"
70
+ />;
71
+ ```
72
+
73
+ ## For Users
74
+
75
+ ### Using the Adoption Insights Plugin
76
+
77
+ The Adoption Insights plugin allows users to explore analytics data through an interactive dashboard.
78
+
79
+ #### Prerequisites
80
+
81
+ - A running Backstage application.
82
+ - The Adoption Insights plugin is installed and configured. See [Installation](#installation) for setup instructions.
83
+
84
+ #### Accessing the Plugin
85
+
86
+ 1. Open your Backstage application.
87
+ 2. Navigate to the **Adoption Insights** section from the sidebar.
88
+ 3. Explore and analyze adoption metrics using the interactive dashboard.
@@ -0,0 +1,16 @@
1
+ dynamicPlugins:
2
+ frontend:
3
+ red-hat-developer-hub.backstage-plugin-adoption-insights:
4
+ appIcons:
5
+ - name: adoptionInsightsIcon
6
+ importName: AdoptionInsightsIcon
7
+ dynamicRoutes:
8
+ - path: /adoption-insights
9
+ importName: AdoptionInsightsPage
10
+ menuItem:
11
+ icon: adoptionInsightsIcon
12
+ text: Adoption Insights
13
+ menuItems:
14
+ adoption-insights:
15
+ parent: admin
16
+ icon: adoptionInsightsIcon
@@ -0,0 +1,106 @@
1
+ import { createApiRef } from '@backstage/core-plugin-api';
2
+ import { generateEventsUrl } from '../utils/utils.esm.js';
3
+
4
+ const adoptionInsightsApiRef = createApiRef({
5
+ id: "plugin.adoption-insights.service"
6
+ });
7
+ class AdoptionInsightsApiClient {
8
+ configApi;
9
+ fetchApi;
10
+ constructor(options) {
11
+ this.configApi = options.configApi;
12
+ this.fetchApi = options.fetchApi;
13
+ }
14
+ async getBaseUrl() {
15
+ return `${this.configApi.getString(
16
+ "backend.baseUrl"
17
+ )}/api/adoption-insights`;
18
+ }
19
+ async getActiveUsers(options) {
20
+ if (!options.start_date || !options.end_date) {
21
+ return Promise.resolve({ grouping: void 0, data: [] });
22
+ }
23
+ const baseUrl = await this.getBaseUrl();
24
+ const url = generateEventsUrl(`${baseUrl}/events`, options);
25
+ const response = await this.fetchApi.fetch(url);
26
+ const data = await response.json();
27
+ return data;
28
+ }
29
+ async getUsers(options) {
30
+ if (!options.start_date || !options.end_date) {
31
+ return Promise.resolve({ data: [] });
32
+ }
33
+ const baseUrl = await this.getBaseUrl();
34
+ const url = generateEventsUrl(`${baseUrl}/events`, options);
35
+ const response = await this.fetchApi.fetch(url);
36
+ const data = await response.json();
37
+ return data;
38
+ }
39
+ async getCatalogEntities(options) {
40
+ if (!options.start_date || !options.end_date) {
41
+ return Promise.resolve({ data: [] });
42
+ }
43
+ const baseUrl = await this.getBaseUrl();
44
+ const url = generateEventsUrl(`${baseUrl}/events`, options);
45
+ const response = await this.fetchApi.fetch(url);
46
+ const data = await response.json();
47
+ return data;
48
+ }
49
+ async getTemplates(options) {
50
+ if (!options.start_date || !options.end_date) {
51
+ return Promise.resolve({ data: [] });
52
+ }
53
+ const baseUrl = await this.getBaseUrl();
54
+ const url = generateEventsUrl(`${baseUrl}/events`, options);
55
+ const response = await this.fetchApi.fetch(url);
56
+ const data = await response.json();
57
+ return data;
58
+ }
59
+ async getTechdocs(options) {
60
+ if (!options.start_date || !options.end_date) {
61
+ return Promise.resolve({ data: [] });
62
+ }
63
+ const baseUrl = await this.getBaseUrl();
64
+ const url = generateEventsUrl(`${baseUrl}/events`, options);
65
+ const response = await this.fetchApi.fetch(url);
66
+ const data = await response.json();
67
+ return data;
68
+ }
69
+ async getPlugins(options) {
70
+ if (!options.start_date || !options.end_date) {
71
+ return Promise.resolve({ data: [] });
72
+ }
73
+ const baseUrl = await this.getBaseUrl();
74
+ const url = generateEventsUrl(`${baseUrl}/events`, options);
75
+ const response = await this.fetchApi.fetch(url);
76
+ const data = await response.json();
77
+ return data;
78
+ }
79
+ async getSearches(options) {
80
+ if (!options.start_date || !options.end_date) {
81
+ return Promise.resolve({ grouping: void 0, data: [] });
82
+ }
83
+ const baseUrl = await this.getBaseUrl();
84
+ const url = generateEventsUrl(`${baseUrl}/events`, options);
85
+ const response = await this.fetchApi.fetch(url);
86
+ const data = await response.json();
87
+ return data;
88
+ }
89
+ async downloadBlob(options) {
90
+ const baseUrl = await this.getBaseUrl();
91
+ const response = await this.fetchApi.fetch(
92
+ `${baseUrl}/events?type=${options.type}&start_date=${options.start_date}&end_date=${options.end_date}&format=${options.format}`
93
+ );
94
+ const blob = await response.blob();
95
+ const blobUrl = window.URL.createObjectURL(blob);
96
+ const link = document.createElement("a");
97
+ link.href = blobUrl;
98
+ link.download = options.blobName ?? "active-users";
99
+ document.body.appendChild(link);
100
+ link.click();
101
+ link.parentNode?.removeChild(link);
102
+ }
103
+ }
104
+
105
+ export { AdoptionInsightsApiClient, adoptionInsightsApiRef };
106
+ //# 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 */\nimport { createApiRef, ConfigApi, FetchApi } from '@backstage/core-plugin-api';\nimport {\n AdoptionInsightsApi,\n APIsViewOptions,\n TemplatesResponse,\n CatalogEntitiesResponse,\n PluginTrendResponse,\n UsersResponse,\n TechdocsResponse,\n ActiveUsersResponse,\n SearchesResponse,\n} from '../types';\nimport { generateEventsUrl } from '../utils/utils';\n\nexport interface InsightsApi {\n downloadBlob(options: APIsViewOptions): Promise<any>;\n getActiveUsers(options: APIsViewOptions): Promise<ActiveUsersResponse>;\n getUsers(options: APIsViewOptions): Promise<UsersResponse>;\n getCatalogEntities(\n options: APIsViewOptions,\n ): Promise<CatalogEntitiesResponse>;\n getTemplates(options: APIsViewOptions): Promise<TemplatesResponse>;\n getTechdocs(options: APIsViewOptions): Promise<TechdocsResponse>;\n getPlugins(options: APIsViewOptions): Promise<PluginTrendResponse>;\n getSearches(options: APIsViewOptions): Promise<SearchesResponse>;\n}\n\nexport const adoptionInsightsApiRef = createApiRef<AdoptionInsightsApi>({\n id: 'plugin.adoption-insights.service',\n});\n\nexport type Options = {\n configApi: ConfigApi;\n fetchApi: FetchApi;\n};\n\nexport class AdoptionInsightsApiClient implements AdoptionInsightsApi {\n private readonly configApi: ConfigApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: Options) {\n this.configApi = options.configApi;\n this.fetchApi = options.fetchApi;\n }\n\n async getBaseUrl() {\n return `${this.configApi.getString(\n 'backend.baseUrl',\n )}/api/adoption-insights`;\n }\n\n async getActiveUsers(options: APIsViewOptions): Promise<ActiveUsersResponse> {\n if (!options.start_date || !options.end_date) {\n return Promise.resolve({ grouping: undefined, data: [] });\n }\n\n const baseUrl = await this.getBaseUrl();\n const url = generateEventsUrl(`${baseUrl}/events`, options);\n\n const response = await this.fetchApi.fetch(url);\n\n const data = await response.json();\n return data as ActiveUsersResponse;\n }\n\n async getUsers(options: APIsViewOptions): Promise<UsersResponse> {\n if (!options.start_date || !options.end_date) {\n return Promise.resolve({ data: [] });\n }\n\n const baseUrl = await this.getBaseUrl();\n const url = generateEventsUrl(`${baseUrl}/events`, options);\n\n const response = await this.fetchApi.fetch(url);\n\n const data = await response.json();\n return data as UsersResponse;\n }\n\n async getCatalogEntities(\n options: APIsViewOptions,\n ): Promise<CatalogEntitiesResponse> {\n if (!options.start_date || !options.end_date) {\n return Promise.resolve({ data: [] });\n }\n\n const baseUrl = await this.getBaseUrl();\n const url = generateEventsUrl(`${baseUrl}/events`, options);\n\n const response = await this.fetchApi.fetch(url);\n\n const data = await response.json();\n return data as CatalogEntitiesResponse;\n }\n\n async getTemplates(options: APIsViewOptions): Promise<TemplatesResponse> {\n if (!options.start_date || !options.end_date) {\n return Promise.resolve({ data: [] });\n }\n\n const baseUrl = await this.getBaseUrl();\n const url = generateEventsUrl(`${baseUrl}/events`, options);\n\n const response = await this.fetchApi.fetch(url);\n\n const data = await response.json();\n return data as TemplatesResponse;\n }\n\n async getTechdocs(options: APIsViewOptions): Promise<TechdocsResponse> {\n if (!options.start_date || !options.end_date) {\n return Promise.resolve({ data: [] });\n }\n\n const baseUrl = await this.getBaseUrl();\n const url = generateEventsUrl(`${baseUrl}/events`, options);\n\n const response = await this.fetchApi.fetch(url);\n\n const data = await response.json();\n return data as TechdocsResponse;\n }\n\n async getPlugins(options: APIsViewOptions): Promise<PluginTrendResponse> {\n if (!options.start_date || !options.end_date) {\n return Promise.resolve({ data: [] });\n }\n\n const baseUrl = await this.getBaseUrl();\n const url = generateEventsUrl(`${baseUrl}/events`, options);\n\n const response = await this.fetchApi.fetch(url);\n\n const data = await response.json();\n return data as PluginTrendResponse;\n }\n\n async getSearches(options: APIsViewOptions): Promise<SearchesResponse> {\n if (!options.start_date || !options.end_date) {\n return Promise.resolve({ grouping: undefined, data: [] });\n }\n\n const baseUrl = await this.getBaseUrl();\n const url = generateEventsUrl(`${baseUrl}/events`, options);\n\n const response = await this.fetchApi.fetch(url);\n\n const data = await response.json();\n return data as SearchesResponse;\n }\n\n async downloadBlob(options: APIsViewOptions): Promise<void> {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchApi.fetch(\n `${baseUrl}/events?type=${options.type}&start_date=${options.start_date}&end_date=${options.end_date}&format=${options.format}`,\n );\n const blob = await response.blob();\n const blobUrl = window.URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = blobUrl;\n link.download = options.blobName ?? 'active-users';\n document.body.appendChild(link);\n link.click();\n link.parentNode?.removeChild(link);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ import React__default from 'react';
2
+ import Box from '@mui/material/Box';
3
+ import { useTheme } from '@mui/material/styles';
4
+ import CircularProgress from '@mui/material/CircularProgress';
5
+ import { ResponsiveContainer, AreaChart, CartesianGrid, XAxis, YAxis, Tooltip, Area, Legend } from 'recharts';
6
+ import CardWrapper from '../CardWrapper/CardWrapper.esm.js';
7
+ import CustomTooltip from './CustomTooltip.esm.js';
8
+ import CustomCursor from '../Common/CustomCursor.esm.js';
9
+ import CustomLegend from './CustomLegend.esm.js';
10
+ import { getAverage, getXAxisTickValues, getXAxisformat } from '../../utils/utils.esm.js';
11
+ import { useActiveUsers } from '../../hooks/useActiveUsers.esm.js';
12
+ import { Typography } from '@material-ui/core';
13
+ import ExportCSVButton from './ExportCSVButton.esm.js';
14
+ import EmptyChartState from '../Common/EmptyChartState.esm.js';
15
+
16
+ const ActiveUsers = () => {
17
+ const theme = useTheme();
18
+ const isDarkMode = theme.palette.mode === "dark";
19
+ const { activeUsers, loading } = useActiveUsers();
20
+ const { data, grouping = "daily" } = activeUsers;
21
+ if (!data || data?.length === 0 || !data?.[0] && !loading) {
22
+ return /* @__PURE__ */ React__default.createElement(CardWrapper, { title: "Active users" }, /* @__PURE__ */ React__default.createElement(
23
+ Box,
24
+ {
25
+ display: "flex",
26
+ justifyContent: "center",
27
+ alignItems: "center",
28
+ height: 200
29
+ },
30
+ /* @__PURE__ */ React__default.createElement(EmptyChartState, null)
31
+ ));
32
+ }
33
+ return /* @__PURE__ */ React__default.createElement(CardWrapper, { title: "Active users", filter: /* @__PURE__ */ React__default.createElement(ExportCSVButton, null) }, loading ? /* @__PURE__ */ React__default.createElement(
34
+ Box,
35
+ {
36
+ display: "flex",
37
+ justifyContent: "center",
38
+ alignItems: "center",
39
+ height: 200
40
+ },
41
+ /* @__PURE__ */ React__default.createElement(CircularProgress, null)
42
+ ) : /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(Typography, { style: { margin: "20px 36px" } }, /* @__PURE__ */ React__default.createElement("b", null, `${Math.round(
43
+ getAverage(data, "total_users")
44
+ ).toLocaleString()} active users per ${grouping === "hourly" ? "hour" : "day"}`), " ", "were conducted during this period."), /* @__PURE__ */ React__default.createElement(Box, { sx: { height: 310, mt: 4, mb: 4, ml: 0, mr: 0 } }, /* @__PURE__ */ React__default.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React__default.createElement(
45
+ AreaChart,
46
+ {
47
+ data,
48
+ margin: { top: 10, right: 50, left: 20, bottom: 0 }
49
+ },
50
+ /* @__PURE__ */ React__default.createElement("defs", null, /* @__PURE__ */ React__default.createElement("linearGradient", { id: "new_users", x1: "0", y1: "0", x2: "0", y2: "1" }, /* @__PURE__ */ React__default.createElement("stop", { offset: "5%", stopColor: "#1976d2", stopOpacity: 0.8 }), /* @__PURE__ */ React__default.createElement("stop", { offset: "95%", stopColor: "#1976d2", stopOpacity: 0.2 })), /* @__PURE__ */ React__default.createElement(
51
+ "linearGradient",
52
+ {
53
+ id: "returning_users",
54
+ x1: "0",
55
+ y1: "0",
56
+ x2: "0",
57
+ y2: "1"
58
+ },
59
+ /* @__PURE__ */ React__default.createElement("stop", { offset: "5%", stopColor: "#B8BBBE", stopOpacity: 0.8 }),
60
+ /* @__PURE__ */ React__default.createElement("stop", { offset: "95%", stopColor: "#B8BBBE", stopOpacity: 0.2 })
61
+ )),
62
+ /* @__PURE__ */ React__default.createElement(
63
+ CartesianGrid,
64
+ {
65
+ stroke: isDarkMode ? "#666" : "#E5E7EB",
66
+ strokeDasharray: 0,
67
+ vertical: false
68
+ }
69
+ ),
70
+ /* @__PURE__ */ React__default.createElement(
71
+ XAxis,
72
+ {
73
+ dataKey: "date",
74
+ tickFormatter: (date) => getXAxisformat(date, grouping),
75
+ ticks: getXAxisTickValues(data, grouping),
76
+ tick: { fill: theme.palette.text.primary },
77
+ axisLine: false,
78
+ tickLine: false,
79
+ padding: { left: 30, right: 30 },
80
+ tickMargin: 10
81
+ }
82
+ ),
83
+ /* @__PURE__ */ React__default.createElement(
84
+ YAxis,
85
+ {
86
+ tick: { fill: theme.palette.text.primary },
87
+ tickLine: false,
88
+ axisLine: false,
89
+ tickFormatter: (value) => value.toLocaleString(),
90
+ tickMargin: 20
91
+ }
92
+ ),
93
+ /* @__PURE__ */ React__default.createElement(
94
+ Tooltip,
95
+ {
96
+ cursor: /* @__PURE__ */ React__default.createElement(CustomCursor, { cursorHeight: 250 }),
97
+ content: /* @__PURE__ */ React__default.createElement(CustomTooltip, { grouping })
98
+ }
99
+ ),
100
+ /* @__PURE__ */ React__default.createElement(
101
+ Area,
102
+ {
103
+ type: "linear",
104
+ dataKey: "returning_users",
105
+ stroke: "#555",
106
+ fill: "url(#returning_users)",
107
+ strokeWidth: 1
108
+ }
109
+ ),
110
+ /* @__PURE__ */ React__default.createElement(
111
+ Area,
112
+ {
113
+ type: "linear",
114
+ dataKey: "new_users",
115
+ stroke: "#1976d2",
116
+ fill: "url(#new_users)",
117
+ strokeWidth: 1
118
+ }
119
+ ),
120
+ /* @__PURE__ */ React__default.createElement(Legend, { content: /* @__PURE__ */ React__default.createElement(CustomLegend, null) })
121
+ )))));
122
+ };
123
+
124
+ export { ActiveUsers as default };
125
+ //# sourceMappingURL=ActiveUsers.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActiveUsers.esm.js","sources":["../../../src/components/ActiveUsers/ActiveUsers.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 */\nimport React from 'react';\n\nimport Box from '@mui/material/Box';\nimport { useTheme } from '@mui/material/styles';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport {\n AreaChart,\n CartesianGrid,\n ResponsiveContainer,\n XAxis,\n YAxis,\n Tooltip,\n Area,\n Legend,\n} from 'recharts';\n\nimport CardWrapper from '../CardWrapper';\nimport CustomTooltip from './CustomTooltip';\nimport CustomCursor from '../Common/CustomCursor';\nimport CustomLegend from './CustomLegend';\nimport {\n getAverage,\n getXAxisformat,\n getXAxisTickValues,\n} from '../../utils/utils';\nimport { useActiveUsers } from '../../hooks/useActiveUsers';\nimport { Typography } from '@material-ui/core';\nimport ExportCSVButton from './ExportCSVButton';\nimport EmptyChartState from '../Common/EmptyChartState';\n\nconst ActiveUsers = () => {\n const theme = useTheme();\n const isDarkMode = theme.palette.mode === 'dark';\n\n const { activeUsers, loading } = useActiveUsers();\n const { data, grouping = 'daily' } = activeUsers;\n\n if (!data || data?.length === 0 || (!data?.[0] && !loading)) {\n return (\n <CardWrapper title=\"Active users\">\n <Box\n display=\"flex\"\n justifyContent=\"center\"\n alignItems=\"center\"\n height={200}\n >\n <EmptyChartState />\n </Box>\n </CardWrapper>\n );\n }\n\n return (\n <CardWrapper title=\"Active users\" filter={<ExportCSVButton />}>\n {loading ? (\n <Box\n display=\"flex\"\n justifyContent=\"center\"\n alignItems=\"center\"\n height={200}\n >\n <CircularProgress />\n </Box>\n ) : (\n <>\n <Typography style={{ margin: '20px 36px' }}>\n <b>\n {`${Math.round(\n getAverage(data, 'total_users'),\n ).toLocaleString()} active users per ${\n grouping === 'hourly' ? 'hour' : 'day'\n }`}\n </b>{' '}\n were conducted during this period.\n </Typography>\n <Box sx={{ height: 310, mt: 4, mb: 4, ml: 0, mr: 0 }}>\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart\n data={data}\n margin={{ top: 10, right: 50, left: 20, bottom: 0 }}\n >\n <defs>\n <linearGradient id=\"new_users\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stopColor=\"#1976d2\" stopOpacity={0.8} />\n <stop offset=\"95%\" stopColor=\"#1976d2\" stopOpacity={0.2} />\n </linearGradient>\n <linearGradient\n id=\"returning_users\"\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop offset=\"5%\" stopColor=\"#B8BBBE\" stopOpacity={0.8} />\n <stop offset=\"95%\" stopColor=\"#B8BBBE\" stopOpacity={0.2} />\n </linearGradient>\n </defs>\n\n <CartesianGrid\n stroke={isDarkMode ? '#666' : '#E5E7EB'}\n strokeDasharray={0}\n vertical={false}\n />\n\n <XAxis\n dataKey=\"date\"\n tickFormatter={date => getXAxisformat(date, grouping)}\n ticks={getXAxisTickValues(data, grouping)}\n tick={{ fill: theme.palette.text.primary }}\n axisLine={false}\n tickLine={false}\n padding={{ left: 30, right: 30 }}\n tickMargin={10}\n />\n <YAxis\n tick={{ fill: theme.palette.text.primary }}\n tickLine={false}\n axisLine={false}\n tickFormatter={value => value.toLocaleString()}\n tickMargin={20}\n />\n <Tooltip\n cursor={<CustomCursor cursorHeight={250} />}\n content={<CustomTooltip grouping={grouping} />}\n />\n <Area\n type=\"linear\"\n dataKey=\"returning_users\"\n stroke=\"#555\"\n fill=\"url(#returning_users)\"\n strokeWidth={1}\n />\n <Area\n type=\"linear\"\n dataKey=\"new_users\"\n stroke=\"#1976d2\"\n fill=\"url(#new_users)\"\n strokeWidth={1}\n />\n <Legend content={<CustomLegend />} />\n </AreaChart>\n </ResponsiveContainer>\n </Box>\n </>\n )}\n </CardWrapper>\n );\n};\n\nexport default ActiveUsers;\n"],"names":["React"],"mappings":";;;;;;;;;;;;;;;AA6CA,MAAM,cAAc,MAAM;AACxB,EAAA,MAAM,QAAQ,QAAS,EAAA;AACvB,EAAM,MAAA,UAAA,GAAa,KAAM,CAAA,OAAA,CAAQ,IAAS,KAAA,MAAA;AAE1C,EAAA,MAAM,EAAE,WAAA,EAAa,OAAQ,EAAA,GAAI,cAAe,EAAA;AAChD,EAAA,MAAM,EAAE,IAAA,EAAM,QAAW,GAAA,OAAA,EAAY,GAAA,WAAA;AAErC,EAAI,IAAA,CAAC,IAAQ,IAAA,IAAA,EAAM,MAAW,KAAA,CAAA,IAAM,CAAC,IAAO,GAAA,CAAC,CAAK,IAAA,CAAC,OAAU,EAAA;AAC3D,IACE,uBAAAA,cAAA,CAAA,aAAA,CAAC,WAAY,EAAA,EAAA,KAAA,EAAM,cACjB,EAAA,kBAAAA,cAAA,CAAA,aAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACC,OAAQ,EAAA,MAAA;AAAA,QACR,cAAe,EAAA,QAAA;AAAA,QACf,UAAW,EAAA,QAAA;AAAA,QACX,MAAQ,EAAA;AAAA,OAAA;AAAA,mDAEP,eAAgB,EAAA,IAAA;AAAA,KAErB,CAAA;AAAA;AAIJ,EACE,uBAAAA,cAAA,CAAA,aAAA,CAAC,eAAY,KAAM,EAAA,cAAA,EAAe,wBAASA,cAAA,CAAA,aAAA,CAAA,eAAA,EAAA,IAAgB,KACxD,OACC,mBAAAA,cAAA,CAAA,aAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,OAAQ,EAAA,MAAA;AAAA,MACR,cAAe,EAAA,QAAA;AAAA,MACf,UAAW,EAAA,QAAA;AAAA,MACX,MAAQ,EAAA;AAAA,KAAA;AAAA,iDAEP,gBAAiB,EAAA,IAAA;AAAA,GAGpB,mBAAAA,cAAA,CAAA,aAAA,CAAAA,cAAA,CAAA,QAAA,EAAA,IAAA,kBACGA,cAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,KAAO,EAAA,EAAE,MAAQ,EAAA,WAAA,EAC3B,EAAA,kBAAAA,cAAA,CAAA,aAAA,CAAC,GACE,EAAA,IAAA,EAAA,CAAA,EAAG,IAAK,CAAA,KAAA;AAAA,IACP,UAAA,CAAW,MAAM,aAAa;AAAA,GAC9B,CAAA,cAAA,EAAgB,CAAA,kBAAA,EAChB,aAAa,QAAW,GAAA,MAAA,GAAS,KACnC,CAAA,CACF,CAAK,EAAA,GAAA,EAAI,oCAEX,CAAA,+CACC,GAAI,EAAA,EAAA,EAAA,EAAI,EAAE,MAAA,EAAQ,GAAK,EAAA,EAAA,EAAI,CAAG,EAAA,EAAA,EAAI,GAAG,EAAI,EAAA,CAAA,EAAG,EAAI,EAAA,CAAA,sBAC9CA,cAAA,CAAA,aAAA,CAAA,mBAAA,EAAA,EAAoB,KAAM,EAAA,MAAA,EAAO,QAAO,MACvC,EAAA,kBAAAA,cAAA,CAAA,aAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,IAAA;AAAA,MACA,MAAA,EAAQ,EAAE,GAAK,EAAA,EAAA,EAAI,OAAO,EAAI,EAAA,IAAA,EAAM,EAAI,EAAA,MAAA,EAAQ,CAAE;AAAA,KAAA;AAAA,oBAEjDA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,kBACEA,cAAA,CAAA,aAAA,CAAA,gBAAA,EAAA,EAAe,IAAG,WAAY,EAAA,EAAA,EAAG,GAAI,EAAA,EAAA,EAAG,GAAI,EAAA,EAAA,EAAG,GAAI,EAAA,EAAA,EAAG,uBACpDA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,EAAK,MAAO,EAAA,IAAA,EAAK,SAAU,EAAA,SAAA,EAAU,WAAa,EAAA,GAAA,EAAK,mBACvDA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,EAAK,MAAO,EAAA,KAAA,EAAM,SAAU,EAAA,SAAA,EAAU,WAAa,EAAA,GAAA,EAAK,CAC3D,CACA,kBAAAA,cAAA,CAAA,aAAA;AAAA,MAAC,gBAAA;AAAA,MAAA;AAAA,QACC,EAAG,EAAA,iBAAA;AAAA,QACH,EAAG,EAAA,GAAA;AAAA,QACH,EAAG,EAAA,GAAA;AAAA,QACH,EAAG,EAAA,GAAA;AAAA,QACH,EAAG,EAAA;AAAA,OAAA;AAAA,mDAEF,MAAK,EAAA,EAAA,MAAA,EAAO,MAAK,SAAU,EAAA,SAAA,EAAU,aAAa,GAAK,EAAA,CAAA;AAAA,mDACvD,MAAK,EAAA,EAAA,MAAA,EAAO,OAAM,SAAU,EAAA,SAAA,EAAU,aAAa,GAAK,EAAA;AAAA,KAE7D,CAAA;AAAA,oBAEAA,cAAA,CAAA,aAAA;AAAA,MAAC,aAAA;AAAA,MAAA;AAAA,QACC,MAAA,EAAQ,aAAa,MAAS,GAAA,SAAA;AAAA,QAC9B,eAAiB,EAAA,CAAA;AAAA,QACjB,QAAU,EAAA;AAAA;AAAA,KACZ;AAAA,oBAEAA,cAAA,CAAA,aAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,OAAQ,EAAA,MAAA;AAAA,QACR,aAAe,EAAA,CAAA,IAAA,KAAQ,cAAe,CAAA,IAAA,EAAM,QAAQ,CAAA;AAAA,QACpD,KAAA,EAAO,kBAAmB,CAAA,IAAA,EAAM,QAAQ,CAAA;AAAA,QACxC,MAAM,EAAE,IAAA,EAAM,KAAM,CAAA,OAAA,CAAQ,KAAK,OAAQ,EAAA;AAAA,QACzC,QAAU,EAAA,KAAA;AAAA,QACV,QAAU,EAAA,KAAA;AAAA,QACV,OAAS,EAAA,EAAE,IAAM,EAAA,EAAA,EAAI,OAAO,EAAG,EAAA;AAAA,QAC/B,UAAY,EAAA;AAAA;AAAA,KACd;AAAA,oBACAA,cAAA,CAAA,aAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,MAAM,EAAE,IAAA,EAAM,KAAM,CAAA,OAAA,CAAQ,KAAK,OAAQ,EAAA;AAAA,QACzC,QAAU,EAAA,KAAA;AAAA,QACV,QAAU,EAAA,KAAA;AAAA,QACV,aAAA,EAAe,CAAS,KAAA,KAAA,KAAA,CAAM,cAAe,EAAA;AAAA,QAC7C,UAAY,EAAA;AAAA;AAAA,KACd;AAAA,oBACAA,cAAA,CAAA,aAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,MAAQ,kBAAAA,cAAA,CAAA,aAAA,CAAC,YAAa,EAAA,EAAA,YAAA,EAAc,GAAK,EAAA,CAAA;AAAA,QACzC,OAAA,kBAAUA,cAAA,CAAA,aAAA,CAAA,aAAA,EAAA,EAAc,QAAoB,EAAA;AAAA;AAAA,KAC9C;AAAA,oBACAA,cAAA,CAAA,aAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,IAAK,EAAA,QAAA;AAAA,QACL,OAAQ,EAAA,iBAAA;AAAA,QACR,MAAO,EAAA,MAAA;AAAA,QACP,IAAK,EAAA,uBAAA;AAAA,QACL,WAAa,EAAA;AAAA;AAAA,KACf;AAAA,oBACAA,cAAA,CAAA,aAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,IAAK,EAAA,QAAA;AAAA,QACL,OAAQ,EAAA,WAAA;AAAA,QACR,MAAO,EAAA,SAAA;AAAA,QACP,IAAK,EAAA,iBAAA;AAAA,QACL,WAAa,EAAA;AAAA;AAAA,KACf;AAAA,oBACCA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,EAAO,OAAS,kBAAAA,cAAA,CAAA,aAAA,CAAC,kBAAa,CAAI,EAAA;AAAA,GAEvC,CACF,CACF,CAEJ,CAAA;AAEJ;;;;"}
@@ -0,0 +1,58 @@
1
+ import React__default from 'react';
2
+ import Typography from '@mui/material/Typography';
3
+ import { useTheme } from '@mui/material/styles';
4
+
5
+ const CustomLegend = (props) => {
6
+ const theme = useTheme();
7
+ const { payload } = props;
8
+ return /* @__PURE__ */ React__default.createElement(
9
+ "div",
10
+ {
11
+ style: {
12
+ display: "flex",
13
+ gap: 16,
14
+ alignItems: "center",
15
+ marginLeft: 50,
16
+ paddingTop: 10
17
+ }
18
+ },
19
+ payload.map((entry) => /* @__PURE__ */ React__default.createElement(
20
+ "div",
21
+ {
22
+ key: entry.value,
23
+ style: { display: "flex", alignItems: "center", gap: 4 }
24
+ },
25
+ /* @__PURE__ */ React__default.createElement("div", null, /* @__PURE__ */ React__default.createElement(
26
+ "div",
27
+ {
28
+ style: {
29
+ width: 20,
30
+ height: 3,
31
+ backgroundColor: entry.color
32
+ }
33
+ }
34
+ ), /* @__PURE__ */ React__default.createElement(
35
+ "div",
36
+ {
37
+ style: {
38
+ width: 20,
39
+ height: 4,
40
+ backgroundColor: entry.color,
41
+ opacity: "0.4"
42
+ }
43
+ }
44
+ )),
45
+ /* @__PURE__ */ React__default.createElement(
46
+ Typography,
47
+ {
48
+ variant: "body2",
49
+ style: { color: theme.palette.text.primary, fontSize: "0.875rem" }
50
+ },
51
+ entry.value === "new_users" ? "New users" : "Returning users"
52
+ )
53
+ ))
54
+ );
55
+ };
56
+
57
+ export { CustomLegend as default };
58
+ //# sourceMappingURL=CustomLegend.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomLegend.esm.js","sources":["../../../src/components/ActiveUsers/CustomLegend.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 */\nimport React from 'react';\n\nimport Typography from '@mui/material/Typography';\nimport { useTheme } from '@mui/material/styles';\n\nconst CustomLegend = (props: any) => {\n const theme = useTheme();\n const { payload } = props;\n\n return (\n <div\n style={{\n display: 'flex',\n gap: 16,\n alignItems: 'center',\n marginLeft: 50,\n paddingTop: 10,\n }}\n >\n {payload.map((entry: any) => (\n <div\n key={entry.value}\n style={{ display: 'flex', alignItems: 'center', gap: 4 }}\n >\n <div>\n <div\n style={{\n width: 20,\n height: 3,\n backgroundColor: entry.color,\n }}\n />\n <div\n style={{\n width: 20,\n height: 4,\n backgroundColor: entry.color,\n opacity: '0.4',\n }}\n />\n </div>\n <Typography\n variant=\"body2\"\n style={{ color: theme.palette.text.primary, fontSize: '0.875rem' }}\n >\n {entry.value === 'new_users' ? 'New users' : 'Returning users'}\n </Typography>\n </div>\n ))}\n </div>\n );\n};\n\nexport default CustomLegend;\n"],"names":["React"],"mappings":";;;;AAoBM,MAAA,YAAA,GAAe,CAAC,KAAe,KAAA;AACnC,EAAA,MAAM,QAAQ,QAAS,EAAA;AACvB,EAAM,MAAA,EAAE,SAAY,GAAA,KAAA;AAEpB,EACE,uBAAAA,cAAA,CAAA,aAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAO,EAAA;AAAA,QACL,OAAS,EAAA,MAAA;AAAA,QACT,GAAK,EAAA,EAAA;AAAA,QACL,UAAY,EAAA,QAAA;AAAA,QACZ,UAAY,EAAA,EAAA;AAAA,QACZ,UAAY,EAAA;AAAA;AACd,KAAA;AAAA,IAEC,OAAA,CAAQ,GAAI,CAAA,CAAC,KACZ,qBAAAA,cAAA,CAAA,aAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,KAAK,KAAM,CAAA,KAAA;AAAA,QACX,OAAO,EAAE,OAAA,EAAS,QAAQ,UAAY,EAAA,QAAA,EAAU,KAAK,CAAE;AAAA,OAAA;AAAA,mDAEtD,KACC,EAAA,IAAA,kBAAAA,cAAA,CAAA,aAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,KAAO,EAAA;AAAA,YACL,KAAO,EAAA,EAAA;AAAA,YACP,MAAQ,EAAA,CAAA;AAAA,YACR,iBAAiB,KAAM,CAAA;AAAA;AACzB;AAAA,OAEF,kBAAAA,cAAA,CAAA,aAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,KAAO,EAAA;AAAA,YACL,KAAO,EAAA,EAAA;AAAA,YACP,MAAQ,EAAA,CAAA;AAAA,YACR,iBAAiB,KAAM,CAAA,KAAA;AAAA,YACvB,OAAS,EAAA;AAAA;AACX;AAAA,OAEJ,CAAA;AAAA,sBACAA,cAAA,CAAA,aAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,OAAQ,EAAA,OAAA;AAAA,UACR,KAAA,EAAO,EAAE,KAAO,EAAA,KAAA,CAAM,QAAQ,IAAK,CAAA,OAAA,EAAS,UAAU,UAAW;AAAA,SAAA;AAAA,QAEhE,KAAA,CAAM,KAAU,KAAA,WAAA,GAAc,WAAc,GAAA;AAAA;AAC/C,KAEH;AAAA,GACH;AAEJ;;;;"}
@@ -0,0 +1,87 @@
1
+ import React__default from 'react';
2
+ import Paper from '@mui/material/Paper';
3
+ import Typography from '@mui/material/Typography';
4
+ import { useTheme } from '@mui/material/styles';
5
+ import Box from '@mui/material/Box';
6
+ import { format } from 'date-fns';
7
+
8
+ const CustomTooltip = ({
9
+ active,
10
+ payload,
11
+ label,
12
+ grouping
13
+ }) => {
14
+ const theme = useTheme();
15
+ if (active && payload?.length) {
16
+ const date = label ? new Date(label) : /* @__PURE__ */ new Date();
17
+ return /* @__PURE__ */ React__default.createElement(
18
+ Paper,
19
+ {
20
+ elevation: 1,
21
+ sx: {
22
+ padding: "12px 16px",
23
+ boxShadow: 4,
24
+ borderRadius: 2
25
+ }
26
+ },
27
+ /* @__PURE__ */ React__default.createElement(
28
+ Typography,
29
+ {
30
+ sx: {
31
+ fontSize: "0.875rem",
32
+ fontWeight: 500,
33
+ marginBottom: "12px"
34
+ }
35
+ },
36
+ grouping === "hourly" ? format(date, "MMMM dd, yyyy hh:mm a") : format(date, "MMMM, dd yyyy")
37
+ ),
38
+ /* @__PURE__ */ React__default.createElement(Box, { display: "flex", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React__default.createElement(Box, { mr: 3 }, /* @__PURE__ */ React__default.createElement(
39
+ Typography,
40
+ {
41
+ sx: {
42
+ fontSize: "0.875rem",
43
+ fontWeight: 500,
44
+ color: theme.palette.text.secondary
45
+ }
46
+ },
47
+ "Returning users"
48
+ ), /* @__PURE__ */ React__default.createElement(
49
+ Typography,
50
+ {
51
+ sx: {
52
+ fontSize: "2.5rem",
53
+ fontWeight: 500,
54
+ color: "#009596",
55
+ lineHeight: 1.2
56
+ }
57
+ },
58
+ payload[0]?.value
59
+ )), /* @__PURE__ */ React__default.createElement(Box, null, /* @__PURE__ */ React__default.createElement(
60
+ Typography,
61
+ {
62
+ sx: {
63
+ fontSize: "0.875rem",
64
+ fontWeight: 500,
65
+ color: theme.palette.text.secondary
66
+ }
67
+ },
68
+ "New users"
69
+ ), /* @__PURE__ */ React__default.createElement(
70
+ Typography,
71
+ {
72
+ sx: {
73
+ fontSize: "2.5rem",
74
+ fontWeight: 500,
75
+ color: "#009596",
76
+ lineHeight: 1.2
77
+ }
78
+ },
79
+ payload[1]?.value
80
+ )))
81
+ );
82
+ }
83
+ return null;
84
+ };
85
+
86
+ export { CustomTooltip as default };
87
+ //# sourceMappingURL=CustomTooltip.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomTooltip.esm.js","sources":["../../../src/components/ActiveUsers/CustomTooltip.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 */\nimport React from 'react';\n\nimport Paper from '@mui/material/Paper';\nimport Typography from '@mui/material/Typography';\nimport { useTheme } from '@mui/material/styles';\nimport Box from '@mui/material/Box';\nimport { format } from 'date-fns';\n\nconst CustomTooltip = ({\n active,\n payload,\n label,\n grouping,\n}: {\n active?: boolean;\n payload?: any[];\n label?: string;\n grouping?: string;\n}) => {\n const theme = useTheme();\n\n if (active && payload?.length) {\n const date = label ? new Date(label) : new Date();\n return (\n <Paper\n elevation={1}\n sx={{\n padding: '12px 16px',\n boxShadow: 4,\n borderRadius: 2,\n }}\n >\n <Typography\n sx={{\n fontSize: '0.875rem',\n fontWeight: 500,\n marginBottom: '12px',\n }}\n >\n {grouping === 'hourly'\n ? format(date, 'MMMM dd, yyyy hh:mm a')\n : format(date, 'MMMM, dd yyyy')}\n </Typography>\n\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\">\n <Box mr={3}>\n <Typography\n sx={{\n fontSize: '0.875rem',\n fontWeight: 500,\n color: theme.palette.text.secondary,\n }}\n >\n Returning users\n </Typography>\n <Typography\n sx={{\n fontSize: '2.5rem',\n fontWeight: 500,\n color: '#009596',\n lineHeight: 1.2,\n }}\n >\n {payload[0]?.value}\n </Typography>\n </Box>\n\n <Box>\n <Typography\n sx={{\n fontSize: '0.875rem',\n fontWeight: 500,\n color: theme.palette.text.secondary,\n }}\n >\n New users\n </Typography>\n <Typography\n sx={{\n fontSize: '2.5rem',\n fontWeight: 500,\n color: '#009596',\n lineHeight: 1.2,\n }}\n >\n {payload[1]?.value}\n </Typography>\n </Box>\n </Box>\n </Paper>\n );\n }\n return null;\n};\n\nexport default CustomTooltip;\n"],"names":["React"],"mappings":";;;;;;;AAuBA,MAAM,gBAAgB,CAAC;AAAA,EACrB,MAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAKM,KAAA;AACJ,EAAA,MAAM,QAAQ,QAAS,EAAA;AAEvB,EAAI,IAAA,MAAA,IAAU,SAAS,MAAQ,EAAA;AAC7B,IAAA,MAAM,OAAO,KAAQ,GAAA,IAAI,KAAK,KAAK,CAAA,uBAAQ,IAAK,EAAA;AAChD,IACE,uBAAAA,cAAA,CAAA,aAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAW,EAAA,CAAA;AAAA,QACX,EAAI,EAAA;AAAA,UACF,OAAS,EAAA,WAAA;AAAA,UACT,SAAW,EAAA,CAAA;AAAA,UACX,YAAc,EAAA;AAAA;AAChB,OAAA;AAAA,sBAEAA,cAAA,CAAA,aAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,EAAI,EAAA;AAAA,YACF,QAAU,EAAA,UAAA;AAAA,YACV,UAAY,EAAA,GAAA;AAAA,YACZ,YAAc,EAAA;AAAA;AAChB,SAAA;AAAA,QAEC,QAAA,KAAa,WACV,MAAO,CAAA,IAAA,EAAM,uBAAuB,CACpC,GAAA,MAAA,CAAO,MAAM,eAAe;AAAA,OAClC;AAAA,sBAEAA,cAAA,CAAA,aAAA,CAAC,GAAI,EAAA,EAAA,OAAA,EAAQ,MAAO,EAAA,cAAA,EAAe,eAAgB,EAAA,UAAA,EAAW,QAC5D,EAAA,kBAAAA,cAAA,CAAA,aAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CACP,EAAA,kBAAAA,cAAA,CAAA,aAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,EAAI,EAAA;AAAA,YACF,QAAU,EAAA,UAAA;AAAA,YACV,UAAY,EAAA,GAAA;AAAA,YACZ,KAAA,EAAO,KAAM,CAAA,OAAA,CAAQ,IAAK,CAAA;AAAA;AAC5B,SAAA;AAAA,QACD;AAAA,OAGD,kBAAAA,cAAA,CAAA,aAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,EAAI,EAAA;AAAA,YACF,QAAU,EAAA,QAAA;AAAA,YACV,UAAY,EAAA,GAAA;AAAA,YACZ,KAAO,EAAA,SAAA;AAAA,YACP,UAAY,EAAA;AAAA;AACd,SAAA;AAAA,QAEC,OAAA,CAAQ,CAAC,CAAG,EAAA;AAAA,OAEjB,CAEA,kBAAAA,cAAA,CAAA,aAAA,CAAC,GACC,EAAA,IAAA,kBAAAA,cAAA,CAAA,aAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,EAAI,EAAA;AAAA,YACF,QAAU,EAAA,UAAA;AAAA,YACV,UAAY,EAAA,GAAA;AAAA,YACZ,KAAA,EAAO,KAAM,CAAA,OAAA,CAAQ,IAAK,CAAA;AAAA;AAC5B,SAAA;AAAA,QACD;AAAA,OAGD,kBAAAA,cAAA,CAAA,aAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,EAAI,EAAA;AAAA,YACF,QAAU,EAAA,QAAA;AAAA,YACV,UAAY,EAAA,GAAA;AAAA,YACZ,KAAO,EAAA,SAAA;AAAA,YACP,UAAY,EAAA;AAAA;AACd,SAAA;AAAA,QAEC,OAAA,CAAQ,CAAC,CAAG,EAAA;AAAA,OAEjB,CACF;AAAA,KACF;AAAA;AAGJ,EAAO,OAAA,IAAA;AACT;;;;"}
@@ -0,0 +1,75 @@
1
+ import React__default from 'react';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import Button from '@mui/material/Button';
4
+ import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
5
+ import Box from '@mui/material/Box';
6
+ import { useTheme } from '@mui/material/styles';
7
+ import { format } from 'date-fns';
8
+ import { adoptionInsightsApiRef } from '../../api/index.esm.js';
9
+ import { useDateRange } from '../Header/DateRangeContext.esm.js';
10
+
11
+ const ExportCSVButton = () => {
12
+ const [loading, setLoading] = React__default.useState(false);
13
+ const api = useApi(adoptionInsightsApiRef);
14
+ const { startDateRange, endDateRange } = useDateRange();
15
+ const theme = useTheme();
16
+ const debounceRef = React__default.useRef(null);
17
+ const handleCSVDownload = async () => {
18
+ try {
19
+ await api.downloadBlob({
20
+ type: "active_users",
21
+ start_date: startDateRange ? format(startDateRange, "yyyy-MM-dd") : void 0,
22
+ end_date: endDateRange ? format(endDateRange, "yyyy-MM-dd") : void 0,
23
+ format: "csv"
24
+ });
25
+ } catch (error) {
26
+ console.error("CSV Download failed:", error);
27
+ } finally {
28
+ setLoading(false);
29
+ }
30
+ };
31
+ const debouncedHandleCSVDownload = () => {
32
+ setLoading(true);
33
+ if (debounceRef.current) {
34
+ clearTimeout(debounceRef.current);
35
+ }
36
+ debounceRef.current = setTimeout(() => {
37
+ handleCSVDownload();
38
+ }, 500);
39
+ };
40
+ return /* @__PURE__ */ React__default.createElement(
41
+ Box,
42
+ {
43
+ sx: {
44
+ height: "100%",
45
+ minWidth: 160,
46
+ display: "flex",
47
+ alignItems: "center",
48
+ justifyContent: "center"
49
+ }
50
+ },
51
+ /* @__PURE__ */ React__default.createElement(
52
+ Button,
53
+ {
54
+ variant: "text",
55
+ startIcon: /* @__PURE__ */ React__default.createElement(FileDownloadOutlinedIcon, null),
56
+ onClick: debouncedHandleCSVDownload,
57
+ sx: {
58
+ color: theme.palette.primary.main,
59
+ textTransform: "none",
60
+ fontSize: "1rem",
61
+ fontWeight: 400,
62
+ textDecoration: "none",
63
+ "&:hover": {
64
+ backgroundColor: "transparent",
65
+ textDecoration: "none"
66
+ }
67
+ }
68
+ },
69
+ loading ? "Downloading..." : "Export CSV"
70
+ )
71
+ );
72
+ };
73
+
74
+ export { ExportCSVButton as default };
75
+ //# sourceMappingURL=ExportCSVButton.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExportCSVButton.esm.js","sources":["../../../src/components/ActiveUsers/ExportCSVButton.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 */\nimport React from 'react';\n\nimport { useApi } from '@backstage/core-plugin-api';\nimport Button from '@mui/material/Button';\nimport FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';\nimport Box from '@mui/material/Box';\nimport { useTheme } from '@mui/material/styles';\nimport { format } from 'date-fns';\n\nimport { adoptionInsightsApiRef } from '../../api';\nimport { useDateRange } from '../Header/DateRangeContext';\n\nconst ExportCSVButton = () => {\n const [loading, setLoading] = React.useState(false);\n const api = useApi(adoptionInsightsApiRef);\n const { startDateRange, endDateRange } = useDateRange();\n const theme = useTheme();\n const debounceRef = React.useRef<NodeJS.Timeout | null>(null);\n\n const handleCSVDownload = async () => {\n try {\n await api.downloadBlob({\n type: 'active_users',\n start_date: startDateRange\n ? format(startDateRange, 'yyyy-MM-dd')\n : undefined,\n end_date: endDateRange ? format(endDateRange, 'yyyy-MM-dd') : undefined,\n format: 'csv',\n });\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('CSV Download failed:', error);\n } finally {\n setLoading(false);\n }\n };\n\n const debouncedHandleCSVDownload = () => {\n setLoading(true);\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n debounceRef.current = setTimeout(() => {\n handleCSVDownload();\n }, 500);\n };\n\n return (\n <Box\n sx={{\n height: '100%',\n minWidth: 160,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Button\n variant=\"text\"\n startIcon={<FileDownloadOutlinedIcon />}\n onClick={debouncedHandleCSVDownload}\n sx={{\n color: theme.palette.primary.main,\n textTransform: 'none',\n fontSize: '1rem',\n fontWeight: 400,\n textDecoration: 'none',\n '&:hover': {\n backgroundColor: 'transparent',\n textDecoration: 'none',\n },\n }}\n >\n {loading ? 'Downloading...' : 'Export CSV'}\n </Button>\n </Box>\n );\n};\n\nexport default ExportCSVButton;\n"],"names":["React"],"mappings":";;;;;;;;;;AA2BA,MAAM,kBAAkB,MAAM;AAC5B,EAAA,MAAM,CAAC,OAAS,EAAA,UAAU,CAAI,GAAAA,cAAA,CAAM,SAAS,KAAK,CAAA;AAClD,EAAM,MAAA,GAAA,GAAM,OAAO,sBAAsB,CAAA;AACzC,EAAA,MAAM,EAAE,cAAA,EAAgB,YAAa,EAAA,GAAI,YAAa,EAAA;AACtD,EAAA,MAAM,QAAQ,QAAS,EAAA;AACvB,EAAM,MAAA,WAAA,GAAcA,cAAM,CAAA,MAAA,CAA8B,IAAI,CAAA;AAE5D,EAAA,MAAM,oBAAoB,YAAY;AACpC,IAAI,IAAA;AACF,MAAA,MAAM,IAAI,YAAa,CAAA;AAAA,QACrB,IAAM,EAAA,cAAA;AAAA,QACN,UAAY,EAAA,cAAA,GACR,MAAO,CAAA,cAAA,EAAgB,YAAY,CACnC,GAAA,KAAA,CAAA;AAAA,QACJ,QAAU,EAAA,YAAA,GAAe,MAAO,CAAA,YAAA,EAAc,YAAY,CAAI,GAAA,KAAA,CAAA;AAAA,QAC9D,MAAQ,EAAA;AAAA,OACT,CAAA;AAAA,aACM,KAAO,EAAA;AAEd,MAAQ,OAAA,CAAA,KAAA,CAAM,wBAAwB,KAAK,CAAA;AAAA,KAC3C,SAAA;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB,GACF;AAEA,EAAA,MAAM,6BAA6B,MAAM;AACvC,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,IAAI,YAAY,OAAS,EAAA;AACvB,MAAA,YAAA,CAAa,YAAY,OAAO,CAAA;AAAA;AAElC,IAAY,WAAA,CAAA,OAAA,GAAU,WAAW,MAAM;AACrC,MAAkB,iBAAA,EAAA;AAAA,OACjB,GAAG,CAAA;AAAA,GACR;AAEA,EACE,uBAAAA,cAAA,CAAA,aAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,EAAI,EAAA;AAAA,QACF,MAAQ,EAAA,MAAA;AAAA,QACR,QAAU,EAAA,GAAA;AAAA,QACV,OAAS,EAAA,MAAA;AAAA,QACT,UAAY,EAAA,QAAA;AAAA,QACZ,cAAgB,EAAA;AAAA;AAClB,KAAA;AAAA,oBAEAA,cAAA,CAAA,aAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,OAAQ,EAAA,MAAA;AAAA,QACR,SAAA,+CAAY,wBAAyB,EAAA,IAAA,CAAA;AAAA,QACrC,OAAS,EAAA,0BAAA;AAAA,QACT,EAAI,EAAA;AAAA,UACF,KAAA,EAAO,KAAM,CAAA,OAAA,CAAQ,OAAQ,CAAA,IAAA;AAAA,UAC7B,aAAe,EAAA,MAAA;AAAA,UACf,QAAU,EAAA,MAAA;AAAA,UACV,UAAY,EAAA,GAAA;AAAA,UACZ,cAAgB,EAAA,MAAA;AAAA,UAChB,SAAW,EAAA;AAAA,YACT,eAAiB,EAAA,aAAA;AAAA,YACjB,cAAgB,EAAA;AAAA;AAClB;AACF,OAAA;AAAA,MAEC,UAAU,gBAAmB,GAAA;AAAA;AAChC,GACF;AAEJ;;;;"}