@backstage-community/plugin-cost-insights 0.25.0 → 0.27.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 (51) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/alpha/plugin.esm.js +1 -0
  3. package/dist/alpha/plugin.esm.js.map +1 -1
  4. package/dist/alpha/router.esm.js +1 -0
  5. package/dist/alpha/router.esm.js.map +1 -1
  6. package/dist/components/CostGrowth/CostGrowth.esm.js +11 -2
  7. package/dist/components/CostGrowth/CostGrowth.esm.js.map +1 -1
  8. package/dist/components/CostInsightsPage/CostInsightsPage.esm.js +5 -1
  9. package/dist/components/CostInsightsPage/CostInsightsPage.esm.js.map +1 -1
  10. package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js +20 -6
  11. package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js.map +1 -1
  12. package/dist/components/CostOverviewCard/CostOverviewCard.esm.js +17 -4
  13. package/dist/components/CostOverviewCard/CostOverviewCard.esm.js.map +1 -1
  14. package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js +11 -3
  15. package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js.map +1 -1
  16. package/dist/components/CostOverviewCard/selector.esm.js +3 -2
  17. package/dist/components/CostOverviewCard/selector.esm.js.map +1 -1
  18. package/dist/components/EntityCosts/EntityCosts.esm.js +5 -1
  19. package/dist/components/EntityCosts/EntityCosts.esm.js.map +1 -1
  20. package/dist/components/PeriodSelect/DateRangePicker.esm.js +218 -0
  21. package/dist/components/PeriodSelect/DateRangePicker.esm.js.map +1 -0
  22. package/dist/components/ProductInsights/ProductInsights.esm.js +9 -2
  23. package/dist/components/ProductInsights/ProductInsights.esm.js.map +1 -1
  24. package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js +32 -8
  25. package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js.map +1 -1
  26. package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js +24 -4
  27. package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js.map +1 -1
  28. package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js +1 -0
  29. package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js.map +1 -1
  30. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js +1 -0
  31. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js.map +1 -1
  32. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js +1 -0
  33. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js.map +1 -1
  34. package/dist/index.d.ts +16 -1
  35. package/dist/index.esm.js +1 -0
  36. package/dist/index.esm.js.map +1 -1
  37. package/dist/testUtils/testUtils.esm.js +30 -11
  38. package/dist/testUtils/testUtils.esm.js.map +1 -1
  39. package/dist/types/Duration.esm.js +1 -0
  40. package/dist/types/Duration.esm.js.map +1 -1
  41. package/dist/utils/change.esm.js +20 -6
  42. package/dist/utils/change.esm.js.map +1 -1
  43. package/dist/utils/currency.esm.js +6 -1
  44. package/dist/utils/currency.esm.js.map +1 -1
  45. package/dist/utils/duration.esm.js +41 -5
  46. package/dist/utils/duration.esm.js.map +1 -1
  47. package/dist/utils/formatters.esm.js +25 -4
  48. package/dist/utils/formatters.esm.js.map +1 -1
  49. package/package.json +12 -10
  50. package/dist/components/PeriodSelect/PeriodSelect.esm.js +0 -72
  51. package/dist/components/PeriodSelect/PeriodSelect.esm.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @backstage-community/plugin-cost-insights
2
2
 
3
+ ## 0.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8a5c766: The product insights and the cost insights graph support date ranges to add custom time frames
8
+
9
+ ## 0.26.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 649ffe1: Backstage version bump to v1.47.2
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [649ffe1]
18
+ - @backstage-community/plugin-cost-insights-common@0.12.0
19
+
3
20
  ## 0.25.0
4
21
 
5
22
  ### Minor Changes
@@ -84,6 +84,7 @@ import '@material-ui/core/Tab';
84
84
  import '@material-ui/core/Tabs';
85
85
  import '@material-ui/core/styles/colorManipulator';
86
86
  import '@material-ui/icons/Fullscreen';
87
+ import '../components/PeriodSelect/DateRangePicker.esm.js';
87
88
  import '@material-ui/core/CircularProgress';
88
89
  import '@backstage/core-components';
89
90
  import '@material-ui/core/Menu';
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../../src/alpha/plugin.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PageBlueprint,\n NavItemBlueprint,\n createFrontendPlugin,\n ApiBlueprint,\n} from '@backstage/frontend-plugin-api';\nimport {\n compatWrapper,\n convertLegacyRouteRef,\n convertLegacyRouteRefs,\n} from '@backstage/core-compat-api';\nimport { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha';\nimport MoneyIcon from '@material-ui/icons/MonetizationOn';\nimport { rootRouteRef } from '../routes';\nimport { costInsightsApiRef } from '../api';\nimport { ExampleCostInsightsClient } from '../example';\n\n/** @alpha */\nexport const CostInsightsApi = ApiBlueprint.make({\n params: defineParams =>\n defineParams({\n api: costInsightsApiRef,\n deps: {},\n factory: ({}) => new ExampleCostInsightsClient(),\n }),\n});\n\n/** @alpha */\nexport const CostInsightsPage = PageBlueprint.make({\n params: {\n path: '/cost-insights',\n routeRef: convertLegacyRouteRef(rootRouteRef),\n loader: () =>\n import('./router').then(m => compatWrapper(<m.CostInsightsRouter />)),\n },\n});\n\n/** @alpha */\nexport const EntityCostInsightsContent = EntityContentBlueprint.make({\n name: 'EntityCostInsightsContent',\n params: {\n path: '/cost-insights',\n title: 'Cost Insights',\n loader: () =>\n import('../components/EntityCosts').then(m =>\n compatWrapper(<m.EntityCosts />),\n ),\n },\n});\n\n/**\n * @alpha\n */\nexport const CostInsightsNavItem = NavItemBlueprint.make({\n params: {\n title: 'Cost Insights',\n routeRef: convertLegacyRouteRef(rootRouteRef),\n icon: MoneyIcon,\n },\n});\n\nexport default createFrontendPlugin({\n pluginId: 'cost-insights',\n extensions: [CostInsightsApi, CostInsightsPage, CostInsightsNavItem],\n routes: convertLegacyRouteRefs({\n root: rootRouteRef,\n }),\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCa,MAAA,eAAA,GAAkB,aAAa,IAAK,CAAA;AAAA,EAC/C,MAAA,EAAQ,kBACN,YAAa,CAAA;AAAA,IACX,GAAK,EAAA,kBAAA;AAAA,IACL,MAAM,EAAC;AAAA,IACP,OAAS,EAAA,CAAC,EAAC,KAAM,IAAI,yBAA0B;AAAA,GAChD;AACL,CAAC;AAGY,MAAA,gBAAA,GAAmB,cAAc,IAAK,CAAA;AAAA,EACjD,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,gBAAA;AAAA,IACN,QAAA,EAAU,sBAAsB,YAAY,CAAA;AAAA,IAC5C,MAAQ,EAAA,MACN,OAAO,iBAAU,CAAE,CAAA,IAAA,CAAK,CAAK,CAAA,KAAA,aAAA,iBAAe,GAAA,CAAA,CAAA,CAAE,kBAAF,EAAA,EAAqB,CAAE,CAAC;AAAA;AAE1E,CAAC;AAGwC,uBAAuB,IAAK,CAAA;AAAA,EACnE,IAAM,EAAA,2BAAA;AAAA,EACN,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,gBAAA;AAAA,IACN,KAAO,EAAA,eAAA;AAAA,IACP,MAAQ,EAAA,MACN,OAAO,wCAA2B,CAAE,CAAA,IAAA;AAAA,MAAK,OACvC,aAAc,iBAAA,GAAA,CAAC,CAAE,CAAA,WAAA,EAAF,EAAc,CAAE;AAAA;AACjC;AAEN,CAAC;AAKY,MAAA,mBAAA,GAAsB,iBAAiB,IAAK,CAAA;AAAA,EACvD,MAAQ,EAAA;AAAA,IACN,KAAO,EAAA,eAAA;AAAA,IACP,QAAA,EAAU,sBAAsB,YAAY,CAAA;AAAA,IAC5C,IAAM,EAAA;AAAA;AAEV,CAAC;AAED,aAAe,oBAAqB,CAAA;AAAA,EAClC,QAAU,EAAA,eAAA;AAAA,EACV,UAAY,EAAA,CAAC,eAAiB,EAAA,gBAAA,EAAkB,mBAAmB,CAAA;AAAA,EACnE,QAAQ,sBAAuB,CAAA;AAAA,IAC7B,IAAM,EAAA;AAAA,GACP;AACH,CAAC,CAAA;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../../src/alpha/plugin.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PageBlueprint,\n NavItemBlueprint,\n createFrontendPlugin,\n ApiBlueprint,\n} from '@backstage/frontend-plugin-api';\nimport {\n compatWrapper,\n convertLegacyRouteRef,\n convertLegacyRouteRefs,\n} from '@backstage/core-compat-api';\nimport { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha';\nimport MoneyIcon from '@material-ui/icons/MonetizationOn';\nimport { rootRouteRef } from '../routes';\nimport { costInsightsApiRef } from '../api';\nimport { ExampleCostInsightsClient } from '../example';\n\n/** @alpha */\nexport const CostInsightsApi = ApiBlueprint.make({\n params: defineParams =>\n defineParams({\n api: costInsightsApiRef,\n deps: {},\n factory: ({}) => new ExampleCostInsightsClient(),\n }),\n});\n\n/** @alpha */\nexport const CostInsightsPage = PageBlueprint.make({\n params: {\n path: '/cost-insights',\n routeRef: convertLegacyRouteRef(rootRouteRef),\n loader: () =>\n import('./router').then(m => compatWrapper(<m.CostInsightsRouter />)),\n },\n});\n\n/** @alpha */\nexport const EntityCostInsightsContent = EntityContentBlueprint.make({\n name: 'EntityCostInsightsContent',\n params: {\n path: '/cost-insights',\n title: 'Cost Insights',\n loader: () =>\n import('../components/EntityCosts').then(m =>\n compatWrapper(<m.EntityCosts />),\n ),\n },\n});\n\n/**\n * @alpha\n */\nexport const CostInsightsNavItem = NavItemBlueprint.make({\n params: {\n title: 'Cost Insights',\n routeRef: convertLegacyRouteRef(rootRouteRef),\n icon: MoneyIcon,\n },\n});\n\nexport default createFrontendPlugin({\n pluginId: 'cost-insights',\n extensions: [CostInsightsApi, CostInsightsPage, CostInsightsNavItem],\n routes: convertLegacyRouteRefs({\n root: rootRouteRef,\n }),\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCa,MAAA,eAAA,GAAkB,aAAa,IAAK,CAAA;AAAA,EAC/C,MAAA,EAAQ,kBACN,YAAa,CAAA;AAAA,IACX,GAAK,EAAA,kBAAA;AAAA,IACL,MAAM,EAAC;AAAA,IACP,OAAS,EAAA,CAAC,EAAC,KAAM,IAAI,yBAA0B;AAAA,GAChD;AACL,CAAC;AAGY,MAAA,gBAAA,GAAmB,cAAc,IAAK,CAAA;AAAA,EACjD,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,gBAAA;AAAA,IACN,QAAA,EAAU,sBAAsB,YAAY,CAAA;AAAA,IAC5C,MAAQ,EAAA,MACN,OAAO,iBAAU,CAAE,CAAA,IAAA,CAAK,CAAK,CAAA,KAAA,aAAA,iBAAe,GAAA,CAAA,CAAA,CAAE,kBAAF,EAAA,EAAqB,CAAE,CAAC;AAAA;AAE1E,CAAC;AAGwC,uBAAuB,IAAK,CAAA;AAAA,EACnE,IAAM,EAAA,2BAAA;AAAA,EACN,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,gBAAA;AAAA,IACN,KAAO,EAAA,eAAA;AAAA,IACP,MAAQ,EAAA,MACN,OAAO,wCAA2B,CAAE,CAAA,IAAA;AAAA,MAAK,OACvC,aAAc,iBAAA,GAAA,CAAC,CAAE,CAAA,WAAA,EAAF,EAAc,CAAE;AAAA;AACjC;AAEN,CAAC;AAKY,MAAA,mBAAA,GAAsB,iBAAiB,IAAK,CAAA;AAAA,EACvD,MAAQ,EAAA;AAAA,IACN,KAAO,EAAA,eAAA;AAAA,IACP,QAAA,EAAU,sBAAsB,YAAY,CAAA;AAAA,IAC5C,IAAM,EAAA;AAAA;AAEV,CAAC;AAED,aAAe,oBAAqB,CAAA;AAAA,EAClC,QAAU,EAAA,eAAA;AAAA,EACV,UAAY,EAAA,CAAC,eAAiB,EAAA,gBAAA,EAAkB,mBAAmB,CAAA;AAAA,EACnE,QAAQ,sBAAuB,CAAA;AAAA,IAC7B,IAAM,EAAA;AAAA,GACP;AACH,CAAC,CAAA;;;;"}
@@ -82,6 +82,7 @@ import '@material-ui/icons/Fullscreen';
82
82
  import '@material-ui/core/InputLabel';
83
83
  import '@material-ui/core/FormControl';
84
84
  import '@material-ui/core/Select';
85
+ import '../components/PeriodSelect/DateRangePicker.esm.js';
85
86
  import { LabelDataflowInstructionsPage } from '../components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js';
86
87
  import '../api/CostInsightsApi.esm.js';
87
88
  import '@material-ui/core/CircularProgress';
@@ -1 +1 @@
1
- {"version":3,"file":"router.esm.js","sources":["../../src/alpha/router.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createSubRouteRef } from '@backstage/core-plugin-api';\nimport { Route, Routes } from 'react-router-dom';\nimport { rootRouteRef } from '../routes';\nimport {\n CostInsightsPage,\n LabelDataflowInstructionsPage,\n ProjectGrowthInstructionsPage,\n} from '../components';\n\nexport const projectGrowthAlertSubRouteRef = createSubRouteRef({\n id: 'cost-insights/investigating-growth',\n parent: rootRouteRef,\n path: '/investigating-growth',\n});\n\nexport const unlabeledDataflowAlertSubRouteRef = createSubRouteRef({\n id: 'cost-insights/labeling-jobs',\n parent: rootRouteRef,\n path: '/labeling-jobs',\n});\n\nexport const CostInsightsRouter = () => (\n <Routes>\n <Route path=\"/\" element={<CostInsightsPage />} />\n <Route\n path={projectGrowthAlertSubRouteRef.path}\n element={<ProjectGrowthInstructionsPage />}\n />\n <Route\n path={unlabeledDataflowAlertSubRouteRef.path}\n element={<LabelDataflowInstructionsPage />}\n />\n </Routes>\n);\n"],"names":["CostInsightsPage"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBO,MAAM,gCAAgC,iBAAkB,CAAA;AAAA,EAC7D,EAAI,EAAA,oCAAA;AAAA,EACJ,MAAQ,EAAA,YAAA;AAAA,EACR,IAAM,EAAA;AACR,CAAC;AAEM,MAAM,oCAAoC,iBAAkB,CAAA;AAAA,EACjE,EAAI,EAAA,6BAAA;AAAA,EACJ,MAAQ,EAAA,YAAA;AAAA,EACR,IAAM,EAAA;AACR,CAAC;AAEY,MAAA,kBAAA,GAAqB,sBAChC,IAAA,CAAC,MACC,EAAA,EAAA,QAAA,EAAA;AAAA,kBAAA,GAAA,CAAC,SAAM,IAAK,EAAA,GAAA,EAAI,OAAS,kBAAA,GAAA,CAACA,wBAAiB,CAAI,EAAA,CAAA;AAAA,kBAC/C,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,MAAM,6BAA8B,CAAA,IAAA;AAAA,MACpC,OAAA,sBAAU,6BAA8B,EAAA,EAAA;AAAA;AAAA,GAC1C;AAAA,kBACA,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,MAAM,iCAAkC,CAAA,IAAA;AAAA,MACxC,OAAA,sBAAU,6BAA8B,EAAA,EAAA;AAAA;AAAA;AAC1C,CACF,EAAA;;;;"}
1
+ {"version":3,"file":"router.esm.js","sources":["../../src/alpha/router.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createSubRouteRef } from '@backstage/core-plugin-api';\nimport { Route, Routes } from 'react-router-dom';\nimport { rootRouteRef } from '../routes';\nimport {\n CostInsightsPage,\n LabelDataflowInstructionsPage,\n ProjectGrowthInstructionsPage,\n} from '../components';\n\nexport const projectGrowthAlertSubRouteRef = createSubRouteRef({\n id: 'cost-insights/investigating-growth',\n parent: rootRouteRef,\n path: '/investigating-growth',\n});\n\nexport const unlabeledDataflowAlertSubRouteRef = createSubRouteRef({\n id: 'cost-insights/labeling-jobs',\n parent: rootRouteRef,\n path: '/labeling-jobs',\n});\n\nexport const CostInsightsRouter = () => (\n <Routes>\n <Route path=\"/\" element={<CostInsightsPage />} />\n <Route\n path={projectGrowthAlertSubRouteRef.path}\n element={<ProjectGrowthInstructionsPage />}\n />\n <Route\n path={unlabeledDataflowAlertSubRouteRef.path}\n element={<LabelDataflowInstructionsPage />}\n />\n </Routes>\n);\n"],"names":["CostInsightsPage"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBO,MAAM,gCAAgC,iBAAkB,CAAA;AAAA,EAC7D,EAAI,EAAA,oCAAA;AAAA,EACJ,MAAQ,EAAA,YAAA;AAAA,EACR,IAAM,EAAA;AACR,CAAC;AAEM,MAAM,oCAAoC,iBAAkB,CAAA;AAAA,EACjE,EAAI,EAAA,6BAAA;AAAA,EACJ,MAAQ,EAAA,YAAA;AAAA,EACR,IAAM,EAAA;AACR,CAAC;AAEY,MAAA,kBAAA,GAAqB,sBAChC,IAAA,CAAC,MACC,EAAA,EAAA,QAAA,EAAA;AAAA,kBAAA,GAAA,CAAC,SAAM,IAAK,EAAA,GAAA,EAAI,OAAS,kBAAA,GAAA,CAACA,wBAAiB,CAAI,EAAA,CAAA;AAAA,kBAC/C,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,MAAM,6BAA8B,CAAA,IAAA;AAAA,MACpC,OAAA,sBAAU,6BAA8B,EAAA,EAAA;AAAA;AAAA,GAC1C;AAAA,kBACA,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,MAAM,iCAAkC,CAAA,IAAA;AAAA,MACxC,OAAA,sBAAU,6BAA8B,EAAA,EAAA;AAAA;AAAA;AAC1C,CACF,EAAA;;;;"}
@@ -4,6 +4,7 @@ import Typography from '@material-ui/core/Typography';
4
4
  import '../../types/Alert.esm.js';
5
5
  import { GrowthType } from '../../types/ChangeStatistic.esm.js';
6
6
  import { CurrencyType } from '../../types/CurrencyType.esm.js';
7
+ import { Duration } from '../../types/Duration.esm.js';
7
8
  import { rateOf } from '../../utils/currency.esm.js';
8
9
  import { growthOf } from '../../utils/change.esm.js';
9
10
  import { useCostGrowthStyles } from '../../utils/styles.esm.js';
@@ -17,15 +18,23 @@ import '../../hooks/useLoading.esm.js';
17
18
  import '../../hooks/useScroll.esm.js';
18
19
  import '../../hooks/useLastCompleteBillingDate.esm.js';
19
20
  import { notEmpty } from '../../utils/assert.esm.js';
21
+ import { DateTime } from 'luxon';
20
22
 
21
23
  const CostGrowth = (props) => {
22
- const { change, duration } = props;
24
+ const { change, duration, customDateRange } = props;
23
25
  const styles = useCostGrowthStyles();
24
26
  const { engineerCost, engineerThreshold } = useConfig();
25
27
  const [currency] = useCurrency();
26
28
  const amount = Math.abs(change.amount);
27
29
  const ratio = Math.abs(change.ratio ?? NaN);
28
- const rate = rateOf(engineerCost, duration);
30
+ let customDays;
31
+ if (duration === Duration.CUSTOM && customDateRange) {
32
+ const startDate = DateTime.fromISO(customDateRange.start);
33
+ const endDate = DateTime.fromISO(customDateRange.end);
34
+ const totalDays = Math.round(endDate.diff(startDate, "days").days) + 1;
35
+ customDays = Math.floor(totalDays / 2);
36
+ }
37
+ const rate = rateOf(engineerCost, duration, customDays);
29
38
  const engineers = amount / rate;
30
39
  const converted = amount / (currency.rate ?? rate);
31
40
  const growth = notEmpty(change.ratio) ? growthOf({ ratio: change.ratio, amount: engineers }, engineerThreshold) : null;
@@ -1 +1 @@
1
- {"version":3,"file":"CostGrowth.esm.js","sources":["../../../src/components/CostGrowth/CostGrowth.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport classnames from 'classnames';\nimport Typography from '@material-ui/core/Typography';\nimport { CurrencyType, Duration, GrowthType } from '../../types';\nimport { ChangeStatistic } from '@backstage-community/plugin-cost-insights-common';\nimport { rateOf } from '../../utils/currency';\nimport { growthOf } from '../../utils/change';\nimport { useCostGrowthStyles as useStyles } from '../../utils/styles';\nimport { formatPercent, formatCurrency } from '../../utils/formatters';\nimport { indefiniteArticleOf } from '../../utils/grammar';\nimport { useConfig, useCurrency } from '../../hooks';\nimport { notEmpty } from '../../utils/assert';\n\n/** @public */\nexport type CostGrowthProps = {\n change: ChangeStatistic;\n duration: Duration;\n};\n\n/** @public */\nexport const CostGrowth = (props: CostGrowthProps) => {\n const { change, duration } = props;\n\n const styles = useStyles();\n const { engineerCost, engineerThreshold } = useConfig();\n const [currency] = useCurrency();\n\n // Only display costs in absolute values\n const amount = Math.abs(change.amount);\n const ratio = Math.abs(change.ratio ?? NaN);\n\n const rate = rateOf(engineerCost, duration);\n const engineers = amount / rate;\n const converted = amount / (currency.rate ?? rate);\n\n // If a ratio cannot be calculated, don't format.\n const growth = notEmpty(change.ratio)\n ? growthOf({ ratio: change.ratio, amount: engineers }, engineerThreshold)\n : null;\n // Determine if growth is significant enough to highlight\n const classes = classnames({\n [styles.excess]: growth === GrowthType.Excess,\n [styles.savings]: growth === GrowthType.Savings,\n });\n\n if (engineers < engineerThreshold) {\n return (\n <Typography component=\"span\" className={classes}>\n Negligible\n </Typography>\n );\n }\n\n if (currency.kind === CurrencyType.USD) {\n // Do not display percentage if ratio cannot be calculated\n if (isNaN(ratio)) {\n return (\n <Typography component=\"span\" className={classes}>\n ~{currency.prefix}\n {formatCurrency(converted)}\n </Typography>\n );\n }\n\n return (\n <Typography component=\"span\" className={classes}>\n {formatPercent(ratio)} or ~{currency.prefix}\n {formatCurrency(converted)}\n </Typography>\n );\n }\n\n if (amount < 1) {\n return (\n <Typography component=\"span\" className={classes}>\n less than {indefiniteArticleOf(['a', 'an'], currency.unit)}\n </Typography>\n );\n }\n\n // Do not display percentage if ratio cannot be calculated\n if (isNaN(ratio)) {\n return (\n <Typography component=\"span\" className={classes}>\n ~{formatCurrency(converted, currency.unit)}\n </Typography>\n );\n }\n\n return (\n <Typography component=\"span\" className={classes}>\n {formatPercent(ratio)} or ~{formatCurrency(converted, currency.unit)}\n </Typography>\n );\n};\n"],"names":["useStyles"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmCa,MAAA,UAAA,GAAa,CAAC,KAA2B,KAAA;AACpD,EAAM,MAAA,EAAE,MAAQ,EAAA,QAAA,EAAa,GAAA,KAAA;AAE7B,EAAA,MAAM,SAASA,mBAAU,EAAA;AACzB,EAAA,MAAM,EAAE,YAAA,EAAc,iBAAkB,EAAA,GAAI,SAAU,EAAA;AACtD,EAAM,MAAA,CAAC,QAAQ,CAAA,GAAI,WAAY,EAAA;AAG/B,EAAA,MAAM,MAAS,GAAA,IAAA,CAAK,GAAI,CAAA,MAAA,CAAO,MAAM,CAAA;AACrC,EAAA,MAAM,KAAQ,GAAA,IAAA,CAAK,GAAI,CAAA,MAAA,CAAO,SAAS,GAAG,CAAA;AAE1C,EAAM,MAAA,IAAA,GAAO,MAAO,CAAA,YAAA,EAAc,QAAQ,CAAA;AAC1C,EAAA,MAAM,YAAY,MAAS,GAAA,IAAA;AAC3B,EAAM,MAAA,SAAA,GAAY,MAAU,IAAA,QAAA,CAAS,IAAQ,IAAA,IAAA,CAAA;AAG7C,EAAA,MAAM,MAAS,GAAA,QAAA,CAAS,MAAO,CAAA,KAAK,IAChC,QAAS,CAAA,EAAE,KAAO,EAAA,MAAA,CAAO,KAAO,EAAA,MAAA,EAAQ,SAAU,EAAA,EAAG,iBAAiB,CACtE,GAAA,IAAA;AAEJ,EAAA,MAAM,UAAU,UAAW,CAAA;AAAA,IACzB,CAAC,MAAA,CAAO,MAAM,GAAG,WAAW,UAAW,CAAA,MAAA;AAAA,IACvC,CAAC,MAAA,CAAO,OAAO,GAAG,WAAW,UAAW,CAAA;AAAA,GACzC,CAAA;AAED,EAAA,IAAI,YAAY,iBAAmB,EAAA;AACjC,IAAA,2BACG,UAAW,EAAA,EAAA,SAAA,EAAU,MAAO,EAAA,SAAA,EAAW,SAAS,QAEjD,EAAA,YAAA,EAAA,CAAA;AAAA;AAIJ,EAAI,IAAA,QAAA,CAAS,IAAS,KAAA,YAAA,CAAa,GAAK,EAAA;AAEtC,IAAI,IAAA,KAAA,CAAM,KAAK,CAAG,EAAA;AAChB,MAAA,uBACG,IAAA,CAAA,UAAA,EAAA,EAAW,SAAU,EAAA,MAAA,EAAO,WAAW,OAAS,EAAA,QAAA,EAAA;AAAA,QAAA,GAAA;AAAA,QAC7C,QAAS,CAAA,MAAA;AAAA,QACV,eAAe,SAAS;AAAA,OAC3B,EAAA,CAAA;AAAA;AAIJ,IAAA,uBACG,IAAA,CAAA,UAAA,EAAA,EAAW,SAAU,EAAA,MAAA,EAAO,WAAW,OACrC,EAAA,QAAA,EAAA;AAAA,MAAA,aAAA,CAAc,KAAK,CAAA;AAAA,MAAE,OAAA;AAAA,MAAM,QAAS,CAAA,MAAA;AAAA,MACpC,eAAe,SAAS;AAAA,KAC3B,EAAA,CAAA;AAAA;AAIJ,EAAA,IAAI,SAAS,CAAG,EAAA;AACd,IAAA,uBACG,IAAA,CAAA,UAAA,EAAA,EAAW,SAAU,EAAA,MAAA,EAAO,WAAW,OAAS,EAAA,QAAA,EAAA;AAAA,MAAA,YAAA;AAAA,MACpC,oBAAoB,CAAC,GAAA,EAAK,IAAI,CAAA,EAAG,SAAS,IAAI;AAAA,KAC3D,EAAA,CAAA;AAAA;AAKJ,EAAI,IAAA,KAAA,CAAM,KAAK,CAAG,EAAA;AAChB,IAAA,uBACG,IAAA,CAAA,UAAA,EAAA,EAAW,SAAU,EAAA,MAAA,EAAO,WAAW,OAAS,EAAA,QAAA,EAAA;AAAA,MAAA,GAAA;AAAA,MAC7C,cAAA,CAAe,SAAW,EAAA,QAAA,CAAS,IAAI;AAAA,KAC3C,EAAA,CAAA;AAAA;AAIJ,EAAA,uBACG,IAAA,CAAA,UAAA,EAAA,EAAW,SAAU,EAAA,MAAA,EAAO,WAAW,OACrC,EAAA,QAAA,EAAA;AAAA,IAAA,aAAA,CAAc,KAAK,CAAA;AAAA,IAAE,OAAA;AAAA,IAAM,cAAA,CAAe,SAAW,EAAA,QAAA,CAAS,IAAI;AAAA,GACrE,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"CostGrowth.esm.js","sources":["../../../src/components/CostGrowth/CostGrowth.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport classnames from 'classnames';\nimport Typography from '@material-ui/core/Typography';\nimport { CurrencyType, Duration, GrowthType } from '../../types';\nimport { ChangeStatistic } from '@backstage-community/plugin-cost-insights-common';\nimport { rateOf } from '../../utils/currency';\nimport { growthOf } from '../../utils/change';\nimport { useCostGrowthStyles as useStyles } from '../../utils/styles';\nimport { formatPercent, formatCurrency } from '../../utils/formatters';\nimport { indefiniteArticleOf } from '../../utils/grammar';\nimport { useConfig, useCurrency } from '../../hooks';\nimport { notEmpty } from '../../utils/assert';\nimport { DateTime } from 'luxon';\n\n/** @public */\nexport type CostGrowthProps = {\n change: ChangeStatistic;\n duration: Duration;\n customDateRange?: { start: string; end: string };\n};\n\n/** @public */\nexport const CostGrowth = (props: CostGrowthProps) => {\n const { change, duration, customDateRange } = props;\n\n const styles = useStyles();\n const { engineerCost, engineerThreshold } = useConfig();\n const [currency] = useCurrency();\n\n // Only display costs in absolute values\n const amount = Math.abs(change.amount);\n const ratio = Math.abs(change.ratio ?? NaN);\n\n // Calculate customDays if we have a custom date range\n // For custom ranges, we use floor division to get the first period length\n // (the rate calculation uses the average period length)\n let customDays: number | undefined;\n if (duration === Duration.CUSTOM && customDateRange) {\n const startDate = DateTime.fromISO(customDateRange.start);\n const endDate = DateTime.fromISO(customDateRange.end);\n const totalDays = Math.round(endDate.diff(startDate, 'days').days) + 1;\n // Use floor division for consistent period calculation\n customDays = Math.floor(totalDays / 2);\n }\n\n const rate = rateOf(engineerCost, duration, customDays);\n const engineers = amount / rate;\n const converted = amount / (currency.rate ?? rate);\n\n // If a ratio cannot be calculated, don't format.\n const growth = notEmpty(change.ratio)\n ? growthOf({ ratio: change.ratio, amount: engineers }, engineerThreshold)\n : null;\n // Determine if growth is significant enough to highlight\n const classes = classnames({\n [styles.excess]: growth === GrowthType.Excess,\n [styles.savings]: growth === GrowthType.Savings,\n });\n\n if (engineers < engineerThreshold) {\n return (\n <Typography component=\"span\" className={classes}>\n Negligible\n </Typography>\n );\n }\n\n if (currency.kind === CurrencyType.USD) {\n // Do not display percentage if ratio cannot be calculated\n if (isNaN(ratio)) {\n return (\n <Typography component=\"span\" className={classes}>\n ~{currency.prefix}\n {formatCurrency(converted)}\n </Typography>\n );\n }\n\n return (\n <Typography component=\"span\" className={classes}>\n {formatPercent(ratio)} or ~{currency.prefix}\n {formatCurrency(converted)}\n </Typography>\n );\n }\n\n if (amount < 1) {\n return (\n <Typography component=\"span\" className={classes}>\n less than {indefiniteArticleOf(['a', 'an'], currency.unit)}\n </Typography>\n );\n }\n\n // Do not display percentage if ratio cannot be calculated\n if (isNaN(ratio)) {\n return (\n <Typography component=\"span\" className={classes}>\n ~{formatCurrency(converted, currency.unit)}\n </Typography>\n );\n }\n\n return (\n <Typography component=\"span\" className={classes}>\n {formatPercent(ratio)} or ~{formatCurrency(converted, currency.unit)}\n </Typography>\n );\n};\n"],"names":["useStyles"],"mappings":"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"}
@@ -83,7 +83,11 @@ const CostInsightsPage = () => {
83
83
  dispatchLoadingInsights(true);
84
84
  const intervals = intervalsOf(
85
85
  pageFilters.duration,
86
- lastCompleteBillingDate
86
+ lastCompleteBillingDate,
87
+ 2,
88
+ pageFilters.customDateRange,
89
+ false
90
+ // Don't use comparison mode for the main cost overview graph
87
91
  );
88
92
  const [
89
93
  fetchedProjects,
@@ -1 +1 @@
1
- {"version":3,"file":"CostInsightsPage.esm.js","sources":["../../../src/components/CostInsightsPage/CostInsightsPage.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport Box from '@material-ui/core/Box';\nimport Collapse from '@material-ui/core/Collapse';\nimport Container from '@material-ui/core/Container';\nimport Divider from '@material-ui/core/Divider';\nimport Grid from '@material-ui/core/Grid';\nimport Typography from '@material-ui/core/Typography';\nimport { default as MaterialAlert } from '@material-ui/lab/Alert';\nimport { costInsightsApiRef } from '../../api';\nimport { ActionItems } from '../ActionItems';\nimport { AlertInsights } from '../AlertInsights';\nimport { CostInsightsLayout } from '../CostInsightsLayout';\nimport { CopyUrlToClipboard } from '../CopyUrlToClipboard';\nimport { CurrencySelect } from '../CurrencySelect';\nimport { WhyCostsMatter } from '../WhyCostsMatter';\nimport {\n CostInsightsHeader,\n CostInsightsHeaderNoGroups,\n} from '../CostInsightsHeader';\nimport { CostInsightsNavigation } from '../CostInsightsNavigation';\nimport { CostOverviewCard } from '../CostOverviewCard';\nimport { ProductInsights } from '../ProductInsights';\n/* https://github.com/backstage/backstage/issues/2574 */\n// import { CostInsightsSupportButton } from '../CostInsightsSupportButton';\nimport {\n useConfig,\n useCurrency,\n useFilters,\n useGroups,\n useLastCompleteBillingDate,\n useLoading,\n} from '../../hooks';\nimport { Alert } from '../../types';\nimport {\n Cost,\n Maybe,\n MetricData,\n Product,\n Project,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { mapLoadingToProps } from './selector';\nimport { ProjectSelect } from '../ProjectSelect';\nimport { intervalsOf } from '../../utils/duration';\nimport { useSubtleTypographyStyles } from '../../utils/styles';\nimport {\n isAlertActive,\n isAlertAccepted,\n isAlertDismissed,\n isAlertSnoozed,\n} from '../../utils/alerts';\n\nimport { Progress } from '@backstage/core-components';\nimport { useApi } from '@backstage/core-plugin-api';\n\nexport const CostInsightsPage = () => {\n const classes = useSubtleTypographyStyles();\n const client = useApi(costInsightsApiRef);\n const config = useConfig();\n const groups = useGroups();\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const [alerts, setAlerts] = useState<Alert[]>([]);\n const [currency, setCurrency] = useCurrency();\n const [projects, setProjects] = useState<Maybe<Project[]>>(null);\n const [products, setProducts] = useState<Maybe<Product[]>>(null);\n const [dailyCost, setDailyCost] = useState<Maybe<Cost>>(null);\n const [metricData, setMetricData] = useState<Maybe<MetricData>>(null);\n const [error, setError] = useState<Maybe<Error>>(null);\n\n const { pageFilters, setPageFilters } = useFilters(p => p);\n\n const active = useMemo(() => alerts.filter(isAlertActive), [alerts]);\n const snoozed = useMemo(() => alerts.filter(isAlertSnoozed), [alerts]);\n const accepted = useMemo(() => alerts.filter(isAlertAccepted), [alerts]);\n const dismissed = useMemo(() => alerts.filter(isAlertDismissed), [alerts]);\n\n const isProductsDisplayed = !!config.products?.length;\n const isActionItemsDisplayed = !!active.length;\n const isAlertInsightsDisplayed = !!alerts.length;\n\n const {\n loadingActions,\n loadingGroups,\n loadingBillingDate,\n loadingInitial,\n dispatchInitial,\n dispatchInsights,\n dispatchNone,\n dispatchReset,\n } = useLoading(mapLoadingToProps);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n // The dispatchLoading functions are derived from loading state using mapLoadingToProps, to\n // provide nicer props for the component. These are re-derived whenever loading state changes,\n // which causes an infinite loop as product panels load and re-trigger the useEffect below.\n // Since the functions don't change, we can memoize - but we trigger the same loop if we satisfy\n // exhaustive-deps by including the function itself in dependencies.\n\n const dispatchLoadingInitial = useCallback(dispatchInitial, []);\n const dispatchLoadingInsights = useCallback(dispatchInsights, []);\n const dispatchLoadingNone = useCallback(dispatchNone, []);\n const dispatchLoadingReset = useCallback(dispatchReset, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n const setProject = (project: Maybe<string>) =>\n setPageFilters({\n ...pageFilters,\n project: project === 'all' ? null : project,\n });\n\n useEffect(() => {\n async function getInsights() {\n setError(null);\n try {\n if (pageFilters.group) {\n dispatchLoadingInsights(true);\n const intervals = intervalsOf(\n pageFilters.duration,\n lastCompleteBillingDate,\n );\n const [\n fetchedProjects,\n fetchedAlerts,\n fetchedMetricData,\n fetchedDailyCost,\n ] = await Promise.all([\n client.getGroupProjects(pageFilters.group),\n client.getAlerts(pageFilters.group),\n pageFilters.metric\n ? client.getDailyMetricData(pageFilters.metric, intervals)\n : null,\n pageFilters.project\n ? client.getProjectDailyCost(pageFilters.project, intervals)\n : client.getGroupDailyCost(pageFilters.group, intervals),\n ]);\n setProjects(fetchedProjects);\n setAlerts(fetchedAlerts);\n setMetricData(fetchedMetricData);\n setDailyCost(fetchedDailyCost);\n } else {\n dispatchLoadingNone(loadingActions);\n }\n } catch (e) {\n setError(e);\n dispatchLoadingNone(loadingActions);\n } finally {\n dispatchLoadingInitial(false);\n dispatchLoadingInsights(false);\n }\n }\n\n // Wait for metadata to finish loading\n if (!(loadingGroups && loadingBillingDate)) {\n getInsights();\n }\n }, [\n client,\n pageFilters,\n loadingActions,\n loadingGroups,\n loadingBillingDate,\n dispatchLoadingInsights,\n dispatchLoadingInitial,\n dispatchLoadingNone,\n lastCompleteBillingDate,\n ]);\n\n if (loadingInitial) {\n return <Progress />;\n }\n\n if (error) {\n return <MaterialAlert severity=\"error\">{error.message}</MaterialAlert>;\n }\n\n // Loaded but no groups found for the user\n if (!pageFilters.group) {\n return (\n <CostInsightsLayout groups={groups}>\n <Box textAlign=\"right\">\n <CopyUrlToClipboard />\n {/* <CostInsightsSupportButton /> */}\n </Box>\n <Container maxWidth=\"lg\">\n <CostInsightsHeaderNoGroups />\n </Container>\n <Divider />\n <Container maxWidth=\"lg\">\n <WhyCostsMatter />\n </Container>\n </CostInsightsLayout>\n );\n }\n\n if (!dailyCost) {\n return (\n <MaterialAlert severity=\"error\">{`Error: Could not fetch cost insights data for team ${pageFilters.group}`}</MaterialAlert>\n );\n }\n\n const onProjectSelect = (project: Maybe<string>) => {\n setProject(project);\n dispatchLoadingReset(loadingActions);\n };\n\n const CostOverviewBanner = () => (\n <Box\n px={3}\n pt={6}\n display=\"flex\"\n justifyContent=\"space-between\"\n alignItems=\"center\"\n minHeight={40}\n >\n <Box>\n <Typography variant=\"h4\">Cost Overview</Typography>\n <Typography classes={classes}>\n Billing data as of {lastCompleteBillingDate}\n </Typography>\n </Box>\n <Box display=\"flex\">\n <Box mr={1}>\n <CurrencySelect\n currency={currency}\n currencies={config.currencies}\n onSelect={setCurrency}\n />\n </Box>\n <ProjectSelect\n project={pageFilters.project}\n projects={projects || []}\n onSelect={onProjectSelect}\n />\n </Box>\n </Box>\n );\n\n return (\n <CostInsightsLayout groups={groups}>\n <Grid container wrap=\"nowrap\">\n {(isProductsDisplayed || isActionItemsDisplayed) && (\n <Grid item>\n <Box position=\"sticky\" top={20}>\n <CostInsightsNavigation\n products={products}\n alerts={active.length}\n />\n </Box>\n </Grid>\n )}\n <Grid item xs>\n <Box\n display=\"flex\"\n flexDirection=\"row\"\n justifyContent=\"flex-end\"\n mb={2}\n >\n <CopyUrlToClipboard />\n {/* <CostInsightsSupportButton /> */}\n </Box>\n <Container maxWidth=\"lg\" disableGutters>\n <Grid container direction=\"column\">\n <Grid item xs>\n <CostInsightsHeader\n groupId={pageFilters.group}\n groups={groups}\n hasCostData={!!dailyCost.aggregation.length}\n alerts={active.length}\n />\n </Grid>\n <Collapse in={isActionItemsDisplayed} enter={false}>\n <Grid item xs>\n <Box px={3} py={6}>\n <ActionItems\n active={active}\n snoozed={snoozed}\n accepted={accepted}\n dismissed={dismissed}\n />\n </Box>\n </Grid>\n <Divider />\n </Collapse>\n <Grid item xs>\n <CostOverviewBanner />\n </Grid>\n <Grid item xs>\n <Box px={3} py={6}>\n {!!dailyCost.aggregation.length && (\n <CostOverviewCard\n dailyCostData={dailyCost}\n metricData={metricData}\n />\n )}\n <WhyCostsMatter />\n </Box>\n </Grid>\n <Collapse in={isAlertInsightsDisplayed} enter={false}>\n <Grid item xs>\n <Box px={6} py={6} mx={-3} bgcolor=\"alertBackground\">\n <AlertInsights\n group={pageFilters.group}\n active={active}\n snoozed={snoozed}\n accepted={accepted}\n dismissed={dismissed}\n onChange={setAlerts}\n />\n </Box>\n </Grid>\n </Collapse>\n {!isAlertInsightsDisplayed && isProductsDisplayed && <Divider />}\n {isProductsDisplayed && (\n <Grid item xs>\n <Box px={3} py={6}>\n <ProductInsights\n group={pageFilters.group}\n project={pageFilters.project}\n products={config.products}\n onLoaded={setProducts}\n />\n </Box>\n </Grid>\n )}\n </Grid>\n </Container>\n </Grid>\n </Grid>\n </CostInsightsLayout>\n );\n};\n"],"names":["MaterialAlert"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEO,MAAM,mBAAmB,MAAM;AACpC,EAAA,MAAM,UAAU,yBAA0B,EAAA;AAC1C,EAAM,MAAA,MAAA,GAAS,OAAO,kBAAkB,CAAA;AACxC,EAAA,MAAM,SAAS,SAAU,EAAA;AACzB,EAAA,MAAM,SAAS,SAAU,EAAA;AACzB,EAAA,MAAM,0BAA0B,0BAA2B,EAAA;AAC3D,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,QAAA,CAAkB,EAAE,CAAA;AAChD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,WAAY,EAAA;AAC5C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAA2B,IAAI,CAAA;AAC/D,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAA2B,IAAI,CAAA;AAC/D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAsB,IAAI,CAAA;AAC5D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAA4B,IAAI,CAAA;AACpE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,EAAE,WAAa,EAAA,cAAA,EAAmB,GAAA,UAAA,CAAW,OAAK,CAAC,CAAA;AAEzD,EAAM,MAAA,MAAA,GAAS,QAAQ,MAAM,MAAA,CAAO,OAAO,aAAa,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACnE,EAAM,MAAA,OAAA,GAAU,QAAQ,MAAM,MAAA,CAAO,OAAO,cAAc,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACrE,EAAM,MAAA,QAAA,GAAW,QAAQ,MAAM,MAAA,CAAO,OAAO,eAAe,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACvE,EAAM,MAAA,SAAA,GAAY,QAAQ,MAAM,MAAA,CAAO,OAAO,gBAAgB,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEzE,EAAA,MAAM,mBAAsB,GAAA,CAAC,CAAC,MAAA,CAAO,QAAU,EAAA,MAAA;AAC/C,EAAM,MAAA,sBAAA,GAAyB,CAAC,CAAC,MAAO,CAAA,MAAA;AACxC,EAAM,MAAA,wBAAA,GAA2B,CAAC,CAAC,MAAO,CAAA,MAAA;AAE1C,EAAM,MAAA;AAAA,IACJ,cAAA;AAAA,IACA,aAAA;AAAA,IACA,kBAAA;AAAA,IACA,cAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,YAAA;AAAA,IACA;AAAA,GACF,GAAI,WAAW,iBAAiB,CAAA;AAShC,EAAA,MAAM,sBAAyB,GAAA,WAAA,CAAY,eAAiB,EAAA,EAAE,CAAA;AAC9D,EAAA,MAAM,uBAA0B,GAAA,WAAA,CAAY,gBAAkB,EAAA,EAAE,CAAA;AAChE,EAAA,MAAM,mBAAsB,GAAA,WAAA,CAAY,YAAc,EAAA,EAAE,CAAA;AACxD,EAAA,MAAM,oBAAuB,GAAA,WAAA,CAAY,aAAe,EAAA,EAAE,CAAA;AAG1D,EAAM,MAAA,UAAA,GAAa,CAAC,OAAA,KAClB,cAAe,CAAA;AAAA,IACb,GAAG,WAAA;AAAA,IACH,OAAA,EAAS,OAAY,KAAA,KAAA,GAAQ,IAAO,GAAA;AAAA,GACrC,CAAA;AAEH,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAe,WAAc,GAAA;AAC3B,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAI,IAAA;AACF,QAAA,IAAI,YAAY,KAAO,EAAA;AACrB,UAAA,uBAAA,CAAwB,IAAI,CAAA;AAC5B,UAAA,MAAM,SAAY,GAAA,WAAA;AAAA,YAChB,WAAY,CAAA,QAAA;AAAA,YACZ;AAAA,WACF;AACA,UAAM,MAAA;AAAA,YACJ,eAAA;AAAA,YACA,aAAA;AAAA,YACA,iBAAA;AAAA,YACA;AAAA,WACF,GAAI,MAAM,OAAA,CAAQ,GAAI,CAAA;AAAA,YACpB,MAAA,CAAO,gBAAiB,CAAA,WAAA,CAAY,KAAK,CAAA;AAAA,YACzC,MAAA,CAAO,SAAU,CAAA,WAAA,CAAY,KAAK,CAAA;AAAA,YAClC,YAAY,MACR,GAAA,MAAA,CAAO,mBAAmB,WAAY,CAAA,MAAA,EAAQ,SAAS,CACvD,GAAA,IAAA;AAAA,YACJ,WAAY,CAAA,OAAA,GACR,MAAO,CAAA,mBAAA,CAAoB,WAAY,CAAA,OAAA,EAAS,SAAS,CAAA,GACzD,MAAO,CAAA,iBAAA,CAAkB,WAAY,CAAA,KAAA,EAAO,SAAS;AAAA,WAC1D,CAAA;AACD,UAAA,WAAA,CAAY,eAAe,CAAA;AAC3B,UAAA,SAAA,CAAU,aAAa,CAAA;AACvB,UAAA,aAAA,CAAc,iBAAiB,CAAA;AAC/B,UAAA,YAAA,CAAa,gBAAgB,CAAA;AAAA,SACxB,MAAA;AACL,UAAA,mBAAA,CAAoB,cAAc,CAAA;AAAA;AACpC,eACO,CAAG,EAAA;AACV,QAAA,QAAA,CAAS,CAAC,CAAA;AACV,QAAA,mBAAA,CAAoB,cAAc,CAAA;AAAA,OAClC,SAAA;AACA,QAAA,sBAAA,CAAuB,KAAK,CAAA;AAC5B,QAAA,uBAAA,CAAwB,KAAK,CAAA;AAAA;AAC/B;AAIF,IAAI,IAAA,EAAE,iBAAiB,kBAAqB,CAAA,EAAA;AAC1C,MAAY,WAAA,EAAA;AAAA;AACd,GACC,EAAA;AAAA,IACD,MAAA;AAAA,IACA,WAAA;AAAA,IACA,cAAA;AAAA,IACA,aAAA;AAAA,IACA,kBAAA;AAAA,IACA,uBAAA;AAAA,IACA,sBAAA;AAAA,IACA,mBAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,cAAgB,EAAA;AAClB,IAAA,2BAAQ,QAAS,EAAA,EAAA,CAAA;AAAA;AAGnB,EAAA,IAAI,KAAO,EAAA;AACT,IAAA,uBAAQ,GAAA,CAAAA,KAAA,EAAA,EAAc,QAAS,EAAA,OAAA,EAAS,gBAAM,OAAQ,EAAA,CAAA;AAAA;AAIxD,EAAI,IAAA,CAAC,YAAY,KAAO,EAAA;AACtB,IACE,uBAAA,IAAA,CAAC,sBAAmB,MAClB,EAAA,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,GAAI,EAAA,EAAA,SAAA,EAAU,OACb,EAAA,QAAA,kBAAA,GAAA,CAAC,sBAAmB,CAEtB,EAAA,CAAA;AAAA,0BACC,SAAU,EAAA,EAAA,QAAA,EAAS,IAClB,EAAA,QAAA,kBAAA,GAAA,CAAC,8BAA2B,CAC9B,EAAA,CAAA;AAAA,0BACC,OAAQ,EAAA,EAAA,CAAA;AAAA,0BACR,SAAU,EAAA,EAAA,QAAA,EAAS,IAClB,EAAA,QAAA,kBAAA,GAAA,CAAC,kBAAe,CAClB,EAAA;AAAA,KACF,EAAA,CAAA;AAAA;AAIJ,EAAA,IAAI,CAAC,SAAW,EAAA;AACd,IAAA,2BACGA,KAAc,EAAA,EAAA,QAAA,EAAS,SAAS,QAAsD,EAAA,CAAA,mDAAA,EAAA,WAAA,CAAY,KAAK,CAAG,CAAA,EAAA,CAAA;AAAA;AAI/G,EAAM,MAAA,eAAA,GAAkB,CAAC,OAA2B,KAAA;AAClD,IAAA,UAAA,CAAW,OAAO,CAAA;AAClB,IAAA,oBAAA,CAAqB,cAAc,CAAA;AAAA,GACrC;AAEA,EAAA,MAAM,qBAAqB,sBACzB,IAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,EAAI,EAAA,CAAA;AAAA,MACJ,EAAI,EAAA,CAAA;AAAA,MACJ,OAAQ,EAAA,MAAA;AAAA,MACR,cAAe,EAAA,eAAA;AAAA,MACf,UAAW,EAAA,QAAA;AAAA,MACX,SAAW,EAAA,EAAA;AAAA,MAEX,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,GACC,EAAA,EAAA,QAAA,EAAA;AAAA,0BAAC,GAAA,CAAA,UAAA,EAAA,EAAW,OAAQ,EAAA,IAAA,EAAK,QAAa,EAAA,eAAA,EAAA,CAAA;AAAA,0BACtC,IAAA,CAAC,cAAW,OAAkB,EAAA,QAAA,EAAA;AAAA,YAAA,qBAAA;AAAA,YACR;AAAA,WACtB,EAAA;AAAA,SACF,EAAA,CAAA;AAAA,wBACA,IAAA,CAAC,GAAI,EAAA,EAAA,OAAA,EAAQ,MACX,EAAA,QAAA,EAAA;AAAA,0BAAC,GAAA,CAAA,GAAA,EAAA,EAAI,IAAI,CACP,EAAA,QAAA,kBAAA,GAAA;AAAA,YAAC,cAAA;AAAA,YAAA;AAAA,cACC,QAAA;AAAA,cACA,YAAY,MAAO,CAAA,UAAA;AAAA,cACnB,QAAU,EAAA;AAAA;AAAA,WAEd,EAAA,CAAA;AAAA,0BACA,GAAA;AAAA,YAAC,aAAA;AAAA,YAAA;AAAA,cACC,SAAS,WAAY,CAAA,OAAA;AAAA,cACrB,QAAA,EAAU,YAAY,EAAC;AAAA,cACvB,QAAU,EAAA;AAAA;AAAA;AACZ,SACF,EAAA;AAAA;AAAA;AAAA,GACF;AAGF,EACE,uBAAA,GAAA,CAAC,sBAAmB,MAClB,EAAA,QAAA,kBAAA,IAAA,CAAC,QAAK,SAAS,EAAA,IAAA,EAAC,MAAK,QACjB,EAAA,QAAA,EAAA;AAAA,IAAuB,CAAA,mBAAA,IAAA,sBAAA,qBACtB,GAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EACR,8BAAC,GAAI,EAAA,EAAA,QAAA,EAAS,QAAS,EAAA,GAAA,EAAK,EAC1B,EAAA,QAAA,kBAAA,GAAA;AAAA,MAAC,sBAAA;AAAA,MAAA;AAAA,QACC,QAAA;AAAA,QACA,QAAQ,MAAO,CAAA;AAAA;AAAA,OAEnB,CACF,EAAA,CAAA;AAAA,oBAED,IAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,IAAE,IACX,EAAA,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UACC,OAAQ,EAAA,MAAA;AAAA,UACR,aAAc,EAAA,KAAA;AAAA,UACd,cAAe,EAAA,UAAA;AAAA,UACf,EAAI,EAAA,CAAA;AAAA,UAEJ,8BAAC,kBAAmB,EAAA,EAAA;AAAA;AAAA,OAEtB;AAAA,sBACA,GAAA,CAAC,SAAU,EAAA,EAAA,QAAA,EAAS,IAAK,EAAA,cAAA,EAAc,IACrC,EAAA,QAAA,kBAAA,IAAA,CAAC,IAAK,EAAA,EAAA,SAAA,EAAS,IAAC,EAAA,SAAA,EAAU,QACxB,EAAA,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,IAAK,EAAA,EAAA,IAAA,EAAI,IAAC,EAAA,EAAA,EAAE,IACX,EAAA,QAAA,kBAAA,GAAA;AAAA,UAAC,kBAAA;AAAA,UAAA;AAAA,YACC,SAAS,WAAY,CAAA,KAAA;AAAA,YACrB,MAAA;AAAA,YACA,WAAa,EAAA,CAAC,CAAC,SAAA,CAAU,WAAY,CAAA,MAAA;AAAA,YACrC,QAAQ,MAAO,CAAA;AAAA;AAAA,SAEnB,EAAA,CAAA;AAAA,wBACC,IAAA,CAAA,QAAA,EAAA,EAAS,EAAI,EAAA,sBAAA,EAAwB,OAAO,KAC3C,EAAA,QAAA,EAAA;AAAA,0BAAC,GAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAE,EAAA,IAAA,EACX,8BAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CAAG,EAAA,EAAA,EAAI,CACd,EAAA,QAAA,kBAAA,GAAA;AAAA,YAAC,WAAA;AAAA,YAAA;AAAA,cACC,MAAA;AAAA,cACA,OAAA;AAAA,cACA,QAAA;AAAA,cACA;AAAA;AAAA,aAEJ,CACF,EAAA,CAAA;AAAA,8BACC,OAAQ,EAAA,EAAA;AAAA,SACX,EAAA,CAAA;AAAA,wBACA,GAAA,CAAC,QAAK,IAAI,EAAA,IAAA,EAAC,IAAE,IACX,EAAA,QAAA,kBAAA,GAAA,CAAC,sBAAmB,CACtB,EAAA,CAAA;AAAA,wBACA,GAAA,CAAC,IAAK,EAAA,EAAA,IAAA,EAAI,IAAC,EAAA,EAAA,EAAE,IACX,EAAA,QAAA,kBAAA,IAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CAAG,EAAA,EAAA,EAAI,CACb,EAAA,QAAA,EAAA;AAAA,UAAC,CAAA,CAAC,SAAU,CAAA,WAAA,CAAY,MACvB,oBAAA,GAAA;AAAA,YAAC,gBAAA;AAAA,YAAA;AAAA,cACC,aAAe,EAAA,SAAA;AAAA,cACf;AAAA;AAAA,WACF;AAAA,8BAED,cAAe,EAAA,EAAA;AAAA,SAAA,EAClB,CACF,EAAA,CAAA;AAAA,wBACA,GAAA,CAAC,YAAS,EAAI,EAAA,wBAAA,EAA0B,OAAO,KAC7C,EAAA,QAAA,kBAAA,GAAA,CAAC,QAAK,IAAI,EAAA,IAAA,EAAC,IAAE,IACX,EAAA,QAAA,kBAAA,GAAA,CAAC,OAAI,EAAI,EAAA,CAAA,EAAG,IAAI,CAAG,EAAA,EAAA,EAAI,CAAI,CAAA,EAAA,OAAA,EAAQ,iBACjC,EAAA,QAAA,kBAAA,GAAA;AAAA,UAAC,aAAA;AAAA,UAAA;AAAA,YACC,OAAO,WAAY,CAAA,KAAA;AAAA,YACnB,MAAA;AAAA,YACA,OAAA;AAAA,YACA,QAAA;AAAA,YACA,SAAA;AAAA,YACA,QAAU,EAAA;AAAA;AAAA,SACZ,EACF,GACF,CACF,EAAA,CAAA;AAAA,QACC,CAAC,wBAAA,IAA4B,mBAAuB,oBAAA,GAAA,CAAC,OAAQ,EAAA,EAAA,CAAA;AAAA,QAC7D,mBACC,oBAAA,GAAA,CAAC,IAAK,EAAA,EAAA,IAAA,EAAI,IAAC,EAAA,EAAA,EAAE,IACX,EAAA,QAAA,kBAAA,GAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CAAG,EAAA,EAAA,EAAI,CACd,EAAA,QAAA,kBAAA,GAAA;AAAA,UAAC,eAAA;AAAA,UAAA;AAAA,YACC,OAAO,WAAY,CAAA,KAAA;AAAA,YACnB,SAAS,WAAY,CAAA,OAAA;AAAA,YACrB,UAAU,MAAO,CAAA,QAAA;AAAA,YACjB,QAAU,EAAA;AAAA;AAAA,WAEd,CACF,EAAA;AAAA,OAAA,EAEJ,CACF,EAAA;AAAA,KACF,EAAA;AAAA,GAAA,EACF,CACF,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"CostInsightsPage.esm.js","sources":["../../../src/components/CostInsightsPage/CostInsightsPage.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport Box from '@material-ui/core/Box';\nimport Collapse from '@material-ui/core/Collapse';\nimport Container from '@material-ui/core/Container';\nimport Divider from '@material-ui/core/Divider';\nimport Grid from '@material-ui/core/Grid';\nimport Typography from '@material-ui/core/Typography';\nimport { default as MaterialAlert } from '@material-ui/lab/Alert';\nimport { costInsightsApiRef } from '../../api';\nimport { ActionItems } from '../ActionItems';\nimport { AlertInsights } from '../AlertInsights';\nimport { CostInsightsLayout } from '../CostInsightsLayout';\nimport { CopyUrlToClipboard } from '../CopyUrlToClipboard';\nimport { CurrencySelect } from '../CurrencySelect';\nimport { WhyCostsMatter } from '../WhyCostsMatter';\nimport {\n CostInsightsHeader,\n CostInsightsHeaderNoGroups,\n} from '../CostInsightsHeader';\nimport { CostInsightsNavigation } from '../CostInsightsNavigation';\nimport { CostOverviewCard } from '../CostOverviewCard';\nimport { ProductInsights } from '../ProductInsights';\n/* https://github.com/backstage/backstage/issues/2574 */\n// import { CostInsightsSupportButton } from '../CostInsightsSupportButton';\nimport {\n useConfig,\n useCurrency,\n useFilters,\n useGroups,\n useLastCompleteBillingDate,\n useLoading,\n} from '../../hooks';\nimport { Alert } from '../../types';\nimport {\n Cost,\n Maybe,\n MetricData,\n Product,\n Project,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { mapLoadingToProps } from './selector';\nimport { ProjectSelect } from '../ProjectSelect';\nimport { intervalsOf } from '../../utils/duration';\nimport { useSubtleTypographyStyles } from '../../utils/styles';\nimport {\n isAlertActive,\n isAlertAccepted,\n isAlertDismissed,\n isAlertSnoozed,\n} from '../../utils/alerts';\n\nimport { Progress } from '@backstage/core-components';\nimport { useApi } from '@backstage/core-plugin-api';\n\nexport const CostInsightsPage = () => {\n const classes = useSubtleTypographyStyles();\n const client = useApi(costInsightsApiRef);\n const config = useConfig();\n const groups = useGroups();\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const [alerts, setAlerts] = useState<Alert[]>([]);\n const [currency, setCurrency] = useCurrency();\n const [projects, setProjects] = useState<Maybe<Project[]>>(null);\n const [products, setProducts] = useState<Maybe<Product[]>>(null);\n const [dailyCost, setDailyCost] = useState<Maybe<Cost>>(null);\n const [metricData, setMetricData] = useState<Maybe<MetricData>>(null);\n const [error, setError] = useState<Maybe<Error>>(null);\n\n const { pageFilters, setPageFilters } = useFilters(p => p);\n\n const active = useMemo(() => alerts.filter(isAlertActive), [alerts]);\n const snoozed = useMemo(() => alerts.filter(isAlertSnoozed), [alerts]);\n const accepted = useMemo(() => alerts.filter(isAlertAccepted), [alerts]);\n const dismissed = useMemo(() => alerts.filter(isAlertDismissed), [alerts]);\n\n const isProductsDisplayed = !!config.products?.length;\n const isActionItemsDisplayed = !!active.length;\n const isAlertInsightsDisplayed = !!alerts.length;\n\n const {\n loadingActions,\n loadingGroups,\n loadingBillingDate,\n loadingInitial,\n dispatchInitial,\n dispatchInsights,\n dispatchNone,\n dispatchReset,\n } = useLoading(mapLoadingToProps);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n // The dispatchLoading functions are derived from loading state using mapLoadingToProps, to\n // provide nicer props for the component. These are re-derived whenever loading state changes,\n // which causes an infinite loop as product panels load and re-trigger the useEffect below.\n // Since the functions don't change, we can memoize - but we trigger the same loop if we satisfy\n // exhaustive-deps by including the function itself in dependencies.\n\n const dispatchLoadingInitial = useCallback(dispatchInitial, []);\n const dispatchLoadingInsights = useCallback(dispatchInsights, []);\n const dispatchLoadingNone = useCallback(dispatchNone, []);\n const dispatchLoadingReset = useCallback(dispatchReset, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n const setProject = (project: Maybe<string>) =>\n setPageFilters({\n ...pageFilters,\n project: project === 'all' ? null : project,\n });\n\n useEffect(() => {\n async function getInsights() {\n setError(null);\n try {\n if (pageFilters.group) {\n dispatchLoadingInsights(true);\n const intervals = intervalsOf(\n pageFilters.duration,\n lastCompleteBillingDate,\n 2,\n pageFilters.customDateRange,\n false, // Don't use comparison mode for the main cost overview graph\n );\n const [\n fetchedProjects,\n fetchedAlerts,\n fetchedMetricData,\n fetchedDailyCost,\n ] = await Promise.all([\n client.getGroupProjects(pageFilters.group),\n client.getAlerts(pageFilters.group),\n pageFilters.metric\n ? client.getDailyMetricData(pageFilters.metric, intervals)\n : null,\n pageFilters.project\n ? client.getProjectDailyCost(pageFilters.project, intervals)\n : client.getGroupDailyCost(pageFilters.group, intervals),\n ]);\n setProjects(fetchedProjects);\n setAlerts(fetchedAlerts);\n setMetricData(fetchedMetricData);\n setDailyCost(fetchedDailyCost);\n } else {\n dispatchLoadingNone(loadingActions);\n }\n } catch (e) {\n setError(e);\n dispatchLoadingNone(loadingActions);\n } finally {\n dispatchLoadingInitial(false);\n dispatchLoadingInsights(false);\n }\n }\n\n // Wait for metadata to finish loading\n if (!(loadingGroups && loadingBillingDate)) {\n getInsights();\n }\n }, [\n client,\n pageFilters,\n loadingActions,\n loadingGroups,\n loadingBillingDate,\n dispatchLoadingInsights,\n dispatchLoadingInitial,\n dispatchLoadingNone,\n lastCompleteBillingDate,\n ]);\n\n if (loadingInitial) {\n return <Progress />;\n }\n\n if (error) {\n return <MaterialAlert severity=\"error\">{error.message}</MaterialAlert>;\n }\n\n // Loaded but no groups found for the user\n if (!pageFilters.group) {\n return (\n <CostInsightsLayout groups={groups}>\n <Box textAlign=\"right\">\n <CopyUrlToClipboard />\n {/* <CostInsightsSupportButton /> */}\n </Box>\n <Container maxWidth=\"lg\">\n <CostInsightsHeaderNoGroups />\n </Container>\n <Divider />\n <Container maxWidth=\"lg\">\n <WhyCostsMatter />\n </Container>\n </CostInsightsLayout>\n );\n }\n\n if (!dailyCost) {\n return (\n <MaterialAlert severity=\"error\">{`Error: Could not fetch cost insights data for team ${pageFilters.group}`}</MaterialAlert>\n );\n }\n\n const onProjectSelect = (project: Maybe<string>) => {\n setProject(project);\n dispatchLoadingReset(loadingActions);\n };\n\n const CostOverviewBanner = () => (\n <Box\n px={3}\n pt={6}\n display=\"flex\"\n justifyContent=\"space-between\"\n alignItems=\"center\"\n minHeight={40}\n >\n <Box>\n <Typography variant=\"h4\">Cost Overview</Typography>\n <Typography classes={classes}>\n Billing data as of {lastCompleteBillingDate}\n </Typography>\n </Box>\n <Box display=\"flex\">\n <Box mr={1}>\n <CurrencySelect\n currency={currency}\n currencies={config.currencies}\n onSelect={setCurrency}\n />\n </Box>\n <ProjectSelect\n project={pageFilters.project}\n projects={projects || []}\n onSelect={onProjectSelect}\n />\n </Box>\n </Box>\n );\n\n return (\n <CostInsightsLayout groups={groups}>\n <Grid container wrap=\"nowrap\">\n {(isProductsDisplayed || isActionItemsDisplayed) && (\n <Grid item>\n <Box position=\"sticky\" top={20}>\n <CostInsightsNavigation\n products={products}\n alerts={active.length}\n />\n </Box>\n </Grid>\n )}\n <Grid item xs>\n <Box\n display=\"flex\"\n flexDirection=\"row\"\n justifyContent=\"flex-end\"\n mb={2}\n >\n <CopyUrlToClipboard />\n {/* <CostInsightsSupportButton /> */}\n </Box>\n <Container maxWidth=\"lg\" disableGutters>\n <Grid container direction=\"column\">\n <Grid item xs>\n <CostInsightsHeader\n groupId={pageFilters.group}\n groups={groups}\n hasCostData={!!dailyCost.aggregation.length}\n alerts={active.length}\n />\n </Grid>\n <Collapse in={isActionItemsDisplayed} enter={false}>\n <Grid item xs>\n <Box px={3} py={6}>\n <ActionItems\n active={active}\n snoozed={snoozed}\n accepted={accepted}\n dismissed={dismissed}\n />\n </Box>\n </Grid>\n <Divider />\n </Collapse>\n <Grid item xs>\n <CostOverviewBanner />\n </Grid>\n <Grid item xs>\n <Box px={3} py={6}>\n {!!dailyCost.aggregation.length && (\n <CostOverviewCard\n dailyCostData={dailyCost}\n metricData={metricData}\n />\n )}\n <WhyCostsMatter />\n </Box>\n </Grid>\n <Collapse in={isAlertInsightsDisplayed} enter={false}>\n <Grid item xs>\n <Box px={6} py={6} mx={-3} bgcolor=\"alertBackground\">\n <AlertInsights\n group={pageFilters.group}\n active={active}\n snoozed={snoozed}\n accepted={accepted}\n dismissed={dismissed}\n onChange={setAlerts}\n />\n </Box>\n </Grid>\n </Collapse>\n {!isAlertInsightsDisplayed && isProductsDisplayed && <Divider />}\n {isProductsDisplayed && (\n <Grid item xs>\n <Box px={3} py={6}>\n <ProductInsights\n group={pageFilters.group}\n project={pageFilters.project}\n products={config.products}\n onLoaded={setProducts}\n />\n </Box>\n </Grid>\n )}\n </Grid>\n </Container>\n </Grid>\n </Grid>\n </CostInsightsLayout>\n );\n};\n"],"names":["MaterialAlert"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEO,MAAM,mBAAmB,MAAM;AACpC,EAAA,MAAM,UAAU,yBAA0B,EAAA;AAC1C,EAAM,MAAA,MAAA,GAAS,OAAO,kBAAkB,CAAA;AACxC,EAAA,MAAM,SAAS,SAAU,EAAA;AACzB,EAAA,MAAM,SAAS,SAAU,EAAA;AACzB,EAAA,MAAM,0BAA0B,0BAA2B,EAAA;AAC3D,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,QAAA,CAAkB,EAAE,CAAA;AAChD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,WAAY,EAAA;AAC5C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAA2B,IAAI,CAAA;AAC/D,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAA2B,IAAI,CAAA;AAC/D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAsB,IAAI,CAAA;AAC5D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAA4B,IAAI,CAAA;AACpE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,EAAE,WAAa,EAAA,cAAA,EAAmB,GAAA,UAAA,CAAW,OAAK,CAAC,CAAA;AAEzD,EAAM,MAAA,MAAA,GAAS,QAAQ,MAAM,MAAA,CAAO,OAAO,aAAa,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACnE,EAAM,MAAA,OAAA,GAAU,QAAQ,MAAM,MAAA,CAAO,OAAO,cAAc,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACrE,EAAM,MAAA,QAAA,GAAW,QAAQ,MAAM,MAAA,CAAO,OAAO,eAAe,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACvE,EAAM,MAAA,SAAA,GAAY,QAAQ,MAAM,MAAA,CAAO,OAAO,gBAAgB,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEzE,EAAA,MAAM,mBAAsB,GAAA,CAAC,CAAC,MAAA,CAAO,QAAU,EAAA,MAAA;AAC/C,EAAM,MAAA,sBAAA,GAAyB,CAAC,CAAC,MAAO,CAAA,MAAA;AACxC,EAAM,MAAA,wBAAA,GAA2B,CAAC,CAAC,MAAO,CAAA,MAAA;AAE1C,EAAM,MAAA;AAAA,IACJ,cAAA;AAAA,IACA,aAAA;AAAA,IACA,kBAAA;AAAA,IACA,cAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,YAAA;AAAA,IACA;AAAA,GACF,GAAI,WAAW,iBAAiB,CAAA;AAShC,EAAA,MAAM,sBAAyB,GAAA,WAAA,CAAY,eAAiB,EAAA,EAAE,CAAA;AAC9D,EAAA,MAAM,uBAA0B,GAAA,WAAA,CAAY,gBAAkB,EAAA,EAAE,CAAA;AAChE,EAAA,MAAM,mBAAsB,GAAA,WAAA,CAAY,YAAc,EAAA,EAAE,CAAA;AACxD,EAAA,MAAM,oBAAuB,GAAA,WAAA,CAAY,aAAe,EAAA,EAAE,CAAA;AAG1D,EAAM,MAAA,UAAA,GAAa,CAAC,OAAA,KAClB,cAAe,CAAA;AAAA,IACb,GAAG,WAAA;AAAA,IACH,OAAA,EAAS,OAAY,KAAA,KAAA,GAAQ,IAAO,GAAA;AAAA,GACrC,CAAA;AAEH,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAe,WAAc,GAAA;AAC3B,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAI,IAAA;AACF,QAAA,IAAI,YAAY,KAAO,EAAA;AACrB,UAAA,uBAAA,CAAwB,IAAI,CAAA;AAC5B,UAAA,MAAM,SAAY,GAAA,WAAA;AAAA,YAChB,WAAY,CAAA,QAAA;AAAA,YACZ,uBAAA;AAAA,YACA,CAAA;AAAA,YACA,WAAY,CAAA,eAAA;AAAA,YACZ;AAAA;AAAA,WACF;AACA,UAAM,MAAA;AAAA,YACJ,eAAA;AAAA,YACA,aAAA;AAAA,YACA,iBAAA;AAAA,YACA;AAAA,WACF,GAAI,MAAM,OAAA,CAAQ,GAAI,CAAA;AAAA,YACpB,MAAA,CAAO,gBAAiB,CAAA,WAAA,CAAY,KAAK,CAAA;AAAA,YACzC,MAAA,CAAO,SAAU,CAAA,WAAA,CAAY,KAAK,CAAA;AAAA,YAClC,YAAY,MACR,GAAA,MAAA,CAAO,mBAAmB,WAAY,CAAA,MAAA,EAAQ,SAAS,CACvD,GAAA,IAAA;AAAA,YACJ,WAAY,CAAA,OAAA,GACR,MAAO,CAAA,mBAAA,CAAoB,WAAY,CAAA,OAAA,EAAS,SAAS,CAAA,GACzD,MAAO,CAAA,iBAAA,CAAkB,WAAY,CAAA,KAAA,EAAO,SAAS;AAAA,WAC1D,CAAA;AACD,UAAA,WAAA,CAAY,eAAe,CAAA;AAC3B,UAAA,SAAA,CAAU,aAAa,CAAA;AACvB,UAAA,aAAA,CAAc,iBAAiB,CAAA;AAC/B,UAAA,YAAA,CAAa,gBAAgB,CAAA;AAAA,SACxB,MAAA;AACL,UAAA,mBAAA,CAAoB,cAAc,CAAA;AAAA;AACpC,eACO,CAAG,EAAA;AACV,QAAA,QAAA,CAAS,CAAC,CAAA;AACV,QAAA,mBAAA,CAAoB,cAAc,CAAA;AAAA,OAClC,SAAA;AACA,QAAA,sBAAA,CAAuB,KAAK,CAAA;AAC5B,QAAA,uBAAA,CAAwB,KAAK,CAAA;AAAA;AAC/B;AAIF,IAAI,IAAA,EAAE,iBAAiB,kBAAqB,CAAA,EAAA;AAC1C,MAAY,WAAA,EAAA;AAAA;AACd,GACC,EAAA;AAAA,IACD,MAAA;AAAA,IACA,WAAA;AAAA,IACA,cAAA;AAAA,IACA,aAAA;AAAA,IACA,kBAAA;AAAA,IACA,uBAAA;AAAA,IACA,sBAAA;AAAA,IACA,mBAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,cAAgB,EAAA;AAClB,IAAA,2BAAQ,QAAS,EAAA,EAAA,CAAA;AAAA;AAGnB,EAAA,IAAI,KAAO,EAAA;AACT,IAAA,uBAAQ,GAAA,CAAAA,KAAA,EAAA,EAAc,QAAS,EAAA,OAAA,EAAS,gBAAM,OAAQ,EAAA,CAAA;AAAA;AAIxD,EAAI,IAAA,CAAC,YAAY,KAAO,EAAA;AACtB,IACE,uBAAA,IAAA,CAAC,sBAAmB,MAClB,EAAA,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,GAAI,EAAA,EAAA,SAAA,EAAU,OACb,EAAA,QAAA,kBAAA,GAAA,CAAC,sBAAmB,CAEtB,EAAA,CAAA;AAAA,0BACC,SAAU,EAAA,EAAA,QAAA,EAAS,IAClB,EAAA,QAAA,kBAAA,GAAA,CAAC,8BAA2B,CAC9B,EAAA,CAAA;AAAA,0BACC,OAAQ,EAAA,EAAA,CAAA;AAAA,0BACR,SAAU,EAAA,EAAA,QAAA,EAAS,IAClB,EAAA,QAAA,kBAAA,GAAA,CAAC,kBAAe,CAClB,EAAA;AAAA,KACF,EAAA,CAAA;AAAA;AAIJ,EAAA,IAAI,CAAC,SAAW,EAAA;AACd,IAAA,2BACGA,KAAc,EAAA,EAAA,QAAA,EAAS,SAAS,QAAsD,EAAA,CAAA,mDAAA,EAAA,WAAA,CAAY,KAAK,CAAG,CAAA,EAAA,CAAA;AAAA;AAI/G,EAAM,MAAA,eAAA,GAAkB,CAAC,OAA2B,KAAA;AAClD,IAAA,UAAA,CAAW,OAAO,CAAA;AAClB,IAAA,oBAAA,CAAqB,cAAc,CAAA;AAAA,GACrC;AAEA,EAAA,MAAM,qBAAqB,sBACzB,IAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,EAAI,EAAA,CAAA;AAAA,MACJ,EAAI,EAAA,CAAA;AAAA,MACJ,OAAQ,EAAA,MAAA;AAAA,MACR,cAAe,EAAA,eAAA;AAAA,MACf,UAAW,EAAA,QAAA;AAAA,MACX,SAAW,EAAA,EAAA;AAAA,MAEX,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,GACC,EAAA,EAAA,QAAA,EAAA;AAAA,0BAAC,GAAA,CAAA,UAAA,EAAA,EAAW,OAAQ,EAAA,IAAA,EAAK,QAAa,EAAA,eAAA,EAAA,CAAA;AAAA,0BACtC,IAAA,CAAC,cAAW,OAAkB,EAAA,QAAA,EAAA;AAAA,YAAA,qBAAA;AAAA,YACR;AAAA,WACtB,EAAA;AAAA,SACF,EAAA,CAAA;AAAA,wBACA,IAAA,CAAC,GAAI,EAAA,EAAA,OAAA,EAAQ,MACX,EAAA,QAAA,EAAA;AAAA,0BAAC,GAAA,CAAA,GAAA,EAAA,EAAI,IAAI,CACP,EAAA,QAAA,kBAAA,GAAA;AAAA,YAAC,cAAA;AAAA,YAAA;AAAA,cACC,QAAA;AAAA,cACA,YAAY,MAAO,CAAA,UAAA;AAAA,cACnB,QAAU,EAAA;AAAA;AAAA,WAEd,EAAA,CAAA;AAAA,0BACA,GAAA;AAAA,YAAC,aAAA;AAAA,YAAA;AAAA,cACC,SAAS,WAAY,CAAA,OAAA;AAAA,cACrB,QAAA,EAAU,YAAY,EAAC;AAAA,cACvB,QAAU,EAAA;AAAA;AAAA;AACZ,SACF,EAAA;AAAA;AAAA;AAAA,GACF;AAGF,EACE,uBAAA,GAAA,CAAC,sBAAmB,MAClB,EAAA,QAAA,kBAAA,IAAA,CAAC,QAAK,SAAS,EAAA,IAAA,EAAC,MAAK,QACjB,EAAA,QAAA,EAAA;AAAA,IAAuB,CAAA,mBAAA,IAAA,sBAAA,qBACtB,GAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EACR,8BAAC,GAAI,EAAA,EAAA,QAAA,EAAS,QAAS,EAAA,GAAA,EAAK,EAC1B,EAAA,QAAA,kBAAA,GAAA;AAAA,MAAC,sBAAA;AAAA,MAAA;AAAA,QACC,QAAA;AAAA,QACA,QAAQ,MAAO,CAAA;AAAA;AAAA,OAEnB,CACF,EAAA,CAAA;AAAA,oBAED,IAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,IAAE,IACX,EAAA,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UACC,OAAQ,EAAA,MAAA;AAAA,UACR,aAAc,EAAA,KAAA;AAAA,UACd,cAAe,EAAA,UAAA;AAAA,UACf,EAAI,EAAA,CAAA;AAAA,UAEJ,8BAAC,kBAAmB,EAAA,EAAA;AAAA;AAAA,OAEtB;AAAA,sBACA,GAAA,CAAC,SAAU,EAAA,EAAA,QAAA,EAAS,IAAK,EAAA,cAAA,EAAc,IACrC,EAAA,QAAA,kBAAA,IAAA,CAAC,IAAK,EAAA,EAAA,SAAA,EAAS,IAAC,EAAA,SAAA,EAAU,QACxB,EAAA,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,IAAK,EAAA,EAAA,IAAA,EAAI,IAAC,EAAA,EAAA,EAAE,IACX,EAAA,QAAA,kBAAA,GAAA;AAAA,UAAC,kBAAA;AAAA,UAAA;AAAA,YACC,SAAS,WAAY,CAAA,KAAA;AAAA,YACrB,MAAA;AAAA,YACA,WAAa,EAAA,CAAC,CAAC,SAAA,CAAU,WAAY,CAAA,MAAA;AAAA,YACrC,QAAQ,MAAO,CAAA;AAAA;AAAA,SAEnB,EAAA,CAAA;AAAA,wBACC,IAAA,CAAA,QAAA,EAAA,EAAS,EAAI,EAAA,sBAAA,EAAwB,OAAO,KAC3C,EAAA,QAAA,EAAA;AAAA,0BAAC,GAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAE,EAAA,IAAA,EACX,8BAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CAAG,EAAA,EAAA,EAAI,CACd,EAAA,QAAA,kBAAA,GAAA;AAAA,YAAC,WAAA;AAAA,YAAA;AAAA,cACC,MAAA;AAAA,cACA,OAAA;AAAA,cACA,QAAA;AAAA,cACA;AAAA;AAAA,aAEJ,CACF,EAAA,CAAA;AAAA,8BACC,OAAQ,EAAA,EAAA;AAAA,SACX,EAAA,CAAA;AAAA,wBACA,GAAA,CAAC,QAAK,IAAI,EAAA,IAAA,EAAC,IAAE,IACX,EAAA,QAAA,kBAAA,GAAA,CAAC,sBAAmB,CACtB,EAAA,CAAA;AAAA,wBACA,GAAA,CAAC,IAAK,EAAA,EAAA,IAAA,EAAI,IAAC,EAAA,EAAA,EAAE,IACX,EAAA,QAAA,kBAAA,IAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CAAG,EAAA,EAAA,EAAI,CACb,EAAA,QAAA,EAAA;AAAA,UAAC,CAAA,CAAC,SAAU,CAAA,WAAA,CAAY,MACvB,oBAAA,GAAA;AAAA,YAAC,gBAAA;AAAA,YAAA;AAAA,cACC,aAAe,EAAA,SAAA;AAAA,cACf;AAAA;AAAA,WACF;AAAA,8BAED,cAAe,EAAA,EAAA;AAAA,SAAA,EAClB,CACF,EAAA,CAAA;AAAA,wBACA,GAAA,CAAC,YAAS,EAAI,EAAA,wBAAA,EAA0B,OAAO,KAC7C,EAAA,QAAA,kBAAA,GAAA,CAAC,QAAK,IAAI,EAAA,IAAA,EAAC,IAAE,IACX,EAAA,QAAA,kBAAA,GAAA,CAAC,OAAI,EAAI,EAAA,CAAA,EAAG,IAAI,CAAG,EAAA,EAAA,EAAI,CAAI,CAAA,EAAA,OAAA,EAAQ,iBACjC,EAAA,QAAA,kBAAA,GAAA;AAAA,UAAC,aAAA;AAAA,UAAA;AAAA,YACC,OAAO,WAAY,CAAA,KAAA;AAAA,YACnB,MAAA;AAAA,YACA,OAAA;AAAA,YACA,QAAA;AAAA,YACA,SAAA;AAAA,YACA,QAAU,EAAA;AAAA;AAAA,SACZ,EACF,GACF,CACF,EAAA,CAAA;AAAA,QACC,CAAC,wBAAA,IAA4B,mBAAuB,oBAAA,GAAA,CAAC,OAAQ,EAAA,EAAA,CAAA;AAAA,QAC7D,mBACC,oBAAA,GAAA,CAAC,IAAK,EAAA,EAAA,IAAA,EAAI,IAAC,EAAA,EAAA,EAAE,IACX,EAAA,QAAA,kBAAA,GAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CAAG,EAAA,EAAA,EAAI,CACd,EAAA,QAAA,kBAAA,GAAA;AAAA,UAAC,eAAA;AAAA,UAAA;AAAA,YACC,OAAO,WAAY,CAAA,KAAA;AAAA,YACnB,SAAS,WAAY,CAAA,OAAA;AAAA,YACrB,UAAU,MAAO,CAAA,QAAA;AAAA,YACjB,QAAU,EAAA;AAAA;AAAA,WAEd,CACF,EAAA;AAAA,OAAA,EAEJ,CACF,EAAA;AAAA,KACF,EAAA;AAAA,GAAA,EACF,CACF,EAAA,CAAA;AAEJ;;;;"}
@@ -10,6 +10,7 @@ import FullScreenIcon from '@material-ui/icons/Fullscreen';
10
10
  import { ResponsiveContainer, AreaChart, CartesianGrid, XAxis, YAxis, Tooltip, Area } from 'recharts';
11
11
  import '../../types/Alert.esm.js';
12
12
  import { DEFAULT_DATE_FORMAT } from '../../types/DateFormat.esm.js';
13
+ import { Duration } from '../../types/Duration.esm.js';
13
14
  import { useCostOverviewStyles } from '../../utils/styles.esm.js';
14
15
  import '@material-ui/core/Paper';
15
16
  import '@material-ui/core/Slide';
@@ -42,7 +43,7 @@ const CostOverviewBreakdownChart = ({
42
43
  const classes = useCostOverviewStyles(theme);
43
44
  const { baseCurrency } = useConfig();
44
45
  const lastCompleteBillingDate = useLastCompleteBillingDate();
45
- const { duration } = useFilters(mapFiltersToProps);
46
+ const { duration, customDateRange } = useFilters(mapFiltersToProps);
46
47
  const [isExpanded, setExpanded] = useState(false);
47
48
  if (!costBreakdown) {
48
49
  return null;
@@ -52,7 +53,8 @@ const CostOverviewBreakdownChart = ({
52
53
  const previousPeriodTotal = getPreviousPeriodTotalCost(
53
54
  flattenedAggregation,
54
55
  duration,
55
- lastCompleteBillingDate
56
+ lastCompleteBillingDate,
57
+ customDateRange
56
58
  );
57
59
  const currentPeriodTotal = totalCost - previousPeriodTotal;
58
60
  const canExpand = costBreakdown.length >= 8;
@@ -148,16 +150,28 @@ const CostOverviewBreakdownChart = ({
148
150
  ] });
149
151
  };
150
152
  const options = {
151
- previousName: formatPeriod(duration, lastCompleteBillingDate, false),
152
- currentName: formatPeriod(duration, lastCompleteBillingDate, true),
153
+ previousName: formatPeriod(
154
+ duration,
155
+ lastCompleteBillingDate,
156
+ false,
157
+ customDateRange
158
+ ),
159
+ currentName: formatPeriod(
160
+ duration,
161
+ lastCompleteBillingDate,
162
+ true,
163
+ customDateRange
164
+ ),
153
165
  hideMarker: true
154
166
  };
167
+ const legendCostStart = duration === Duration.CUSTOM ? 0 : previousPeriodTotal;
168
+ const legendCostEnd = duration === Duration.CUSTOM ? totalCost : currentPeriodTotal;
155
169
  return /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", children: [
156
170
  /* @__PURE__ */ jsx(Box, { display: "flex", flexDirection: "row", children: /* @__PURE__ */ jsx(
157
171
  BarChartLegend,
158
172
  {
159
- costStart: previousPeriodTotal,
160
- costEnd: currentPeriodTotal,
173
+ costStart: legendCostStart,
174
+ costEnd: legendCostEnd,
161
175
  options
162
176
  }
163
177
  ) }),
@@ -1 +1 @@
1
- {"version":3,"file":"CostOverviewBreakdownChart.esm.js","sources":["../../../src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useState } from 'react';\nimport { DateTime } from 'luxon';\nimport Box from '@material-ui/core/Box';\nimport Divider from '@material-ui/core/Divider';\nimport { emphasize } from '@material-ui/core/styles/colorManipulator';\nimport Typography from '@material-ui/core/Typography';\nimport { useTheme } from '@material-ui/core/styles';\nimport { default as FullScreenIcon } from '@material-ui/icons/Fullscreen';\nimport {\n Area,\n AreaChart,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip as RechartsTooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport { DEFAULT_DATE_FORMAT, CostInsightsTheme } from '../../types';\nimport { Cost } from '@backstage-community/plugin-cost-insights-common';\nimport {\n BarChartLegend,\n BarChartTooltip as Tooltip,\n BarChartTooltipItem as TooltipItem,\n} from '../BarChart';\nimport {\n formatGraphValue,\n isInvalid,\n overviewGraphTickFormatter,\n} from '../../utils/graphs';\nimport { useCostOverviewStyles as useStyles } from '../../utils/styles';\nimport { useConfig, useFilters, useLastCompleteBillingDate } from '../../hooks';\nimport { mapFiltersToProps } from './selector';\nimport { getPreviousPeriodTotalCost } from '../../utils/change';\nimport { formatPeriod } from '../../utils/formatters';\nimport { aggregationSum } from '../../utils/sum';\nimport { BarChartLegendOptions } from '../BarChart';\nimport { TooltipRenderer } from '../../types';\n\nexport type CostOverviewBreakdownChartProps = {\n costBreakdown: Cost[];\n responsive?: boolean;\n};\n\nconst LOW_COST_THRESHOLD = 0.1;\n\nexport const CostOverviewBreakdownChart = ({\n costBreakdown,\n responsive = true,\n}: CostOverviewBreakdownChartProps) => {\n const theme = useTheme<CostInsightsTheme>();\n const classes = useStyles(theme);\n const { baseCurrency } = useConfig();\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const { duration } = useFilters(mapFiltersToProps);\n const [isExpanded, setExpanded] = useState(false);\n\n if (!costBreakdown) {\n return null;\n }\n\n const flattenedAggregation = costBreakdown\n .map(cost => cost.aggregation)\n .flat();\n\n const totalCost = aggregationSum(flattenedAggregation);\n\n const previousPeriodTotal = getPreviousPeriodTotalCost(\n flattenedAggregation,\n duration,\n lastCompleteBillingDate,\n );\n const currentPeriodTotal = totalCost - previousPeriodTotal;\n const canExpand = costBreakdown.length >= 8;\n const otherCategoryIds: string[] = [];\n\n const breakdownsByDate = costBreakdown.reduce(\n (breakdownByDate, breakdown) => {\n const breakdownTotal = aggregationSum(breakdown.aggregation);\n // Group breakdown items with less than 10% of the total cost into \"Other\" category if needed\n const isOtherCategory =\n canExpand && breakdownTotal < totalCost * LOW_COST_THRESHOLD;\n\n const updatedBreakdownByDate = { ...breakdownByDate };\n if (isOtherCategory) {\n otherCategoryIds.push(breakdown.id);\n }\n breakdown.aggregation.forEach(curAggregation => {\n const costsForDate = updatedBreakdownByDate[curAggregation.date] || {};\n\n updatedBreakdownByDate[curAggregation.date] = {\n ...costsForDate,\n [breakdown.id]:\n (costsForDate[breakdown.id] || 0) + curAggregation.amount,\n };\n });\n\n return updatedBreakdownByDate;\n },\n {} as Record<string, Record<string, number>>,\n );\n\n const chartData: Record<string, number>[] = Object.keys(breakdownsByDate)\n .map(date => {\n const costsForDate = Object.keys(breakdownsByDate[date]).reduce(\n (dateCosts, breakdown) => {\n // Group costs for items that belong to 'Other' in the chart.\n const cost = breakdownsByDate[date][breakdown];\n const breakdownCost =\n !isExpanded && otherCategoryIds.includes(breakdown)\n ? { Other: (dateCosts.Other || 0) + cost }\n : { [breakdown]: cost };\n return { ...dateCosts, ...breakdownCost };\n },\n {} as Record<string, number>,\n );\n return {\n ...costsForDate,\n date: Date.parse(date),\n };\n })\n .sort((a, b) => a.date - b.date);\n\n const sortedBreakdowns = costBreakdown.sort(\n (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation),\n );\n\n const renderAreas = () => {\n const separatedBreakdowns = sortedBreakdowns\n // Check that the breakdown is a separate group and hasn't been added to 'Other'\n .filter(\n breakdown =>\n breakdown.id !== 'Other' && !otherCategoryIds.includes(breakdown.id),\n )\n .map(breakdown => breakdown.id);\n // Keep 'Other' category at the bottom of the stack\n const breakdownsToDisplay = isExpanded\n ? sortedBreakdowns.map(breakdown => breakdown.id)\n : ['Other', ...separatedBreakdowns];\n\n return breakdownsToDisplay.map((breakdown, i) => {\n // Logic to handle case where there are more items than data viz colors.\n const color =\n theme.palette.dataViz[\n (breakdownsToDisplay.length - 1 - i) %\n (theme.palette.dataViz.length - 1)\n ];\n return (\n <Area\n key={breakdown}\n dataKey={breakdown}\n isAnimationActive={false}\n stackId=\"1\"\n stroke={color}\n fill={color}\n onClick={() => setExpanded(true)}\n style={{\n cursor:\n breakdown === 'Other' && !isExpanded ? 'pointer' : undefined,\n }}\n />\n );\n });\n };\n\n const tooltipRenderer: TooltipRenderer = ({ label, payload = [] }) => {\n if (isInvalid({ label, payload })) return null;\n\n const date =\n typeof label === 'number'\n ? DateTime.fromMillis(label)\n : DateTime.fromISO(label!);\n const dateTitle = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);\n const formatGraphValueWith = formatGraphValue(baseCurrency);\n const items = payload.map((p, i) => ({\n label: p.dataKey as string,\n value: formatGraphValueWith(Number(p.value), i),\n fill: p.color!,\n }));\n const expandText = (\n <Box>\n <Divider\n style={{\n backgroundColor: emphasize(theme.palette.divider, 1),\n margin: '10px 0',\n }}\n />\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\">\n <FullScreenIcon />\n <Typography>Click to expand</Typography>\n </Box>\n </Box>\n );\n return (\n <Tooltip title={dateTitle}>\n {items.reverse().map((item, index) => (\n <TooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n {canExpand && !isExpanded ? expandText : null}\n </Tooltip>\n );\n };\n\n const options: Partial<BarChartLegendOptions> = {\n previousName: formatPeriod(duration, lastCompleteBillingDate, false),\n currentName: formatPeriod(duration, lastCompleteBillingDate, true),\n hideMarker: true,\n };\n\n return (\n <Box display=\"flex\" flexDirection=\"column\">\n <Box display=\"flex\" flexDirection=\"row\">\n <BarChartLegend\n costStart={previousPeriodTotal}\n costEnd={currentPeriodTotal}\n options={options}\n />\n </Box>\n <ResponsiveContainer\n width={responsive ? '100%' : classes.container.width}\n height={classes.container.height}\n >\n <AreaChart\n data={chartData}\n margin={{\n top: 16,\n right: 30,\n bottom: 40,\n }}\n >\n <CartesianGrid stroke={classes.cartesianGrid.stroke} />\n <XAxis\n dataKey=\"date\"\n domain={['dataMin', 'dataMax']}\n tickFormatter={overviewGraphTickFormatter}\n tickCount={6}\n type=\"number\"\n stroke={classes.axis.fill}\n />\n <YAxis\n domain={[() => 0, 'dataMax']}\n tick={{ fill: classes.axis.fill }}\n tickFormatter={formatGraphValue(baseCurrency)}\n width={classes.yAxis.width}\n />\n {renderAreas()}\n <RechartsTooltip content={tooltipRenderer} animationDuration={100} />\n </AreaChart>\n </ResponsiveContainer>\n </Box>\n );\n};\n"],"names":["useStyles","Tooltip","TooltipItem","RechartsTooltip"],"mappings":"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"}
1
+ {"version":3,"file":"CostOverviewBreakdownChart.esm.js","sources":["../../../src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useState } from 'react';\nimport { DateTime } from 'luxon';\nimport Box from '@material-ui/core/Box';\nimport Divider from '@material-ui/core/Divider';\nimport { emphasize } from '@material-ui/core/styles/colorManipulator';\nimport Typography from '@material-ui/core/Typography';\nimport { useTheme } from '@material-ui/core/styles';\nimport { default as FullScreenIcon } from '@material-ui/icons/Fullscreen';\nimport {\n Area,\n AreaChart,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip as RechartsTooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport { DEFAULT_DATE_FORMAT, CostInsightsTheme, Duration } from '../../types';\nimport { Cost } from '@backstage-community/plugin-cost-insights-common';\nimport {\n BarChartLegend,\n BarChartTooltip as Tooltip,\n BarChartTooltipItem as TooltipItem,\n} from '../BarChart';\nimport {\n formatGraphValue,\n isInvalid,\n overviewGraphTickFormatter,\n} from '../../utils/graphs';\nimport { useCostOverviewStyles as useStyles } from '../../utils/styles';\nimport { useConfig, useFilters, useLastCompleteBillingDate } from '../../hooks';\nimport { mapFiltersToProps } from './selector';\nimport { getPreviousPeriodTotalCost } from '../../utils/change';\nimport { formatPeriod } from '../../utils/formatters';\nimport { aggregationSum } from '../../utils/sum';\nimport { BarChartLegendOptions } from '../BarChart';\nimport { TooltipRenderer } from '../../types';\n\nexport type CostOverviewBreakdownChartProps = {\n costBreakdown: Cost[];\n responsive?: boolean;\n};\n\nconst LOW_COST_THRESHOLD = 0.1;\n\nexport const CostOverviewBreakdownChart = ({\n costBreakdown,\n responsive = true,\n}: CostOverviewBreakdownChartProps) => {\n const theme = useTheme<CostInsightsTheme>();\n const classes = useStyles(theme);\n const { baseCurrency } = useConfig();\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const { duration, customDateRange } = useFilters(mapFiltersToProps);\n const [isExpanded, setExpanded] = useState(false);\n\n if (!costBreakdown) {\n return null;\n }\n\n const flattenedAggregation = costBreakdown\n .map(cost => cost.aggregation)\n .flat();\n\n const totalCost = aggregationSum(flattenedAggregation);\n\n const previousPeriodTotal = getPreviousPeriodTotalCost(\n flattenedAggregation,\n duration,\n lastCompleteBillingDate,\n customDateRange,\n );\n const currentPeriodTotal = totalCost - previousPeriodTotal;\n const canExpand = costBreakdown.length >= 8;\n const otherCategoryIds: string[] = [];\n\n const breakdownsByDate = costBreakdown.reduce(\n (breakdownByDate, breakdown) => {\n const breakdownTotal = aggregationSum(breakdown.aggregation);\n // Group breakdown items with less than 10% of the total cost into \"Other\" category if needed\n const isOtherCategory =\n canExpand && breakdownTotal < totalCost * LOW_COST_THRESHOLD;\n\n const updatedBreakdownByDate = { ...breakdownByDate };\n if (isOtherCategory) {\n otherCategoryIds.push(breakdown.id);\n }\n breakdown.aggregation.forEach(curAggregation => {\n const costsForDate = updatedBreakdownByDate[curAggregation.date] || {};\n\n updatedBreakdownByDate[curAggregation.date] = {\n ...costsForDate,\n [breakdown.id]:\n (costsForDate[breakdown.id] || 0) + curAggregation.amount,\n };\n });\n\n return updatedBreakdownByDate;\n },\n {} as Record<string, Record<string, number>>,\n );\n\n const chartData: Record<string, number>[] = Object.keys(breakdownsByDate)\n .map(date => {\n const costsForDate = Object.keys(breakdownsByDate[date]).reduce(\n (dateCosts, breakdown) => {\n // Group costs for items that belong to 'Other' in the chart.\n const cost = breakdownsByDate[date][breakdown];\n const breakdownCost =\n !isExpanded && otherCategoryIds.includes(breakdown)\n ? { Other: (dateCosts.Other || 0) + cost }\n : { [breakdown]: cost };\n return { ...dateCosts, ...breakdownCost };\n },\n {} as Record<string, number>,\n );\n return {\n ...costsForDate,\n date: Date.parse(date),\n };\n })\n .sort((a, b) => a.date - b.date);\n\n const sortedBreakdowns = costBreakdown.sort(\n (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation),\n );\n\n const renderAreas = () => {\n const separatedBreakdowns = sortedBreakdowns\n // Check that the breakdown is a separate group and hasn't been added to 'Other'\n .filter(\n breakdown =>\n breakdown.id !== 'Other' && !otherCategoryIds.includes(breakdown.id),\n )\n .map(breakdown => breakdown.id);\n // Keep 'Other' category at the bottom of the stack\n const breakdownsToDisplay = isExpanded\n ? sortedBreakdowns.map(breakdown => breakdown.id)\n : ['Other', ...separatedBreakdowns];\n\n return breakdownsToDisplay.map((breakdown, i) => {\n // Logic to handle case where there are more items than data viz colors.\n const color =\n theme.palette.dataViz[\n (breakdownsToDisplay.length - 1 - i) %\n (theme.palette.dataViz.length - 1)\n ];\n return (\n <Area\n key={breakdown}\n dataKey={breakdown}\n isAnimationActive={false}\n stackId=\"1\"\n stroke={color}\n fill={color}\n onClick={() => setExpanded(true)}\n style={{\n cursor:\n breakdown === 'Other' && !isExpanded ? 'pointer' : undefined,\n }}\n />\n );\n });\n };\n\n const tooltipRenderer: TooltipRenderer = ({ label, payload = [] }) => {\n if (isInvalid({ label, payload })) return null;\n\n const date =\n typeof label === 'number'\n ? DateTime.fromMillis(label)\n : DateTime.fromISO(label!);\n const dateTitle = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);\n const formatGraphValueWith = formatGraphValue(baseCurrency);\n const items = payload.map((p, i) => ({\n label: p.dataKey as string,\n value: formatGraphValueWith(Number(p.value), i),\n fill: p.color!,\n }));\n const expandText = (\n <Box>\n <Divider\n style={{\n backgroundColor: emphasize(theme.palette.divider, 1),\n margin: '10px 0',\n }}\n />\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\">\n <FullScreenIcon />\n <Typography>Click to expand</Typography>\n </Box>\n </Box>\n );\n return (\n <Tooltip title={dateTitle}>\n {items.reverse().map((item, index) => (\n <TooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n {canExpand && !isExpanded ? expandText : null}\n </Tooltip>\n );\n };\n\n const options: Partial<BarChartLegendOptions> = {\n previousName: formatPeriod(\n duration,\n lastCompleteBillingDate,\n false,\n customDateRange,\n ),\n currentName: formatPeriod(\n duration,\n lastCompleteBillingDate,\n true,\n customDateRange,\n ),\n hideMarker: true,\n };\n\n // For custom date ranges, only show total cost (no previous/current split)\n const legendCostStart =\n duration === Duration.CUSTOM ? 0 : previousPeriodTotal;\n const legendCostEnd =\n duration === Duration.CUSTOM ? totalCost : currentPeriodTotal;\n\n return (\n <Box display=\"flex\" flexDirection=\"column\">\n <Box display=\"flex\" flexDirection=\"row\">\n <BarChartLegend\n costStart={legendCostStart}\n costEnd={legendCostEnd}\n options={options}\n />\n </Box>\n <ResponsiveContainer\n width={responsive ? '100%' : classes.container.width}\n height={classes.container.height}\n >\n <AreaChart\n data={chartData}\n margin={{\n top: 16,\n right: 30,\n bottom: 40,\n }}\n >\n <CartesianGrid stroke={classes.cartesianGrid.stroke} />\n <XAxis\n dataKey=\"date\"\n domain={['dataMin', 'dataMax']}\n tickFormatter={overviewGraphTickFormatter}\n tickCount={6}\n type=\"number\"\n stroke={classes.axis.fill}\n />\n <YAxis\n domain={[() => 0, 'dataMax']}\n tick={{ fill: classes.axis.fill }}\n tickFormatter={formatGraphValue(baseCurrency)}\n width={classes.yAxis.width}\n />\n {renderAreas()}\n <RechartsTooltip content={tooltipRenderer} animationDuration={100} />\n </AreaChart>\n </ResponsiveContainer>\n </Box>\n );\n};\n"],"names":["useStyles","Tooltip","TooltipItem","RechartsTooltip"],"mappings":"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"}
@@ -12,7 +12,14 @@ import { CostOverviewChart } from './CostOverviewChart.esm.js';
12
12
  import { CostOverviewBreakdownChart } from './CostOverviewBreakdownChart.esm.js';
13
13
  import { CostOverviewHeader } from './CostOverviewHeader.esm.js';
14
14
  import { MetricSelect } from '../MetricSelect/MetricSelect.esm.js';
15
- import { PeriodSelect } from '../PeriodSelect/PeriodSelect.esm.js';
15
+ import '@material-ui/core/MenuItem';
16
+ import '@material-ui/core/Select';
17
+ import '../../types/Alert.esm.js';
18
+ import 'luxon';
19
+ import 'pluralize';
20
+ import '../../utils/duration.esm.js';
21
+ import { findAlways } from '../../utils/assert.esm.js';
22
+ import { useOverviewTabsStyles } from '../../utils/styles.esm.js';
16
23
  import { useConfig } from '../../hooks/useConfig.esm.js';
17
24
  import '../../hooks/useCurrency.esm.js';
18
25
  import { useFilters } from '../../hooks/useFilters.esm.js';
@@ -20,10 +27,9 @@ import '../../hooks/useGroups.esm.js';
20
27
  import '../../hooks/useLoading.esm.js';
21
28
  import '../../hooks/useScroll.esm.js';
22
29
  import '../../hooks/useLastCompleteBillingDate.esm.js';
30
+ import { DateRangePicker } from '../PeriodSelect/DateRangePicker.esm.js';
23
31
  import { mapFiltersToProps } from './selector.esm.js';
24
32
  import { DefaultNavigation } from '../../utils/navigation.esm.js';
25
- import { findAlways } from '../../utils/assert.esm.js';
26
- import { useOverviewTabsStyles } from '../../utils/styles.esm.js';
27
33
  import { ScrollAnchor } from '../../utils/scroll.esm.js';
28
34
 
29
35
  const CostOverviewCard = ({
@@ -80,7 +86,14 @@ const CostOverviewCard = ({
80
86
  /* @__PURE__ */ jsx(ScrollAnchor, { id: DefaultNavigation.CostOverviewCard }),
81
87
  /* @__PURE__ */ jsxs(CardContent, { children: [
82
88
  dailyCostData.groupedCosts && /* @__PURE__ */ jsx(OverviewTabs, {}),
83
- /* @__PURE__ */ jsx(CostOverviewHeader, { title: tabs[safeTabIndex].title, children: /* @__PURE__ */ jsx(PeriodSelect, { duration: filters.duration, onSelect: setDuration }) }),
89
+ /* @__PURE__ */ jsx(CostOverviewHeader, { title: tabs[safeTabIndex].title, children: /* @__PURE__ */ jsx(
90
+ DateRangePicker,
91
+ {
92
+ duration: filters.duration,
93
+ onSelect: setDuration,
94
+ customDateRange: filters.customDateRange
95
+ }
96
+ ) }),
84
97
  /* @__PURE__ */ jsx(Divider, {}),
85
98
  /* @__PURE__ */ jsx(Box, { ml: 2, my: 1, display: "flex", flexDirection: "column", children: safeTabIndex === 0 ? /* @__PURE__ */ jsx(
86
99
  CostOverviewChart,
@@ -1 +1 @@
1
- {"version":3,"file":"CostOverviewCard.esm.js","sources":["../../../src/components/CostOverviewCard/CostOverviewCard.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, useState } from 'react';\nimport Box from '@material-ui/core/Box';\nimport { capitalize } from '@material-ui/core/utils';\nimport Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport Divider from '@material-ui/core/Divider';\nimport Tab from '@material-ui/core/Tab';\nimport Tabs from '@material-ui/core/Tabs';\nimport { useTheme } from '@material-ui/core/styles';\nimport { CostOverviewChart } from './CostOverviewChart';\nimport { CostOverviewBreakdownChart } from './CostOverviewBreakdownChart';\nimport { CostOverviewHeader } from './CostOverviewHeader';\nimport { MetricSelect } from '../MetricSelect';\nimport { PeriodSelect } from '../PeriodSelect';\nimport { useConfig, useFilters } from '../../hooks';\nimport { mapFiltersToProps } from './selector';\nimport { DefaultNavigation } from '../../utils/navigation';\nimport { findAlways } from '../../utils/assert';\nimport { CostInsightsTheme } from '../../types';\nimport {\n Cost,\n Maybe,\n MetricData,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { useOverviewTabsStyles } from '../../utils/styles';\nimport { ScrollAnchor } from '../../utils/scroll';\n\nexport type CostOverviewCardProps = {\n dailyCostData: Cost;\n metricData: Maybe<MetricData>;\n};\n\nexport const CostOverviewCard = ({\n dailyCostData,\n metricData,\n}: CostOverviewCardProps) => {\n const theme = useTheme<CostInsightsTheme>();\n const styles = useOverviewTabsStyles(theme);\n const config = useConfig();\n const [tabIndex, setTabIndex] = useState(0);\n const { setDuration, setProject, setMetric, ...filters } =\n useFilters(mapFiltersToProps);\n\n // Reset tabIndex if breakdowns available change\n useEffect(() => {\n // Intentionally off-by-one to account for the overview tab\n const lastIndex = Object.keys(dailyCostData.groupedCosts ?? {}).length;\n if (tabIndex > lastIndex) {\n setTabIndex(0);\n }\n }, [dailyCostData, tabIndex, setTabIndex]);\n\n const metric = filters.metric\n ? findAlways(config.metrics, m => m.kind === filters.metric)\n : null;\n\n const breakdownTabs = Object.keys(dailyCostData.groupedCosts ?? {}).map(\n key => ({\n id: key,\n label: `Breakdown by ${key}`,\n title: `Cloud Cost By ${capitalize(key)}`,\n }),\n );\n const tabs = [\n { id: 'overview', label: 'Total cost', title: 'Cloud Cost' },\n ].concat(breakdownTabs);\n // tabIndex can temporarily be invalid while the useEffect above processes\n const safeTabIndex = tabIndex > tabs.length - 1 ? 0 : tabIndex;\n\n const OverviewTabs = () => {\n return (\n <>\n <Tabs\n indicatorColor=\"primary\"\n onChange={(_, index) => setTabIndex(index)}\n value={safeTabIndex}\n variant=\"scrollable\"\n scrollButtons=\"auto\"\n >\n {tabs.map((tab, index) => (\n <Tab\n className={styles.default}\n label={tab.label}\n key={tab.id}\n value={index}\n classes={{ selected: styles.selected }}\n />\n ))}\n </Tabs>\n </>\n );\n };\n\n // Metrics can only be selected on the total cost graph\n const showMetricSelect = config.metrics.length && safeTabIndex === 0;\n\n return (\n <Card style={{ position: 'relative', overflow: 'visible' }}>\n <ScrollAnchor id={DefaultNavigation.CostOverviewCard} />\n <CardContent>\n {dailyCostData.groupedCosts && <OverviewTabs />}\n <CostOverviewHeader title={tabs[safeTabIndex].title}>\n <PeriodSelect duration={filters.duration} onSelect={setDuration} />\n </CostOverviewHeader>\n <Divider />\n <Box ml={2} my={1} display=\"flex\" flexDirection=\"column\">\n {safeTabIndex === 0 ? (\n <CostOverviewChart\n dailyCostData={dailyCostData}\n metric={metric}\n metricData={metricData}\n />\n ) : (\n <CostOverviewBreakdownChart\n costBreakdown={dailyCostData.groupedCosts![tabs[safeTabIndex].id]}\n />\n )}\n </Box>\n <Box display=\"flex\" justifyContent=\"flex-end\" alignItems=\"center\">\n {showMetricSelect && (\n <MetricSelect\n metric={filters.metric}\n metrics={config.metrics}\n onSelect={setMetric}\n />\n )}\n </Box>\n </CardContent>\n </Card>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDO,MAAM,mBAAmB,CAAC;AAAA,EAC/B,aAAA;AAAA,EACA;AACF,CAA6B,KAAA;AAC3B,EAAA,MAAM,QAAQ,QAA4B,EAAA;AAC1C,EAAM,MAAA,MAAA,GAAS,sBAAsB,KAAK,CAAA;AAC1C,EAAA,MAAM,SAAS,SAAU,EAAA;AACzB,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,CAAC,CAAA;AAC1C,EAAM,MAAA,EAAE,aAAa,UAAY,EAAA,SAAA,EAAW,GAAG,OAAQ,EAAA,GACrD,WAAW,iBAAiB,CAAA;AAG9B,EAAA,SAAA,CAAU,MAAM;AAEd,IAAA,MAAM,YAAY,MAAO,CAAA,IAAA,CAAK,cAAc,YAAgB,IAAA,EAAE,CAAE,CAAA,MAAA;AAChE,IAAA,IAAI,WAAW,SAAW,EAAA;AACxB,MAAA,WAAA,CAAY,CAAC,CAAA;AAAA;AACf,GACC,EAAA,CAAC,aAAe,EAAA,QAAA,EAAU,WAAW,CAAC,CAAA;AAEzC,EAAM,MAAA,MAAA,GAAS,OAAQ,CAAA,MAAA,GACnB,UAAW,CAAA,MAAA,CAAO,OAAS,EAAA,CAAA,CAAA,KAAK,CAAE,CAAA,IAAA,KAAS,OAAQ,CAAA,MAAM,CACzD,GAAA,IAAA;AAEJ,EAAA,MAAM,gBAAgB,MAAO,CAAA,IAAA,CAAK,cAAc,YAAgB,IAAA,EAAE,CAAE,CAAA,GAAA;AAAA,IAClE,CAAQ,GAAA,MAAA;AAAA,MACN,EAAI,EAAA,GAAA;AAAA,MACJ,KAAA,EAAO,gBAAgB,GAAG,CAAA,CAAA;AAAA,MAC1B,KAAO,EAAA,CAAA,cAAA,EAAiB,UAAW,CAAA,GAAG,CAAC,CAAA;AAAA,KACzC;AAAA,GACF;AACA,EAAA,MAAM,IAAO,GAAA;AAAA,IACX,EAAE,EAAI,EAAA,UAAA,EAAY,KAAO,EAAA,YAAA,EAAc,OAAO,YAAa;AAAA,GAC7D,CAAE,OAAO,aAAa,CAAA;AAEtB,EAAA,MAAM,YAAe,GAAA,QAAA,GAAW,IAAK,CAAA,MAAA,GAAS,IAAI,CAAI,GAAA,QAAA;AAEtD,EAAA,MAAM,eAAe,MAAM;AACzB,IAAA,uBAEI,GAAA,CAAA,QAAA,EAAA,EAAA,QAAA,kBAAA,GAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,cAAe,EAAA,SAAA;AAAA,QACf,QAAU,EAAA,CAAC,CAAG,EAAA,KAAA,KAAU,YAAY,KAAK,CAAA;AAAA,QACzC,KAAO,EAAA,YAAA;AAAA,QACP,OAAQ,EAAA,YAAA;AAAA,QACR,aAAc,EAAA,MAAA;AAAA,QAEb,QAAK,EAAA,IAAA,CAAA,GAAA,CAAI,CAAC,GAAA,EAAK,KACd,qBAAA,GAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YACC,WAAW,MAAO,CAAA,OAAA;AAAA,YAClB,OAAO,GAAI,CAAA,KAAA;AAAA,YAEX,KAAO,EAAA,KAAA;AAAA,YACP,OAAS,EAAA,EAAE,QAAU,EAAA,MAAA,CAAO,QAAS;AAAA,WAAA;AAAA,UAFhC,GAAI,CAAA;AAAA,SAIZ;AAAA;AAAA,KAEL,EAAA,CAAA;AAAA,GAEJ;AAGA,EAAA,MAAM,gBAAmB,GAAA,MAAA,CAAO,OAAQ,CAAA,MAAA,IAAU,YAAiB,KAAA,CAAA;AAEnE,EACE,uBAAA,IAAA,CAAC,QAAK,KAAO,EAAA,EAAE,UAAU,UAAY,EAAA,QAAA,EAAU,WAC7C,EAAA,QAAA,EAAA;AAAA,oBAAC,GAAA,CAAA,YAAA,EAAA,EAAa,EAAI,EAAA,iBAAA,CAAkB,gBAAkB,EAAA,CAAA;AAAA,yBACrD,WACE,EAAA,EAAA,QAAA,EAAA;AAAA,MAAc,aAAA,CAAA,YAAA,wBAAiB,YAAa,EAAA,EAAA,CAAA;AAAA,sBAC5C,GAAA,CAAA,kBAAA,EAAA,EAAmB,KAAO,EAAA,IAAA,CAAK,YAAY,CAAE,CAAA,KAAA,EAC5C,QAAC,kBAAA,GAAA,CAAA,YAAA,EAAA,EAAa,QAAU,EAAA,OAAA,CAAQ,QAAU,EAAA,QAAA,EAAU,aAAa,CACnE,EAAA,CAAA;AAAA,0BACC,OAAQ,EAAA,EAAA,CAAA;AAAA,sBACT,GAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CAAG,EAAA,EAAA,EAAI,CAAG,EAAA,OAAA,EAAQ,MAAO,EAAA,aAAA,EAAc,QAC7C,EAAA,QAAA,EAAA,YAAA,KAAiB,CAChB,mBAAA,GAAA;AAAA,QAAC,iBAAA;AAAA,QAAA;AAAA,UACC,aAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA;AAAA,OAGF,mBAAA,GAAA;AAAA,QAAC,0BAAA;AAAA,QAAA;AAAA,UACC,eAAe,aAAc,CAAA,YAAA,CAAc,IAAK,CAAA,YAAY,EAAE,EAAE;AAAA;AAAA,OAGtE,EAAA,CAAA;AAAA,sBACA,GAAA,CAAC,OAAI,OAAQ,EAAA,MAAA,EAAO,gBAAe,UAAW,EAAA,UAAA,EAAW,UACtD,QACC,EAAA,gBAAA,oBAAA,GAAA;AAAA,QAAC,YAAA;AAAA,QAAA;AAAA,UACC,QAAQ,OAAQ,CAAA,MAAA;AAAA,UAChB,SAAS,MAAO,CAAA,OAAA;AAAA,UAChB,QAAU,EAAA;AAAA;AAAA,OAGhB,EAAA;AAAA,KACF,EAAA;AAAA,GACF,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"CostOverviewCard.esm.js","sources":["../../../src/components/CostOverviewCard/CostOverviewCard.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, useState } from 'react';\nimport Box from '@material-ui/core/Box';\nimport { capitalize } from '@material-ui/core/utils';\nimport Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport Divider from '@material-ui/core/Divider';\nimport Tab from '@material-ui/core/Tab';\nimport Tabs from '@material-ui/core/Tabs';\nimport { useTheme } from '@material-ui/core/styles';\nimport { CostOverviewChart } from './CostOverviewChart';\nimport { CostOverviewBreakdownChart } from './CostOverviewBreakdownChart';\nimport { CostOverviewHeader } from './CostOverviewHeader';\nimport { MetricSelect } from '../MetricSelect';\nimport { DateRangePicker } from '../PeriodSelect';\nimport { useConfig, useFilters } from '../../hooks';\nimport { mapFiltersToProps } from './selector';\nimport { DefaultNavigation } from '../../utils/navigation';\nimport { findAlways } from '../../utils/assert';\nimport { CostInsightsTheme } from '../../types';\nimport {\n Cost,\n Maybe,\n MetricData,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { useOverviewTabsStyles } from '../../utils/styles';\nimport { ScrollAnchor } from '../../utils/scroll';\n\nexport type CostOverviewCardProps = {\n dailyCostData: Cost;\n metricData: Maybe<MetricData>;\n};\n\nexport const CostOverviewCard = ({\n dailyCostData,\n metricData,\n}: CostOverviewCardProps) => {\n const theme = useTheme<CostInsightsTheme>();\n const styles = useOverviewTabsStyles(theme);\n const config = useConfig();\n const [tabIndex, setTabIndex] = useState(0);\n const { setDuration, setProject, setMetric, ...filters } =\n useFilters(mapFiltersToProps);\n\n // Reset tabIndex if breakdowns available change\n useEffect(() => {\n // Intentionally off-by-one to account for the overview tab\n const lastIndex = Object.keys(dailyCostData.groupedCosts ?? {}).length;\n if (tabIndex > lastIndex) {\n setTabIndex(0);\n }\n }, [dailyCostData, tabIndex, setTabIndex]);\n\n const metric = filters.metric\n ? findAlways(config.metrics, m => m.kind === filters.metric)\n : null;\n\n const breakdownTabs = Object.keys(dailyCostData.groupedCosts ?? {}).map(\n key => ({\n id: key,\n label: `Breakdown by ${key}`,\n title: `Cloud Cost By ${capitalize(key)}`,\n }),\n );\n const tabs = [\n { id: 'overview', label: 'Total cost', title: 'Cloud Cost' },\n ].concat(breakdownTabs);\n // tabIndex can temporarily be invalid while the useEffect above processes\n const safeTabIndex = tabIndex > tabs.length - 1 ? 0 : tabIndex;\n\n const OverviewTabs = () => {\n return (\n <>\n <Tabs\n indicatorColor=\"primary\"\n onChange={(_, index) => setTabIndex(index)}\n value={safeTabIndex}\n variant=\"scrollable\"\n scrollButtons=\"auto\"\n >\n {tabs.map((tab, index) => (\n <Tab\n className={styles.default}\n label={tab.label}\n key={tab.id}\n value={index}\n classes={{ selected: styles.selected }}\n />\n ))}\n </Tabs>\n </>\n );\n };\n\n // Metrics can only be selected on the total cost graph\n const showMetricSelect = config.metrics.length && safeTabIndex === 0;\n\n return (\n <Card style={{ position: 'relative', overflow: 'visible' }}>\n <ScrollAnchor id={DefaultNavigation.CostOverviewCard} />\n <CardContent>\n {dailyCostData.groupedCosts && <OverviewTabs />}\n <CostOverviewHeader title={tabs[safeTabIndex].title}>\n <DateRangePicker\n duration={filters.duration}\n onSelect={setDuration}\n customDateRange={filters.customDateRange}\n />\n </CostOverviewHeader>\n <Divider />\n <Box ml={2} my={1} display=\"flex\" flexDirection=\"column\">\n {safeTabIndex === 0 ? (\n <CostOverviewChart\n dailyCostData={dailyCostData}\n metric={metric}\n metricData={metricData}\n />\n ) : (\n <CostOverviewBreakdownChart\n costBreakdown={dailyCostData.groupedCosts![tabs[safeTabIndex].id]}\n />\n )}\n </Box>\n <Box display=\"flex\" justifyContent=\"flex-end\" alignItems=\"center\">\n {showMetricSelect && (\n <MetricSelect\n metric={filters.metric}\n metrics={config.metrics}\n onSelect={setMetric}\n />\n )}\n </Box>\n </CardContent>\n </Card>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -26,13 +26,14 @@ const CostOverviewLegend = ({
26
26
  metricData
27
27
  }) => {
28
28
  const theme = useTheme();
29
- const { duration } = useFilters(mapFiltersToProps);
29
+ const { duration, customDateRange } = useFilters(mapFiltersToProps);
30
30
  const lastCompleteBillingDate = useLastCompleteBillingDate();
31
31
  const comparedChange = metricData ? getComparedChange(
32
32
  dailyCostData,
33
33
  metricData,
34
34
  duration,
35
- lastCompleteBillingDate
35
+ lastCompleteBillingDate,
36
+ customDateRange
36
37
  ) : null;
37
38
  return /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "row", children: [
38
39
  dailyCostData.change && /* @__PURE__ */ jsx(Box, { mr: 2, children: /* @__PURE__ */ jsx(LegendItem, { title: "Cost Trend", markerColor: theme.palette.blue, children: formatChange(dailyCostData.change) }) }),
@@ -49,7 +50,14 @@ const CostOverviewLegend = ({
49
50
  LegendItem,
50
51
  {
51
52
  title: choose(["Your Savings", "Your Excess"], comparedChange),
52
- children: /* @__PURE__ */ jsx(CostGrowth, { change: comparedChange, duration })
53
+ children: /* @__PURE__ */ jsx(
54
+ CostGrowth,
55
+ {
56
+ change: comparedChange,
57
+ duration,
58
+ customDateRange
59
+ }
60
+ )
53
61
  }
54
62
  )
55
63
  ] })
@@ -1 +1 @@
1
- {"version":3,"file":"CostOverviewLegend.esm.js","sources":["../../../src/components/CostOverviewCard/CostOverviewLegend.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { PropsWithChildren } from 'react';\nimport Box from '@material-ui/core/Box';\nimport { useTheme } from '@material-ui/core/styles';\nimport { LegendItem } from '../LegendItem';\nimport { CostInsightsTheme } from '../../types';\nimport {\n MetricData,\n Maybe,\n Cost,\n Metric,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { useLastCompleteBillingDate, useFilters } from '../../hooks';\nimport { getComparedChange, choose } from '../../utils/change';\nimport { mapFiltersToProps } from './selector';\nimport { formatChange } from '../../utils/formatters';\nimport { CostGrowth } from '../CostGrowth';\n\ntype CostOverviewLegendProps = {\n metric: Maybe<Metric>;\n metricData: Maybe<MetricData>;\n dailyCostData: Cost;\n};\n\nexport const CostOverviewLegend = ({\n dailyCostData,\n metric,\n metricData,\n}: PropsWithChildren<CostOverviewLegendProps>) => {\n const theme = useTheme<CostInsightsTheme>();\n const { duration } = useFilters(mapFiltersToProps);\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n\n const comparedChange = metricData\n ? getComparedChange(\n dailyCostData,\n metricData,\n duration,\n lastCompleteBillingDate,\n )\n : null;\n\n return (\n <Box display=\"flex\" flexDirection=\"row\">\n {dailyCostData.change && (\n <Box mr={2}>\n <LegendItem title=\"Cost Trend\" markerColor={theme.palette.blue}>\n {formatChange(dailyCostData.change)}\n </LegendItem>\n </Box>\n )}\n {metricData && metric && comparedChange && (\n <>\n <Box mr={2}>\n <LegendItem\n title={`${metric.name} Trend`}\n markerColor={theme.palette.magenta}\n >\n {formatChange(metricData.change)}\n </LegendItem>\n </Box>\n <LegendItem\n title={choose(['Your Savings', 'Your Excess'], comparedChange)}\n >\n <CostGrowth change={comparedChange} duration={duration} />\n </LegendItem>\n </>\n )}\n </Box>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuCO,MAAM,qBAAqB,CAAC;AAAA,EACjC,aAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAkD,KAAA;AAChD,EAAA,MAAM,QAAQ,QAA4B,EAAA;AAC1C,EAAA,MAAM,EAAE,QAAA,EAAa,GAAA,UAAA,CAAW,iBAAiB,CAAA;AACjD,EAAA,MAAM,0BAA0B,0BAA2B,EAAA;AAE3D,EAAA,MAAM,iBAAiB,UACnB,GAAA,iBAAA;AAAA,IACE,aAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GAEF,GAAA,IAAA;AAEJ,EAAA,uBACG,IAAA,CAAA,GAAA,EAAA,EAAI,OAAQ,EAAA,MAAA,EAAO,eAAc,KAC/B,EAAA,QAAA,EAAA;AAAA,IAAA,aAAA,CAAc,0BACZ,GAAA,CAAA,GAAA,EAAA,EAAI,EAAI,EAAA,CAAA,EACP,8BAAC,UAAW,EAAA,EAAA,KAAA,EAAM,YAAa,EAAA,WAAA,EAAa,MAAM,OAAQ,CAAA,IAAA,EACvD,uBAAa,aAAc,CAAA,MAAM,GACpC,CACF,EAAA,CAAA;AAAA,IAED,UAAA,IAAc,MAAU,IAAA,cAAA,oBAErB,IAAA,CAAA,QAAA,EAAA,EAAA,QAAA,EAAA;AAAA,sBAAC,GAAA,CAAA,GAAA,EAAA,EAAI,IAAI,CACP,EAAA,QAAA,kBAAA,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,KAAA,EAAO,CAAG,EAAA,MAAA,CAAO,IAAI,CAAA,MAAA,CAAA;AAAA,UACrB,WAAA,EAAa,MAAM,OAAQ,CAAA,OAAA;AAAA,UAE1B,QAAA,EAAA,YAAA,CAAa,WAAW,MAAM;AAAA;AAAA,OAEnC,EAAA,CAAA;AAAA,sBACA,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,OAAO,MAAO,CAAA,CAAC,cAAgB,EAAA,aAAa,GAAG,cAAc,CAAA;AAAA,UAE7D,QAAC,kBAAA,GAAA,CAAA,UAAA,EAAA,EAAW,MAAQ,EAAA,cAAA,EAAgB,QAAoB,EAAA;AAAA;AAAA;AAC1D,KACF,EAAA;AAAA,GAEJ,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"CostOverviewLegend.esm.js","sources":["../../../src/components/CostOverviewCard/CostOverviewLegend.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { PropsWithChildren } from 'react';\nimport Box from '@material-ui/core/Box';\nimport { useTheme } from '@material-ui/core/styles';\nimport { LegendItem } from '../LegendItem';\nimport { CostInsightsTheme } from '../../types';\nimport {\n MetricData,\n Maybe,\n Cost,\n Metric,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { useLastCompleteBillingDate, useFilters } from '../../hooks';\nimport { getComparedChange, choose } from '../../utils/change';\nimport { mapFiltersToProps } from './selector';\nimport { formatChange } from '../../utils/formatters';\nimport { CostGrowth } from '../CostGrowth';\n\ntype CostOverviewLegendProps = {\n metric: Maybe<Metric>;\n metricData: Maybe<MetricData>;\n dailyCostData: Cost;\n};\n\nexport const CostOverviewLegend = ({\n dailyCostData,\n metric,\n metricData,\n}: PropsWithChildren<CostOverviewLegendProps>) => {\n const theme = useTheme<CostInsightsTheme>();\n const { duration, customDateRange } = useFilters(mapFiltersToProps);\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n\n const comparedChange = metricData\n ? getComparedChange(\n dailyCostData,\n metricData,\n duration,\n lastCompleteBillingDate,\n customDateRange,\n )\n : null;\n\n return (\n <Box display=\"flex\" flexDirection=\"row\">\n {dailyCostData.change && (\n <Box mr={2}>\n <LegendItem title=\"Cost Trend\" markerColor={theme.palette.blue}>\n {formatChange(dailyCostData.change)}\n </LegendItem>\n </Box>\n )}\n {metricData && metric && comparedChange && (\n <>\n <Box mr={2}>\n <LegendItem\n title={`${metric.name} Trend`}\n markerColor={theme.palette.magenta}\n >\n {formatChange(metricData.change)}\n </LegendItem>\n </Box>\n <LegendItem\n title={choose(['Your Savings', 'Your Excess'], comparedChange)}\n >\n <CostGrowth\n change={comparedChange}\n duration={duration}\n customDateRange={customDateRange}\n />\n </LegendItem>\n </>\n )}\n </Box>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuCO,MAAM,qBAAqB,CAAC;AAAA,EACjC,aAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAkD,KAAA;AAChD,EAAA,MAAM,QAAQ,QAA4B,EAAA;AAC1C,EAAA,MAAM,EAAE,QAAA,EAAU,eAAgB,EAAA,GAAI,WAAW,iBAAiB,CAAA;AAClE,EAAA,MAAM,0BAA0B,0BAA2B,EAAA;AAE3D,EAAA,MAAM,iBAAiB,UACnB,GAAA,iBAAA;AAAA,IACE,aAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,uBAAA;AAAA,IACA;AAAA,GAEF,GAAA,IAAA;AAEJ,EAAA,uBACG,IAAA,CAAA,GAAA,EAAA,EAAI,OAAQ,EAAA,MAAA,EAAO,eAAc,KAC/B,EAAA,QAAA,EAAA;AAAA,IAAA,aAAA,CAAc,0BACZ,GAAA,CAAA,GAAA,EAAA,EAAI,EAAI,EAAA,CAAA,EACP,8BAAC,UAAW,EAAA,EAAA,KAAA,EAAM,YAAa,EAAA,WAAA,EAAa,MAAM,OAAQ,CAAA,IAAA,EACvD,uBAAa,aAAc,CAAA,MAAM,GACpC,CACF,EAAA,CAAA;AAAA,IAED,UAAA,IAAc,MAAU,IAAA,cAAA,oBAErB,IAAA,CAAA,QAAA,EAAA,EAAA,QAAA,EAAA;AAAA,sBAAC,GAAA,CAAA,GAAA,EAAA,EAAI,IAAI,CACP,EAAA,QAAA,kBAAA,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,KAAA,EAAO,CAAG,EAAA,MAAA,CAAO,IAAI,CAAA,MAAA,CAAA;AAAA,UACrB,WAAA,EAAa,MAAM,OAAQ,CAAA,OAAA;AAAA,UAE1B,QAAA,EAAA,YAAA,CAAa,WAAW,MAAM;AAAA;AAAA,OAEnC,EAAA,CAAA;AAAA,sBACA,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,OAAO,MAAO,CAAA,CAAC,cAAgB,EAAA,aAAa,GAAG,cAAc,CAAA;AAAA,UAE7D,QAAA,kBAAA,GAAA;AAAA,YAAC,UAAA;AAAA,YAAA;AAAA,cACC,MAAQ,EAAA,cAAA;AAAA,cACR,QAAA;AAAA,cACA;AAAA;AAAA;AACF;AAAA;AACF,KACF,EAAA;AAAA,GAEJ,EAAA,CAAA;AAEJ;;;;"}
@@ -4,9 +4,10 @@ const mapFiltersToProps = ({
4
4
  }) => ({
5
5
  ...pageFilters,
6
6
  project: pageFilters.project || "all",
7
- setDuration: (duration) => setPageFilters({
7
+ setDuration: (duration, customDateRange) => setPageFilters({
8
8
  ...pageFilters,
9
- duration
9
+ duration,
10
+ customDateRange
10
11
  }),
11
12
  setProject: (project) => setPageFilters({
12
13
  ...pageFilters,
@@ -1 +1 @@
1
- {"version":3,"file":"selector.esm.js","sources":["../../../src/components/CostOverviewCard/selector.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Duration, PageFilters } from '../../types';\nimport { Maybe } from '@backstage-community/plugin-cost-insights-common';\nimport { MapFiltersToProps } from '../../hooks/useFilters';\n\ntype CostOverviewFilterProps = PageFilters & {\n setDuration: (duration: Duration) => void;\n setProject: (project: Maybe<string>) => void;\n setMetric: (metric: Maybe<string>) => void;\n};\n\nexport const mapFiltersToProps: MapFiltersToProps<CostOverviewFilterProps> = ({\n pageFilters,\n setPageFilters,\n}) => ({\n ...pageFilters,\n project: pageFilters.project || 'all',\n setDuration: (duration: Duration) =>\n setPageFilters({\n ...pageFilters,\n duration,\n }),\n setProject: (project: Maybe<string>) =>\n setPageFilters({\n ...pageFilters,\n project: project === 'all' ? null : project,\n }),\n setMetric: (metric: Maybe<string>) =>\n setPageFilters({\n ...pageFilters,\n metric: metric,\n }),\n});\n"],"names":[],"mappings":"AAyBO,MAAM,oBAAgE,CAAC;AAAA,EAC5E,WAAA;AAAA,EACA;AACF,CAAO,MAAA;AAAA,EACL,GAAG,WAAA;AAAA,EACH,OAAA,EAAS,YAAY,OAAW,IAAA,KAAA;AAAA,EAChC,WAAA,EAAa,CAAC,QAAA,KACZ,cAAe,CAAA;AAAA,IACb,GAAG,WAAA;AAAA,IACH;AAAA,GACD,CAAA;AAAA,EACH,UAAA,EAAY,CAAC,OAAA,KACX,cAAe,CAAA;AAAA,IACb,GAAG,WAAA;AAAA,IACH,OAAA,EAAS,OAAY,KAAA,KAAA,GAAQ,IAAO,GAAA;AAAA,GACrC,CAAA;AAAA,EACH,SAAA,EAAW,CAAC,MAAA,KACV,cAAe,CAAA;AAAA,IACb,GAAG,WAAA;AAAA,IACH;AAAA,GACD;AACL,CAAA;;;;"}
1
+ {"version":3,"file":"selector.esm.js","sources":["../../../src/components/CostOverviewCard/selector.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Duration, PageFilters } from '../../types';\nimport { Maybe } from '@backstage-community/plugin-cost-insights-common';\nimport { MapFiltersToProps } from '../../hooks/useFilters';\n\ntype CostOverviewFilterProps = PageFilters & {\n setDuration: (\n duration: Duration,\n customDateRange?: { start: string; end: string },\n ) => void;\n setProject: (project: Maybe<string>) => void;\n setMetric: (metric: Maybe<string>) => void;\n};\n\nexport const mapFiltersToProps: MapFiltersToProps<CostOverviewFilterProps> = ({\n pageFilters,\n setPageFilters,\n}) => ({\n ...pageFilters,\n project: pageFilters.project || 'all',\n setDuration: (\n duration: Duration,\n customDateRange?: { start: string; end: string },\n ) =>\n setPageFilters({\n ...pageFilters,\n duration,\n customDateRange,\n }),\n setProject: (project: Maybe<string>) =>\n setPageFilters({\n ...pageFilters,\n project: project === 'all' ? null : project,\n }),\n setMetric: (metric: Maybe<string>) =>\n setPageFilters({\n ...pageFilters,\n metric: metric,\n }),\n});\n"],"names":[],"mappings":"AA4BO,MAAM,oBAAgE,CAAC;AAAA,EAC5E,WAAA;AAAA,EACA;AACF,CAAO,MAAA;AAAA,EACL,GAAG,WAAA;AAAA,EACH,OAAA,EAAS,YAAY,OAAW,IAAA,KAAA;AAAA,EAChC,WAAa,EAAA,CACX,QACA,EAAA,eAAA,KAEA,cAAe,CAAA;AAAA,IACb,GAAG,WAAA;AAAA,IACH,QAAA;AAAA,IACA;AAAA,GACD,CAAA;AAAA,EACH,UAAA,EAAY,CAAC,OAAA,KACX,cAAe,CAAA;AAAA,IACb,GAAG,WAAA;AAAA,IACH,OAAA,EAAS,OAAY,KAAA,KAAA,GAAQ,IAAO,GAAA;AAAA,GACrC,CAAA;AAAA,EACH,SAAA,EAAW,CAAC,MAAA,KACV,cAAe,CAAA;AAAA,IACb,GAAG,WAAA;AAAA,IACH;AAAA,GACD;AACL,CAAA;;;;"}
@@ -44,7 +44,11 @@ const EntityCostsCard = () => {
44
44
  dispatchLoadingInsights(true);
45
45
  const intervals = intervalsOf(
46
46
  pageFilters.duration,
47
- lastCompleteBillingDate
47
+ lastCompleteBillingDate,
48
+ 2,
49
+ pageFilters.customDateRange,
50
+ false
51
+ // Don't use comparison mode for entity costs
48
52
  );
49
53
  const fetchedDailyCost = await client.getCatalogEntityDailyCost(
50
54
  stringifyEntityRef(entity),