Merge pull request #3572 from backstage/remove-calendar-month-option

Remove calendar month option
This commit is contained in:
brendasukh
2020-12-08 12:32:47 -05:00
committed by GitHub
14 changed files with 52 additions and 63 deletions
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': minor
---
Remove calendar MoM period option and fix quarter end date logic
@@ -38,7 +38,7 @@ export type ProductInsightsOptions = {
group: string;
/**
* A time duration, such as P1M. See the Duration type for a detailed explanation
* A time duration, such as P3M. See the Duration type for a detailed explanation
* of how the durations are interpreted in Cost Insights.
*/
duration: Duration;
@@ -90,7 +90,7 @@ export type CostInsightsApi = {
* reduction) and compare it to metrics important to the business.
*
* @param group The group id from getUserGroups or query parameters
* @param intervals An ISO 8601 repeating interval string, such as R2/P1M/2020-09-01
* @param intervals An ISO 8601 repeating interval string, such as R2/P30D/2020-09-01
* https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals
*/
getGroupDailyCost(group: string, intervals: string): Promise<Cost>;
@@ -108,7 +108,7 @@ export type CostInsightsApi = {
* (or reduction) and compare it to metrics important to the business.
*
* @param project The project id from getGroupProjects or query parameters
* @param intervals An ISO 8601 repeating interval string, such as R2/P1M/2020-09-01
* @param intervals An ISO 8601 repeating interval string, such as R2/P30D/2020-09-01
* https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals
*/
getProjectDailyCost(project: string, intervals: string): Promise<Cost>;
@@ -119,7 +119,7 @@ export type CostInsightsApi = {
* (or reduction) of a project or group's daily costs.
*
* @param metric A metric from the cost-insights configuration in app-config.yaml.
* @param intervals An ISO 8601 repeating interval string, such as R2/P1M/2020-09-01
* @param intervals An ISO 8601 repeating interval string, such as R2/P30D/2020-09-01
* https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals
*/
getDailyMetricData(metric: string, intervals: string): Promise<MetricData>;
@@ -54,7 +54,7 @@ describe.each`
it(`formats ${engineers.unit}s correctly for ${expected}`, async () => {
const { getByText } = await renderInTestApp(
<MockContext engineerCost={engineerCost} currency={engineers}>
<CostGrowth change={{ ratio, amount }} duration={Duration.P1M} />
<CostGrowth change={{ ratio, amount }} duration={Duration.P30D} />
</MockContext>,
);
expect(getByText(expected)).toBeInTheDocument();
@@ -73,7 +73,7 @@ describe.each`
it(`formats ${usd.unit}s correctly for ${expected}`, async () => {
const { getByText } = await renderInTestApp(
<MockContext engineerCost={engineerCost} currency={usd}>
<CostGrowth change={{ ratio, amount }} duration={Duration.P1M} />
<CostGrowth change={{ ratio, amount }} duration={Duration.P30D} />
</MockContext>,
);
expect(getByText(expected)).toBeInTheDocument();
@@ -92,7 +92,7 @@ describe.each`
it(`formats ${carbon.unit}s correctly for ${expected}`, async () => {
const { getByText } = await renderInTestApp(
<MockContext engineerCost={engineerCost} currency={carbon}>
<CostGrowth change={{ ratio, amount }} duration={Duration.P1M} />
<CostGrowth change={{ ratio, amount }} duration={Duration.P30D} />
</MockContext>,
);
expect(getByText(expected)).toBeInTheDocument();
@@ -66,7 +66,6 @@ describe('<PeriodSelect />', () => {
describe.each`
duration
${Duration.P1M}
${Duration.P3M}
${Duration.P90D}
${Duration.P30D}
@@ -74,8 +73,9 @@ describe('<PeriodSelect />', () => {
it(`Should select ${duration}`, async () => {
const mockOnSelect = jest.fn();
const mockAggregation =
// Can't select an option that's already the default
DefaultPageFilters.duration === duration
? Duration.P1M
? Duration.P30D
: DefaultPageFilters.duration;
const rendered = await renderInTestApp(
@@ -89,7 +89,6 @@ describe('<PeriodSelect />', () => {
const button = getByRole(periodSelect, 'button');
UserEvent.click(button);
await waitFor(() => rendered.getByText('Past 60 Days'));
UserEvent.click(rendered.getByTestId(`period-select-option-${duration}`));
expect(mockOnSelect).toHaveBeenLastCalledWith(duration);
});
@@ -17,10 +17,7 @@
import React from 'react';
import { MenuItem, Select, SelectProps } from '@material-ui/core';
import { Duration } from '../../types';
import {
formatLastTwoLookaheadQuarters,
formatLastTwoMonths,
} from '../../utils/formatters';
import { formatLastTwoLookaheadQuarters } from '../../utils/formatters';
import { findAlways } from '../../utils/assert';
import { useSelectStyles as useStyles } from '../../utils/styles';
import { useLastCompleteBillingDate } from '../../hooks';
@@ -42,10 +39,6 @@ export function getDefaultOptions(
value: Duration.P30D,
label: 'Past 60 Days',
},
{
value: Duration.P1M,
label: formatLastTwoMonths(lastCompleteBillingDate),
},
{
value: Duration.P3M,
label: formatLastTwoLookaheadQuarters(lastCompleteBillingDate),
@@ -81,7 +81,7 @@ describe('<ProductInsightsCard/>', () => {
const rendered = await renderProductInsightsCardInTestApp(
mockProductCost,
MockComputeEngine,
Duration.P1M,
Duration.P30D,
);
expect(
rendered.queryByTestId(`scroll-test-compute-engine`),
@@ -113,7 +113,7 @@ describe('<ProductInsightsCard/>', () => {
const rendered = await renderProductInsightsCardInTestApp(
entity,
MockComputeEngine,
Duration.P1M,
Duration.P30D,
);
const subheaderRgx = new RegExp(subheader);
expect(rendered.getByText(subheaderRgx)).toBeInTheDocument();
+3 -4
View File
@@ -15,15 +15,14 @@
*/
/**
* 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 -
* Time periods for cost comparison; slight abuse of ISO 8601 periods. We take P3M to mean
* 'last completed quarter', and P30D/P90D to be '[month|quarter] relative to today'. So if
* it's September 15, P3M represents costs for Q2 and P30D represents August 16 -
* September 15.
*/
export enum Duration {
P30D = 'P30D',
P90D = 'P90D',
P1M = 'P1M',
P3M = 'P3M',
}
@@ -80,7 +80,7 @@ describe('getPreviousPeriodTotalCost', () => {
expect(
getPreviousPeriodTotalCost(
mockGroupDailyCost.aggregation,
Duration.P1M,
Duration.P30D,
exclusiveEndDate,
),
).toEqual(100_000);
@@ -18,7 +18,6 @@ import { assertNever } from '../utils/assert';
export const rateOf = (cost: number, duration: Duration) => {
switch (duration) {
case Duration.P1M:
case Duration.P30D:
return cost / 12;
case Duration.P90D:
@@ -15,7 +15,11 @@
*/
import { Duration } from '../types';
import { inclusiveEndDateOf, inclusiveStartDateOf } from './duration';
import {
inclusiveEndDateOf,
inclusiveStartDateOf,
quarterEndDate,
} from './duration';
const lastCompleteBillingDate = '2020-06-05';
@@ -23,7 +27,6 @@ describe.each`
duration | startDate | endDate
${Duration.P30D} | ${'2020-04-06'} | ${'2020-06-05'}
${Duration.P90D} | ${'2019-12-08'} | ${'2020-06-05'}
${Duration.P1M} | ${'2020-04-01'} | ${'2020-05-31'}
${Duration.P3M} | ${'2019-10-01'} | ${'2020-03-31'}
`('Calculates interval dates correctly', ({ duration, startDate, endDate }) => {
it(`Calculates dates correctly for ${duration}`, () => {
@@ -33,3 +36,14 @@ describe.each`
expect(inclusiveEndDateOf(duration, lastCompleteBillingDate)).toBe(endDate);
});
});
describe.each`
inclusiveEndDate | expectedQuarterEndDate
${'2020-12-31'} | ${'2020-12-31'}
${'2020-12-30'} | ${'2020-09-30'}
${'2021-02-19'} | ${'2020-12-31'}
`('quarterEndDate', ({ inclusiveEndDate, expectedQuarterEndDate }) => {
it(`calculates quarter end date correctly from inclusive end date ${inclusiveEndDate}`, () => {
expect(quarterEndDate(inclusiveEndDate)).toBe(expectedQuarterEndDate);
});
});
+14 -13
View File
@@ -37,12 +37,6 @@ export function inclusiveStartDateOf(
.utc()
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
case Duration.P1M:
return moment(exclusiveEndDate)
.utc()
.startOf('month')
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(exclusiveEndDate)
.utc()
@@ -65,15 +59,10 @@ export function exclusiveEndDateOf(
.utc()
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
case Duration.P1M:
return moment(inclusiveEndDate)
.utc()
.startOf('month')
.format(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(inclusiveEndDate)
return moment(quarterEndDate(inclusiveEndDate))
.utc()
.startOf('quarter')
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
default:
return assertNever(duration);
@@ -94,3 +83,15 @@ export function inclusiveEndDateOf(
export function intervalsOf(duration: Duration, inclusiveEndDate: string) {
return `R2/${duration}/${exclusiveEndDateOf(duration, inclusiveEndDate)}`;
}
export function quarterEndDate(inclusiveEndDate: string): string {
const endDate = moment(inclusiveEndDate).utc();
const endOfQuarter = endDate.endOf('quarter').format(DEFAULT_DATE_FORMAT);
if (endOfQuarter === inclusiveEndDate) {
return endDate.format(DEFAULT_DATE_FORMAT);
}
return endDate
.startOf('quarter')
.subtract(1, 'day')
.format(DEFAULT_DATE_FORMAT);
}
@@ -57,8 +57,6 @@ describe('date formatters', () => {
describe.each`
duration | date | isEndDate | output
${Duration.P1M} | ${'2020-10-11'} | ${true} | ${'September 2020'}
${Duration.P1M} | ${'2020-10-11'} | ${false} | ${'August 2020'}
${Duration.P3M} | ${'2020-10-11'} | ${true} | ${'Q3 2020'}
${Duration.P3M} | ${'2020-10-11'} | ${false} | ${'Q2 2020'}
${Duration.P30D} | ${'2020-10-11'} | ${true} | ${'Last 30 Days'}
@@ -104,19 +104,6 @@ export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string) {
return `${start} vs ${end}`;
}
export function formatLastTwoMonths(inclusiveEndDate: string) {
const exclusiveEndDate = moment(inclusiveEndDate)
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
const start = moment(inclusiveStartDateOf(Duration.P1M, exclusiveEndDate))
.utc()
.format('MMMM');
const end = moment(inclusiveEndDateOf(Duration.P1M, inclusiveEndDate))
.utc()
.format('MMMM');
return `${start} vs ${end}`;
}
const formatRelativePeriod = (
duration: Duration,
date: string,
@@ -137,12 +124,6 @@ export function formatPeriod(
isEndDate: boolean,
) {
switch (duration) {
case Duration.P1M:
return monthOf(
isEndDate
? inclusiveEndDateOf(duration, date)
: inclusiveStartDateOf(duration, date),
);
case Duration.P3M:
return quarterOf(
isEndDate
+1 -1
View File
@@ -143,7 +143,7 @@ export const MockProductTypes: Record<string, string> = {
export const MockProductFilters: ProductFilters = Object.keys(
MockProductTypes,
).map(productType => ({ duration: Duration.P1M, productType }));
).map(productType => ({ duration: Duration.P30D, productType }));
export const MockProducts: Product[] = Object.keys(MockProductTypes).map(
productType =>