From 3b55f5f81fef3e7a1f79edaa6142e852e11c5923 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Sun, 21 Feb 2021 13:36:32 -0500 Subject: [PATCH 1/3] declare default properties using accessors --- .../src/alerts/ProjectGrowthAlert.test.tsx | 77 +++++++++++++++++++ .../src/alerts/ProjectGrowthAlert.tsx | 12 ++- .../alerts/UnlabeledDataflowAlert.test.tsx | 72 +++++++++++++++++ .../src/alerts/UnlabeledDataflowAlert.tsx | 17 ++-- 4 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx create mode 100644 plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx diff --git a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx new file mode 100644 index 0000000000..22b55d85dd --- /dev/null +++ b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import pluralize from 'pluralize'; +import { renderInTestApp } from '@backstage/test-utils'; +import { ProjectGrowthAlert } from './ProjectGrowthAlert'; +import { ProjectGrowthData } from '../types'; +import { + MockCurrencyProvider, + MockConfigProvider, + MockBillingDateProvider, +} from '../utils/tests'; + +const mockData: ProjectGrowthData = { + project: 'test-project', + periodStart: '2021-01-01', + periodEnd: '2021-02-01', + aggregation: [0, 0], + change: { + ratio: 0, + amount: 0 + }, + products: [ + { + id: 'product-a', + aggregation: [0, 0] + } + ], +}; + +// suppress recharts componentDidUpdate deprecation warnings +jest.spyOn(console, 'warn').mockImplementation(() => { }); + +async function renderInContext(children: JSX.Element) { + return renderInTestApp( + + + + {children} + + + + ) +} + +class CustomProjectGrowthAlert extends ProjectGrowthAlert { + get url() { + return 'path/to/resource'; + } + get title() { + return `Investigate cost growth in ${pluralize('project', this.data.products.length, true)}`; + } +} + +describe('ProjectGrowthAlert', () => { + describe('constructor', () => { + it('should create a project growth alert', async () => { + const alert = new ProjectGrowthAlert(mockData); + const { getByText, queryByText } = await renderInContext(alert.element); + + expect(alert.url).toBe('/cost-insights/investigating-growth'); + expect(alert.title).toBe('Investigate cost growth in project test-project'); + expect(alert.subtitle).toBe('Cost growth outpacing business growth is unsustainable long-term.'); + expect(getByText('1 product')).toBeInTheDocument(); + expect(queryByText('sorted by cost')).not.toBeInTheDocument(); + }); + + it('a subclass can inherit and override defaults using accessors', async () => { + const alert = new CustomProjectGrowthAlert(mockData); + const { getByText, queryByText } = await renderInContext(alert.element); + + expect(alert.url).toBe('path/to/resource'); + expect(alert.title).toBe('Investigate cost growth in 1 project'); + expect(alert.subtitle).toBe('Cost growth outpacing business growth is unsustainable long-term.'); + expect(getByText('1 product')).toBeInTheDocument(); + expect(queryByText('sorted by cost')).not.toBeInTheDocument(); + }); + }); +}) diff --git a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.tsx b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.tsx index 67eb1b5071..088e85939d 100644 --- a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.tsx +++ b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.tsx @@ -27,18 +27,22 @@ import { Alert, ProjectGrowthData } from '../types'; export class ProjectGrowthAlert implements Alert { data: ProjectGrowthData; - url = '/cost-insights/investigating-growth'; - subtitle = - 'Cost growth outpacing business growth is unsustainable long-term.'; - constructor(data: ProjectGrowthData) { this.data = data; } + get url() { + return '/cost-insights/investigating-growth'; + } + get title() { return `Investigate cost growth in project ${this.data.project}`; } + get subtitle() { + return 'Cost growth outpacing business growth is unsustainable long-term.'; + } + get element() { return ; } diff --git a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx new file mode 100644 index 0000000000..d6cbd0006f --- /dev/null +++ b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import pluralize from 'pluralize'; +import { renderInTestApp } from '@backstage/test-utils'; +import { UnlabeledDataflowAlert } from './UnlabeledDataflowAlert'; +import { UnlabeledDataflowData } from '../types'; +import { + MockCurrencyProvider, + MockConfigProvider, + MockBillingDateProvider, +} from '../utils/tests'; + +const mockData: UnlabeledDataflowData = { + periodStart: '2021-02-01', + periodEnd: '2021-03-31', + unlabeledCost: 0, + labeledCost: 0, + projects: [ + { + id: 'project-a', + labeledCost: 0, + unlabeledCost: 0 + } + ] +}; + +// suppress recharts componentDidUpdate deprecation warnings +jest.spyOn(console, 'warn').mockImplementation(() => { }); + +async function renderInContext(children: JSX.Element) { + return renderInTestApp( + + + + {children} + + + + ) +} + +class CustomUnlabeledDataflowAlert extends UnlabeledDataflowAlert { + get url() { + return 'path/to/resource'; + } + get title() { + return `Add labels to ${pluralize('workflow', this.data.projects.length, true)}`; + } +} + +describe('UnlabeledDataflowAlert', () => { + describe('constructor', () => { + it('should create an unlabeled dataflow alert', async () => { + const alert = new UnlabeledDataflowAlert(mockData); + const { getByText } = await renderInContext(alert.element); + + expect(alert.url).toBe('/cost-insights/labeling-jobs'); + expect(alert.title).toBe('Add labels to workflows'); + expect(alert.subtitle).toBe('Labels show in billing data, enabling cost insights for each workflow.'); + expect(getByText('Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.')).toBeInTheDocument(); + }); + + it('a subclass can inherit and override defaults using accessors', async () => { + const alert = new CustomUnlabeledDataflowAlert(mockData); + const { getByText } = await renderInContext(alert.element); + + expect(alert.url).toBe('path/to/resource'); + expect(alert.title).toBe('Add labels to 1 workflow'); + expect(alert.subtitle).toBe('Labels show in billing data, enabling cost insights for each workflow.'); + expect(getByText('Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.')).toBeInTheDocument(); + }); + }); +}) diff --git a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.tsx b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.tsx index e889e0e3d4..7d11f3cc81 100644 --- a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.tsx +++ b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.tsx @@ -28,15 +28,22 @@ export class UnlabeledDataflowAlert implements Alert { data: UnlabeledDataflowData; status?: AlertStatus; - url = '/cost-insights/labeling-jobs'; - title = 'Add labels to workflows'; - subtitle = - 'Labels show in billing data, enabling cost insights for each workflow.'; - constructor(data: UnlabeledDataflowData) { this.data = data; } + get url() { + return '/cost-insights/labeling-jobs'; + } + + get title() { + return 'Add labels to workflows'; + } + + get subtitle() { + return 'Labels show in billing data, enabling cost insights for each workflow.'; + } + get element() { return ; } From 38205492aa060f35682566b21f88d34a20625cb5 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Sun, 21 Feb 2021 13:38:35 -0500 Subject: [PATCH 2/3] changeset --- .changeset/cost-insights-fresh-radios-doubt.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cost-insights-fresh-radios-doubt.md diff --git a/.changeset/cost-insights-fresh-radios-doubt.md b/.changeset/cost-insights-fresh-radios-doubt.md new file mode 100644 index 0000000000..849f5140f6 --- /dev/null +++ b/.changeset/cost-insights-fresh-radios-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Default alert properties can be overridden using accessors From 1da01d95ed6c36eef8a01e0aced928f48b0f832c Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Mon, 22 Feb 2021 11:08:27 -0500 Subject: [PATCH 3/3] lint add accessors to vale add headers --- .github/styles/vocab.txt | 1 + .../src/alerts/ProjectGrowthAlert.test.tsx | 52 +++++++++++----- .../alerts/UnlabeledDataflowAlert.test.tsx | 60 ++++++++++++++----- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 2a8854b8a2..c20c0b67c5 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -94,6 +94,7 @@ Zalando Zhou Zolotusky abc +accessors adamdmharvey andrewthauer api diff --git a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx index 22b55d85dd..05a10bb2f0 100644 --- a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx +++ b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx @@ -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 React from 'react'; import pluralize from 'pluralize'; import { renderInTestApp } from '@backstage/test-utils'; @@ -16,29 +32,27 @@ const mockData: ProjectGrowthData = { aggregation: [0, 0], change: { ratio: 0, - amount: 0 + amount: 0, }, products: [ { id: 'product-a', - aggregation: [0, 0] - } + aggregation: [0, 0], + }, ], }; // suppress recharts componentDidUpdate deprecation warnings -jest.spyOn(console, 'warn').mockImplementation(() => { }); +jest.spyOn(console, 'warn').mockImplementation(() => {}); async function renderInContext(children: JSX.Element) { return renderInTestApp( - - {children} - + {children} - - ) + , + ); } class CustomProjectGrowthAlert extends ProjectGrowthAlert { @@ -46,7 +60,11 @@ class CustomProjectGrowthAlert extends ProjectGrowthAlert { return 'path/to/resource'; } get title() { - return `Investigate cost growth in ${pluralize('project', this.data.products.length, true)}`; + return `Investigate cost growth in ${pluralize( + 'project', + this.data.products.length, + true, + )}`; } } @@ -57,8 +75,12 @@ describe('ProjectGrowthAlert', () => { const { getByText, queryByText } = await renderInContext(alert.element); expect(alert.url).toBe('/cost-insights/investigating-growth'); - expect(alert.title).toBe('Investigate cost growth in project test-project'); - expect(alert.subtitle).toBe('Cost growth outpacing business growth is unsustainable long-term.'); + expect(alert.title).toBe( + 'Investigate cost growth in project test-project', + ); + expect(alert.subtitle).toBe( + 'Cost growth outpacing business growth is unsustainable long-term.', + ); expect(getByText('1 product')).toBeInTheDocument(); expect(queryByText('sorted by cost')).not.toBeInTheDocument(); }); @@ -69,9 +91,11 @@ describe('ProjectGrowthAlert', () => { expect(alert.url).toBe('path/to/resource'); expect(alert.title).toBe('Investigate cost growth in 1 project'); - expect(alert.subtitle).toBe('Cost growth outpacing business growth is unsustainable long-term.'); + expect(alert.subtitle).toBe( + 'Cost growth outpacing business growth is unsustainable long-term.', + ); expect(getByText('1 product')).toBeInTheDocument(); expect(queryByText('sorted by cost')).not.toBeInTheDocument(); }); }); -}) +}); diff --git a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx index d6cbd0006f..95e213f06a 100644 --- a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx +++ b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx @@ -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 React from 'react'; import pluralize from 'pluralize'; import { renderInTestApp } from '@backstage/test-utils'; @@ -18,24 +34,22 @@ const mockData: UnlabeledDataflowData = { { id: 'project-a', labeledCost: 0, - unlabeledCost: 0 - } - ] + unlabeledCost: 0, + }, + ], }; // suppress recharts componentDidUpdate deprecation warnings -jest.spyOn(console, 'warn').mockImplementation(() => { }); +jest.spyOn(console, 'warn').mockImplementation(() => {}); async function renderInContext(children: JSX.Element) { return renderInTestApp( - - {children} - + {children} - - ) + , + ); } class CustomUnlabeledDataflowAlert extends UnlabeledDataflowAlert { @@ -43,7 +57,11 @@ class CustomUnlabeledDataflowAlert extends UnlabeledDataflowAlert { return 'path/to/resource'; } get title() { - return `Add labels to ${pluralize('workflow', this.data.projects.length, true)}`; + return `Add labels to ${pluralize( + 'workflow', + this.data.projects.length, + true, + )}`; } } @@ -55,8 +73,14 @@ describe('UnlabeledDataflowAlert', () => { expect(alert.url).toBe('/cost-insights/labeling-jobs'); expect(alert.title).toBe('Add labels to workflows'); - expect(alert.subtitle).toBe('Labels show in billing data, enabling cost insights for each workflow.'); - expect(getByText('Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.')).toBeInTheDocument(); + expect(alert.subtitle).toBe( + 'Labels show in billing data, enabling cost insights for each workflow.', + ); + expect( + getByText( + 'Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.', + ), + ).toBeInTheDocument(); }); it('a subclass can inherit and override defaults using accessors', async () => { @@ -65,8 +89,14 @@ describe('UnlabeledDataflowAlert', () => { expect(alert.url).toBe('path/to/resource'); expect(alert.title).toBe('Add labels to 1 workflow'); - expect(alert.subtitle).toBe('Labels show in billing data, enabling cost insights for each workflow.'); - expect(getByText('Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.')).toBeInTheDocument(); + expect(alert.subtitle).toBe( + 'Labels show in billing data, enabling cost insights for each workflow.', + ); + expect( + getByText( + 'Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.', + ), + ).toBeInTheDocument(); }); }); -}) +});