Merge pull request #3572 from backstage/remove-calendar-month-option
Remove calendar month option
This commit is contained in:
@@ -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),
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
Reference in New Issue
Block a user