@openchoreo/backstage-plugin-openchoreo-observability 1.1.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 (196) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +22 -0
  3. package/dist/api/FinOpsAgentApi.esm.js +43 -0
  4. package/dist/api/FinOpsAgentApi.esm.js.map +1 -0
  5. package/dist/api/ObservabilityApi.esm.js +568 -0
  6. package/dist/api/ObservabilityApi.esm.js.map +1 -0
  7. package/dist/api/ObserverUrlCache.esm.js +59 -0
  8. package/dist/api/ObserverUrlCache.esm.js.map +1 -0
  9. package/dist/api/RCAAgentApi.esm.js +104 -0
  10. package/dist/api/RCAAgentApi.esm.js.map +1 -0
  11. package/dist/components/Alerts/AlertRow.esm.js +203 -0
  12. package/dist/components/Alerts/AlertRow.esm.js.map +1 -0
  13. package/dist/components/Alerts/AlertsActions.esm.js +77 -0
  14. package/dist/components/Alerts/AlertsActions.esm.js.map +1 -0
  15. package/dist/components/Alerts/AlertsFilter.esm.js +95 -0
  16. package/dist/components/Alerts/AlertsFilter.esm.js.map +1 -0
  17. package/dist/components/Alerts/AlertsTable.esm.js +63 -0
  18. package/dist/components/Alerts/AlertsTable.esm.js.map +1 -0
  19. package/dist/components/Alerts/ObservabilityAlertsPage.esm.js +251 -0
  20. package/dist/components/Alerts/ObservabilityAlertsPage.esm.js.map +1 -0
  21. package/dist/components/Alerts/types.esm.js +4 -0
  22. package/dist/components/Alerts/types.esm.js.map +1 -0
  23. package/dist/components/CostAnalysis/CostAnalysisPage.esm.js +141 -0
  24. package/dist/components/CostAnalysis/CostAnalysisPage.esm.js.map +1 -0
  25. package/dist/components/CostAnalysis/CostAnalysisReport.esm.js +128 -0
  26. package/dist/components/CostAnalysis/CostAnalysisReport.esm.js.map +1 -0
  27. package/dist/components/CostAnalysis/CostAnalysisReportView.esm.js +517 -0
  28. package/dist/components/CostAnalysis/CostAnalysisReportView.esm.js.map +1 -0
  29. package/dist/components/CostAnalysis/CostAnalysisTable.esm.js +149 -0
  30. package/dist/components/CostAnalysis/CostAnalysisTable.esm.js.map +1 -0
  31. package/dist/components/CostAnalysis/FinOpsApplyButton.esm.js +112 -0
  32. package/dist/components/CostAnalysis/FinOpsApplyButton.esm.js.map +1 -0
  33. package/dist/components/CostAnalysis/index.esm.js +2 -0
  34. package/dist/components/CostAnalysis/index.esm.js.map +1 -0
  35. package/dist/components/Incidents/IncidentRow.esm.js +258 -0
  36. package/dist/components/Incidents/IncidentRow.esm.js.map +1 -0
  37. package/dist/components/Incidents/IncidentsActions.esm.js +77 -0
  38. package/dist/components/Incidents/IncidentsActions.esm.js.map +1 -0
  39. package/dist/components/Incidents/IncidentsFilter.esm.js +144 -0
  40. package/dist/components/Incidents/IncidentsFilter.esm.js.map +1 -0
  41. package/dist/components/Incidents/IncidentsTable.esm.js +68 -0
  42. package/dist/components/Incidents/IncidentsTable.esm.js.map +1 -0
  43. package/dist/components/Incidents/ObservabilityProjectIncidentsPage.esm.js +283 -0
  44. package/dist/components/Incidents/ObservabilityProjectIncidentsPage.esm.js.map +1 -0
  45. package/dist/components/Incidents/types.esm.js +8 -0
  46. package/dist/components/Incidents/types.esm.js.map +1 -0
  47. package/dist/components/Metrics/ChartTooltip.esm.js +53 -0
  48. package/dist/components/Metrics/ChartTooltip.esm.js.map +1 -0
  49. package/dist/components/Metrics/HTTPMetricsSection.esm.js +109 -0
  50. package/dist/components/Metrics/HTTPMetricsSection.esm.js.map +1 -0
  51. package/dist/components/Metrics/MetricGraphByComponent.esm.js +99 -0
  52. package/dist/components/Metrics/MetricGraphByComponent.esm.js.map +1 -0
  53. package/dist/components/Metrics/MetricsActions.esm.js +30 -0
  54. package/dist/components/Metrics/MetricsActions.esm.js.map +1 -0
  55. package/dist/components/Metrics/MetricsFilters.esm.js +49 -0
  56. package/dist/components/Metrics/MetricsFilters.esm.js.map +1 -0
  57. package/dist/components/Metrics/ObservabilityMetricsPage.esm.js +196 -0
  58. package/dist/components/Metrics/ObservabilityMetricsPage.esm.js.map +1 -0
  59. package/dist/components/Metrics/styles.esm.js +55 -0
  60. package/dist/components/Metrics/styles.esm.js.map +1 -0
  61. package/dist/components/Metrics/utils.esm.js +221 -0
  62. package/dist/components/Metrics/utils.esm.js.map +1 -0
  63. package/dist/components/RCA/RCAActions.esm.js +42 -0
  64. package/dist/components/RCA/RCAActions.esm.js.map +1 -0
  65. package/dist/components/RCA/RCAFilters.esm.js +100 -0
  66. package/dist/components/RCA/RCAFilters.esm.js.map +1 -0
  67. package/dist/components/RCA/RCAPage.esm.js +137 -0
  68. package/dist/components/RCA/RCAPage.esm.js.map +1 -0
  69. package/dist/components/RCA/RCAReport/EntityLinkContext.esm.js +9 -0
  70. package/dist/components/RCA/RCAReport/EntityLinkContext.esm.js.map +1 -0
  71. package/dist/components/RCA/RCAReport/FormattedText.esm.js +94 -0
  72. package/dist/components/RCA/RCAReport/FormattedText.esm.js.map +1 -0
  73. package/dist/components/RCA/RCAReport/RCAReportView.esm.js +308 -0
  74. package/dist/components/RCA/RCAReport/RCAReportView.esm.js.map +1 -0
  75. package/dist/components/RCA/RCAReport/sections/AssessmentSection.esm.js +113 -0
  76. package/dist/components/RCA/RCAReport/sections/AssessmentSection.esm.js.map +1 -0
  77. package/dist/components/RCA/RCAReport/sections/ChatPanelSection.esm.js +272 -0
  78. package/dist/components/RCA/RCAReport/sections/ChatPanelSection.esm.js.map +1 -0
  79. package/dist/components/RCA/RCAReport/sections/ExcludedCausesSection.esm.js +48 -0
  80. package/dist/components/RCA/RCAReport/sections/ExcludedCausesSection.esm.js.map +1 -0
  81. package/dist/components/RCA/RCAReport/sections/IncidentOverviewSection.esm.js +205 -0
  82. package/dist/components/RCA/RCAReport/sections/IncidentOverviewSection.esm.js.map +1 -0
  83. package/dist/components/RCA/RCAReport/sections/InvestigationPathSection.esm.js +121 -0
  84. package/dist/components/RCA/RCAReport/sections/InvestigationPathSection.esm.js.map +1 -0
  85. package/dist/components/RCA/RCAReport/sections/PatchTabContent.esm.js +593 -0
  86. package/dist/components/RCA/RCAReport/sections/PatchTabContent.esm.js.map +1 -0
  87. package/dist/components/RCA/RCAReport/sections/QuickFixesPanelSection.esm.js +35 -0
  88. package/dist/components/RCA/RCAReport/sections/QuickFixesPanelSection.esm.js.map +1 -0
  89. package/dist/components/RCA/RCAReport/sections/RecommendationsSection.esm.js +54 -0
  90. package/dist/components/RCA/RCAReport/sections/RecommendationsSection.esm.js.map +1 -0
  91. package/dist/components/RCA/RCAReport/sections/RootCausesSection.esm.js +526 -0
  92. package/dist/components/RCA/RCAReport/sections/RootCausesSection.esm.js.map +1 -0
  93. package/dist/components/RCA/RCAReport/sections/SystemTimelineSection.esm.js +70 -0
  94. package/dist/components/RCA/RCAReport/sections/SystemTimelineSection.esm.js.map +1 -0
  95. package/dist/components/RCA/RCAReport/sections/VisibilityImprovementsSection.esm.js +54 -0
  96. package/dist/components/RCA/RCAReport/sections/VisibilityImprovementsSection.esm.js.map +1 -0
  97. package/dist/components/RCA/RCAReport/styles.esm.js +467 -0
  98. package/dist/components/RCA/RCAReport/styles.esm.js.map +1 -0
  99. package/dist/components/RCA/RCAReport.esm.js +109 -0
  100. package/dist/components/RCA/RCAReport.esm.js.map +1 -0
  101. package/dist/components/RCA/RCATable.esm.js +140 -0
  102. package/dist/components/RCA/RCATable.esm.js.map +1 -0
  103. package/dist/components/RuntimeLogs/LogEntry.esm.js +182 -0
  104. package/dist/components/RuntimeLogs/LogEntry.esm.js.map +1 -0
  105. package/dist/components/RuntimeLogs/LogsActions.esm.js +106 -0
  106. package/dist/components/RuntimeLogs/LogsActions.esm.js.map +1 -0
  107. package/dist/components/RuntimeLogs/LogsFilter.esm.js +200 -0
  108. package/dist/components/RuntimeLogs/LogsFilter.esm.js.map +1 -0
  109. package/dist/components/RuntimeLogs/LogsTable.esm.js +91 -0
  110. package/dist/components/RuntimeLogs/LogsTable.esm.js.map +1 -0
  111. package/dist/components/RuntimeLogs/ObservabilityProjectRuntimeLogsPage.esm.js +220 -0
  112. package/dist/components/RuntimeLogs/ObservabilityProjectRuntimeLogsPage.esm.js.map +1 -0
  113. package/dist/components/RuntimeLogs/ObservabilityRuntimeLogsPage.esm.js +222 -0
  114. package/dist/components/RuntimeLogs/ObservabilityRuntimeLogsPage.esm.js.map +1 -0
  115. package/dist/components/RuntimeLogs/styles.esm.js +256 -0
  116. package/dist/components/RuntimeLogs/styles.esm.js.map +1 -0
  117. package/dist/components/RuntimeLogs/types.esm.js +16 -0
  118. package/dist/components/RuntimeLogs/types.esm.js.map +1 -0
  119. package/dist/components/TimeRangeFilter/TimeRangeFilter.esm.js +280 -0
  120. package/dist/components/TimeRangeFilter/TimeRangeFilter.esm.js.map +1 -0
  121. package/dist/components/Traces/ObservabilityTracesPage.esm.js +165 -0
  122. package/dist/components/Traces/ObservabilityTracesPage.esm.js.map +1 -0
  123. package/dist/components/Traces/TracesActions.esm.js +37 -0
  124. package/dist/components/Traces/TracesActions.esm.js.map +1 -0
  125. package/dist/components/Traces/TracesFilters.esm.js +121 -0
  126. package/dist/components/Traces/TracesFilters.esm.js.map +1 -0
  127. package/dist/components/Traces/TracesTable.esm.js +137 -0
  128. package/dist/components/Traces/TracesTable.esm.js.map +1 -0
  129. package/dist/components/Traces/WaterfallView.esm.js +567 -0
  130. package/dist/components/Traces/WaterfallView.esm.js.map +1 -0
  131. package/dist/components/Traces/styles.esm.js +198 -0
  132. package/dist/components/Traces/styles.esm.js.map +1 -0
  133. package/dist/components/Traces/utils.esm.js +19 -0
  134. package/dist/components/Traces/utils.esm.js.map +1 -0
  135. package/dist/hooks/useAutoSelectFirstEnvironment.esm.js +25 -0
  136. package/dist/hooks/useAutoSelectFirstEnvironment.esm.js.map +1 -0
  137. package/dist/hooks/useComponentAlerts.esm.js +80 -0
  138. package/dist/hooks/useComponentAlerts.esm.js.map +1 -0
  139. package/dist/hooks/useDataPlaneNetPolProvider.esm.js +55 -0
  140. package/dist/hooks/useDataPlaneNetPolProvider.esm.js.map +1 -0
  141. package/dist/hooks/useDebouncedSearch.esm.js +24 -0
  142. package/dist/hooks/useDebouncedSearch.esm.js.map +1 -0
  143. package/dist/hooks/useFilters.esm.js +29 -0
  144. package/dist/hooks/useFilters.esm.js.map +1 -0
  145. package/dist/hooks/useFinOpsReport.esm.js +66 -0
  146. package/dist/hooks/useFinOpsReport.esm.js.map +1 -0
  147. package/dist/hooks/useFinOpsReports.esm.js +70 -0
  148. package/dist/hooks/useFinOpsReports.esm.js.map +1 -0
  149. package/dist/hooks/useGetComponentsByProject.esm.js +57 -0
  150. package/dist/hooks/useGetComponentsByProject.esm.js.map +1 -0
  151. package/dist/hooks/useGetEnvironmentsByNamespace.esm.js +53 -0
  152. package/dist/hooks/useGetEnvironmentsByNamespace.esm.js.map +1 -0
  153. package/dist/hooks/useGetNamespaceAndProjectByEntity.esm.js +40 -0
  154. package/dist/hooks/useGetNamespaceAndProjectByEntity.esm.js.map +1 -0
  155. package/dist/hooks/useMetrics.esm.js +107 -0
  156. package/dist/hooks/useMetrics.esm.js.map +1 -0
  157. package/dist/hooks/useProjectIncidents.esm.js +111 -0
  158. package/dist/hooks/useProjectIncidents.esm.js.map +1 -0
  159. package/dist/hooks/useProjectRuntimeLogs.esm.js +185 -0
  160. package/dist/hooks/useProjectRuntimeLogs.esm.js.map +1 -0
  161. package/dist/hooks/useRCAReportByAlert.esm.js +54 -0
  162. package/dist/hooks/useRCAReportByAlert.esm.js.map +1 -0
  163. package/dist/hooks/useRCAReports.esm.js +75 -0
  164. package/dist/hooks/useRCAReports.esm.js.map +1 -0
  165. package/dist/hooks/useRuntimeLogs.esm.js +147 -0
  166. package/dist/hooks/useRuntimeLogs.esm.js.map +1 -0
  167. package/dist/hooks/useSpanDetails.esm.js +71 -0
  168. package/dist/hooks/useSpanDetails.esm.js.map +1 -0
  169. package/dist/hooks/useTraceSpans.esm.js +80 -0
  170. package/dist/hooks/useTraceSpans.esm.js.map +1 -0
  171. package/dist/hooks/useTraces.esm.js +112 -0
  172. package/dist/hooks/useTraces.esm.js.map +1 -0
  173. package/dist/hooks/useUpdateIncident.esm.js +50 -0
  174. package/dist/hooks/useUpdateIncident.esm.js.map +1 -0
  175. package/dist/hooks/useUrlFilters.esm.js +63 -0
  176. package/dist/hooks/useUrlFilters.esm.js.map +1 -0
  177. package/dist/hooks/useUrlFiltersForAlerts.esm.js +80 -0
  178. package/dist/hooks/useUrlFiltersForAlerts.esm.js.map +1 -0
  179. package/dist/hooks/useUrlFiltersForIncidents.esm.js +87 -0
  180. package/dist/hooks/useUrlFiltersForIncidents.esm.js.map +1 -0
  181. package/dist/hooks/useUrlFiltersForRuntimeLogs.esm.js +135 -0
  182. package/dist/hooks/useUrlFiltersForRuntimeLogs.esm.js.map +1 -0
  183. package/dist/index.d.ts +41 -0
  184. package/dist/index.esm.js +2 -0
  185. package/dist/index.esm.js.map +1 -0
  186. package/dist/plugin.esm.js +107 -0
  187. package/dist/plugin.esm.js.map +1 -0
  188. package/dist/routes.esm.js +8 -0
  189. package/dist/routes.esm.js.map +1 -0
  190. package/dist/types.esm.js +18 -0
  191. package/dist/types.esm.js.map +1 -0
  192. package/dist/utils/applyResourceChange.esm.js +79 -0
  193. package/dist/utils/applyResourceChange.esm.js.map +1 -0
  194. package/dist/utils/urlTimeRange.esm.js +52 -0
  195. package/dist/utils/urlTimeRange.esm.js.map +1 -0
  196. package/package.json +81 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFinOpsReports.esm.js","sources":["../../src/hooks/useFinOpsReports.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { observabilityApiRef } from '../api/ObservabilityApi';\nimport { Filters, FinOpsReportSummary } from '../types';\nimport { Entity } from '@backstage/catalog-model';\nimport { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';\nimport { calculateTimeRange } from '@openchoreo/backstage-plugin-react';\n\nexport function useFinOpsReports(filters: Filters, entity: Entity) {\n const observabilityApi = useApi(observabilityApiRef);\n const [reports, setReports] = useState<FinOpsReportSummary[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [totalCount, setTotalCount] = useState<number | undefined>(undefined);\n\n const namespace =\n entity.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE] || '';\n const projectName = entity.metadata.name as string;\n\n const fetchReports = useCallback(async () => {\n if (!filters.environment || !filters.timeRange || !namespace) {\n setReports([]);\n setTotalCount(undefined);\n setError(null);\n return;\n }\n\n try {\n setLoading(true);\n setError(null);\n\n const { startTime, endTime } = calculateTimeRange(filters.timeRange);\n\n const response = await observabilityApi.getFinOpsReports(\n namespace,\n projectName,\n filters.environment.name,\n {\n limit: 100,\n startTime,\n endTime,\n status: filters.rcaStatus,\n },\n );\n\n setReports(response.reports);\n setTotalCount(response.totalCount);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : 'Failed to fetch cost analysis reports',\n );\n } finally {\n setLoading(false);\n }\n }, [\n observabilityApi,\n filters.environment,\n filters.timeRange,\n filters.rcaStatus,\n namespace,\n projectName,\n ]);\n\n // Auto-fetch reports when filters or entity scope change\n useEffect(() => {\n fetchReports();\n }, [fetchReports]);\n\n const refresh = useCallback(() => {\n fetchReports();\n }, [fetchReports]);\n\n return {\n reports,\n loading,\n error,\n refresh,\n totalCount,\n };\n}\n"],"names":[],"mappings":";;;;;;AAQO,SAAS,gBAAA,CAAiB,SAAkB,MAAA,EAAgB;AACjE,EAAA,MAAM,gBAAA,GAAmB,OAAO,mBAAmB,CAAA;AACnD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,QAAA,CAAgC,EAAE,CAAA;AAChE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAA6B,MAAS,CAAA;AAE1E,EAAA,MAAM,YACJ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,kBAAA,CAAmB,SAAS,CAAA,IAAK,EAAA;AACjE,EAAA,MAAM,WAAA,GAAc,OAAO,QAAA,CAAS,IAAA;AAEpC,EAAA,MAAM,YAAA,GAAe,YAAY,YAAY;AAC3C,IAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,IAAe,CAAC,OAAA,CAAQ,SAAA,IAAa,CAAC,SAAA,EAAW;AAC5D,MAAA,UAAA,CAAW,EAAE,CAAA;AACb,MAAA,aAAA,CAAc,MAAS,CAAA;AACvB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAA,MAAM,EAAE,SAAA,EAAW,OAAA,EAAQ,GAAI,kBAAA,CAAmB,QAAQ,SAAS,CAAA;AAEnE,MAAA,MAAM,QAAA,GAAW,MAAM,gBAAA,CAAiB,gBAAA;AAAA,QACtC,SAAA;AAAA,QACA,WAAA;AAAA,QACA,QAAQ,WAAA,CAAY,IAAA;AAAA,QACpB;AAAA,UACE,KAAA,EAAO,GAAA;AAAA,UACP,SAAA;AAAA,UACA,OAAA;AAAA,UACA,QAAQ,OAAA,CAAQ;AAAA;AAClB,OACF;AAEA,MAAA,UAAA,CAAW,SAAS,OAAO,CAAA;AAC3B,MAAA,aAAA,CAAc,SAAS,UAAU,CAAA;AAAA,IACnC,SAAS,GAAA,EAAK;AACZ,MAAA,QAAA;AAAA,QACE,GAAA,YAAe,KAAA,GACX,GAAA,CAAI,OAAA,GACJ;AAAA,OACN;AAAA,IACF,CAAA,SAAE;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA,IAClB;AAAA,EACF,CAAA,EAAG;AAAA,IACD,gBAAA;AAAA,IACA,OAAA,CAAQ,WAAA;AAAA,IACR,OAAA,CAAQ,SAAA;AAAA,IACR,OAAA,CAAQ,SAAA;AAAA,IACR,SAAA;AAAA,IACA;AAAA,GACD,CAAA;AAGD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,YAAA,EAAa;AAAA,EACf,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,MAAM,OAAA,GAAU,YAAY,MAAM;AAChC,IAAA,YAAA,EAAa;AAAA,EACf,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
@@ -0,0 +1,57 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import { catalogApiRef } from '@backstage/plugin-catalog-react';
4
+ import { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';
5
+
6
+ const useGetComponentsByProject = (entity) => {
7
+ const catalogApi = useApi(catalogApiRef);
8
+ const namespace = entity.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE];
9
+ const project = entity.metadata.name;
10
+ const [components, setComponents] = useState([]);
11
+ const [loading, setLoading] = useState(true);
12
+ const [error, setError] = useState(null);
13
+ useEffect(() => {
14
+ const fetchComponents = async () => {
15
+ if (!namespace || !project) {
16
+ setLoading(false);
17
+ setError("Namespace or project name not found in entity annotations");
18
+ setComponents([]);
19
+ return;
20
+ }
21
+ try {
22
+ setLoading(true);
23
+ setError(null);
24
+ const catalogEntities = await catalogApi.getEntities({
25
+ filter: {
26
+ kind: "Component"
27
+ }
28
+ });
29
+ const projectComponents = catalogEntities.items.filter((component) => {
30
+ const compNamespace = component.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE];
31
+ const compProject = component.metadata.annotations?.[CHOREO_ANNOTATIONS.PROJECT];
32
+ return compNamespace === namespace && compProject === project;
33
+ }).map((component) => ({
34
+ uid: component.metadata.annotations?.[CHOREO_ANNOTATIONS.COMPONENT_UID] || component.metadata.uid,
35
+ name: component.metadata.name,
36
+ displayName: component.metadata.title || component.metadata.name,
37
+ description: component.metadata.description,
38
+ project: component.metadata.annotations?.[CHOREO_ANNOTATIONS.PROJECT] || project,
39
+ namespace: component.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE] || namespace
40
+ }));
41
+ setComponents(projectComponents);
42
+ } catch (err) {
43
+ setError(
44
+ err instanceof Error ? err.message : "Failed to fetch components"
45
+ );
46
+ setComponents([]);
47
+ } finally {
48
+ setLoading(false);
49
+ }
50
+ };
51
+ fetchComponents();
52
+ }, [namespace, project, catalogApi]);
53
+ return { components, loading, error };
54
+ };
55
+
56
+ export { useGetComponentsByProject };
57
+ //# sourceMappingURL=useGetComponentsByProject.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGetComponentsByProject.esm.js","sources":["../../src/hooks/useGetComponentsByProject.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport { Entity } from '@backstage/catalog-model';\nimport { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';\n\nexport interface Component {\n uid?: string;\n name: string;\n displayName?: string;\n description?: string;\n project?: string;\n namespace?: string;\n}\n\nexport interface UseGetComponentsByProjectResult {\n components: Component[];\n loading: boolean;\n error: string | null;\n}\n\n/**\n * Hook to fetch components for a specific project entity from the openchoreo backend.\n *\n * @param entity - The Backstage project entity to fetch components for\n * @returns Object containing components array, loading state, and error\n */\nexport const useGetComponentsByProject = (\n entity: Entity,\n): UseGetComponentsByProjectResult => {\n const catalogApi = useApi(catalogApiRef);\n const namespace = entity.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE];\n const project = entity.metadata.name;\n const [components, setComponents] = useState<Component[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const fetchComponents = async () => {\n if (!namespace || !project) {\n setLoading(false);\n setError('Namespace or project name not found in entity annotations');\n setComponents([]);\n return;\n }\n\n try {\n setLoading(true);\n setError(null);\n\n // Fetch components from Backstage catalog API\n // Filter by kind=Component and matching namespace/project annotations\n const catalogEntities = await catalogApi.getEntities({\n filter: {\n kind: 'Component',\n },\n });\n\n // Filter components that belong to this project and namespace\n const projectComponents = catalogEntities.items\n .filter(component => {\n const compNamespace =\n component.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE];\n const compProject =\n component.metadata.annotations?.[CHOREO_ANNOTATIONS.PROJECT];\n\n return compNamespace === namespace && compProject === project;\n })\n .map(component => ({\n uid:\n component.metadata.annotations?.[\n CHOREO_ANNOTATIONS.COMPONENT_UID\n ] || component.metadata.uid,\n name: component.metadata.name,\n displayName: component.metadata.title || component.metadata.name,\n description: component.metadata.description,\n project:\n component.metadata.annotations?.[CHOREO_ANNOTATIONS.PROJECT] ||\n project,\n namespace:\n component.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE] ||\n namespace,\n }));\n\n setComponents(projectComponents);\n } catch (err) {\n setError(\n err instanceof Error ? err.message : 'Failed to fetch components',\n );\n setComponents([]);\n } finally {\n setLoading(false);\n }\n };\n\n fetchComponents();\n }, [namespace, project, catalogApi]);\n\n return { components, loading, error };\n};\n"],"names":[],"mappings":";;;;;AA2BO,MAAM,yBAAA,GAA4B,CACvC,MAAA,KACoC;AACpC,EAAA,MAAM,UAAA,GAAa,OAAO,aAAa,CAAA;AACvC,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,mBAAmB,SAAS,CAAA;AAC5E,EAAA,MAAM,OAAA,GAAU,OAAO,QAAA,CAAS,IAAA;AAChC,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,QAAA,CAAsB,EAAE,CAAA;AAC5D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,kBAAkB,YAAY;AAClC,MAAA,IAAI,CAAC,SAAA,IAAa,CAAC,OAAA,EAAS;AAC1B,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA,QAAA,CAAS,2DAA2D,CAAA;AACpE,QAAA,aAAA,CAAc,EAAE,CAAA;AAChB,QAAA;AAAA,MACF;AAEA,MAAA,IAAI;AACF,QAAA,UAAA,CAAW,IAAI,CAAA;AACf,QAAA,QAAA,CAAS,IAAI,CAAA;AAIb,QAAA,MAAM,eAAA,GAAkB,MAAM,UAAA,CAAW,WAAA,CAAY;AAAA,UACnD,MAAA,EAAQ;AAAA,YACN,IAAA,EAAM;AAAA;AACR,SACD,CAAA;AAGD,QAAA,MAAM,iBAAA,GAAoB,eAAA,CAAgB,KAAA,CACvC,MAAA,CAAO,CAAA,SAAA,KAAa;AACnB,UAAA,MAAM,aAAA,GACJ,SAAA,CAAU,QAAA,CAAS,WAAA,GAAc,mBAAmB,SAAS,CAAA;AAC/D,UAAA,MAAM,WAAA,GACJ,SAAA,CAAU,QAAA,CAAS,WAAA,GAAc,mBAAmB,OAAO,CAAA;AAE7D,UAAA,OAAO,aAAA,KAAkB,aAAa,WAAA,KAAgB,OAAA;AAAA,QACxD,CAAC,CAAA,CACA,GAAA,CAAI,CAAA,SAAA,MAAc;AAAA,UACjB,GAAA,EACE,UAAU,QAAA,CAAS,WAAA,GACjB,mBAAmB,aACrB,CAAA,IAAK,UAAU,QAAA,CAAS,GAAA;AAAA,UAC1B,IAAA,EAAM,UAAU,QAAA,CAAS,IAAA;AAAA,UACzB,WAAA,EAAa,SAAA,CAAU,QAAA,CAAS,KAAA,IAAS,UAAU,QAAA,CAAS,IAAA;AAAA,UAC5D,WAAA,EAAa,UAAU,QAAA,CAAS,WAAA;AAAA,UAChC,SACE,SAAA,CAAU,QAAA,CAAS,WAAA,GAAc,kBAAA,CAAmB,OAAO,CAAA,IAC3D,OAAA;AAAA,UACF,WACE,SAAA,CAAU,QAAA,CAAS,WAAA,GAAc,kBAAA,CAAmB,SAAS,CAAA,IAC7D;AAAA,SACJ,CAAE,CAAA;AAEJ,QAAA,aAAA,CAAc,iBAAiB,CAAA;AAAA,MACjC,SAAS,GAAA,EAAK;AACZ,QAAA,QAAA;AAAA,UACE,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,SACvC;AACA,QAAA,aAAA,CAAc,EAAE,CAAA;AAAA,MAClB,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,eAAA,EAAgB;AAAA,EAClB,CAAA,EAAG,CAAC,SAAA,EAAW,OAAA,EAAS,UAAU,CAAC,CAAA;AAEnC,EAAA,OAAO,EAAE,UAAA,EAAY,OAAA,EAAS,KAAA,EAAM;AACtC;;;;"}
@@ -0,0 +1,53 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useApi, discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
3
+
4
+ const useGetEnvironmentsByNamespace = (namespaceName, projectName) => {
5
+ const discoveryApi = useApi(discoveryApiRef);
6
+ const fetchApi = useApi(fetchApiRef);
7
+ const [environments, setEnvironments] = useState([]);
8
+ const [loading, setLoading] = useState(true);
9
+ const [error, setError] = useState(null);
10
+ useEffect(() => {
11
+ const fetchEnvironments = async () => {
12
+ if (!namespaceName) {
13
+ setLoading(false);
14
+ setError("Namespace name is required");
15
+ setEnvironments([]);
16
+ return;
17
+ }
18
+ try {
19
+ setLoading(true);
20
+ setError(null);
21
+ const baseUrl = await discoveryApi.getBaseUrl(
22
+ "openchoreo-observability-backend"
23
+ );
24
+ const params = new URLSearchParams({ namespace: namespaceName });
25
+ if (projectName) {
26
+ params.set("project", projectName);
27
+ }
28
+ const response = await fetchApi.fetch(
29
+ `${baseUrl}/environments?${params.toString()}`
30
+ );
31
+ if (!response.ok) {
32
+ throw new Error(
33
+ `Failed to fetch environments: ${response.status} ${response.statusText}`
34
+ );
35
+ }
36
+ const data = await response.json();
37
+ setEnvironments(data.environments || []);
38
+ } catch (err) {
39
+ setError(
40
+ err instanceof Error ? err.message : "Failed to fetch environments"
41
+ );
42
+ setEnvironments([]);
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ };
47
+ fetchEnvironments();
48
+ }, [namespaceName, projectName, discoveryApi, fetchApi]);
49
+ return { environments, loading, error };
50
+ };
51
+
52
+ export { useGetEnvironmentsByNamespace };
53
+ //# sourceMappingURL=useGetEnvironmentsByNamespace.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGetEnvironmentsByNamespace.esm.js","sources":["../../src/hooks/useGetEnvironmentsByNamespace.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';\nimport { Environment } from '../types';\n\nexport interface UseGetEnvironmentsByNamespaceResult {\n environments: Environment[];\n loading: boolean;\n error: string | null;\n}\n\n/**\n * Hook to fetch environments for a specific namespace from the observability backend.\n *\n * When `projectName` is provided, the backend restricts the result to\n * environments referenced by the project's deployment pipeline.\n *\n * @param namespaceName - The name of the namespace to fetch environments for\n * @param projectName - Optional project name; when provided, environments are filtered to those in the project's deployment pipeline\n * @returns Object containing environments array, loading state, and error\n */\nexport const useGetEnvironmentsByNamespace = (\n namespaceName: string | undefined,\n projectName?: string,\n): UseGetEnvironmentsByNamespaceResult => {\n const discoveryApi = useApi(discoveryApiRef);\n const fetchApi = useApi(fetchApiRef);\n const [environments, setEnvironments] = useState<Environment[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const fetchEnvironments = async () => {\n if (!namespaceName) {\n setLoading(false);\n setError('Namespace name is required');\n setEnvironments([]);\n return;\n }\n\n try {\n setLoading(true);\n setError(null);\n\n const baseUrl = await discoveryApi.getBaseUrl(\n 'openchoreo-observability-backend',\n );\n const params = new URLSearchParams({ namespace: namespaceName });\n if (projectName) {\n params.set('project', projectName);\n }\n const response = await fetchApi.fetch(\n `${baseUrl}/environments?${params.toString()}`,\n );\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch environments: ${response.status} ${response.statusText}`,\n );\n }\n\n const data = await response.json();\n setEnvironments(data.environments || []);\n } catch (err) {\n setError(\n err instanceof Error ? err.message : 'Failed to fetch environments',\n );\n setEnvironments([]);\n } finally {\n setLoading(false);\n }\n };\n\n fetchEnvironments();\n }, [namespaceName, projectName, discoveryApi, fetchApi]);\n\n return { environments, loading, error };\n};\n"],"names":[],"mappings":";;;AAqBO,MAAM,6BAAA,GAAgC,CAC3C,aAAA,EACA,WAAA,KACwC;AACxC,EAAA,MAAM,YAAA,GAAe,OAAO,eAAe,CAAA;AAC3C,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AACnC,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,QAAA,CAAwB,EAAE,CAAA;AAClE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,oBAAoB,YAAY;AACpC,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA,QAAA,CAAS,4BAA4B,CAAA;AACrC,QAAA,eAAA,CAAgB,EAAE,CAAA;AAClB,QAAA;AAAA,MACF;AAEA,MAAA,IAAI;AACF,QAAA,UAAA,CAAW,IAAI,CAAA;AACf,QAAA,QAAA,CAAS,IAAI,CAAA;AAEb,QAAA,MAAM,OAAA,GAAU,MAAM,YAAA,CAAa,UAAA;AAAA,UACjC;AAAA,SACF;AACA,QAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,EAAE,SAAA,EAAW,eAAe,CAAA;AAC/D,QAAA,IAAI,WAAA,EAAa;AACf,UAAA,MAAA,CAAO,GAAA,CAAI,WAAW,WAAW,CAAA;AAAA,QACnC;AACA,QAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,KAAA;AAAA,UAC9B,CAAA,EAAG,OAAO,CAAA,cAAA,EAAiB,MAAA,CAAO,UAAU,CAAA;AAAA,SAC9C;AAEA,QAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,8BAAA,EAAiC,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA;AAAA,WACzE;AAAA,QACF;AAEA,QAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,QAAA,eAAA,CAAgB,IAAA,CAAK,YAAA,IAAgB,EAAE,CAAA;AAAA,MACzC,SAAS,GAAA,EAAK;AACZ,QAAA,QAAA;AAAA,UACE,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,SACvC;AACA,QAAA,eAAA,CAAgB,EAAE,CAAA;AAAA,MACpB,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,iBAAA,EAAkB;AAAA,EACpB,GAAG,CAAC,aAAA,EAAe,WAAA,EAAa,YAAA,EAAc,QAAQ,CAAC,CAAA;AAEvD,EAAA,OAAO,EAAE,YAAA,EAAc,OAAA,EAAS,KAAA,EAAM;AACxC;;;;"}
@@ -0,0 +1,40 @@
1
+ import { useMemo } from 'react';
2
+ import { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';
3
+
4
+ const useGetNamespaceAndProjectByEntity = (entity) => {
5
+ const result = useMemo(() => {
6
+ try {
7
+ const namespace = entity?.metadata?.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE];
8
+ const project = entity?.metadata?.annotations?.[CHOREO_ANNOTATIONS.PROJECT];
9
+ if (!namespace) {
10
+ return {
11
+ namespace: void 0,
12
+ project: void 0,
13
+ error: `Namespace annotation '${CHOREO_ANNOTATIONS.NAMESPACE}' not found in entity`
14
+ };
15
+ }
16
+ if (!project) {
17
+ return {
18
+ namespace,
19
+ project: void 0,
20
+ error: `Project annotation '${CHOREO_ANNOTATIONS.PROJECT}' not found in entity`
21
+ };
22
+ }
23
+ return {
24
+ namespace,
25
+ project,
26
+ error: null
27
+ };
28
+ } catch (err) {
29
+ return {
30
+ namespace: void 0,
31
+ project: void 0,
32
+ error: err instanceof Error ? err.message : "Unknown error"
33
+ };
34
+ }
35
+ }, [entity]);
36
+ return result;
37
+ };
38
+
39
+ export { useGetNamespaceAndProjectByEntity };
40
+ //# sourceMappingURL=useGetNamespaceAndProjectByEntity.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGetNamespaceAndProjectByEntity.esm.js","sources":["../../src/hooks/useGetNamespaceAndProjectByEntity.ts"],"sourcesContent":["import { Entity } from '@backstage/catalog-model';\nimport { useMemo } from 'react';\nimport { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';\n\nexport interface UseGetNamespaceAndProjectByEntityResult {\n namespace: string | undefined;\n project: string | undefined;\n error: string | null;\n}\n\n/**\n * Hook to extract the namespace and project name from an entity's annotations.\n *\n * @param entity - The Backstage entity to extract the namespace and project from\n * @returns Object containing namespace name, project name, and error\n */\nexport const useGetNamespaceAndProjectByEntity = (\n entity: Entity,\n): UseGetNamespaceAndProjectByEntityResult => {\n const result = useMemo(() => {\n try {\n const namespace =\n entity?.metadata?.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE];\n const project =\n entity?.metadata?.annotations?.[CHOREO_ANNOTATIONS.PROJECT];\n\n if (!namespace) {\n return {\n namespace: undefined,\n project: undefined,\n error: `Namespace annotation '${CHOREO_ANNOTATIONS.NAMESPACE}' not found in entity`,\n };\n }\n\n if (!project) {\n return {\n namespace,\n project: undefined,\n error: `Project annotation '${CHOREO_ANNOTATIONS.PROJECT}' not found in entity`,\n };\n }\n\n return {\n namespace,\n project,\n error: null,\n };\n } catch (err) {\n return {\n namespace: undefined,\n project: undefined,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n }, [entity]);\n\n return result;\n};\n"],"names":[],"mappings":";;;AAgBO,MAAM,iCAAA,GAAoC,CAC/C,MAAA,KAC4C;AAC5C,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAM;AAC3B,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,GACJ,MAAA,EAAQ,QAAA,EAAU,WAAA,GAAc,mBAAmB,SAAS,CAAA;AAC9D,MAAA,MAAM,OAAA,GACJ,MAAA,EAAQ,QAAA,EAAU,WAAA,GAAc,mBAAmB,OAAO,CAAA;AAE5D,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,OAAO;AAAA,UACL,SAAA,EAAW,KAAA,CAAA;AAAA,UACX,OAAA,EAAS,KAAA,CAAA;AAAA,UACT,KAAA,EAAO,CAAA,sBAAA,EAAyB,kBAAA,CAAmB,SAAS,CAAA,qBAAA;AAAA,SAC9D;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA,OAAO;AAAA,UACL,SAAA;AAAA,UACA,OAAA,EAAS,KAAA,CAAA;AAAA,UACT,KAAA,EAAO,CAAA,oBAAA,EAAuB,kBAAA,CAAmB,OAAO,CAAA,qBAAA;AAAA,SAC1D;AAAA,MACF;AAEA,MAAA,OAAO;AAAA,QACL,SAAA;AAAA,QACA,OAAA;AAAA,QACA,KAAA,EAAO;AAAA,OACT;AAAA,IACF,SAAS,GAAA,EAAK;AACZ,MAAA,OAAO;AAAA,QACL,SAAA,EAAW,MAAA;AAAA,QACX,OAAA,EAAS,MAAA;AAAA,QACT,KAAA,EAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,OAC9C;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,OAAO,MAAA;AACT;;;;"}
@@ -0,0 +1,107 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import { observabilityApiRef } from '../api/ObservabilityApi.esm.js';
4
+ import { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';
5
+ import { calculateTimeRange } from '@openchoreo/backstage-plugin-react';
6
+
7
+ function useMetrics(filters, entity, namespaceName, project, metricType = "resource") {
8
+ const observabilityApi = useApi(observabilityApiRef);
9
+ const [metrics, setMetrics] = useState(
10
+ null
11
+ );
12
+ const [loading, setLoading] = useState(false);
13
+ const [error, setError] = useState(null);
14
+ const fetchMetrics = useCallback(
15
+ async (_reset = false) => {
16
+ if (!filters.environment || !filters.timeRange) {
17
+ return;
18
+ }
19
+ try {
20
+ setLoading(true);
21
+ setError(null);
22
+ const componentName = entity.metadata.annotations?.[CHOREO_ANNOTATIONS.COMPONENT];
23
+ if (!componentName) {
24
+ throw new Error("Component name not found in entity annotations");
25
+ }
26
+ const { startTime, endTime } = calculateTimeRange(filters.timeRange, {
27
+ startTime: filters.customStartTime,
28
+ endTime: filters.customEndTime
29
+ });
30
+ const step = calculateStep(filters.timeRange, startTime, endTime);
31
+ const response = await observabilityApi.getMetrics(
32
+ filters.environment.name,
33
+ componentName,
34
+ namespaceName,
35
+ project,
36
+ { startTime, endTime, step, type: metricType }
37
+ );
38
+ setMetrics(response);
39
+ } catch (err) {
40
+ setError(
41
+ err instanceof Error ? err.message : "Failed to fetch metrics"
42
+ );
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ },
47
+ [
48
+ observabilityApi,
49
+ filters.environment,
50
+ filters.timeRange,
51
+ filters.customStartTime,
52
+ filters.customEndTime,
53
+ namespaceName,
54
+ project,
55
+ entity,
56
+ metricType
57
+ ]
58
+ );
59
+ const refresh = useCallback(() => {
60
+ setMetrics(null);
61
+ fetchMetrics(true);
62
+ }, [fetchMetrics]);
63
+ return {
64
+ metrics,
65
+ loading,
66
+ error,
67
+ fetchMetrics,
68
+ refresh
69
+ };
70
+ }
71
+ function calculateStep(timeRange, startTime, endTime) {
72
+ switch (timeRange) {
73
+ case "10m":
74
+ return "15s";
75
+ case "30m":
76
+ return "30s";
77
+ case "1h":
78
+ return "1m";
79
+ case "24h":
80
+ return "5m";
81
+ case "7d":
82
+ return "30m";
83
+ case "14d":
84
+ return "1h";
85
+ case "30d":
86
+ return "2h";
87
+ case "custom":
88
+ return stepForCustomRange(startTime, endTime);
89
+ default:
90
+ return "1m";
91
+ }
92
+ }
93
+ function stepForCustomRange(startTime, endTime) {
94
+ if (!startTime || !endTime) return "1m";
95
+ const durationMs = new Date(endTime).getTime() - new Date(startTime).getTime();
96
+ if (!Number.isFinite(durationMs) || durationMs <= 0) return "1m";
97
+ const minutes = durationMs / (60 * 1e3);
98
+ if (minutes <= 30) return "15s";
99
+ if (minutes <= 120) return "30s";
100
+ if (minutes <= 24 * 60) return "5m";
101
+ if (minutes <= 7 * 24 * 60) return "30m";
102
+ if (minutes <= 14 * 24 * 60) return "1h";
103
+ return "2h";
104
+ }
105
+
106
+ export { useMetrics };
107
+ //# sourceMappingURL=useMetrics.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMetrics.esm.js","sources":["../../src/hooks/useMetrics.ts"],"sourcesContent":["import { useCallback, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { observabilityApiRef } from '../api/ObservabilityApi';\nimport { Filters, HttpMetrics, MetricType, ResourceMetrics } from '../types';\nimport { Entity } from '@backstage/catalog-model';\nimport { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';\nimport { calculateTimeRange } from '@openchoreo/backstage-plugin-react';\n\nexport function useMetrics(\n filters: Filters,\n entity: Entity,\n namespaceName: string,\n project: string,\n metricType: MetricType = 'resource',\n) {\n const observabilityApi = useApi(observabilityApiRef);\n const [metrics, setMetrics] = useState<ResourceMetrics | HttpMetrics | null>(\n null,\n );\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const fetchMetrics = useCallback(\n async (_reset: boolean = false) => {\n if (!filters.environment || !filters.timeRange) {\n return;\n }\n\n try {\n setLoading(true);\n setError(null);\n\n const componentName =\n entity.metadata.annotations?.[CHOREO_ANNOTATIONS.COMPONENT];\n if (!componentName) {\n throw new Error('Component name not found in entity annotations');\n }\n\n const { startTime, endTime } = calculateTimeRange(filters.timeRange, {\n startTime: filters.customStartTime,\n endTime: filters.customEndTime,\n });\n const step = calculateStep(filters.timeRange, startTime, endTime);\n\n const response = await observabilityApi.getMetrics(\n filters.environment.name,\n componentName,\n namespaceName,\n project,\n { startTime, endTime, step, type: metricType },\n );\n\n setMetrics(response);\n } catch (err) {\n setError(\n err instanceof Error ? err.message : 'Failed to fetch metrics',\n );\n } finally {\n setLoading(false);\n }\n },\n [\n observabilityApi,\n filters.environment,\n filters.timeRange,\n filters.customStartTime,\n filters.customEndTime,\n namespaceName,\n project,\n entity,\n metricType,\n ],\n );\n\n const refresh = useCallback(() => {\n setMetrics(null);\n fetchMetrics(true);\n }, [fetchMetrics]);\n\n return {\n metrics,\n loading,\n error,\n fetchMetrics,\n refresh,\n };\n}\n\nfunction calculateStep(\n timeRange: string,\n startTime?: string,\n endTime?: string,\n): string {\n switch (timeRange) {\n case '10m':\n return '15s';\n case '30m':\n return '30s';\n case '1h':\n return '1m';\n case '24h':\n return '5m';\n case '7d':\n return '30m';\n case '14d':\n return '1h';\n case '30d':\n return '2h';\n case 'custom':\n return stepForCustomRange(startTime, endTime);\n default:\n return '1m';\n }\n}\n\n/** Pick a step that yields ~120-720 data points for the chosen window. */\nfunction stepForCustomRange(startTime?: string, endTime?: string): string {\n if (!startTime || !endTime) return '1m';\n const durationMs =\n new Date(endTime).getTime() - new Date(startTime).getTime();\n if (!Number.isFinite(durationMs) || durationMs <= 0) return '1m';\n const minutes = durationMs / (60 * 1000);\n if (minutes <= 30) return '15s';\n if (minutes <= 120) return '30s';\n if (minutes <= 24 * 60) return '5m';\n if (minutes <= 7 * 24 * 60) return '30m';\n if (minutes <= 14 * 24 * 60) return '1h';\n return '2h';\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { useState, useRef, useMemo, useCallback } from 'react';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import { observabilityApiRef } from '../api/ObservabilityApi.esm.js';
4
+ import { calculateTimeRange } from '@openchoreo/backstage-plugin-react';
5
+ import { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';
6
+
7
+ function useProjectIncidents(entity, filters) {
8
+ const observabilityApi = useApi(observabilityApiRef);
9
+ const [incidents, setIncidents] = useState([]);
10
+ const [loading, setLoading] = useState(false);
11
+ const [error, setError] = useState(null);
12
+ const [totalCount, setTotalCount] = useState(0);
13
+ const requestVersionRef = useRef(0);
14
+ const namespace = entity.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE] || "";
15
+ const projectName = entity.metadata.name || "";
16
+ const selectedComponents = useMemo(
17
+ () => Array.from(new Set(filters.components || [])),
18
+ [filters.components]
19
+ );
20
+ const fetchIncidents = useCallback(
21
+ async (_reset = true) => {
22
+ if (!filters.environment || !namespace || !projectName) {
23
+ return;
24
+ }
25
+ const version = ++requestVersionRef.current;
26
+ try {
27
+ setLoading(true);
28
+ setError(null);
29
+ const { startTime, endTime } = calculateTimeRange(filters.timeRange, {
30
+ startTime: filters.customStartTime,
31
+ endTime: filters.customEndTime
32
+ });
33
+ const sortOrder = filters.sortOrder ?? "desc";
34
+ const queryOptions = {
35
+ limit: 100,
36
+ startTime,
37
+ endTime,
38
+ sortOrder
39
+ };
40
+ if (selectedComponents.length > 0) {
41
+ const responses = await Promise.all(
42
+ selectedComponents.map(
43
+ (componentName) => observabilityApi.getIncidents(
44
+ namespace,
45
+ projectName,
46
+ filters.environment,
47
+ componentName,
48
+ queryOptions
49
+ )
50
+ )
51
+ );
52
+ if (version !== requestVersionRef.current) return;
53
+ const merged = responses.flatMap((r) => r.incidents);
54
+ merged.sort((a, b) => {
55
+ const at = (a.triggeredAt || a.timestamp || "").toString();
56
+ const bt = (b.triggeredAt || b.timestamp || "").toString();
57
+ return sortOrder === "desc" ? bt.localeCompare(at) : at.localeCompare(bt);
58
+ });
59
+ setIncidents(merged);
60
+ setTotalCount(responses.reduce((acc, r) => acc + (r.total || 0), 0));
61
+ } else {
62
+ const response = await observabilityApi.getIncidents(
63
+ namespace,
64
+ projectName,
65
+ filters.environment,
66
+ void 0,
67
+ queryOptions
68
+ );
69
+ if (version !== requestVersionRef.current) return;
70
+ setIncidents(response.incidents);
71
+ setTotalCount(response.total);
72
+ }
73
+ } catch (err) {
74
+ if (version !== requestVersionRef.current) return;
75
+ setError(
76
+ err instanceof Error ? err.message : "Failed to fetch incidents"
77
+ );
78
+ } finally {
79
+ if (version === requestVersionRef.current) {
80
+ setLoading(false);
81
+ }
82
+ }
83
+ },
84
+ [
85
+ observabilityApi,
86
+ filters.environment,
87
+ filters.timeRange,
88
+ filters.customStartTime,
89
+ filters.customEndTime,
90
+ filters.sortOrder,
91
+ namespace,
92
+ projectName,
93
+ selectedComponents
94
+ ]
95
+ );
96
+ const refresh = useCallback(() => {
97
+ setIncidents([]);
98
+ fetchIncidents(true);
99
+ }, [fetchIncidents]);
100
+ return {
101
+ incidents,
102
+ loading,
103
+ error,
104
+ totalCount,
105
+ fetchIncidents,
106
+ refresh
107
+ };
108
+ }
109
+
110
+ export { useProjectIncidents };
111
+ //# sourceMappingURL=useProjectIncidents.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProjectIncidents.esm.js","sources":["../../src/hooks/useProjectIncidents.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Entity } from '@backstage/catalog-model';\nimport { observabilityApiRef } from '../api/ObservabilityApi';\nimport { calculateTimeRange } from '@openchoreo/backstage-plugin-react';\nimport { IncidentSummary } from '../types';\nimport { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';\n\nexport interface UseProjectIncidentsFilters {\n environment: string;\n timeRange: string;\n /** ISO start time, used when `timeRange === 'custom'` */\n customStartTime?: string;\n /** ISO end time, used when `timeRange === 'custom'` */\n customEndTime?: string;\n components?: string[];\n sortOrder?: 'asc' | 'desc';\n}\n\nexport interface UseProjectIncidentsResult {\n incidents: IncidentSummary[];\n loading: boolean;\n error: string | null;\n totalCount: number;\n fetchIncidents: (reset?: boolean) => Promise<void>;\n refresh: () => void;\n}\n\nexport function useProjectIncidents(\n entity: Entity,\n filters: UseProjectIncidentsFilters,\n): UseProjectIncidentsResult {\n const observabilityApi = useApi(observabilityApiRef);\n const [incidents, setIncidents] = useState<IncidentSummary[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [totalCount, setTotalCount] = useState(0);\n const requestVersionRef = useRef(0);\n\n const namespace =\n entity.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE] || '';\n const projectName = entity.metadata.name || '';\n\n const selectedComponents = useMemo(\n () => Array.from(new Set(filters.components || [])),\n [filters.components],\n );\n\n const fetchIncidents = useCallback(\n async (_reset = true) => {\n if (!filters.environment || !namespace || !projectName) {\n return;\n }\n\n const version = ++requestVersionRef.current;\n\n try {\n setLoading(true);\n setError(null);\n\n const { startTime, endTime } = calculateTimeRange(filters.timeRange, {\n startTime: filters.customStartTime,\n endTime: filters.customEndTime,\n });\n const sortOrder = filters.sortOrder ?? 'desc';\n\n const queryOptions = {\n limit: 100,\n startTime,\n endTime,\n sortOrder,\n };\n\n if (selectedComponents.length > 0) {\n const responses = await Promise.all(\n selectedComponents.map(componentName =>\n observabilityApi.getIncidents(\n namespace,\n projectName,\n filters.environment,\n componentName,\n queryOptions,\n ),\n ),\n );\n\n if (version !== requestVersionRef.current) return;\n\n const merged = responses.flatMap(r => r.incidents);\n merged.sort((a, b) => {\n const at = (a.triggeredAt || a.timestamp || '').toString();\n const bt = (b.triggeredAt || b.timestamp || '').toString();\n return sortOrder === 'desc'\n ? bt.localeCompare(at)\n : at.localeCompare(bt);\n });\n setIncidents(merged);\n setTotalCount(responses.reduce((acc, r) => acc + (r.total || 0), 0));\n } else {\n const response = await observabilityApi.getIncidents(\n namespace,\n projectName,\n filters.environment,\n undefined,\n queryOptions,\n );\n\n if (version !== requestVersionRef.current) return;\n\n setIncidents(response.incidents);\n setTotalCount(response.total);\n }\n } catch (err) {\n if (version !== requestVersionRef.current) return;\n setError(\n err instanceof Error ? err.message : 'Failed to fetch incidents',\n );\n } finally {\n if (version === requestVersionRef.current) {\n setLoading(false);\n }\n }\n },\n [\n observabilityApi,\n filters.environment,\n filters.timeRange,\n filters.customStartTime,\n filters.customEndTime,\n filters.sortOrder,\n namespace,\n projectName,\n selectedComponents,\n ],\n );\n\n const refresh = useCallback(() => {\n setIncidents([]);\n fetchIncidents(true);\n }, [fetchIncidents]);\n\n return {\n incidents,\n loading,\n error,\n totalCount,\n fetchIncidents,\n refresh,\n };\n}\n"],"names":[],"mappings":";;;;;;AA4BO,SAAS,mBAAA,CACd,QACA,OAAA,EAC2B;AAC3B,EAAA,MAAM,gBAAA,GAAmB,OAAO,mBAAmB,CAAA;AACnD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,QAAA,CAA4B,EAAE,CAAA;AAChE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,CAAC,CAAA;AAC9C,EAAA,MAAM,iBAAA,GAAoB,OAAO,CAAC,CAAA;AAElC,EAAA,MAAM,YACJ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,kBAAA,CAAmB,SAAS,CAAA,IAAK,EAAA;AACjE,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,QAAA,CAAS,IAAA,IAAQ,EAAA;AAE5C,EAAA,MAAM,kBAAA,GAAqB,OAAA;AAAA,IACzB,MAAM,MAAM,IAAA,CAAK,IAAI,IAAI,OAAA,CAAQ,UAAA,IAAc,EAAE,CAAC,CAAA;AAAA,IAClD,CAAC,QAAQ,UAAU;AAAA,GACrB;AAEA,EAAA,MAAM,cAAA,GAAiB,WAAA;AAAA,IACrB,OAAO,SAAS,IAAA,KAAS;AACvB,MAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,IAAe,CAAC,SAAA,IAAa,CAAC,WAAA,EAAa;AACtD,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,EAAE,iBAAA,CAAkB,OAAA;AAEpC,MAAA,IAAI;AACF,QAAA,UAAA,CAAW,IAAI,CAAA;AACf,QAAA,QAAA,CAAS,IAAI,CAAA;AAEb,QAAA,MAAM,EAAE,SAAA,EAAW,OAAA,EAAQ,GAAI,kBAAA,CAAmB,QAAQ,SAAA,EAAW;AAAA,UACnE,WAAW,OAAA,CAAQ,eAAA;AAAA,UACnB,SAAS,OAAA,CAAQ;AAAA,SAClB,CAAA;AACD,QAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,MAAA;AAEvC,QAAA,MAAM,YAAA,GAAe;AAAA,UACnB,KAAA,EAAO,GAAA;AAAA,UACP,SAAA;AAAA,UACA,OAAA;AAAA,UACA;AAAA,SACF;AAEA,QAAA,IAAI,kBAAA,CAAmB,SAAS,CAAA,EAAG;AACjC,UAAA,MAAM,SAAA,GAAY,MAAM,OAAA,CAAQ,GAAA;AAAA,YAC9B,kBAAA,CAAmB,GAAA;AAAA,cAAI,mBACrB,gBAAA,CAAiB,YAAA;AAAA,gBACf,SAAA;AAAA,gBACA,WAAA;AAAA,gBACA,OAAA,CAAQ,WAAA;AAAA,gBACR,aAAA;AAAA,gBACA;AAAA;AACF;AACF,WACF;AAEA,UAAA,IAAI,OAAA,KAAY,kBAAkB,OAAA,EAAS;AAE3C,UAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,CAAA,CAAA,KAAK,EAAE,SAAS,CAAA;AACjD,UAAA,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM;AACpB,YAAA,MAAM,MAAM,CAAA,CAAE,WAAA,IAAe,CAAA,CAAE,SAAA,IAAa,IAAI,QAAA,EAAS;AACzD,YAAA,MAAM,MAAM,CAAA,CAAE,WAAA,IAAe,CAAA,CAAE,SAAA,IAAa,IAAI,QAAA,EAAS;AACzD,YAAA,OAAO,SAAA,KAAc,SACjB,EAAA,CAAG,aAAA,CAAc,EAAE,CAAA,GACnB,EAAA,CAAG,cAAc,EAAE,CAAA;AAAA,UACzB,CAAC,CAAA;AACD,UAAA,YAAA,CAAa,MAAM,CAAA;AACnB,UAAA,aAAA,CAAc,SAAA,CAAU,MAAA,CAAO,CAAC,GAAA,EAAK,CAAA,KAAM,OAAO,CAAA,CAAE,KAAA,IAAS,CAAA,CAAA,EAAI,CAAC,CAAC,CAAA;AAAA,QACrE,CAAA,MAAO;AACL,UAAA,MAAM,QAAA,GAAW,MAAM,gBAAA,CAAiB,YAAA;AAAA,YACtC,SAAA;AAAA,YACA,WAAA;AAAA,YACA,OAAA,CAAQ,WAAA;AAAA,YACR,KAAA,CAAA;AAAA,YACA;AAAA,WACF;AAEA,UAAA,IAAI,OAAA,KAAY,kBAAkB,OAAA,EAAS;AAE3C,UAAA,YAAA,CAAa,SAAS,SAAS,CAAA;AAC/B,UAAA,aAAA,CAAc,SAAS,KAAK,CAAA;AAAA,QAC9B;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,OAAA,KAAY,kBAAkB,OAAA,EAAS;AAC3C,QAAA,QAAA;AAAA,UACE,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,SACvC;AAAA,MACF,CAAA,SAAE;AACA,QAAA,IAAI,OAAA,KAAY,kBAAkB,OAAA,EAAS;AACzC,UAAA,UAAA,CAAW,KAAK,CAAA;AAAA,QAClB;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACA;AAAA,MACE,gBAAA;AAAA,MACA,OAAA,CAAQ,WAAA;AAAA,MACR,OAAA,CAAQ,SAAA;AAAA,MACR,OAAA,CAAQ,eAAA;AAAA,MACR,OAAA,CAAQ,aAAA;AAAA,MACR,OAAA,CAAQ,SAAA;AAAA,MACR,SAAA;AAAA,MACA,WAAA;AAAA,MACA;AAAA;AACF,GACF;AAEA,EAAA,MAAM,OAAA,GAAU,YAAY,MAAM;AAChC,IAAA,YAAA,CAAa,EAAE,CAAA;AACf,IAAA,cAAA,CAAe,IAAI,CAAA;AAAA,EACrB,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAEnB,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,UAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
@@ -0,0 +1,185 @@
1
+ import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import { observabilityApiRef } from '../api/ObservabilityApi.esm.js';
4
+ import { calculateTimeRange } from '@openchoreo/backstage-plugin-react';
5
+ import { LOG_LEVELS } from '../components/RuntimeLogs/types.esm.js';
6
+
7
+ const sortByTimestamp = (logs, sortOrder = "asc") => {
8
+ const sorted = [...logs].sort((a, b) => {
9
+ const aTime = a.timestamp ? new Date(a.timestamp).getTime() : 0;
10
+ const bTime = b.timestamp ? new Date(b.timestamp).getTime() : 0;
11
+ return sortOrder === "asc" ? aTime - bTime : bTime - aTime;
12
+ });
13
+ return sorted;
14
+ };
15
+ function useProjectRuntimeLogs(filters, _entity, options) {
16
+ const observabilityApi = useApi(observabilityApiRef);
17
+ const [logs, setLogs] = useState([]);
18
+ const [loading, setLoading] = useState(false);
19
+ const [error, setError] = useState(null);
20
+ const [totalCount, setTotalCount] = useState(0);
21
+ const [hasMore, setHasMore] = useState(true);
22
+ const pollingIntervalRef = useRef(null);
23
+ const inFlightRef = useRef(false);
24
+ const logsRef = useRef([]);
25
+ useEffect(() => {
26
+ logsRef.current = logs;
27
+ }, [logs]);
28
+ const selectedComponents = useMemo(
29
+ () => Array.from(new Set(filters.components || [])),
30
+ [filters.components]
31
+ );
32
+ const fetchLogs = useCallback(
33
+ async (reset = false) => {
34
+ if (!filters.environment || !options.environmentName || !options.namespaceName || !options.projectName) {
35
+ return;
36
+ }
37
+ if (inFlightRef.current) {
38
+ return;
39
+ }
40
+ try {
41
+ inFlightRef.current = true;
42
+ setLoading(true);
43
+ setError(null);
44
+ const { startTime: initialStartTime, endTime: initialEndTime } = calculateTimeRange(filters.timeRange, {
45
+ startTime: filters.customStartTime,
46
+ endTime: filters.customEndTime
47
+ });
48
+ let startTime = initialStartTime;
49
+ let endTime = initialEndTime;
50
+ const sortOrder = filters.sortOrder || "asc";
51
+ if (!reset && logsRef.current.length > 0) {
52
+ const lastLog = logsRef.current[logsRef.current.length - 1];
53
+ if (sortOrder === "desc") {
54
+ endTime = lastLog.timestamp || endTime;
55
+ } else {
56
+ startTime = lastLog.timestamp || startTime;
57
+ }
58
+ }
59
+ const limit = options.limit || 50;
60
+ const queryOptions = {
61
+ limit,
62
+ startTime,
63
+ endTime,
64
+ logLevels: filters.logLevel.length === LOG_LEVELS.length && LOG_LEVELS.every((l) => filters.logLevel.includes(l)) ? [] : filters.logLevel,
65
+ searchQuery: filters.searchQuery,
66
+ sortOrder
67
+ };
68
+ const responses = selectedComponents.length > 0 ? await Promise.all(
69
+ selectedComponents.map(
70
+ (componentName) => observabilityApi.getRuntimeLogs(
71
+ options.namespaceName,
72
+ options.projectName,
73
+ options.environmentName,
74
+ componentName,
75
+ queryOptions
76
+ )
77
+ )
78
+ ) : [
79
+ await observabilityApi.getRuntimeLogs(
80
+ options.namespaceName,
81
+ options.projectName,
82
+ options.environmentName,
83
+ void 0,
84
+ queryOptions
85
+ )
86
+ ];
87
+ const flattenedLogs = selectedComponents.length > 0 ? responses.flatMap(
88
+ (response, index) => (response.logs || []).map((log) => ({
89
+ ...log,
90
+ metadata: {
91
+ ...log.metadata,
92
+ componentName: log.metadata?.componentName || selectedComponents[index]
93
+ }
94
+ }))
95
+ ) : responses.flatMap((response) => response.logs || []);
96
+ const mergedLogs = sortByTimestamp(flattenedLogs, sortOrder);
97
+ const nextLogs = reset ? mergedLogs : sortByTimestamp([...logsRef.current, ...mergedLogs], sortOrder);
98
+ setLogs(nextLogs);
99
+ if (reset) {
100
+ const total = responses.reduce((sum, response) => {
101
+ return sum + (response.total || 0);
102
+ }, 0);
103
+ setTotalCount(total);
104
+ }
105
+ setHasMore(
106
+ responses.some((response) => (response.logs || []).length === limit)
107
+ );
108
+ } catch (err) {
109
+ setError(err instanceof Error ? err.message : "Failed to fetch logs");
110
+ } finally {
111
+ setLoading(false);
112
+ inFlightRef.current = false;
113
+ }
114
+ },
115
+ [
116
+ filters.environment,
117
+ filters.timeRange,
118
+ filters.customStartTime,
119
+ filters.customEndTime,
120
+ filters.logLevel,
121
+ filters.searchQuery,
122
+ filters.sortOrder,
123
+ observabilityApi,
124
+ options.environmentName,
125
+ options.namespaceName,
126
+ options.projectName,
127
+ options.limit,
128
+ selectedComponents
129
+ ]
130
+ );
131
+ useEffect(() => {
132
+ if (pollingIntervalRef.current) {
133
+ clearInterval(pollingIntervalRef.current);
134
+ pollingIntervalRef.current = null;
135
+ }
136
+ if (!filters.isLive || !filters.environment || !options.environmentName || !options.namespaceName || !options.projectName) {
137
+ return void 0;
138
+ }
139
+ pollingIntervalRef.current = setInterval(() => {
140
+ fetchLogs(true);
141
+ }, 5e3);
142
+ return () => {
143
+ if (pollingIntervalRef.current) {
144
+ clearInterval(pollingIntervalRef.current);
145
+ pollingIntervalRef.current = null;
146
+ }
147
+ inFlightRef.current = false;
148
+ };
149
+ }, [
150
+ fetchLogs,
151
+ filters.environment,
152
+ filters.isLive,
153
+ options.environmentName,
154
+ options.namespaceName,
155
+ options.projectName
156
+ ]);
157
+ const loadMore = useCallback(() => {
158
+ if (!loading && hasMore && !error && logs.length > 0) {
159
+ fetchLogs(false);
160
+ }
161
+ }, [loading, hasMore, error, logs.length, fetchLogs]);
162
+ const refresh = useCallback(() => {
163
+ setLogs([]);
164
+ fetchLogs(true);
165
+ }, [fetchLogs]);
166
+ const clearLogs = useCallback(() => {
167
+ setLogs([]);
168
+ setTotalCount(0);
169
+ setHasMore(true);
170
+ }, []);
171
+ return {
172
+ logs,
173
+ loading,
174
+ error,
175
+ totalCount,
176
+ hasMore,
177
+ fetchLogs,
178
+ loadMore,
179
+ refresh,
180
+ clearLogs
181
+ };
182
+ }
183
+
184
+ export { useProjectRuntimeLogs };
185
+ //# sourceMappingURL=useProjectRuntimeLogs.esm.js.map