preserve enums in types

This commit is contained in:
Ryan Vazquez
2020-10-29 14:28:42 -04:00
parent 0ee9e9f668
commit 59e42b4ca7
36 changed files with 136 additions and 146 deletions
@@ -18,13 +18,13 @@ import { createApiRef } from '@backstage/core';
import {
Alert,
Cost,
Duration,
Group,
Project,
ProductCost,
Maybe,
MetricData,
} from '../types';
import { Duration } from '../utils/duration';
export type CostInsightsApi = {
/**
@@ -23,20 +23,22 @@ import {
ChangeStatistic,
Cost,
DateAggregation,
Duration,
Group,
Maybe,
MetricData,
ProductCost,
Project,
ProjectGrowthAlert,
ProjectGrowthData,
Trendline,
UnlabeledDataflowAlert,
UnlabeledDataflowData,
} from '../types';
import {
ProjectGrowthAlert,
UnlabeledDataflowAlert
} from '../utils/alerts';
import {
DEFAULT_DATE_FORMAT,
Duration,
exclusiveEndDateOf,
inclusiveStartDateOf,
} from '../utils/duration';
@@ -17,8 +17,9 @@
import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { AlertActionCard } from './AlertActionCard';
import { ProjectGrowthAlert, ProjectGrowthData } from '../../types';
import { MockScrollProvider } from '../../utils/tests';
import { ProjectGrowthAlert } from '../../utils/alerts';
import { ProjectGrowthData } from '../../types';
const data: ProjectGrowthData = {
aggregation: [500000.8, 970502.8],
@@ -17,11 +17,10 @@
import React, { PropsWithChildren } from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { CostGrowth } from './CostGrowth';
import { Currency } from '../../types';
import { Duration } from '../../utils/duration';
import { Currency, CurrencyType, Duration } from '../../types';
import { findAlways } from '../../utils/assert';
import { MockConfigProvider, MockCurrencyProvider } from '../../utils/tests';
import { defaultCurrencies, CurrencyType } from '../../utils/currency';
import { defaultCurrencies } from '../../utils/currency';
const engineers = findAlways(defaultCurrencies, c => c.kind === null);
const usd = findAlways(defaultCurrencies, c => c.kind === CurrencyType.USD);
@@ -38,10 +37,10 @@ const MockContext = ({
currency: Currency;
engineerCost: number;
}>) => (
<MockConfigProvider engineerCost={engineerCost}>
<MockCurrencyProvider currency={currency}>{children}</MockCurrencyProvider>
</MockConfigProvider>
);
<MockConfigProvider engineerCost={engineerCost}>
<MockCurrencyProvider currency={currency}>{children}</MockCurrencyProvider>
</MockConfigProvider>
);
describe.each`
engineerCost | ratio | amount | expected
@@ -16,9 +16,8 @@
import React from 'react';
import classnames from 'classnames';
import { ChangeStatistic } from '../../types';
import { rateOf, CurrencyType } from '../../utils/currency';
import { Duration } from '../../utils/duration';
import { ChangeStatistic, CurrencyType, Duration } from '../../types';
import { rateOf } from '../../utils/currency';
import { growthOf, GrowthType, EngineerThreshold } from '../../utils/change';
import { useCostGrowthStyles as useStyles } from '../../utils/styles';
import { formatPercent, formatCurrency } from '../../utils/formatters';
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Maybe, PageFilters } from '../../types';
import { Duration } from '../../utils/duration';
import { Duration, Maybe, PageFilters } from '../../types';
import { MapFiltersToProps } from '../../hooks/useFilters';
type CostOverviewFilterProps = PageFilters & {
@@ -16,8 +16,7 @@
import React from 'react';
import { MenuItem, Select, SelectProps } from '@material-ui/core';
import { Currency } from '../../types';
import { CurrencyType } from '../../utils/currency';
import { Currency, CurrencyType } from '../../types';
import { findAlways } from '../../utils/assert';
import { useSelectStyles as useStyles } from '../../utils/styles';
@@ -20,9 +20,8 @@ import { renderInTestApp } from '@backstage/test-utils';
import UserEvent from '@testing-library/user-event';
import { PeriodSelect, getDefaultOptions } from './PeriodSelect';
import { getDefaultPageFilters } from '../../utils/filters';
import { Duration } from '../../utils/duration';
import { MockBillingDateProvider } from '../../utils/tests';
import { Group } from '../../types';
import { Group, Duration } from '../../types';
const DefaultPageFilters = getDefaultPageFilters([{ id: 'tools' }] as Group[]);
const lastCompleteBillingDate = '2020-05-01';
@@ -16,11 +16,11 @@
import React from 'react';
import { MenuItem, Select, SelectProps } from '@material-ui/core';
import { Duration } from '../../types';
import {
formatLastTwoLookaheadQuarters,
formatLastTwoMonths,
} from '../../utils/formatters';
import { Duration } from '../../utils/duration';
import { findAlways } from '../../utils/assert';
import { useSelectStyles as useStyles } from '../../utils/styles';
import { useLastCompleteBillingDate } from '../../hooks';
@@ -35,8 +35,7 @@ import {
MockScrollProvider,
MockLoadingProvider,
} from '../../utils/tests';
import { Duration } from '../../utils/duration';
import { Product, ProductCost, ProductPeriod } from '../../types';
import { Duration, Product, ProductCost, ProductPeriod } from '../../types';
const costInsightsApi = (
productCost: ProductCost,
@@ -28,10 +28,9 @@ import {
useLoading,
useScroll,
} from '../../hooks';
import { Duration } from '../../utils/duration';
import { useProductInsightsCardStyles as useStyles } from '../../utils/styles';
import { mapFiltersToProps, mapLoadingToProps } from './selector';
import { Maybe, Product, ProductCost } from '../../types';
import { Duration, Maybe, Product, ProductCost } from '../../types';
import { pluralOf } from '../../utils/grammar';
import { formatPeriod } from '../../utils/formatters';
@@ -16,8 +16,7 @@
import { MapFiltersToProps } from '../../hooks/useFilters';
import { MapLoadingToProps } from '../../hooks/useLoading';
import { PageFilters, ProductPeriod } from '../../types';
import { Duration } from '../../utils/duration';
import { Duration, PageFilters, ProductPeriod } from '../../types';
import { findAlways } from '../../utils/assert';
type ProductInsightsCardFilterProps = PageFilters & {
@@ -20,8 +20,7 @@ import { Box } from '@material-ui/core';
import { InfoCard } from '@backstage/core';
import { ResourceGrowthBarChart } from '../ResourceGrowthBarChart';
import { ResourceGrowthBarChartLegend } from '../ResourceGrowthBarChartLegend';
import { ProjectGrowthData } from '../../types';
import { Duration } from '../../utils/duration';
import { Duration, ProjectGrowthData } from '../../types';
import { pluralOf } from '../../utils/grammar';
type ProjectGrowthAlertProps = {
@@ -32,9 +31,8 @@ export const ProjectGrowthAlertCard = ({ alert }: ProjectGrowthAlertProps) => {
const [costStart, costEnd] = alert.aggregation;
const subheader = `
${alert.products.length} ${pluralOf(alert.products.length, 'product')}${
alert.products.length > 1 ? ', sorted by cost' : ''
}`;
${alert.products.length} ${pluralOf(alert.products.length, 'product')}${alert.products.length > 1 ? ', sorted by cost' : ''
}`;
const previousName = moment(alert.periodStart, 'YYYY-[Q]Q').format(
'[Q]Q YYYY',
);
@@ -18,14 +18,14 @@ import React from 'react';
import { Box, Typography } from '@material-ui/core';
import { InfoCard } from '@backstage/core';
import { AlertInstructionsLayout } from '../AlertInstructionsLayout';
import { Duration } from '../../utils/duration';
import {
Alert,
Duration,
Entity,
Product,
ProjectGrowthAlert,
ProjectGrowthData,
} from '../../types';
import { ProjectGrowthAlert } from '../../utils/alerts';
import { ResourceGrowthBarChartLegend } from '../ResourceGrowthBarChartLegend';
import { ResourceGrowthBarChart } from '../ResourceGrowthBarChart';
@@ -17,10 +17,10 @@
import React, { PropsWithChildren } from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { ResourceGrowthBarChartLegend } from './ResourceGrowthBarChartLegend';
import { Duration } from '../../utils/duration';
import { defaultCurrencies } from '../../utils/currency';
import { findAlways } from '../../utils/assert';
import { MockConfigProvider, MockCurrencyProvider } from '../../utils/tests';
import { Duration } from '../../types';
const engineers = findAlways(defaultCurrencies, c => c.kind === null);
@@ -19,8 +19,7 @@ import { Box, useTheme } from '@material-ui/core';
import { LegendItem } from '../LegendItem';
import { CostGrowth } from '../CostGrowth';
import { currencyFormatter } from '../../utils/formatters';
import { Duration } from '../../utils/duration';
import { ChangeStatistic, CostInsightsTheme } from '../../types';
import { ChangeStatistic, Duration, CostInsightsTheme } from '../../types';
export type ResourceGrowthBarChartLegendProps = {
change: ChangeStatistic;
+2 -1
View File
@@ -19,4 +19,5 @@ export * from './api';
export * from './components';
export { useCurrency } from './hooks';
export * from './types';
export * from './utils/public';
export * from './utils/tests';
export * from './utils/duration';
@@ -14,11 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import { ChangeStatistic } from './ChangeStatistic';
import { Maybe } from './Maybe';
import { UnlabeledDataflowAlertCard } from '../components/UnlabeledDataflowAlertCard';
import { ProjectGrowthAlertCard } from '../components/ProjectGrowthAlertCard';
/**
* Generic alert type with required fields for display. The `element` field will be rendered in
@@ -57,11 +54,6 @@ export enum DataKey {
Name = 'name',
}
/**
* The alerts below are examples of Alert implementation; the CostInsightsApi permits returning
* any implementation of the Alert type, so adopters can create their own. The CostInsightsApi
* fetches alert data from the backend, then creates Alert classes with the data.
*/
export interface ProjectGrowthData {
project: string;
periodStart: string;
@@ -71,26 +63,6 @@ export interface ProjectGrowthData {
products: Array<AlertCost>;
}
export class ProjectGrowthAlert implements Alert {
data: ProjectGrowthData;
constructor(data: ProjectGrowthData) {
this.data = data;
}
get title() {
return `Investigate cost growth in project ${this.data.project}`;
}
subtitle =
'Cost growth outpacing business growth is unsustainable long-term.';
url = '/cost-insights/investigating-growth';
get element() {
return <ProjectGrowthAlertCard alert={this.data} />;
}
}
export interface UnlabeledDataflowData {
periodStart: string;
periodEnd: string;
@@ -99,23 +71,6 @@ export interface UnlabeledDataflowData {
labeledCost: number;
}
export class UnlabeledDataflowAlert implements Alert {
data: UnlabeledDataflowData;
constructor(data: UnlabeledDataflowData) {
this.data = data;
}
title = 'Add labels to workflows';
subtitle =
'Labels show in billing data, enabling cost insights for each workflow.';
url = '/cost-insights/labeling-jobs';
get element() {
return <UnlabeledDataflowAlertCard alert={this.data} />;
}
}
export interface UnlabeledDataflowAlertProject {
id: string;
unlabeledCost: number;
@@ -21,3 +21,10 @@ export interface Currency {
prefix?: string;
rate?: number;
}
export enum CurrencyType {
USD = 'USD',
CarbonOffsetTons = 'CARBON_OFFSET_TONS',
Beers = 'BEERS',
IceCream = 'PINTS_OF_ICE_CREAM',
}
@@ -14,11 +14,15 @@
* limitations under the License.
*/
export enum IconType {
Compute = 'compute',
Data = 'data',
Database = 'database',
Storage = 'storage',
Search = 'search',
ML = 'ml',
/**
* Time periods for cost comparison; slight abuse of ISO 8601 periods. We take P1M and P3M to mean
* 'last completed [month|quarter]', and P30D/P90D to be '[month|quarter] relative to today'. So if
* it's September 15, P1M represents costs for the month of August and P30D represents August 16 -
* September 15.
*/
export enum Duration {
P30D = 'P30D',
P90D = 'P90D',
P1M = 'P1M',
P3M = 'P3M',
}
+1 -1
View File
@@ -15,7 +15,7 @@
*/
import { Maybe } from './Maybe';
import { Duration } from '../utils/duration';
import { Duration } from './Duration';
export interface PageFilters {
group: Maybe<string>;
+9
View File
@@ -18,3 +18,12 @@ export type Icon = {
kind: string;
component: JSX.Element;
};
export enum IconType {
Compute = 'compute',
Data = 'data',
Database = 'database',
Storage = 'storage',
Search = 'search',
ML = 'ml',
}
+1
View File
@@ -19,6 +19,7 @@ export * from './ChangeStatistic';
export * from './ChartData';
export * from './Cost';
export * from './DateAggregation';
export * from './Duration';
export * from './Currency';
export * from './Entity';
export * from './Icon';
@@ -0,0 +1,47 @@
import React from 'react';
import { Alert, UnlabeledDataflowData, ProjectGrowthData } from '../types';
import { UnlabeledDataflowAlertCard } from '../components/UnlabeledDataflowAlertCard';
import { ProjectGrowthAlertCard } from '../components/ProjectGrowthAlertCard';
/**
* The alerts below are examples of Alert implementation; the CostInsightsApi permits returning
* any implementation of the Alert type, so adopters can create their own. The CostInsightsApi
* fetches alert data from the backend, then creates Alert classes with the data.
*/
export class UnlabeledDataflowAlert implements Alert {
data: UnlabeledDataflowData;
constructor(data: UnlabeledDataflowData) {
this.data = data;
}
title = 'Add labels to workflows';
subtitle =
'Labels show in billing data, enabling cost insights for each workflow.';
url = '/cost-insights/labeling-jobs';
get element() {
return <UnlabeledDataflowAlertCard alert={this.data} />;
}
}
export class ProjectGrowthAlert implements Alert {
data: ProjectGrowthData;
constructor(data: ProjectGrowthData) {
this.data = data;
}
get title() {
return `Investigate cost growth in project ${this.data.project}`;
}
subtitle =
'Cost growth outpacing business growth is unsustainable long-term.';
url = '/cost-insights/investigating-growth';
get element() {
return <ProjectGrowthAlertCard alert={this.data} />;
}
}
+1 -9
View File
@@ -13,17 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Currency } from '../types';
import { Duration } from '../utils/duration';
import { Currency, CurrencyType, Duration } from '../types';
import { assertNever } from '../utils/assert';
export enum CurrencyType {
USD = 'USD',
CarbonOffsetTons = 'CARBON_OFFSET_TONS',
Beers = 'BEERS',
IceCream = 'PINTS_OF_ICE_CREAM',
}
export const rateOf = (cost: number, duration: Duration) => {
switch (duration) {
case Duration.P1M:
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import { Duration, inclusiveEndDateOf, inclusiveStartDateOf } from './duration';
import { Duration } from '../types';
import { inclusiveEndDateOf, inclusiveStartDateOf } from './duration';
const lastCompleteBillingDate = '2020-06-05';
+2 -14
View File
@@ -15,20 +15,8 @@
*/
import moment from 'moment';
import { assertNever } from '../utils/assert';
/**
* Time periods for cost comparison; slight abuse of ISO 8601 periods. We take P1M and P3M to mean
* 'last completed [month|quarter]', and P30D/P90D to be '[month|quarter] relative to today'. So if
* it's September 15, P1M represents costs for the month of August and P30D represents August 16 -
* September 15.
*/
export enum Duration {
P30D = 'P30D',
P90D = 'P90D',
P1M = 'P1M',
P3M = 'P3M',
}
import { Duration } from '../types';
import { assertNever } from './assert';
export const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
+1 -2
View File
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import { Group, PageFilters } from '../types';
import { Duration } from './duration';
import { Duration, Group, PageFilters } from '../types';
export function getDefaultPageFilters(groups: Group[]): PageFilters {
return {
@@ -19,7 +19,7 @@ import {
lengthyCurrencyFormatter,
quarterOf,
} from './formatters';
import { Duration } from '../utils/duration';
import { Duration } from '../types';
Date.now = jest.fn(() => new Date(Date.parse('2019-12-07')).valueOf());
@@ -15,8 +15,8 @@
*/
import moment from 'moment';
import { Duration } from '../types';
import {
Duration,
inclusiveEndDateOf,
inclusiveStartDateOf,
} from '../utils/duration';
+1 -2
View File
@@ -16,8 +16,7 @@
import qs from 'qs';
import * as yup from 'yup';
import { Group, PageFilters } from '../types';
import { Duration } from '../utils/duration';
import { Duration, Group, PageFilters } from '../types';
import { getDefaultPageFilters } from '../utils/filters';
import { ConfigContextProps } from '../hooks/useConfig';
@@ -1,3 +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.
*/
import { Loading } from '../types';
export enum DefaultLoadingAction {
+1 -1
View File
@@ -17,6 +17,7 @@
import { Config } from '@backstage/config';
import { ConfigApi } from '@backstage/core';
import {
Duration,
Entity,
Product,
ProductCost,
@@ -30,7 +31,6 @@ import {
getDefaultState as getDefaultLoadingState,
} from '../utils/loading';
import { findAlways } from '../utils/assert';
import { Duration } from '../utils/duration';
type mockAlertRenderer<T> = (alert: T) => T;
type mockEntityRenderer<T> = (entity: T) => T;
@@ -23,7 +23,7 @@ import Search from '@material-ui/icons/Search';
import CloudQueue from '@material-ui/icons/CloudQueue';
import School from '@material-ui/icons/School';
import ViewHeadline from '@material-ui/icons/ViewHeadline';
import { IconType } from '../utils/icon';
import { IconType } from '../types';
export enum DefaultNavigation {
CostOverviewCard = 'cost-overview-card',
@@ -1,20 +0,0 @@
/*
* 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 '../tests';
export { Duration } from '../duration';
export { CurrencyType } from '../currency';
export { IconType } from '../icon';
+1 -2
View File
@@ -32,8 +32,7 @@ import {
BillingDateContextProps,
} from '../hooks/useLastCompleteBillingDate';
import { ScrollContext, ScrollContextProps } from '../hooks/useScroll';
import { Duration } from '../utils/duration';
import { Group } from '../types';
import { Group, Duration } from '../types';
/*
Mock Providers and types are exposed publicly to allow users to test custom implementations