Merge pull request #2816 from spotify/timbonicus/custom-alerts

Enable custom alert types in Cost Insights
This commit is contained in:
Tim
2020-10-09 07:29:12 -06:00
committed by GitHub
23 changed files with 285 additions and 298 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': minor
---
Enable custom alert types in Cost Insights
+1
View File
@@ -141,6 +141,7 @@ Raghunandan
rankdir
readme
Readme
Recharts
Redash
replicasets
repo
@@ -16,13 +16,17 @@
/* eslint-disable no-restricted-imports */
import {
CostInsightsApi,
Alert,
Cost,
CostInsightsApi,
Duration,
Project,
ProductCost,
Group,
ProductCost,
Project,
ProjectGrowthAlert,
ProjectGrowthData,
UnlabeledDataflowAlert,
UnlabeledDataflowData,
} from '@backstage/plugin-cost-insights';
export class ExampleCostInsightsClient implements CostInsightsApi {
@@ -176,32 +180,44 @@ export class ExampleCostInsightsClient implements CostInsightsApi {
}
async getAlerts(group: string): Promise<Alert[]> {
const alerts: Alert[] = await this.request({ group }, [
{
id: 'projectGrowth',
project: 'example-project',
periodStart: 'Q1 2020',
periodEnd: 'Q2 2020',
aggregation: [60_000, 120_000],
change: {
ratio: 1,
amount: 60000,
},
products: [
{
id: 'Compute Engine',
aggregation: [58_000, 118_000],
},
{
id: 'Cloud Dataflow',
aggregation: [1200, 1500],
},
{
id: 'Cloud Storage',
aggregation: [800, 500],
},
],
const projectGrowthData: ProjectGrowthData = {
project: 'example-project',
periodStart: 'Q2 2020',
periodEnd: 'Q3 2020',
aggregation: [60_000, 120_000],
change: {
ratio: 1,
amount: 60000,
},
products: [
{ id: 'Compute Engine', aggregation: [58_000, 118_000] },
{ id: 'Cloud Dataflow', aggregation: [1200, 1500] },
{ id: 'Cloud Storage', aggregation: [800, 500] },
],
};
const unlabeledDataflowData: UnlabeledDataflowData = {
periodStart: '2020-09-01',
periodEnd: '2020-09-30',
labeledCost: 6_200,
unlabeledCost: 7_000,
projects: [
{
id: 'example-project-1',
unlabeledCost: 5_000,
labeledCost: 3_000,
},
{
id: 'example-project-2',
unlabeledCost: 2_000,
labeledCost: 3_200,
},
],
};
const alerts: Alert[] = await this.request({ group }, [
new ProjectGrowthAlert(projectGrowthData),
new UnlabeledDataflowAlert(unlabeledDataflowData),
]);
return alerts;
+8
View File
@@ -104,3 +104,11 @@ costInsights:
metricC:
name: Metric C
```
## Alerts
The CostInsightsApi `getAlerts` method may return any type of alert or recommendation (called collectively "Action Items" in Cost Insights) that implements the [Alert type](https://github.com/spotify/backstage/blob/master/plugins/cost-insights/src/types/Alert.tsx). This allows you to deliver any alerts or recommendations specific to your infrastructure or company migrations.
The Alert type includes an `element` field to supply the JSX Element that will be rendered in the Cost Insights "Action Items" section; we recommend using Backstage's [InfoCard](https://backstage.io/storybook/?path=/story/layout-information-card--default) and [Recharts](http://recharts.org/en-US/) to show actionable visualizations.
The Alert `url` should link to documentation or instructions for resolving the alert.
+2 -1
View File
@@ -29,6 +29,8 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@material-ui/styles": "^4.9.6",
"@types/react": "^16.9",
"@types/recharts": "^1.8.14",
"canvas": "^2.6.1",
"classnames": "^2.2.6",
"history": "^5.0.0",
@@ -48,7 +50,6 @@
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"@types/recharts": "^1.8.14",
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
@@ -17,19 +17,18 @@
import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import AlertActionCard from './AlertActionCard';
import { AlertType, ProjectGrowthAlert } from '../../types';
import { getAlertText } from '../../utils/alerts';
import { ProjectGrowthAlert, ProjectGrowthData } from '../../types';
import { MockScrollProvider } from '../../utils/tests';
const alert = {
id: AlertType.ProjectGrowth,
const data: ProjectGrowthData = {
aggregation: [500000.8, 970502.8],
project: 'test-project',
periodStart: '2019-10-01',
periodEnd: '2020-03-31',
change: { ratio: 120, amount: 120000 },
products: [],
} as ProjectGrowthAlert;
};
const alert = new ProjectGrowthAlert(data);
describe('<AlertActionCard/>', () => {
it('Renders an alert', async () => {
@@ -40,11 +39,7 @@ describe('<AlertActionCard/>', () => {
);
expect(rendered.getByText('1')).toBeInTheDocument();
const text = getAlertText(alert);
expect(text).toBeDefined();
if (text) {
expect(rendered.getByText(text.title)).toBeInTheDocument();
expect(rendered.getByText(text.subtitle)).toBeInTheDocument();
}
expect(rendered.getByText(alert.title)).toBeInTheDocument();
expect(rendered.getByText(alert.subtitle)).toBeInTheDocument();
});
});
@@ -17,10 +17,9 @@ import React from 'react';
import { Avatar, Card, CardHeader } from '@material-ui/core';
import { useScroll } from '../../hooks';
import { Alert } from '../../types';
import { getAlertText, getAlertNavigation } from '../../utils/alerts';
import {
useAlertActionCardStyles as useStyles,
useAlertActionCardHeader as useHeaderStyles,
useAlertActionCardStyles as useStyles,
} from '../../utils/styles';
type AlertActionCardProps = {
@@ -29,9 +28,8 @@ type AlertActionCardProps = {
};
const AlertActionCard = ({ alert, number }: AlertActionCardProps) => {
const { scrollIntoView } = useScroll(getAlertNavigation(alert, number));
const { scrollIntoView } = useScroll(`alert-${number}`);
const headerClasses = useHeaderStyles();
const text = getAlertText(alert);
const classes = useStyles();
return (
@@ -39,8 +37,8 @@ const AlertActionCard = ({ alert, number }: AlertActionCardProps) => {
<CardHeader
classes={headerClasses}
avatar={<Avatar className={classes.avatar}>{number}</Avatar>}
title={text?.title}
subheader={text?.subtitle}
title={alert.title}
subheader={alert.subtitle}
/>
</Card>
);
@@ -25,7 +25,7 @@ type AlertActionCardList = {
const AlertActionCardList: FC<AlertActionCardList> = ({ alerts }) => (
<Paper>
{alerts.map((alert, index) => (
<Fragment key={`${alert.id}-${index}`}>
<Fragment key={`alert-${index}`}>
<AlertActionCard alert={alert} number={index + 1} />
{index < alerts.length - 1 && <Divider variant="fullWidth" />}
</Fragment>
@@ -19,7 +19,6 @@ import { Grid } from '@material-ui/core';
import AlertInsightsSection from './AlertInsightsSection';
import AlertInsightsHeader from './AlertInsightsHeader';
import { Alert } from '../../types';
import { renderAlert } from '../../utils/alerts';
const title = "Your team's action items";
const subtitle =
@@ -37,11 +36,7 @@ const AlertInsights = ({ alerts }: AlertInsightsProps) => (
<Grid item container direction="column" spacing={4}>
{alerts.map((alert, index) => (
<Grid item key={`alert-card-${index}`}>
<AlertInsightsSection
alert={alert}
number={index + 1}
render={renderAlert}
/>
<AlertInsightsSection alert={alert} number={index + 1} />
</Grid>
))}
</Grid>
@@ -16,45 +16,28 @@
import React from 'react';
import { Box, Button } from '@material-ui/core';
import AlertInsightsSectionHeader from './AlertInsightsSectionHeader';
import {
getAlertButtonText,
getAlertText,
getAlertUrl,
} from '../../utils/alerts';
import { Alert, Currency } from '../../types';
import { useCurrency } from '../../hooks';
import { Alert } from '../../types';
type AlertInsightsSectionProps = {
alert: Alert;
number: number;
render: (alert: Alert, currency: Currency) => JSX.Element;
};
const AlertInsightsSection = ({
alert,
number,
render,
}: AlertInsightsSectionProps) => {
const [currency] = useCurrency();
const text = getAlertText(alert);
const url = getAlertUrl(alert);
const buttonText = getAlertButtonText(alert);
const AlertInsightsSection = ({ alert, number }: AlertInsightsSectionProps) => {
return (
<Box display="flex" flexDirection="column">
<AlertInsightsSectionHeader
alert={alert}
title={text.title}
subtitle={text.subtitle}
title={alert.title}
subtitle={alert.subtitle}
number={number}
/>
<Box textAlign="left" mt={0} mb={4}>
<Button variant="contained" color="primary" href={url}>
{buttonText}
<Button variant="contained" color="primary" href={alert.url}>
{alert.buttonText || 'View Instructions'}
</Button>
{/* <Button color="primary">Dismiss notification</Button> */}
</Box>
{render(alert, currency)}
{alert.element}
</Box>
);
};
@@ -15,26 +15,22 @@
*/
import React from 'react';
import { Avatar, Box, Typography, Grid } from '@material-ui/core';
import { Alert } from '../../types';
import { getAlertNavigation } from '../../utils/alerts';
import { Avatar, Box, Grid, Typography } from '@material-ui/core';
import { useAlertInsightsSectionStyles as useStyles } from '../../utils/styles';
import { useScroll } from '../../hooks';
type AlertInsightsSectionHeaderProps = {
alert: Alert;
number: number;
title: string;
subtitle: string;
};
const AlertInsightsSectionHeader = ({
alert,
number,
title,
subtitle,
}: AlertInsightsSectionHeaderProps) => {
const { ScrollAnchor } = useScroll(getAlertNavigation(alert, number));
const { ScrollAnchor } = useScroll(`alert-${number}`);
const classes = useStyles();
return (
<Box position="relative" mb={3} textAlign="left">
@@ -19,6 +19,7 @@ import { Box, Button, Container, makeStyles } from '@material-ui/core';
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
import { Header, Page, pageTheme } from '@backstage/core';
import { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider';
import { ConfigProvider, CurrencyProvider } from '../../hooks';
const useStyles = makeStyles(theme => ({
root: {
@@ -39,21 +40,29 @@ const AlertInstructionsLayout = ({
const classes = useStyles();
return (
<CostInsightsThemeProvider>
<Page theme={pageTheme.tool}>
<Header title="Cost Insights" pageTitleOverride={title} type="Tool" />
<Container maxWidth="md" disableGutters className={classes.root}>
<Box mb={3}>
<Button
variant="outlined"
startIcon={<ChevronLeftIcon />}
href="/cost-insights"
>
Back to Cost Insights
</Button>
</Box>
{children}
</Container>
</Page>
<ConfigProvider>
<CurrencyProvider>
<Page theme={pageTheme.tool}>
<Header
title="Cost Insights"
pageTitleOverride={title}
type="Tool"
/>
<Container maxWidth="md" disableGutters className={classes.root}>
<Box mb={3}>
<Button
variant="outlined"
startIcon={<ChevronLeftIcon />}
href="/cost-insights"
>
Back to Cost Insights
</Button>
</Box>
{children}
</Container>
</Page>
</CurrencyProvider>
</ConfigProvider>
</CostInsightsThemeProvider>
);
};
@@ -17,7 +17,7 @@
import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import ProjectGrowthAlertCard from './ProjectGrowthAlertCard';
import { createMockProjectGrowthAlert } from '../../utils/mockData';
import { createMockProjectGrowthData } from '../../utils/mockData';
import { MockCurrencyProvider, MockConfigProvider } from '../../utils/tests';
import { AlertCost, defaultCurrencies, findAlways } from '../../types';
@@ -29,8 +29,8 @@ const MockAlertCosts: AlertCost[] = [
{ id: 'test-id-2', aggregation: [235, 400] },
];
const MockProjectGrowthAlert = createMockProjectGrowthAlert(alert => ({
...alert,
const MockProjectGrowthAlert = createMockProjectGrowthData(data => ({
...data,
project: MockProject,
products: MockAlertCosts,
}));
@@ -19,11 +19,11 @@ import { Box } from '@material-ui/core';
import { InfoCard } from '@backstage/core';
import ResourceGrowthBarChart from '../ResourceGrowthBarChart';
import ResourceGrowthBarChartLegend from '../ResourceGrowthBarChartLegend';
import { Duration, ProjectGrowthAlert } from '../../types';
import { Duration, ProjectGrowthData } from '../../types';
import { pluralOf } from '../../utils/grammar';
type ProjectGrowthAlertProps = {
alert: ProjectGrowthAlert;
alert: ProjectGrowthData;
};
const ProjectGrowthAlertCard = ({ alert }: ProjectGrowthAlertProps) => {
@@ -18,20 +18,19 @@ import React from 'react';
import { Box, Typography } from '@material-ui/core';
import { InfoCard } from '@backstage/core';
import AlertInstructionsLayout from '../AlertInstructionsLayout';
import ProjectGrowthAlertCard from '../ProjectGrowthAlertCard';
import {
AlertType,
Alert,
Duration,
Entity,
Product,
ProjectGrowthAlert,
ProjectGrowthData,
} from '../../types';
import ResourceGrowthBarChartLegend from '../ResourceGrowthBarChartLegend';
import ResourceGrowthBarChart from '../ResourceGrowthBarChart';
const ProjectGrowthInstructionsPage = () => {
const projectGrowthAlert: ProjectGrowthAlert = {
id: AlertType.ProjectGrowth,
const alertData: ProjectGrowthData = {
project: 'example-project',
periodStart: 'Q1 2020',
periodEnd: 'Q2 2020',
@@ -55,6 +54,7 @@ const ProjectGrowthInstructionsPage = () => {
},
],
};
const projectGrowthAlert: Alert = new ProjectGrowthAlert(alertData);
const product: Product = {
kind: 'ComputeEngine',
@@ -135,7 +135,7 @@ const ProjectGrowthInstructionsPage = () => {
comparison of cloud products over the examined time period:
</Typography>
<Box mt={2} mb={2}>
<ProjectGrowthAlertCard alert={projectGrowthAlert} />
{projectGrowthAlert.element}
</Box>
<Typography paragraph>
This allows you to quickly see which cloud products contributed to the
@@ -17,23 +17,23 @@
import React from 'react';
import UnlabeledDataflowAlertCard from './UnlabeledDataflowAlertCard';
import {
createMockUnlabeledDataflowAlert,
createMockUnlabeledDataflowData,
createMockUnlabeledDataflowAlertProject,
} from '../../utils/mockData';
import { renderInTestApp } from '@backstage/test-utils';
const MockUnlabeledDataflowAlertMultipleProjects = createMockUnlabeledDataflowAlert(
alert => ({
...alert,
const MockUnlabeledDataflowAlertMultipleProjects = createMockUnlabeledDataflowData(
data => ({
...data,
projects: [...Array(10)].map(() =>
createMockUnlabeledDataflowAlertProject(),
),
}),
);
const MockUnlabeledDataflowAlertSingleProject = createMockUnlabeledDataflowAlert(
alert => ({
...alert,
const MockUnlabeledDataflowAlertSingleProject = createMockUnlabeledDataflowData(
data => ({
...data,
projects: [...Array(1)].map(() =>
createMockUnlabeledDataflowAlertProject(),
),
@@ -19,11 +19,11 @@ import { Box } from '@material-ui/core';
import { InfoCard } from '@backstage/core';
import UnlabeledDataflowBarChart from '../UnlabeledDataflowBarChart';
import UnlabeledDataflowBarChartLegend from '../UnlabeledDataflowBarChartLegend';
import { UnlabeledDataflowAlert } from '../../types';
import { UnlabeledDataflowData } from '../../types';
import { pluralOf } from '../../utils/grammar';
type UnlabeledDataflowAlertProps = {
alert: UnlabeledDataflowAlert;
alert: UnlabeledDataflowData;
};
const UnlabeledDataflowAlertCard = ({ alert }: UnlabeledDataflowAlertProps) => {
@@ -0,0 +1,18 @@
/*
* 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 { default as BarChart } from './BarChart';
export { default as LegendItem } from './LegendItem';
+1
View File
@@ -16,4 +16,5 @@
export { plugin } from './plugin';
export * from './api';
export * from './components';
export * from './types';
-77
View File
@@ -1,77 +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.
*/
import { ChangeStatistic } from './ChangeStatistic';
import { Maybe } from './Maybe';
export type Alert = ProjectGrowthAlert | UnlabeledDataflowAlert;
export interface AlertProps {
alert: Alert;
}
export enum AlertType {
ProjectGrowth = 'projectGrowth',
UnlabeledDataflow = 'unlabeledDataflow',
}
export interface AlertCost {
id: string;
aggregation: [number, number];
}
export interface ResourceData {
previous: number;
current: number;
name: Maybe<string>;
}
export interface BarChartData {
previousFill: string;
currentFill: string;
previousName: string;
currentName: string;
}
export enum DataKey {
Previous = 'previous',
Current = 'current',
Name = 'name',
}
export interface ProjectGrowthAlert {
id: AlertType.ProjectGrowth;
project: string;
periodStart: string;
periodEnd: string;
aggregation: [number, number];
change: ChangeStatistic;
products: Array<AlertCost>;
}
export interface UnlabeledDataflowAlert {
id: AlertType.UnlabeledDataflow;
periodStart: string;
periodEnd: string;
projects: Array<UnlabeledDataflowAlertProject>;
unlabeledCost: number;
labeledCost: number;
}
export interface UnlabeledDataflowAlertProject {
id: string;
unlabeledCost: number;
labeledCost: number;
}
+123
View File
@@ -0,0 +1,123 @@
/*
* 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 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
* the Cost Insights "Action Items" section. This should use data fetched in the CostInsightsApi
* implementation to render an InfoCard or other visualization.
*/
export type Alert = {
title: string;
subtitle: string;
url: string;
buttonText?: string; // Default: View Instructions
element: JSX.Element;
};
export interface AlertCost {
id: string;
aggregation: [number, number];
}
export interface ResourceData {
previous: number;
current: number;
name: Maybe<string>;
}
export interface BarChartData {
previousFill: string;
currentFill: string;
previousName: string;
currentName: string;
}
export enum DataKey {
Previous = 'previous',
Current = 'current',
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;
periodEnd: string;
aggregation: [number, number];
change: ChangeStatistic;
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;
projects: Array<UnlabeledDataflowAlertProject>;
unlabeledCost: number;
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;
labeledCost: number;
}
@@ -1,82 +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.
*/
import React from 'react';
import { Alert, AlertType, assertNever } from '../types';
import ProjectGrowthAlertCard from '../components/ProjectGrowthAlertCard';
import UnlabeledDataflowAlertCard from '../components/UnlabeledDataflowAlertCard';
export function getAlertText(alert: Alert) {
switch (alert.id) {
case AlertType.ProjectGrowth:
return {
title: `Investigate cost growth in project ${alert.project}`,
subtitle:
'Cost growth outpacing business growth is unsustainable long-term.',
} as AlertText;
case AlertType.UnlabeledDataflow:
return {
title: 'Add labels to workflows',
subtitle:
'Labels show in billing data, enabling cost insights for each workflow.',
};
default:
return assertNever(alert);
}
}
export function getAlertUrl(alert: Alert) {
switch (alert.id) {
case AlertType.ProjectGrowth:
return '/cost-insights/investigating-growth' as AlertUrl;
case AlertType.UnlabeledDataflow:
return '/cost-insights/labeling-jobs' as AlertUrl;
default:
return assertNever(alert);
}
}
export function getAlertButtonText(alert: Alert) {
switch (alert.id) {
case AlertType.ProjectGrowth:
case AlertType.UnlabeledDataflow:
return 'View Instructions' as AlertButtonText;
default:
return assertNever(alert);
}
}
export function getAlertNavigation(alert: Alert, number: number) {
return `${alert.id}-${number}`;
}
export function renderAlert(alert: Alert) {
switch (alert.id) {
case AlertType.ProjectGrowth:
return <ProjectGrowthAlertCard alert={alert} />;
case AlertType.UnlabeledDataflow:
return <UnlabeledDataflowAlertCard alert={alert} />;
default:
return assertNever(alert);
}
}
export type AlertUrl = string;
export type AlertButtonText = string;
export interface AlertText {
title: string;
subtitle: string;
}
+19 -22
View File
@@ -15,17 +15,16 @@
*/
import {
AlertType,
Entity,
ProjectGrowthAlert,
Product,
UnlabeledDataflowAlert,
UnlabeledDataflowAlertProject,
getDefaultState,
DefaultLoadingAction,
Duration,
ProductCost,
Entity,
findAlways,
getDefaultState,
Product,
ProductCost,
ProjectGrowthData,
UnlabeledDataflowAlertProject,
UnlabeledDataflowData,
} from '../types';
import { Config } from '@backstage/config';
import { ConfigApi } from '@backstage/core';
@@ -73,11 +72,10 @@ export const createMockProductCost = (
return { ...defaultProduct };
};
export const createMockProjectGrowthAlert = (
callback?: mockAlertRenderer<ProjectGrowthAlert>,
): ProjectGrowthAlert => {
const defaultAlert: ProjectGrowthAlert = {
id: AlertType.ProjectGrowth,
export const createMockProjectGrowthData = (
callback?: mockAlertRenderer<ProjectGrowthData>,
): ProjectGrowthData => {
const data: ProjectGrowthData = {
project: 'test-project-growth-alert',
periodStart: '2019-10-01',
periodEnd: '2020-03-31',
@@ -90,17 +88,16 @@ export const createMockProjectGrowthAlert = (
};
if (typeof callback === 'function') {
return callback({ ...defaultAlert });
return callback({ ...data });
}
return { ...defaultAlert };
return { ...data };
};
export const createMockUnlabeledDataflowAlert = (
callback?: mockAlertRenderer<UnlabeledDataflowAlert>,
): UnlabeledDataflowAlert => {
const defaultAlert: UnlabeledDataflowAlert = {
id: AlertType.UnlabeledDataflow,
export const createMockUnlabeledDataflowData = (
callback?: mockAlertRenderer<UnlabeledDataflowData>,
): UnlabeledDataflowData => {
const data: UnlabeledDataflowData = {
periodStart: '2020-05-01',
periodEnd: '2020-06-1',
projects: [],
@@ -109,10 +106,10 @@ export const createMockUnlabeledDataflowAlert = (
};
if (typeof callback === 'function') {
return callback({ ...defaultAlert });
return callback({ ...data });
}
return { ...defaultAlert };
return { ...data };
};
export const createMockUnlabeledDataflowAlertProject = (