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 ;
}