Merge pull request #2734 from spotify/timbonicus/example-data

Better data for ExampleCostInsightsClient
This commit is contained in:
Tim
2020-10-09 11:14:31 -06:00
committed by GitHub
4 changed files with 99 additions and 44 deletions
+4 -1
View File
@@ -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",
@@ -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(/\/(?<duration>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<DataPoint> = 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<any> {
return new Promise(resolve => setTimeout(resolve, 0, res));
@@ -58,30 +121,17 @@ export class ExampleCostInsightsClient implements CostInsightsApi {
metric: string | null,
intervals: string,
): Promise<Cost> {
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<Cost> {
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',
@@ -61,7 +61,7 @@ const CostOverviewCard = ({
</CostOverviewHeader>
<Divider />
<Box marginY={1} display="flex" flexDirection="column">
<CostOverviewChartLegend change={change} title={name} />
<CostOverviewChartLegend change={change} title={`${name} Trend`} />
<CostOverviewChart
responsive
metric={metric}
+15
View File
@@ -5457,6 +5457,11 @@
dependencies:
"@types/node" "*"
"@types/regression@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@types/regression/-/regression-2.0.0.tgz#0677ea78d7bdb37039c02ebbccf062042f756ae3"
integrity sha512-Ch2FD53M1HpFLL6zSTc/sfuyqQcIPy+/PV3xFT6QYtk9EOiMI29XOYmLNxBb1Y0lfMOR/NNa86J1gRc/1jGLyw==
"@types/relateurl@*":
version "0.2.28"
resolved "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz#6bda7db8653fa62643f5ee69e9f69c11a392e3a6"
@@ -9529,6 +9534,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"
@@ -19877,6 +19887,11 @@ regjsparser@^0.6.4:
dependencies:
jsesc "~0.5.0"
regression@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz#8d29c3e8224a10850c35e337e85a8b2fac3b0c87"
integrity sha1-jSnD6CJKEIUMNeM36FqLL6w7DIc=
relateurl@^0.2.7:
version "0.2.7"
resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"