diff --git a/packages/app/package.json b/packages/app/package.json index 76e20bc0dc..a3b150cef6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -27,8 +27,8 @@ "@backstage/plugin-sentry": "^0.1.1-alpha.24", "@backstage/plugin-tech-radar": "^0.1.1-alpha.24", "@backstage/plugin-techdocs": "^0.1.1-alpha.24", - "@backstage/plugin-welcome": "^0.1.1-alpha.24", "@backstage/plugin-user-settings": "^0.1.1-alpha.24", + "@backstage/plugin-welcome": "^0.1.1-alpha.24", "@backstage/test-utils": "^0.1.1-alpha.24", "@backstage/theme": "^0.1.1-alpha.24", "@material-ui/core": "^4.11.0", @@ -36,6 +36,7 @@ "@octokit/rest": "^18.0.0", "@roadiehq/backstage-plugin-github-pull-requests": "^0.4.3", "@roadiehq/backstage-plugin-travis-ci": "^0.2.3", + "dayjs": "^1.9.1", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", @@ -44,6 +45,7 @@ "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3", + "regression": "^2.0.1", "zen-observable": "^0.8.15" }, "devDependencies": { @@ -55,6 +57,7 @@ "@types/jquery": "^3.3.34", "@types/node": "^12.0.0", "@types/react-dom": "^16.9.8", + "@types/regression": "^2.0.0", "@types/zen-observable": "^0.8.0", "cross-env": "^7.0.0", "cypress": "^4.2.0", diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index a109c3b2eb..aa795e9eee 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -15,21 +15,84 @@ */ /* eslint-disable no-restricted-imports */ +import dayjs from 'dayjs'; +import regression, { DataPoint } from 'regression'; import { Alert, + ChangeStatistic, Cost, CostInsightsApi, + DateAggregation, Duration, + exclusiveEndDateOf, Group, + inclusiveStartDateOf, + Maybe, ProductCost, Project, ProjectGrowthAlert, ProjectGrowthData, + Trendline, UnlabeledDataflowAlert, UnlabeledDataflowData, - Maybe, } from '@backstage/plugin-cost-insights'; +function durationOf(intervals: string): Duration { + const match = intervals.match(/\/(?P\d+[DM])\//); + const { duration } = match!.groups!; + return duration as Duration; +} + +function aggregationFor( + duration: Duration, + baseline: number, +): DateAggregation[] { + const days = dayjs(exclusiveEndDateOf(duration)).diff( + inclusiveStartDateOf(duration), + 'day', + ); + + return [...Array(days).keys()].reduce( + (values: DateAggregation[], i: number): DateAggregation[] => { + const last = values.length ? values[values.length - 1].amount : baseline; + values.push({ + date: dayjs(inclusiveStartDateOf(duration)) + .add(i, 'day') + .format('YYYY-MM-DD'), + amount: last + (baseline / 20) * (Math.random() * 2 - 1), + }); + return values; + }, + [], + ); +} + +function trendlineOf(aggregation: DateAggregation[]): Trendline { + const data: ReadonlyArray = aggregation.map(a => [ + Date.parse(a.date) / 1000, + a.amount, + ]); + const result = regression.linear(data, { precision: 5 }); + return { + slope: result.equation[0], + intercept: result.equation[1], + }; +} + +function changeOf(aggregation: DateAggregation[]): ChangeStatistic { + const half = Math.ceil(aggregation.length / 2); + const before = aggregation + .slice(0, half) + .reduce((sum, a) => sum + a.amount, 0); + const after = aggregation + .slice(half, aggregation.length) + .reduce((sum, a) => sum + a.amount, 0); + return { + ratio: (after - before) / before, + amount: after - before, + }; +} + export class ExampleCostInsightsClient implements CostInsightsApi { private request(_: any, res: any): Promise { return new Promise(resolve => setTimeout(resolve, 0, res)); @@ -58,30 +121,17 @@ export class ExampleCostInsightsClient implements CostInsightsApi { metric: string | null, intervals: string, ): Promise { + const aggregation = aggregationFor( + durationOf(intervals), + metric ? 0.3 : 8_000, + ); const groupDailyCost: Cost = await this.request( { group, metric, intervals }, { id: metric, // costs with null ids will appear as "All Projects" in Cost Overview panel - aggregation: [ - { date: '2020-08-01', amount: 75_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-02', amount: 120_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-03', amount: 110_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-04', amount: 90_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-05', amount: 80_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-06', amount: 85_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-07', amount: 82_500 / (metric ? 200_000 : 1) }, - { date: '2020-08-08', amount: 100_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-09', amount: 130_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-10', amount: 140_000 / (metric ? 200_000 : 1) }, - ], - change: { - ratio: 0.86, - amount: 65_000, - }, - trendline: { - slope: 0, - intercept: 90_000, - }, + aggregation: aggregation, + change: changeOf(aggregation), + trendline: trendlineOf(aggregation), }, ); @@ -93,30 +143,17 @@ export class ExampleCostInsightsClient implements CostInsightsApi { metric: string | null, intervals: string, ): Promise { + const aggregation = aggregationFor( + durationOf(intervals), + metric ? 0.1 : 1_500, + ); const projectDailyCost: Cost = await this.request( { project, metric, intervals }, { id: 'project-a', - aggregation: [ - { date: '2020-08-01', amount: 1000 }, - { date: '2020-08-02', amount: 2000 }, - { date: '2020-08-03', amount: 3000 }, - { date: '2020-08-04', amount: 4000 }, - { date: '2020-08-05', amount: 5000 }, - { date: '2020-08-06', amount: 6000 }, - { date: '2020-08-07', amount: 7000 }, - { date: '2020-08-08', amount: 8000 }, - { date: '2020-08-09', amount: 9000 }, - { date: '2020-08-10', amount: 10_000 }, - ], - change: { - ratio: 0.5, - amount: 10000, - }, - trendline: { - slope: 0, - intercept: 0, - }, + aggregation: aggregation, + change: changeOf(aggregation), + trendline: trendlineOf(aggregation), }, ); @@ -168,7 +205,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi { entities: [ { id: null, // entities with null ids will be appear as "Unlabeled" in product panels - aggregation: [45_000, 50_000], + aggregation: [15_000, 30_000], }, { id: 'entity-a', diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index 1d9543c8c0..b8d1be5d01 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -61,7 +61,7 @@ const CostOverviewCard = ({ - +