diff --git a/.changeset/good-islands-cheer.md b/.changeset/good-islands-cheer.md new file mode 100644 index 0000000000..2e7109b312 --- /dev/null +++ b/.changeset/good-islands-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': minor +--- + +Enable custom alert types in Cost Insights diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index c9774ae237..59a0eadb49 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -141,6 +141,7 @@ Raghunandan rankdir readme Readme +Recharts Redash replicasets repo diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index c01092dc51..083a0e2ace 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -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 { - 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; diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index 01dd415ec2..26f9d9371a 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -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. diff --git a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.test.tsx b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.test.tsx index 6e6f935137..0600f89c60 100644 --- a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.test.tsx +++ b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.test.tsx @@ -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('', () => { it('Renders an alert', async () => { @@ -40,11 +39,7 @@ describe('', () => { ); 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(); }); }); diff --git a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.tsx b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.tsx index f568f999f8..a83c7bc7ce 100644 --- a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.tsx +++ b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.tsx @@ -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) => { {number}} - title={text?.title} - subheader={text?.subtitle} + title={alert.title} + subheader={alert.subtitle} /> ); diff --git a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx index 4ddc26e040..1ea1fb6a96 100644 --- a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx +++ b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx @@ -25,7 +25,7 @@ type AlertActionCardList = { const AlertActionCardList: FC = ({ alerts }) => ( {alerts.map((alert, index) => ( - + {index < alerts.length - 1 && } diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx index 59dac3a758..e0aa0f02d9 100644 --- a/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx +++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx @@ -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) => ( {alerts.map((alert, index) => ( - + ))} diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.tsx index c00d924cf2..d2305e390d 100644 --- a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.tsx +++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.tsx @@ -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 ( - {/* */} - {render(alert, currency)} + {alert.element} ); }; diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSectionHeader.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSectionHeader.tsx index 5d5f94b9be..a561735d58 100644 --- a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSectionHeader.tsx +++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSectionHeader.tsx @@ -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 ( diff --git a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx index 0a422c335a..e7ecc8894b 100644 --- a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx +++ b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx @@ -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 ( - -
- - - - - {children} - - + + + +
+ + + + + {children} + + + + ); }; diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx index 5c3856cc9e..b914b3885d 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx @@ -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, })); diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx index c27b201202..8d1d1d29b1 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx @@ -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) => { diff --git a/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx b/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx index f8f4a90a9e..cde989c9dd 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx @@ -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: - + {projectGrowthAlert.element} This allows you to quickly see which cloud products contributed to the diff --git a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx index 6beb204b0d..edc4152e3c 100644 --- a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx +++ b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx @@ -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(), ), diff --git a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.tsx b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.tsx index 8dff85e867..5c264a33ef 100644 --- a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.tsx +++ b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.tsx @@ -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) => { diff --git a/plugins/cost-insights/src/types/Alert.ts b/plugins/cost-insights/src/types/Alert.ts deleted file mode 100644 index 8d560096e3..0000000000 --- a/plugins/cost-insights/src/types/Alert.ts +++ /dev/null @@ -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; -} - -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; -} - -export interface UnlabeledDataflowAlert { - id: AlertType.UnlabeledDataflow; - periodStart: string; - periodEnd: string; - projects: Array; - unlabeledCost: number; - labeledCost: number; -} - -export interface UnlabeledDataflowAlertProject { - id: string; - unlabeledCost: number; - labeledCost: number; -} diff --git a/plugins/cost-insights/src/types/Alert.tsx b/plugins/cost-insights/src/types/Alert.tsx new file mode 100644 index 0000000000..98214bed8f --- /dev/null +++ b/plugins/cost-insights/src/types/Alert.tsx @@ -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; +} + +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; +} + +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 ; + } +} + +export interface UnlabeledDataflowData { + periodStart: string; + periodEnd: string; + projects: Array; + 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 ; + } +} + +export interface UnlabeledDataflowAlertProject { + id: string; + unlabeledCost: number; + labeledCost: number; +} diff --git a/plugins/cost-insights/src/utils/alerts.tsx b/plugins/cost-insights/src/utils/alerts.tsx deleted file mode 100644 index 07602be191..0000000000 --- a/plugins/cost-insights/src/utils/alerts.tsx +++ /dev/null @@ -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 ; - case AlertType.UnlabeledDataflow: - return ; - default: - return assertNever(alert); - } -} - -export type AlertUrl = string; -export type AlertButtonText = string; - -export interface AlertText { - title: string; - subtitle: string; -} diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 87dbcf8158..e1d715c573 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -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 => { - const defaultAlert: ProjectGrowthAlert = { - id: AlertType.ProjectGrowth, +export const createMockProjectGrowthData = ( + callback?: mockAlertRenderer, +): 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 => { - const defaultAlert: UnlabeledDataflowAlert = { - id: AlertType.UnlabeledDataflow, +export const createMockUnlabeledDataflowData = ( + callback?: mockAlertRenderer, +): 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 = (