diff --git a/plugins/cost-insights/dev/index.tsx b/plugins/cost-insights/dev/index.tsx
index e4732fc364..8cfa12fc18 100644
--- a/plugins/cost-insights/dev/index.tsx
+++ b/plugins/cost-insights/dev/index.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import { createDevApp } from '@backstage/dev-utils';
+import { ExampleCostInsightsClient } from '../src/example';
import { costInsightsApiRef } from '../src/api';
-import { ExampleCostInsightsClient } from '../src/client';
import { costInsightsPlugin } from '../src/plugin';
createDevApp()
diff --git a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx
index 05a10bb2f0..35a0248ef5 100644
--- a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx
+++ b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx
@@ -23,7 +23,7 @@ import {
MockCurrencyProvider,
MockConfigProvider,
MockBillingDateProvider,
-} from '../utils/tests';
+} from '../testUtils';
const mockData: ProjectGrowthData = {
project: 'test-project',
diff --git a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx
index 95e213f06a..00b8332cd9 100644
--- a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx
+++ b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx
@@ -23,7 +23,7 @@ import {
MockCurrencyProvider,
MockConfigProvider,
MockBillingDateProvider,
-} from '../utils/tests';
+} from '../testUtils';
const mockData: UnlabeledDataflowData = {
periodStart: '2021-02-01',
diff --git a/plugins/cost-insights/src/alerts/index.ts b/plugins/cost-insights/src/alerts/index.ts
index b55c012a60..a732083454 100644
--- a/plugins/cost-insights/src/alerts/index.ts
+++ b/plugins/cost-insights/src/alerts/index.ts
@@ -16,6 +16,3 @@
export { ProjectGrowthAlert } from './ProjectGrowthAlert';
export { UnlabeledDataflowAlert } from './UnlabeledDataflowAlert';
-export { KubernetesMigrationAlert } from './KubernetesMigrationAlert';
-export type { MigrationAlert } from './KubernetesMigrationAlert';
-export type { MigrationData } from './KubernetesMigrationAlert';
diff --git a/plugins/cost-insights/src/components/ActionItems/ActionItemCard.test.tsx b/plugins/cost-insights/src/components/ActionItems/ActionItemCard.test.tsx
index b72f050e5b..70ae44bcdc 100644
--- a/plugins/cost-insights/src/components/ActionItems/ActionItemCard.test.tsx
+++ b/plugins/cost-insights/src/components/ActionItems/ActionItemCard.test.tsx
@@ -17,7 +17,7 @@
import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { ActionItemCard } from './ActionItemCard';
-import { MockScrollProvider } from '../../utils/tests';
+import { MockScrollProvider } from '../../testUtils';
import { ProjectGrowthAlert } from '../../alerts';
import { ProjectGrowthData } from '../../types';
diff --git a/plugins/cost-insights/src/components/ActionItems/ActionItems.test.tsx b/plugins/cost-insights/src/components/ActionItems/ActionItems.test.tsx
index 677ba99c6a..39b8d1ad05 100644
--- a/plugins/cost-insights/src/components/ActionItems/ActionItems.test.tsx
+++ b/plugins/cost-insights/src/components/ActionItems/ActionItems.test.tsx
@@ -17,7 +17,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import { ActionItems } from './ActionItems';
-import { MockScrollProvider } from '../../utils/tests';
+import { MockScrollProvider } from '../../testUtils';
function renderInContext(children: JSX.Element) {
return render({children});
diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsights.test.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsights.test.tsx
index 98f75b1d41..5509d8f1ea 100644
--- a/plugins/cost-insights/src/components/AlertInsights/AlertInsights.test.tsx
+++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsights.test.tsx
@@ -17,7 +17,7 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { AlertInsights } from './AlertInsights';
-import { MockScrollProvider, MockLoadingProvider } from '../../utils/tests';
+import { MockScrollProvider, MockLoadingProvider } from '../../testUtils';
function renderInContext(children: JSX.Element) {
return render(
diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.test.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.test.tsx
index 937e099356..093ca1ff0c 100644
--- a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.test.tsx
+++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.test.tsx
@@ -17,7 +17,7 @@ import React from 'react';
import { AlertInsightsSection } from './AlertInsightsSection';
import { render } from '@testing-library/react';
import { Alert } from '../../types';
-import { MockScrollProvider } from '../../utils/tests';
+import { MockScrollProvider } from '../../testUtils';
const mockAlert: Alert = {
subtitle:
diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertStatusSummary.test.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertStatusSummary.test.tsx
index 830db19fcd..ab39e0364e 100644
--- a/plugins/cost-insights/src/components/AlertInsights/AlertStatusSummary.test.tsx
+++ b/plugins/cost-insights/src/components/AlertInsights/AlertStatusSummary.test.tsx
@@ -17,7 +17,7 @@ import React from 'react';
import { AlertStatusSummary } from './AlertStatusSummary';
import { render } from '@testing-library/react';
import { Alert, AlertStatus } from '../../types';
-import { MockScrollProvider } from '../../utils/tests';
+import { MockScrollProvider } from '../../testUtils';
const mockSnoozed: Alert = {
title: 'snoozed-title',
diff --git a/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx b/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx
index 4e40a5ddda..cd031f9d1b 100644
--- a/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx
+++ b/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx
@@ -18,7 +18,7 @@ import React from 'react';
import { fireEvent } from '@testing-library/react';
import { BarChart, BarChartProps } from './BarChart';
import { ResourceData } from '../../types';
-import { createMockEntity } from '../../utils/mockData';
+import { createMockEntity } from '../../testUtils';
import { resourceSort } from '../../utils/sort';
import { renderInTestApp } from '@backstage/test-utils';
diff --git a/plugins/cost-insights/src/components/CostGrowth/CostGrowth.test.tsx b/plugins/cost-insights/src/components/CostGrowth/CostGrowth.test.tsx
index 12668140b6..f8c06845c9 100644
--- a/plugins/cost-insights/src/components/CostGrowth/CostGrowth.test.tsx
+++ b/plugins/cost-insights/src/components/CostGrowth/CostGrowth.test.tsx
@@ -19,7 +19,7 @@ import { renderInTestApp } from '@backstage/test-utils';
import { CostGrowth } from './CostGrowth';
import { Currency, CurrencyType, Duration } from '../../types';
import { findAlways } from '../../utils/assert';
-import { MockConfigProvider, MockCurrencyProvider } from '../../utils/tests';
+import { MockConfigProvider, MockCurrencyProvider } from '../../testUtils';
import { defaultCurrencies } from '../../utils/currency';
const engineers = findAlways(defaultCurrencies, c => c.kind === null);
diff --git a/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx b/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx
index f94ab48c6f..9b53cb9d1a 100644
--- a/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx
+++ b/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx
@@ -19,7 +19,7 @@ import { default as HappyFace } from '@material-ui/icons/SentimentSatisfiedAlt';
import { renderInTestApp } from '@backstage/test-utils';
import { CostInsightsNavigation } from './CostInsightsNavigation';
import { Product, Icon } from '../../types';
-import { MockConfigProvider, MockScrollProvider } from '../../utils/tests';
+import { MockConfigProvider, MockScrollProvider } from '../../testUtils';
import { getDefaultNavigationItems } from '../../utils/navigation';
const mockIcons: Icon[] = [
diff --git a/plugins/cost-insights/src/components/CostInsightsTabs/CostInsightsTabs.test.tsx b/plugins/cost-insights/src/components/CostInsightsTabs/CostInsightsTabs.test.tsx
index a910d43c22..24204abd95 100644
--- a/plugins/cost-insights/src/components/CostInsightsTabs/CostInsightsTabs.test.tsx
+++ b/plugins/cost-insights/src/components/CostInsightsTabs/CostInsightsTabs.test.tsx
@@ -18,7 +18,7 @@ import React from 'react';
import { CostInsightsTabs } from './CostInsightsTabs';
import UserEvent from '@testing-library/user-event';
import { Group } from '../../types';
-import { MockFilterProvider, MockLoadingProvider } from '../../utils/tests';
+import { MockFilterProvider, MockLoadingProvider } from '../../testUtils';
import { renderInTestApp } from '@backstage/test-utils';
const mockSetPageFilters = jest.fn();
diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.test.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.test.tsx
index 6fca8f67c9..3ff4d31135 100644
--- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.test.tsx
+++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.test.tsx
@@ -24,13 +24,11 @@ import {
getGroupedProjects,
MockAggregatedDailyCosts,
trendlineOf,
-} from '../../utils/mockData';
-import {
MockBillingDateProvider,
MockConfigProvider,
MockFilterProvider,
MockScrollProvider,
-} from '../../utils/tests';
+} from '../../testUtils';
import { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider';
const mockGroupDailyCost: Cost = {
diff --git a/plugins/cost-insights/src/components/PeriodSelect/PeriodSelect.test.tsx b/plugins/cost-insights/src/components/PeriodSelect/PeriodSelect.test.tsx
index ae513c04e0..0c43e4d403 100644
--- a/plugins/cost-insights/src/components/PeriodSelect/PeriodSelect.test.tsx
+++ b/plugins/cost-insights/src/components/PeriodSelect/PeriodSelect.test.tsx
@@ -20,7 +20,7 @@ import { renderInTestApp } from '@backstage/test-utils';
import UserEvent from '@testing-library/user-event';
import { PeriodSelect, getDefaultOptions } from './PeriodSelect';
import { getDefaultPageFilters } from '../../utils/filters';
-import { MockBillingDateProvider } from '../../utils/tests';
+import { MockBillingDateProvider } from '../../testUtils';
import { Group, Duration } from '../../types';
const DefaultPageFilters = getDefaultPageFilters([{ id: 'tools' }] as Group[]);
diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx
index 3719bc8f6c..f782659ff2 100644
--- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx
+++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx
@@ -18,8 +18,8 @@ import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { ProductInsights } from './ProductInsights';
import { ProductInsightsOptions } from '../../api';
-import { mockDefaultLoadingState } from '../../utils/mockData';
import {
+ mockDefaultLoadingState,
MockConfigProvider,
MockCostInsightsApiProvider,
MockCurrencyProvider,
@@ -27,7 +27,7 @@ import {
MockBillingDateProvider,
MockScrollProvider,
MockLoadingProvider,
-} from '../../utils/tests';
+} from '../../testUtils';
import { Entity, Product } from '../../types';
// suppress recharts componentDidUpdate warnings
diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx
index a2f87b1d86..cd171feba6 100644
--- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx
+++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx
@@ -22,15 +22,13 @@ import {
createMockEntity,
mockDefaultLoadingState,
MockComputeEngine,
-} from '../../utils/mockData';
-import {
MockConfigProvider,
MockCostInsightsApiProvider,
MockCurrencyProvider,
MockBillingDateProvider,
MockScrollProvider,
MockLoadingProvider,
-} from '../../utils/tests';
+} from '../../testUtils';
import { Duration, Entity, Product } from '../../types';
// suppress recharts componentDidUpdate warnings
diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx
index 0e24f042bc..52f334c4cb 100644
--- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx
+++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx
@@ -17,12 +17,12 @@
import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { ProjectGrowthAlertCard } from './ProjectGrowthAlertCard';
-import { createMockProjectGrowthData } from '../../utils/mockData';
import {
+ createMockProjectGrowthData,
MockCurrencyProvider,
MockConfigProvider,
MockBillingDateProvider,
-} from '../../utils/tests';
+} from '../../testUtils';
import { AlertCost } from '../../types';
import { defaultCurrencies } from '../../utils/currency';
import { findAlways } from '../../utils/assert';
diff --git a/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.test.tsx b/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.test.tsx
index 16270429e8..1bd57a9eed 100644
--- a/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.test.tsx
+++ b/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.test.tsx
@@ -18,7 +18,7 @@ import React from 'react';
import { getByRole, waitFor } from '@testing-library/react';
import UserEvent from '@testing-library/user-event';
import { ProjectSelect } from './ProjectSelect';
-import { MockFilterProvider } from '../../utils/tests';
+import { MockFilterProvider } from '../../testUtils';
import { renderInTestApp } from '@backstage/test-utils';
const mockProjects = [
diff --git a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx
index df38c78a03..5aea9be548 100644
--- a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx
+++ b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx
@@ -19,7 +19,7 @@ import { UnlabeledDataflowAlertCard } from './UnlabeledDataflowAlertCard';
import {
createMockUnlabeledDataflowData,
createMockUnlabeledDataflowAlertProject,
-} from '../../utils/mockData';
+} from '../../testUtils';
import { renderInTestApp } from '@backstage/test-utils';
const MockUnlabeledDataflowAlertMultipleProjects = createMockUnlabeledDataflowData(
diff --git a/plugins/cost-insights/src/components/index.ts b/plugins/cost-insights/src/components/index.ts
index c390666419..4388250d31 100644
--- a/plugins/cost-insights/src/components/index.ts
+++ b/plugins/cost-insights/src/components/index.ts
@@ -14,6 +14,28 @@
* limitations under the License.
*/
+export * from './ActionItems';
+export * from './AlertInsights';
+export * from './AlertInstructionsLayout';
export * from './BarChart';
+export * from './CopyUrlToClipboard';
export * from './CostGrowth';
+export * from './CostInsightsHeader';
+export * from './CostInsightsLayout';
+export * from './CostInsightsNavigation';
+export * from './CostInsightsPage';
+export * from './CostInsightsSupportButton';
+export * from './CostInsightsTabs';
+export * from './CostOverviewCard';
+export * from './CurrencySelect';
+export * from './LabelDataflowInstructionsPage';
export * from './LegendItem';
+export * from './MetricSelect';
+export * from './PeriodSelect';
+export * from './ProductInsights';
+export * from './ProductInsightsCard';
+export * from './ProjectGrowthAlertCard';
+export * from './ProjectGrowthInstructionsPage';
+export * from './ProjectSelect';
+export * from './UnlabeledDataflowAlertCard';
+export * from './WhyCostsMatter';
diff --git a/plugins/cost-insights/src/alerts/KubernetesMigrationAlert.tsx b/plugins/cost-insights/src/example/alerts/KubernetesMigrationAlert.tsx
similarity index 86%
rename from plugins/cost-insights/src/alerts/KubernetesMigrationAlert.tsx
rename to plugins/cost-insights/src/example/alerts/KubernetesMigrationAlert.tsx
index 31a4d7e0e9..4878462f62 100644
--- a/plugins/cost-insights/src/alerts/KubernetesMigrationAlert.tsx
+++ b/plugins/cost-insights/src/example/alerts/KubernetesMigrationAlert.tsx
@@ -16,8 +16,8 @@
import React from 'react';
import pluralize from 'pluralize';
-import { MigrationAlertCard } from '../components/MigrationAlertCard';
-import { CostInsightsApi } from '../api';
+import { KubernetesMigrationAlertCard } from '../components';
+import { CostInsightsApi } from '../../api';
import {
Alert,
AlertForm,
@@ -26,19 +26,22 @@ import {
AlertSnoozeFormData,
ChangeStatistic,
Entity,
-} from '../types';
-import { MigrationDismissForm, MigrationDismissFormData } from '../forms';
+} from '../../types';
+import {
+ KubernetesMigrationDismissForm,
+ KubernetesMigrationDismissFormData,
+} from '../forms';
-export interface MigrationData {
+export interface KubernetesMigrationData {
startDate: string;
endDate: string;
change: ChangeStatistic;
services: Array;
}
-export interface MigrationAlert extends Alert {
+export interface KubernetesMigrationApi extends Alert {
api: CostInsightsApi;
- data: MigrationData;
+ data: KubernetesMigrationData;
}
/**
@@ -63,9 +66,9 @@ export interface MigrationAlert extends Alert {
* Custom forms must implement the corresponding event hook. See /forms for example implementations.
*/
-export class KubernetesMigrationAlert implements MigrationAlert {
+export class KubernetesMigrationAlert implements KubernetesMigrationApi {
api: CostInsightsApi;
- data: MigrationData;
+ data: KubernetesMigrationData;
subtitle =
'Services running on Kubernetes are estimated to save 50% or more compared to Compute Engine.';
@@ -74,11 +77,11 @@ export class KubernetesMigrationAlert implements MigrationAlert {
AcceptForm = null;
// Overrides default Dismiss form with a custom form component.
DismissForm: AlertForm<
- MigrationAlert,
- MigrationDismissFormData
- > = MigrationDismissForm;
+ KubernetesMigrationAlert,
+ KubernetesMigrationDismissFormData
+ > = KubernetesMigrationDismissForm;
- constructor(api: CostInsightsApi, data: MigrationData) {
+ constructor(api: CostInsightsApi, data: KubernetesMigrationData) {
this.api = api;
this.data = data;
}
@@ -98,7 +101,7 @@ export class KubernetesMigrationAlert implements MigrationAlert {
true,
)}, sorted by cost`;
return (
- ,
+ options: AlertOptions,
): Promise {
const alerts = await this.api.getAlerts(options.group);
return new Promise(resolve =>
diff --git a/plugins/cost-insights/src/example/alerts/index.ts b/plugins/cost-insights/src/example/alerts/index.ts
new file mode 100644
index 0000000000..eadd8d7ef4
--- /dev/null
+++ b/plugins/cost-insights/src/example/alerts/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { KubernetesMigrationAlert } from './KubernetesMigrationAlert';
+export type { KubernetesMigrationApi } from './KubernetesMigrationAlert';
+export type { KubernetesMigrationData } from './KubernetesMigrationAlert';
diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/example/client.ts
similarity index 96%
rename from plugins/cost-insights/src/client.ts
rename to plugins/cost-insights/src/example/client.ts
index 2e70170fc1..00d0a25233 100644
--- a/plugins/cost-insights/src/client.ts
+++ b/plugins/cost-insights/src/example/client.ts
@@ -16,7 +16,7 @@
/* eslint-disable no-restricted-imports */
import dayjs from 'dayjs';
-import { CostInsightsApi, ProductInsightsOptions } from '../src/api';
+import { CostInsightsApi, ProductInsightsOptions } from '../api';
import {
Alert,
Cost,
@@ -27,12 +27,9 @@ import {
Project,
ProjectGrowthData,
UnlabeledDataflowData,
-} from '../src/types';
-import {
- ProjectGrowthAlert,
- UnlabeledDataflowAlert,
- KubernetesMigrationAlert,
-} from '../src/alerts';
+} from '../types';
+import { KubernetesMigrationAlert } from './alerts';
+import { ProjectGrowthAlert, UnlabeledDataflowAlert } from '../alerts';
import {
aggregationFor,
changeOf,
@@ -40,7 +37,7 @@ import {
getGroupedProducts,
getGroupedProjects,
trendlineOf,
-} from './utils/mockData';
+} from '../testUtils';
export class ExampleCostInsightsClient implements CostInsightsApi {
private request(_: any, res: any): Promise {
diff --git a/plugins/cost-insights/src/components/MigrationAlertCard/MigrationAlertCard.tsx b/plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.tsx
similarity index 75%
rename from plugins/cost-insights/src/components/MigrationAlertCard/MigrationAlertCard.tsx
rename to plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.tsx
index 56d636d8aa..3cba2b7e4d 100644
--- a/plugins/cost-insights/src/components/MigrationAlertCard/MigrationAlertCard.tsx
+++ b/plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.tsx
@@ -17,30 +17,30 @@
import React from 'react';
import { Box } from '@material-ui/core';
import { InfoCard } from '@backstage/core';
-import { MigrationBarChartLegend } from './MigrationBarChartLegend';
-import { MigrationBarChart } from './MigrationBarChart';
-import { MigrationData } from '../../alerts';
+import { KubernetesMigrationBarChartLegend } from './KubernetesMigrationBarChartLegend';
+import { KubernetesMigrationBarChart } from './KubernetesMigrationBarChart';
+import { KubernetesMigrationData } from '../../alerts';
-type MigrationAlertProps = {
- data: MigrationData;
+type KubernetesMigrationAlertProps = {
+ data: KubernetesMigrationData;
title: string;
subheader: string;
currentProduct: string;
comparedProduct: string;
};
-export const MigrationAlertCard = ({
+export const KubernetesMigrationAlertCard = ({
data,
title,
subheader,
currentProduct,
comparedProduct,
-}: MigrationAlertProps) => {
+}: KubernetesMigrationAlertProps) => {
return (
-
- ;
};
-export const MigrationBarChart = ({
+export const KubernetesMigrationBarChart = ({
currentProduct,
comparedProduct,
services,
diff --git a/plugins/cost-insights/src/components/MigrationAlertCard/MigrationBarChartLegend.tsx b/plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.tsx
similarity index 89%
rename from plugins/cost-insights/src/components/MigrationAlertCard/MigrationBarChartLegend.tsx
rename to plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.tsx
index a84e865e28..2c74535d0d 100644
--- a/plugins/cost-insights/src/components/MigrationAlertCard/MigrationBarChartLegend.tsx
+++ b/plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.tsx
@@ -16,10 +16,9 @@
import React from 'react';
import { Box, useTheme } from '@material-ui/core';
-import { CostGrowth } from '../CostGrowth';
-import { LegendItem } from '../LegendItem';
-import { ChangeStatistic, CostInsightsTheme, Duration } from '../../types';
-import { monthOf } from '../../utils/formatters';
+import { CostGrowth, LegendItem } from '../../../components';
+import { ChangeStatistic, CostInsightsTheme, Duration } from '../../../types';
+import { monthOf } from '../../../utils/formatters';
export type MigrationBarChartLegendProps = {
change: ChangeStatistic;
@@ -28,7 +27,7 @@ export type MigrationBarChartLegendProps = {
comparedProduct: string;
};
-export const MigrationBarChartLegend = ({
+export const KubernetesMigrationBarChartLegend = ({
currentProduct,
comparedProduct,
change,
diff --git a/plugins/cost-insights/src/components/MigrationAlertCard/index.ts b/plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/index.ts
similarity index 90%
rename from plugins/cost-insights/src/components/MigrationAlertCard/index.ts
rename to plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/index.ts
index 4feb749c27..65f0c097f3 100644
--- a/plugins/cost-insights/src/components/MigrationAlertCard/index.ts
+++ b/plugins/cost-insights/src/example/components/KubernetesMigrationAlertCard/index.ts
@@ -14,4 +14,4 @@
* limitations under the License.
*/
-export { MigrationAlertCard } from './MigrationAlertCard';
+export * from './KubernetesMigrationAlertCard';
diff --git a/plugins/cost-insights/src/example/components/index.ts b/plugins/cost-insights/src/example/components/index.ts
new file mode 100644
index 0000000000..65f0c097f3
--- /dev/null
+++ b/plugins/cost-insights/src/example/components/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './KubernetesMigrationAlertCard';
diff --git a/plugins/cost-insights/src/forms/MigrationDismissForm.tsx b/plugins/cost-insights/src/example/forms/KubernetesMigrationDismissForm.tsx
similarity index 86%
rename from plugins/cost-insights/src/forms/MigrationDismissForm.tsx
rename to plugins/cost-insights/src/example/forms/KubernetesMigrationDismissForm.tsx
index ce71af3a37..835577787c 100644
--- a/plugins/cost-insights/src/forms/MigrationDismissForm.tsx
+++ b/plugins/cost-insights/src/example/forms/KubernetesMigrationDismissForm.tsx
@@ -28,22 +28,22 @@ import {
FormGroup,
Typography,
} from '@material-ui/core';
-import { AlertFormProps, Entity } from '../types';
-import { MigrationAlert } from '../alerts';
-import { findAlways } from '../utils/assert';
+import { AlertFormProps, Entity } from '../../types';
+import { KubernetesMigrationAlert } from '../alerts';
+import { findAlways } from '../../utils/assert';
-export type MigrationDismissFormData = {
+export type KubernetesMigrationDismissFormData = {
services: Entity[];
};
-export type MigrationDismissFormProps = AlertFormProps<
- MigrationAlert,
- MigrationDismissFormData
+export type KubernetesMigrationDismissFormProps = AlertFormProps<
+ KubernetesMigrationAlert,
+ KubernetesMigrationDismissFormData
>;
-export const MigrationDismissForm = forwardRef<
+export const KubernetesMigrationDismissForm = forwardRef<
HTMLFormElement,
- MigrationDismissFormProps
+ KubernetesMigrationDismissFormProps
>(({ onSubmit, disableSubmit, alert }, ref) => {
const [services, setServices] = useState(alert.data.services);
diff --git a/plugins/cost-insights/src/example/forms/index.ts b/plugins/cost-insights/src/example/forms/index.ts
new file mode 100644
index 0000000000..2ad52201f9
--- /dev/null
+++ b/plugins/cost-insights/src/example/forms/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './KubernetesMigrationDismissForm';
diff --git a/plugins/cost-insights/src/example/index.ts b/plugins/cost-insights/src/example/index.ts
new file mode 100644
index 0000000000..c27f03bdda
--- /dev/null
+++ b/plugins/cost-insights/src/example/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './alerts';
+export * from './components';
+export * from './forms';
+export * from './client';
diff --git a/plugins/cost-insights/src/forms/index.ts b/plugins/cost-insights/src/forms/index.ts
index 5b8384a293..4692bc1de3 100644
--- a/plugins/cost-insights/src/forms/index.ts
+++ b/plugins/cost-insights/src/forms/index.ts
@@ -17,5 +17,3 @@
export { AlertAcceptForm } from './AlertAcceptForm';
export { AlertSnoozeForm } from './AlertSnoozeForm';
export { AlertDismissForm } from './AlertDismissForm';
-export { MigrationDismissForm } from './MigrationDismissForm';
-export type { MigrationDismissFormData } from './MigrationDismissForm';
diff --git a/plugins/cost-insights/src/index.ts b/plugins/cost-insights/src/index.ts
index 0ae9d05b09..2d12eec9d9 100644
--- a/plugins/cost-insights/src/index.ts
+++ b/plugins/cost-insights/src/index.ts
@@ -21,10 +21,9 @@ export {
CostInsightsProjectGrowthInstructionsPage,
CostInsightsLabelDataflowInstructionsPage,
} from './plugin';
-export * from './client';
+export { ExampleCostInsightsClient } from './example';
+export { BarChart, LegendItem, CostGrowth } from './components';
+export { MockConfigProvider, MockCurrencyProvider } from './testUtils';
export * from './api';
-export { ProjectGrowthAlert, UnlabeledDataflowAlert } from './alerts';
-export * from './components';
-export { useCurrency } from './hooks';
+export * from './alerts';
export * from './types';
-export * from './utils/tests';
diff --git a/plugins/cost-insights/src/testUtils/alerts.ts b/plugins/cost-insights/src/testUtils/alerts.ts
new file mode 100644
index 0000000000..2e8e1b114a
--- /dev/null
+++ b/plugins/cost-insights/src/testUtils/alerts.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ ProjectGrowthData,
+ UnlabeledDataflowAlertProject,
+ UnlabeledDataflowData,
+} from '../types';
+
+type mockAlertRenderer = (alert: T) => T;
+type mockEntityRenderer = (entity: T) => T;
+
+export const createMockUnlabeledDataflowData = (
+ callback?: mockAlertRenderer,
+): UnlabeledDataflowData => {
+ const data: UnlabeledDataflowData = {
+ periodStart: '2020-05-01',
+ periodEnd: '2020-06-1',
+ projects: [],
+ unlabeledCost: 0,
+ labeledCost: 0,
+ };
+
+ if (typeof callback === 'function') {
+ return callback({ ...data });
+ }
+
+ return { ...data };
+};
+
+export const createMockUnlabeledDataflowAlertProject = (
+ callback?: mockEntityRenderer,
+): UnlabeledDataflowAlertProject => {
+ const defaultProject: UnlabeledDataflowAlertProject = {
+ id: 'test-alert-project',
+ unlabeledCost: 2000.0,
+ labeledCost: 3200.0,
+ };
+ if (typeof callback === 'function') {
+ return callback({ ...defaultProject });
+ }
+ return { ...defaultProject };
+};
+
+export const createMockProjectGrowthData = (
+ callback?: mockAlertRenderer,
+): ProjectGrowthData => {
+ const data: ProjectGrowthData = {
+ project: 'test-project-growth-alert',
+ periodStart: '2019-Q4',
+ periodEnd: '2020-Q1',
+ aggregation: [670532.1, 970502.8],
+ change: {
+ ratio: 0.5,
+ amount: 180000,
+ },
+ products: [],
+ };
+
+ if (typeof callback === 'function') {
+ return callback({ ...data });
+ }
+
+ return { ...data };
+};
diff --git a/plugins/cost-insights/src/testUtils/config.ts b/plugins/cost-insights/src/testUtils/config.ts
new file mode 100644
index 0000000000..7d48121506
--- /dev/null
+++ b/plugins/cost-insights/src/testUtils/config.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Config } from '@backstage/config';
+import { ConfigApi } from '@backstage/core';
+import { MockProductTypes } from './products';
+
+export const MockProductsConfig: Partial = {
+ keys: () => Object.keys(MockProductTypes),
+};
+
+export const MockMetricsConfig: Partial = {
+ getOptionalString: () => 'daily-cost',
+ keys: () => ['daily-cost'],
+};
+
+export const MockCostInsightsConfig: Partial = {
+ getConfig: () => MockProductsConfig as Config,
+ getOptionalConfig: () => MockMetricsConfig as Config,
+};
diff --git a/plugins/cost-insights/src/testUtils/filters.ts b/plugins/cost-insights/src/testUtils/filters.ts
new file mode 100644
index 0000000000..e401b1f2e7
--- /dev/null
+++ b/plugins/cost-insights/src/testUtils/filters.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Duration, ProductFilters } from '../types';
+import { MockProductTypes } from './products';
+
+export const MockProductFilters: ProductFilters = Object.keys(
+ MockProductTypes,
+).map(productType => ({ duration: Duration.P30D, productType }));
diff --git a/plugins/cost-insights/src/testUtils/index.ts b/plugins/cost-insights/src/testUtils/index.ts
new file mode 100644
index 0000000000..a1748b15b2
--- /dev/null
+++ b/plugins/cost-insights/src/testUtils/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export * from './alerts';
+export * from './config';
+export * from './filters';
+export * from './loading';
+export * from './mockData';
+export * from './products';
+export * from './providers';
+export * from './testUtils';
diff --git a/plugins/cost-insights/src/testUtils/loading.ts b/plugins/cost-insights/src/testUtils/loading.ts
new file mode 100644
index 0000000000..6c6f761ee8
--- /dev/null
+++ b/plugins/cost-insights/src/testUtils/loading.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { MockProducts } from './products';
+import {
+ DefaultLoadingAction,
+ getDefaultState as getDefaultLoadingState,
+} from '../utils/loading';
+
+export const MockDefaultLoadingActions = ([
+ DefaultLoadingAction.UserGroups,
+ DefaultLoadingAction.CostInsightsInitial,
+ DefaultLoadingAction.CostInsightsPage,
+] as string[]).concat(MockProducts.map(product => product.kind));
+
+export const mockDefaultLoadingState = getDefaultLoadingState(
+ MockDefaultLoadingActions,
+);
diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/testUtils/mockData.ts
similarity index 58%
rename from plugins/cost-insights/src/utils/mockData.ts
rename to plugins/cost-insights/src/testUtils/mockData.ts
index d4ac4cd2df..5149da278d 100644
--- a/plugins/cost-insights/src/utils/mockData.ts
+++ b/plugins/cost-insights/src/testUtils/mockData.ts
@@ -14,264 +14,7 @@
* limitations under the License.
*/
-import dayjs from 'dayjs';
-import regression, { DataPoint } from 'regression';
-import { Config } from '@backstage/config';
-import { ConfigApi } from '@backstage/core';
-import {
- ChangeStatistic,
- Duration,
- Entity,
- Product,
- ProductFilters,
- ProjectGrowthData,
- Trendline,
- UnlabeledDataflowAlertProject,
- UnlabeledDataflowData,
- DateAggregation,
- DEFAULT_DATE_FORMAT,
-} from '../types';
-import {
- DefaultLoadingAction,
- getDefaultState as getDefaultLoadingState,
-} from '../utils/loading';
-import { findAlways } from '../utils/assert';
-import { inclusiveEndDateOf, inclusiveStartDateOf } from './duration';
-
-type mockAlertRenderer = (alert: T) => T;
-type mockEntityRenderer = (entity: T) => T;
-
-type IntervalFields = {
- duration: Duration;
- endDate: string;
-};
-
-export const createMockEntity = (
- callback?: mockEntityRenderer,
-): Entity => {
- const defaultEntity: Entity = {
- id: 'test-entity',
- aggregation: [100, 200],
- entities: {},
- change: {
- ratio: 0,
- amount: 0,
- },
- };
-
- if (typeof callback === 'function') {
- return callback({ ...defaultEntity });
- }
- return { ...defaultEntity };
-};
-
-export const createMockProduct = (
- callback?: mockEntityRenderer,
-): Product => {
- const defaultProduct: Product = {
- kind: 'compute-engine',
- name: 'Compute Engine',
- };
- if (typeof callback === 'function') {
- return callback({ ...defaultProduct });
- }
- return { ...defaultProduct };
-};
-
-export const createMockProjectGrowthData = (
- callback?: mockAlertRenderer,
-): ProjectGrowthData => {
- const data: ProjectGrowthData = {
- project: 'test-project-growth-alert',
- periodStart: '2019-Q4',
- periodEnd: '2020-Q1',
- aggregation: [670532.1, 970502.8],
- change: {
- ratio: 0.5,
- amount: 180000,
- },
- products: [],
- };
-
- if (typeof callback === 'function') {
- return callback({ ...data });
- }
-
- return { ...data };
-};
-
-export const createMockUnlabeledDataflowData = (
- callback?: mockAlertRenderer,
-): UnlabeledDataflowData => {
- const data: UnlabeledDataflowData = {
- periodStart: '2020-05-01',
- periodEnd: '2020-06-1',
- projects: [],
- unlabeledCost: 0,
- labeledCost: 0,
- };
-
- if (typeof callback === 'function') {
- return callback({ ...data });
- }
-
- return { ...data };
-};
-
-export const createMockUnlabeledDataflowAlertProject = (
- callback?: mockEntityRenderer,
-): UnlabeledDataflowAlertProject => {
- const defaultProject: UnlabeledDataflowAlertProject = {
- id: 'test-alert-project',
- unlabeledCost: 2000.0,
- labeledCost: 3200.0,
- };
- if (typeof callback === 'function') {
- return callback({ ...defaultProject });
- }
- return { ...defaultProject };
-};
-
-export const MockProductTypes: Record = {
- 'compute-engine': 'Compute Engine',
- 'cloud-dataflow': 'Cloud Dataflow',
- 'cloud-storage': 'Cloud Storage',
- 'big-query': 'Big Query',
- 'big-table': 'BigTable',
- 'cloud-pub-sub': 'Cloud Pub/Sub',
-};
-
-export const MockProductFilters: ProductFilters = Object.keys(
- MockProductTypes,
-).map(productType => ({ duration: Duration.P30D, productType }));
-
-export const MockProducts: Product[] = Object.keys(MockProductTypes).map(
- productType =>
- createMockProduct(() => ({
- kind: productType,
- name: MockProductTypes[productType],
- })),
-);
-
-export const MockDefaultLoadingActions = ([
- DefaultLoadingAction.UserGroups,
- DefaultLoadingAction.CostInsightsInitial,
- DefaultLoadingAction.CostInsightsPage,
-] as string[]).concat(MockProducts.map(product => product.kind));
-
-export const mockDefaultLoadingState = getDefaultLoadingState(
- MockDefaultLoadingActions,
-);
-
-export const MockComputeEngine = findAlways(
- MockProducts,
- p => p.kind === 'compute-engine',
-);
-export const MockCloudDataflow = findAlways(
- MockProducts,
- p => p.kind === 'cloud-dataflow',
-);
-export const MockCloudStorage = findAlways(
- MockProducts,
- p => p.kind === 'cloud-storage',
-);
-export const MockBigQuery = findAlways(
- MockProducts,
- p => p.kind === 'big-query',
-);
-export const MockBigtable = findAlways(
- MockProducts,
- p => p.kind === 'big-table',
-);
-
-export const MockProductsConfig: Partial = {
- keys: () => Object.keys(MockProductTypes),
-};
-
-export const MockMetricsConfig: Partial = {
- getOptionalString: () => 'daily-cost',
- keys: () => ['daily-cost'],
-};
-
-export const MockCostInsightsConfig: Partial = {
- getConfig: () => MockProductsConfig as Config,
- getOptionalConfig: () => MockMetricsConfig as Config,
-};
-
-export 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],
- };
-}
-
-export function changeOf(aggregation: DateAggregation[]): ChangeStatistic {
- const firstAmount = aggregation.length ? aggregation[0].amount : 0;
- const lastAmount = aggregation.length
- ? aggregation[aggregation.length - 1].amount
- : 0;
- const ratio =
- firstAmount !== 0 ? (lastAmount - firstAmount) / firstAmount : 0;
- return {
- ratio: ratio,
- amount: lastAmount - firstAmount,
- };
-}
-
-export function aggregationFor(
- intervals: string,
- baseline: number,
-): DateAggregation[] {
- const { duration, endDate } = parseIntervals(intervals);
- const inclusiveEndDate = inclusiveEndDateOf(duration, endDate);
- const days = dayjs(endDate).diff(
- inclusiveStartDateOf(duration, inclusiveEndDate),
- 'day',
- );
-
- function nextDelta(): number {
- const varianceFromBaseline = 0.15;
- // Let's give positive vibes in trendlines - higher change for positive delta with >0.5 value
- const positiveTrendChance = 0.55;
- const normalization = positiveTrendChance - 1;
- return baseline * (Math.random() + normalization) * varianceFromBaseline;
- }
-
- return [...Array(days).keys()].reduce(
- (values: DateAggregation[], i: number): DateAggregation[] => {
- const last = values.length ? values[values.length - 1].amount : baseline;
- const date = dayjs(inclusiveStartDateOf(duration, inclusiveEndDate))
- .add(i, 'day')
- .format(DEFAULT_DATE_FORMAT);
- const amount = Math.max(0, last + nextDelta());
- values.push({
- date: date,
- amount: amount,
- });
- return values;
- },
- [],
- );
-}
-
-function parseIntervals(intervals: string): IntervalFields {
- const match = intervals.match(
- /\/(?P\d+[DM])\/(?\d{4}-\d{2}-\d{2})/,
- );
- if (Object.keys(match?.groups || {}).length !== 2) {
- throw new Error(`Invalid intervals: ${intervals}`);
- }
- const { duration, date } = match!.groups!;
- return {
- duration: duration as Duration,
- endDate: date,
- };
-}
+import { DateAggregation, Entity } from '../types';
export const MockAggregatedDailyCosts: DateAggregation[] = [
{
@@ -520,7 +263,7 @@ export const MockAggregatedDailyCosts: DateAggregation[] = [
},
];
-export const SampleBigQueryInsights: Entity = {
+export const MockBigQueryInsights: Entity = {
id: 'bigQuery',
aggregation: [10_000, 30_000],
change: {
@@ -560,7 +303,7 @@ export const SampleBigQueryInsights: Entity = {
},
};
-export const SampleCloudDataflowInsights: Entity = {
+export const MockCloudDataflowInsights: Entity = {
id: 'cloudDataflow',
aggregation: [100_000, 158_000],
change: {
@@ -579,7 +322,7 @@ export const SampleCloudDataflowInsights: Entity = {
entities: {
SKU: [
{
- id: 'Sample SKU A',
+ id: 'Mock SKU A',
aggregation: [3_000, 4_000],
change: {
ratio: 0.333333,
@@ -588,7 +331,7 @@ export const SampleCloudDataflowInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU B',
+ id: 'Mock SKU B',
aggregation: [7_000, 8_000],
change: {
ratio: 0.14285714,
@@ -609,7 +352,7 @@ export const SampleCloudDataflowInsights: Entity = {
entities: {
SKU: [
{
- id: 'Sample SKU A',
+ id: 'Mock SKU A',
aggregation: [20_000, 15_000],
change: {
ratio: -0.25,
@@ -618,7 +361,7 @@ export const SampleCloudDataflowInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU B',
+ id: 'Mock SKU B',
aggregation: [30_000, 35_000],
change: {
ratio: -0.16666666666666666,
@@ -627,7 +370,7 @@ export const SampleCloudDataflowInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU C',
+ id: 'Mock SKU C',
aggregation: [10_000, 20_000],
change: {
ratio: 1,
@@ -648,7 +391,7 @@ export const SampleCloudDataflowInsights: Entity = {
entities: {
SKU: [
{
- id: 'Sample SKU A',
+ id: 'Mock SKU A',
aggregation: [4_000, 4_000],
change: {
ratio: 0,
@@ -657,7 +400,7 @@ export const SampleCloudDataflowInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU B',
+ id: 'Mock SKU B',
aggregation: [8_000, 4_000],
change: {
ratio: -0.5,
@@ -681,7 +424,7 @@ export const SampleCloudDataflowInsights: Entity = {
},
};
-export const SampleCloudStorageInsights: Entity = {
+export const MockCloudStorageInsights: Entity = {
id: 'cloudStorage',
aggregation: [45_000, 45_000],
change: {
@@ -700,7 +443,7 @@ export const SampleCloudStorageInsights: Entity = {
entities: {
SKU: [
{
- id: 'Sample SKU A',
+ id: 'Mock SKU A',
aggregation: [10_000, 11_000],
change: {
ratio: 0.1,
@@ -709,7 +452,7 @@ export const SampleCloudStorageInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU B',
+ id: 'Mock SKU B',
aggregation: [2_000, 5_000],
change: {
ratio: 1.5,
@@ -718,7 +461,7 @@ export const SampleCloudStorageInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU C',
+ id: 'Mock SKU C',
aggregation: [3_000, 4_000],
change: {
ratio: 0.3333,
@@ -739,7 +482,7 @@ export const SampleCloudStorageInsights: Entity = {
entities: {
SKU: [
{
- id: 'Sample SKU A',
+ id: 'Mock SKU A',
aggregation: [12_000, 13_000],
change: {
ratio: 0.08333333333333333,
@@ -748,7 +491,7 @@ export const SampleCloudStorageInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU B',
+ id: 'Mock SKU B',
aggregation: [16_000, 12_000],
change: {
ratio: -0.25,
@@ -757,7 +500,7 @@ export const SampleCloudStorageInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU C',
+ id: 'Mock SKU C',
aggregation: [2_000, 0],
change: {
ratio: -1,
@@ -781,7 +524,7 @@ export const SampleCloudStorageInsights: Entity = {
},
};
-export const SampleComputeEngineInsights: Entity = {
+export const MockComputeEngineInsights: Entity = {
id: 'computeEngine',
aggregation: [80_000, 90_000],
change: {
@@ -800,7 +543,7 @@ export const SampleComputeEngineInsights: Entity = {
entities: {
SKU: [
{
- id: 'Sample SKU A',
+ id: 'Mock SKU A',
aggregation: [4_000, 2_000],
change: {
ratio: -0.5,
@@ -809,7 +552,7 @@ export const SampleComputeEngineInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU B',
+ id: 'Mock SKU B',
aggregation: [7_000, 6_000],
change: {
ratio: -0.14285714285714285,
@@ -818,7 +561,7 @@ export const SampleComputeEngineInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU C',
+ id: 'Mock SKU C',
aggregation: [9_000, 2_000],
change: {
ratio: -0.7777777777777778,
@@ -859,7 +602,7 @@ export const SampleComputeEngineInsights: Entity = {
entities: {
SKU: [
{
- id: 'Sample SKU A',
+ id: 'Mock SKU A',
aggregation: [1_000, 2_000],
change: {
ratio: 1,
@@ -868,7 +611,7 @@ export const SampleComputeEngineInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU B',
+ id: 'Mock SKU B',
aggregation: [4_000, 8_000],
change: {
ratio: 1,
@@ -877,7 +620,7 @@ export const SampleComputeEngineInsights: Entity = {
entities: {},
},
{
- id: 'Sample SKU C',
+ id: 'Mock SKU C',
aggregation: [5_000, 10_000],
change: {
ratio: 1,
@@ -921,7 +664,7 @@ export const SampleComputeEngineInsights: Entity = {
},
};
-export const SampleEventsInsights: Entity = {
+export const MockEventsInsights: Entity = {
id: 'events',
aggregation: [20_000, 10_000],
change: {
@@ -940,7 +683,7 @@ export const SampleEventsInsights: Entity = {
entities: {
product: [
{
- id: 'Sample Product A',
+ id: 'Mock Product A',
aggregation: [5_000, 2_000],
change: {
ratio: -0.6,
@@ -949,7 +692,7 @@ export const SampleEventsInsights: Entity = {
entities: {},
},
{
- id: 'Sample Product B',
+ id: 'Mock Product B',
aggregation: [7_000, 2_500],
change: {
ratio: -0.64285714285,
@@ -958,7 +701,7 @@ export const SampleEventsInsights: Entity = {
entities: {},
},
{
- id: 'Sample Product C',
+ id: 'Mock Product C',
aggregation: [3_000, 2_500],
change: {
ratio: -0.16666666666,
@@ -979,7 +722,7 @@ export const SampleEventsInsights: Entity = {
entities: {
product: [
{
- id: 'Sample Product A',
+ id: 'Mock Product A',
aggregation: [2_000, 1_000],
change: {
ratio: -0.5,
@@ -988,7 +731,7 @@ export const SampleEventsInsights: Entity = {
entities: {},
},
{
- id: 'Sample Product B',
+ id: 'Mock Product B',
aggregation: [1_000, 1_500],
change: {
ratio: 0.5,
@@ -997,7 +740,7 @@ export const SampleEventsInsights: Entity = {
entities: {},
},
{
- id: 'Sample Product C',
+ id: 'Mock Product C',
aggregation: [2_000, 500],
change: {
ratio: -0.75,
@@ -1011,72 +754,3 @@ export const SampleEventsInsights: Entity = {
],
},
};
-
-export function entityOf(product: string): Entity {
- switch (product) {
- case 'computeEngine':
- return SampleComputeEngineInsights;
- case 'cloudDataflow':
- return SampleCloudDataflowInsights;
- case 'cloudStorage':
- return SampleCloudStorageInsights;
- case 'bigQuery':
- return SampleBigQueryInsights;
- case 'events':
- return SampleEventsInsights;
- default:
- throw new Error(
- `Cannot get insights for ${product}. Make sure product matches product property in app-info.yaml`,
- );
- }
-}
-
-export const getGroupedProducts = (intervals: string) => [
- {
- id: 'Cloud Dataflow',
- aggregation: aggregationFor(intervals, 1_700),
- },
- {
- id: 'Compute Engine',
- aggregation: aggregationFor(intervals, 350),
- },
- {
- id: 'Cloud Storage',
- aggregation: aggregationFor(intervals, 1_300),
- },
- {
- id: 'BigQuery',
- aggregation: aggregationFor(intervals, 2_000),
- },
- {
- id: 'Cloud SQL',
- aggregation: aggregationFor(intervals, 750),
- },
- {
- id: 'Cloud Spanner',
- aggregation: aggregationFor(intervals, 50),
- },
- {
- id: 'Cloud Pub/Sub',
- aggregation: aggregationFor(intervals, 1_000),
- },
- {
- id: 'Cloud Bigtable',
- aggregation: aggregationFor(intervals, 250),
- },
-];
-
-export const getGroupedProjects = (intervals: string) => [
- {
- id: 'project-a',
- aggregation: aggregationFor(intervals, 1_700),
- },
- {
- id: 'project-b',
- aggregation: aggregationFor(intervals, 350),
- },
- {
- id: 'project-c',
- aggregation: aggregationFor(intervals, 1_300),
- },
-];
diff --git a/plugins/cost-insights/src/testUtils/products.ts b/plugins/cost-insights/src/testUtils/products.ts
new file mode 100644
index 0000000000..0ecac46244
--- /dev/null
+++ b/plugins/cost-insights/src/testUtils/products.ts
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Entity, Product } from '../types';
+import { findAlways } from '../utils/assert';
+
+type mockEntityRenderer = (entity: T) => T;
+
+export const createMockEntity = (
+ callback?: mockEntityRenderer,
+): Entity => {
+ const defaultEntity: Entity = {
+ id: 'test-entity',
+ aggregation: [100, 200],
+ entities: {},
+ change: {
+ ratio: 0,
+ amount: 0,
+ },
+ };
+
+ if (typeof callback === 'function') {
+ return callback({ ...defaultEntity });
+ }
+ return { ...defaultEntity };
+};
+
+export const createMockProduct = (
+ callback?: mockEntityRenderer,
+): Product => {
+ const defaultProduct: Product = {
+ kind: 'compute-engine',
+ name: 'Compute Engine',
+ };
+ if (typeof callback === 'function') {
+ return callback({ ...defaultProduct });
+ }
+ return { ...defaultProduct };
+};
+
+export const MockProductTypes: Record = {
+ 'compute-engine': 'Compute Engine',
+ 'cloud-dataflow': 'Cloud Dataflow',
+ 'cloud-storage': 'Cloud Storage',
+ 'big-query': 'Big Query',
+ 'big-table': 'BigTable',
+ 'cloud-pub-sub': 'Cloud Pub/Sub',
+};
+
+export const MockProducts: Product[] = Object.keys(MockProductTypes).map(
+ productType =>
+ createMockProduct(() => ({
+ kind: productType,
+ name: MockProductTypes[productType],
+ })),
+);
+
+export const MockComputeEngine = findAlways(
+ MockProducts,
+ p => p.kind === 'compute-engine',
+);
+export const MockCloudDataflow = findAlways(
+ MockProducts,
+ p => p.kind === 'cloud-dataflow',
+);
+export const MockCloudStorage = findAlways(
+ MockProducts,
+ p => p.kind === 'cloud-storage',
+);
+export const MockBigQuery = findAlways(
+ MockProducts,
+ p => p.kind === 'big-query',
+);
+export const MockBigtable = findAlways(
+ MockProducts,
+ p => p.kind === 'big-table',
+);
diff --git a/plugins/cost-insights/src/utils/tests.tsx b/plugins/cost-insights/src/testUtils/providers.tsx
similarity index 92%
rename from plugins/cost-insights/src/utils/tests.tsx
rename to plugins/cost-insights/src/testUtils/providers.tsx
index aae2800573..1097d86be4 100644
--- a/plugins/cost-insights/src/utils/tests.tsx
+++ b/plugins/cost-insights/src/testUtils/providers.tsx
@@ -34,18 +34,6 @@ import {
import { ScrollContext, ScrollContextProps } from '../hooks/useScroll';
import { Group, Duration } from '../types';
-/*
- Mock Providers and types are exposed publicly to allow users to test custom implementations
- such as alerts, which may require context.
-
- Utility functions such as getDefaultPageFilters, etc. are intentionally
- omitted as we do not want to expose explictly or implicitly internal implementations
- that may be subject to change.
-
- Each Mock Provider provides minimal defaults which can be overridden, allowing users to define
- context props only when necessary.
-*/
-
type PartialPropsWithChildren = PropsWithChildren>;
export const MockGroups: Group[] = [{ id: 'tech' }, { id: 'mock-group' }];
diff --git a/plugins/cost-insights/src/testUtils/testUtils.ts b/plugins/cost-insights/src/testUtils/testUtils.ts
new file mode 100644
index 0000000000..37913f635e
--- /dev/null
+++ b/plugins/cost-insights/src/testUtils/testUtils.ts
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import dayjs from 'dayjs';
+import regression, { DataPoint } from 'regression';
+import {
+ ChangeStatistic,
+ Duration,
+ Entity,
+ Trendline,
+ DateAggregation,
+ DEFAULT_DATE_FORMAT,
+} from '../types';
+import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration';
+import {
+ MockComputeEngineInsights,
+ MockCloudDataflowInsights,
+ MockCloudStorageInsights,
+ MockBigQueryInsights,
+ MockEventsInsights,
+} from './mockData';
+
+type IntervalFields = {
+ duration: Duration;
+ endDate: string;
+};
+
+function parseIntervals(intervals: string): IntervalFields {
+ const match = intervals.match(
+ /\/(?P\d+[DM])\/(?\d{4}-\d{2}-\d{2})/,
+ );
+ if (Object.keys(match?.groups || {}).length !== 2) {
+ throw new Error(`Invalid intervals: ${intervals}`);
+ }
+ const { duration, date } = match!.groups!;
+ return {
+ duration: duration as Duration,
+ endDate: date,
+ };
+}
+
+export function aggregationFor(
+ intervals: string,
+ baseline: number,
+): DateAggregation[] {
+ const { duration, endDate } = parseIntervals(intervals);
+ const inclusiveEndDate = inclusiveEndDateOf(duration, endDate);
+ const days = dayjs(endDate).diff(
+ inclusiveStartDateOf(duration, inclusiveEndDate),
+ 'day',
+ );
+
+ function nextDelta(): number {
+ const varianceFromBaseline = 0.15;
+ // Let's give positive vibes in trendlines - higher change for positive delta with >0.5 value
+ const positiveTrendChance = 0.55;
+ const normalization = positiveTrendChance - 1;
+ return baseline * (Math.random() + normalization) * varianceFromBaseline;
+ }
+
+ return [...Array(days).keys()].reduce(
+ (values: DateAggregation[], i: number): DateAggregation[] => {
+ const last = values.length ? values[values.length - 1].amount : baseline;
+ const date = dayjs(inclusiveStartDateOf(duration, inclusiveEndDate))
+ .add(i, 'day')
+ .format(DEFAULT_DATE_FORMAT);
+ const amount = Math.max(0, last + nextDelta());
+ values.push({
+ date: date,
+ amount: amount,
+ });
+ return values;
+ },
+ [],
+ );
+}
+
+export function changeOf(aggregation: DateAggregation[]): ChangeStatistic {
+ const firstAmount = aggregation.length ? aggregation[0].amount : 0;
+ const lastAmount = aggregation.length
+ ? aggregation[aggregation.length - 1].amount
+ : 0;
+ const ratio =
+ firstAmount !== 0 ? (lastAmount - firstAmount) / firstAmount : 0;
+ return {
+ ratio: ratio,
+ amount: lastAmount - firstAmount,
+ };
+}
+
+export 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],
+ };
+}
+
+export function entityOf(product: string): Entity {
+ switch (product) {
+ case 'computeEngine':
+ return MockComputeEngineInsights;
+ case 'cloudDataflow':
+ return MockCloudDataflowInsights;
+ case 'cloudStorage':
+ return MockCloudStorageInsights;
+ case 'bigQuery':
+ return MockBigQueryInsights;
+ case 'events':
+ return MockEventsInsights;
+ default:
+ throw new Error(
+ `Cannot get insights for ${product}. Make sure product matches product property in app-info.yaml`,
+ );
+ }
+}
+
+export const getGroupedProducts = (intervals: string) => [
+ {
+ id: 'Cloud Dataflow',
+ aggregation: aggregationFor(intervals, 1_700),
+ },
+ {
+ id: 'Compute Engine',
+ aggregation: aggregationFor(intervals, 350),
+ },
+ {
+ id: 'Cloud Storage',
+ aggregation: aggregationFor(intervals, 1_300),
+ },
+ {
+ id: 'BigQuery',
+ aggregation: aggregationFor(intervals, 2_000),
+ },
+ {
+ id: 'Cloud SQL',
+ aggregation: aggregationFor(intervals, 750),
+ },
+ {
+ id: 'Cloud Spanner',
+ aggregation: aggregationFor(intervals, 50),
+ },
+ {
+ id: 'Cloud Pub/Sub',
+ aggregation: aggregationFor(intervals, 1_000),
+ },
+ {
+ id: 'Cloud Bigtable',
+ aggregation: aggregationFor(intervals, 250),
+ },
+];
+
+export const getGroupedProjects = (intervals: string) => [
+ {
+ id: 'project-a',
+ aggregation: aggregationFor(intervals, 1_700),
+ },
+ {
+ id: 'project-b',
+ aggregation: aggregationFor(intervals, 350),
+ },
+ {
+ id: 'project-c',
+ aggregation: aggregationFor(intervals, 1_300),
+ },
+];
diff --git a/plugins/cost-insights/src/utils/change.test.ts b/plugins/cost-insights/src/utils/change.test.ts
index 9516dfe270..7208e9bc4f 100644
--- a/plugins/cost-insights/src/utils/change.test.ts
+++ b/plugins/cost-insights/src/utils/change.test.ts
@@ -22,7 +22,7 @@ import {
Duration,
Cost,
} from '../types';
-import { MockAggregatedDailyCosts, trendlineOf, changeOf } from './mockData';
+import { MockAggregatedDailyCosts, trendlineOf, changeOf } from '../testUtils';
const GrowthMap = {
[GrowthType.Negligible]: 'negligible growth',