From 49ac6cec38c138b9dd1a1fe2be141737df30ef14 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 2 Oct 2020 21:02:10 -0600 Subject: [PATCH 1/5] Better example data --- packages/app/package.json | 3 + .../ExampleCostInsightsClient.ts | 118 +++++++++++------- .../CostOverviewCard/CostOverviewCard.tsx | 2 +- yarn.lock | 10 ++ 4 files changed, 88 insertions(+), 45 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 2373887cc6..d194d8dd2e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -36,6 +36,7 @@ "@roadiehq/backstage-plugin-github-pull-requests": "0.3.0", "@roadiehq/backstage-plugin-travis-ci": "^0.2.3", "history": "^5.0.0", + "moment": "^2.27.0", "prop-types": "^15.7.2", "react": "^16.12.0", "react-dom": "^16.12.0", @@ -43,6 +44,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": { @@ -54,6 +56,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 c01092dc51..ede37758fd 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -15,16 +15,72 @@ */ /* eslint-disable no-restricted-imports */ +import moment from 'moment'; +import regression from 'regression'; import { - CostInsightsApi, Alert, + ChangeStatistic, Cost, + CostInsightsApi, + DateAggregation, Duration, - Project, - ProductCost, Group, + ProductCost, + Project, + Trendline, } 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 = moment.duration(duration).asDays() * 2; + + return [...Array(days).keys()].reduce( + (values: DateAggregation[], i: number): DateAggregation[] => { + const last = values.length ? values[values.length - 1].amount : baseline; + values.push({ + date: moment() + .subtract(days, 'days') + .add(i, 'days') + .format('YYYY-MM-DD'), + amount: last + (baseline / 20) * (Math.random() * 2 - 1), + }); + return values; + }, + [], + ); +} + +function trendlineOf(aggregation: DateAggregation[]): Trendline { + const data = [...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)); @@ -53,30 +109,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), }, ); @@ -88,30 +131,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), }, ); @@ -134,7 +164,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 ef174132a3..8bcdc5b323 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -64,7 +64,7 @@ const CostOverviewCard = ({ - + Date: Fri, 2 Oct 2020 21:14:03 -0600 Subject: [PATCH 2/5] Use actual duration days --- .../plugins/cost-insights/ExampleCostInsightsClient.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index ede37758fd..b05ba1b5e9 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -24,7 +24,9 @@ import { CostInsightsApi, DateAggregation, Duration, + exclusiveEndDateOf, Group, + inclusiveStartDateOf, ProductCost, Project, Trendline, @@ -40,14 +42,16 @@ function aggregationFor( duration: Duration, baseline: number, ): DateAggregation[] { - const days = moment.duration(duration).asDays() * 2; + const days = moment(exclusiveEndDateOf(duration)).diff( + inclusiveStartDateOf(duration), + 'days', + ); return [...Array(days).keys()].reduce( (values: DateAggregation[], i: number): DateAggregation[] => { const last = values.length ? values[values.length - 1].amount : baseline; values.push({ - date: moment() - .subtract(days, 'days') + date: moment(inclusiveStartDateOf(duration)) .add(i, 'days') .format('YYYY-MM-DD'), amount: last + (baseline / 20) * (Math.random() * 2 - 1), From 87b17c7049253e11e08918a8a98aa064b7c8a8c2 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Thu, 8 Oct 2020 20:04:43 -0600 Subject: [PATCH 3/5] Fix yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 0594e7737f..ddc17865cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19849,7 +19849,7 @@ regjsparser@^0.6.4: regression@^2.0.1: version "2.0.1" - resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/regression/-/regression-2.0.1.tgz#8d29c3e8224a10850c35e337e85a8b2fac3b0c87" + resolved "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz#8d29c3e8224a10850c35e337e85a8b2fac3b0c87" integrity sha1-jSnD6CJKEIUMNeM36FqLL6w7DIc= relateurl@^0.2.7: From 25222df5bff1ba60bfe171a8e43a403a11f59a81 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Thu, 8 Oct 2020 20:16:26 -0600 Subject: [PATCH 4/5] Fix regression types --- .../src/plugins/cost-insights/ExampleCostInsightsClient.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index b05ba1b5e9..e074717b66 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -16,7 +16,7 @@ /* eslint-disable no-restricted-imports */ import moment from 'moment'; -import regression from 'regression'; +import regression, { DataPoint } from 'regression'; import { Alert, ChangeStatistic, @@ -63,7 +63,10 @@ function aggregationFor( } function trendlineOf(aggregation: DateAggregation[]): Trendline { - const data = [...aggregation.map(a => [Date.parse(a.date) / 1000, a.amount])]; + 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], From 68351f5a5c76a462bdf98bf847c4432fc29addd7 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Thu, 8 Oct 2020 20:57:11 -0600 Subject: [PATCH 5/5] moment -> dayjs --- packages/app/package.json | 4 ++-- .../plugins/cost-insights/ExampleCostInsightsClient.ts | 10 +++++----- yarn.lock | 5 +++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index e9f63a275d..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,8 +36,8 @@ "@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", - "moment": "^2.27.0", "prop-types": "^15.7.2", "react": "^16.12.0", "react-dom": "^16.12.0", diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index e074717b66..bd612cdb59 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -15,7 +15,7 @@ */ /* eslint-disable no-restricted-imports */ -import moment from 'moment'; +import dayjs from 'dayjs'; import regression, { DataPoint } from 'regression'; import { Alert, @@ -42,17 +42,17 @@ function aggregationFor( duration: Duration, baseline: number, ): DateAggregation[] { - const days = moment(exclusiveEndDateOf(duration)).diff( + const days = dayjs(exclusiveEndDateOf(duration)).diff( inclusiveStartDateOf(duration), - 'days', + 'day', ); return [...Array(days).keys()].reduce( (values: DateAggregation[], i: number): DateAggregation[] => { const last = values.length ? values[values.length - 1].amount : baseline; values.push({ - date: moment(inclusiveStartDateOf(duration)) - .add(i, 'days') + date: dayjs(inclusiveStartDateOf(duration)) + .add(i, 'day') .format('YYYY-MM-DD'), amount: last + (baseline / 20) * (Math.random() * 2 - 1), }); diff --git a/yarn.lock b/yarn.lock index 007b5a199e..b3a0039dce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9544,6 +9544,11 @@ dateformat@^3.0.0: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dayjs@^1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.9.1.tgz#201a755f7db5103ed6de63ba93a984141c754541" + integrity sha512-01NCTBg8cuMJG1OQc6PR7T66+AFYiPwgDvdJmvJBn29NGzIG+DIFxPLNjHzwz3cpFIvG+NcwIjP9hSaPVoOaDg== + de-indent@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"