Merge pull request #6159 from vannarath-poeu/master

Replaced moment and dayjs with luxon
This commit is contained in:
Fredrik Adelöw
2021-06-24 14:23:03 +02:00
committed by GitHub
15 changed files with 87 additions and 73 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': patch
---
Replaced moment and dayjs with Luxon
+1
View File
@@ -52,6 +52,7 @@ cookiecutter
css
Datadog
dataflow
dayjs
deadnaming
debounce
Debounce
+1 -1
View File
@@ -346,7 +346,7 @@ export type DateAggregation = {
};
// @public (undocumented)
export const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
export const DEFAULT_DATE_FORMAT = "yyyy-LL-dd";
// @public
export enum Duration {
+1 -2
View File
@@ -41,9 +41,8 @@
"@types/react": "^16.9",
"@types/recharts": "^1.8.14",
"classnames": "^2.2.6",
"dayjs": "^1.9.4",
"history": "^5.0.0",
"moment": "^2.27.0",
"luxon": "^1.26.0",
"pluralize": "^8.0.0",
"qs": "^6.9.4",
"react": "^16.13.1",
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React, { useState } from 'react';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { DateTime } from 'luxon';
import {
useTheme,
Box,
@@ -54,8 +53,6 @@ import { formatPeriod } from '../../utils/formatters';
import { aggregationSum } from '../../utils/sum';
import { BarChartLegendOptions } from '../BarChart/BarChartLegend';
dayjs.extend(utc);
export type CostOverviewBreakdownChartProps = {
costBreakdown: Cost[];
};
@@ -184,7 +181,11 @@ export const CostOverviewBreakdownChart = ({
}) => {
if (isInvalid({ label, payload })) return null;
const dateTitle = dayjs(label).utc().format(DEFAULT_DATE_FORMAT);
const date =
typeof label === 'number'
? DateTime.fromMillis(label)
: DateTime.fromISO(label!);
const dateTitle = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);
const items = payload.map(p => ({
label: p.dataKey as string,
value: formatGraphValue(p.value as number),
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React from 'react';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { DateTime } from 'luxon';
import { useTheme, Box } from '@material-ui/core';
import {
ComposedChart,
@@ -52,8 +51,6 @@ import { groupByDate, toDataMax, trendFrom } from '../../utils/charts';
import { aggregationSort } from '../../utils/sort';
import { CostOverviewLegend } from './CostOverviewLegend';
dayjs.extend(utc);
type CostOverviewChartProps = {
metric: Maybe<Metric>;
metricData: Maybe<MetricData>;
@@ -108,7 +105,11 @@ export const CostOverviewChart = ({
if (isInvalid({ label, payload })) return null;
const dataKeys = [data.dailyCost.dataKey, data.metric.dataKey];
const title = dayjs(label).utc().format(DEFAULT_DATE_FORMAT);
const date =
typeof label === 'number'
? DateTime.fromMillis(label)
: DateTime.fromISO(label!);
const title = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);
const items = payload
.filter(p => dataKeys.includes(p.dataKey as string))
.map(p => ({
@@ -15,7 +15,7 @@
*/
import React from 'react';
import moment from 'moment';
import { DateTime } from 'luxon';
import { Box } from '@material-ui/core';
import { BarChart, BarChartLegend, BarChartLegendOptions } from '../BarChart';
import { LegendItem } from '../LegendItem';
@@ -38,8 +38,12 @@ export const ProjectGrowthAlertChart = ({
const resourceData = alert.products.map(resourceOf);
const options: Partial<BarChartLegendOptions> = {
previousName: moment(alert.periodStart, 'YYYY-[Q]Q').format('[Q]Q YYYY'),
currentName: moment(alert.periodEnd, 'YYYY-[Q]Q').format('[Q]Q YYYY'),
previousName: DateTime.fromFormat(alert.periodStart, "yyyy-'Q'q").toFormat(
"'Q'q yyyy",
),
currentName: DateTime.fromFormat(alert.periodEnd, "yyyy-'Q'q").toFormat(
"'Q'q yyyy",
),
};
return (
@@ -15,7 +15,7 @@
*/
import React from 'react';
import moment from 'moment';
import { DateTime } from 'luxon';
import { Box, Typography } from '@material-ui/core';
import { AlertInstructionsLayout } from '../AlertInstructionsLayout';
import { ProductInsightsChart } from '../ProductInsightsCard';
@@ -30,7 +30,7 @@ import {
import { ProjectGrowthAlert } from '../../alerts';
import { InfoCard } from '@backstage/core-components';
const today = moment().format(DEFAULT_DATE_FORMAT);
const today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT);
export const ProjectGrowthInstructionsPage = () => {
const alertData: ProjectGrowthData = {
+5 -5
View File
@@ -15,7 +15,7 @@
*/
/* eslint-disable no-restricted-imports */
import dayjs from 'dayjs';
import { DateTime } from 'luxon';
import { CostInsightsApi, ProductInsightsOptions } from '../api';
import {
Alert,
@@ -46,7 +46,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi {
getLastCompleteBillingDate(): Promise<string> {
return Promise.resolve(
dayjs().subtract(1, 'day').format(DEFAULT_DATE_FORMAT),
DateTime.now().minus({ days: 1 }).toFormat(DEFAULT_DATE_FORMAT),
);
}
@@ -175,13 +175,13 @@ export class ExampleCostInsightsClient implements CostInsightsApi {
],
};
const today = dayjs();
const today = DateTime.now();
const alerts: Alert[] = await this.request({ group }, [
new ProjectGrowthAlert(projectGrowthData),
new UnlabeledDataflowAlert(unlabeledDataflowData),
new KubernetesMigrationAlert(this, {
startDate: today.subtract(30, 'day').format(DEFAULT_DATE_FORMAT),
endDate: today.format(DEFAULT_DATE_FORMAT),
startDate: today.minus({ days: 30 }).toFormat(DEFAULT_DATE_FORMAT),
endDate: today.toFormat(DEFAULT_DATE_FORMAT),
change: {
ratio: 0,
amount: 0,
@@ -21,7 +21,7 @@ import React, {
forwardRef,
FormEventHandler,
} from 'react';
import dayjs from 'dayjs';
import { DateTime } from 'luxon';
import {
Box,
FormControl,
@@ -57,7 +57,7 @@ export const AlertSnoozeForm = forwardRef<
e.preventDefault();
if (duration) {
const repeatInterval = 1;
const today = dayjs().format(DEFAULT_DATE_FORMAT);
const today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT);
onSubmit({
intervals: intervalsOf(duration, today, repeatInterval),
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import dayjs from 'dayjs';
import { DateTime } from 'luxon';
import regression, { DataPoint } from 'regression';
import {
ChangeStatistic,
@@ -58,9 +58,9 @@ export function aggregationFor(
): DateAggregation[] {
const { duration, endDate } = parseIntervals(intervals);
const inclusiveEndDate = inclusiveEndDateOf(duration, endDate);
const days = dayjs(endDate).diff(
inclusiveStartDateOf(duration, inclusiveEndDate),
'day',
const days = DateTime.fromISO(endDate).diff(
DateTime.fromISO(inclusiveStartDateOf(duration, inclusiveEndDate)),
'days',
);
function nextDelta(): number {
@@ -74,9 +74,11 @@ export function aggregationFor(
return [...Array(days).keys()].reduce(
(values: DateAggregation[], i: number): DateAggregation[] => {
const last = values.length ? values[values.length - 1].amount : baseline;
const date = dayjs(inclusiveStartDateOf(duration, inclusiveEndDate))
.add(i, 'day')
.format(DEFAULT_DATE_FORMAT);
const date = DateTime.fromISO(
inclusiveStartDateOf(duration, inclusiveEndDate),
)
.plus({ days: i })
.toFormat(DEFAULT_DATE_FORMAT);
const amount = Math.max(0, last + nextDelta());
values.push({
date: date,
+1 -1
View File
@@ -27,4 +27,4 @@ export enum Duration {
P3M = 'P3M',
}
export const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
export const DEFAULT_DATE_FORMAT = 'yyyy-LL-dd';
+4 -11
View File
@@ -24,13 +24,10 @@ import {
Duration,
DateAggregation,
} from '../types';
import dayjs, { OpUnitType } from 'dayjs';
import durationPlugin from 'dayjs/plugin/duration';
import { DateTime, Duration as LuxonDuration } from 'luxon';
import { inclusiveStartDateOf } from './duration';
import { notEmpty } from './assert';
dayjs.extend(durationPlugin);
// Used for displaying status colors
export function growthOf(change: ChangeStatistic): GrowthType {
const exceedsEngineerThreshold = Math.abs(change.amount) >= EngineerThreshold;
@@ -85,16 +82,12 @@ export function getPreviousPeriodTotalCost(
duration: Duration,
inclusiveEndDate: string,
): number {
const dayjsDuration = dayjs.duration(duration);
const luxonDuration = LuxonDuration.fromISO(duration);
const startDate = inclusiveStartDateOf(duration, inclusiveEndDate);
// dayjs doesn't allow adding an ISO 8601 period to dates.
const [amount, type]: [number, OpUnitType] = dayjsDuration.days()
? [dayjsDuration.days(), 'day']
: [dayjsDuration.months(), 'month'];
const nextPeriodStart = dayjs(startDate).add(amount, type);
const nextPeriodStart = DateTime.fromISO(startDate).plus(luxonDuration);
// Add up costs that incurred before the start of the next period.
return aggregation.reduce((acc, costByDate) => {
return dayjs(costByDate.date).isBefore(nextPeriodStart)
return DateTime.fromISO(costByDate.date) < nextPeriodStart
? acc + costByDate.amount
: acc;
}, 0);
+25 -19
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import moment from 'moment';
import { DateTime, Duration as LuxonDuration } from 'luxon';
import { Duration, DEFAULT_DATE_FORMAT } from '../types';
import { assertNever } from './assert';
@@ -34,14 +34,18 @@ export function inclusiveStartDateOf(
case Duration.P7D:
case Duration.P30D:
case Duration.P90D:
return moment(inclusiveEndDate)
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
return DateTime.fromISO(inclusiveEndDate)
.minus(
LuxonDuration.fromISO(duration).plus(LuxonDuration.fromISO(duration)),
)
.toFormat(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(inclusiveEndDate)
return DateTime.fromISO(inclusiveEndDate)
.startOf('quarter')
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
.minus(
LuxonDuration.fromISO(duration).plus(LuxonDuration.fromISO(duration)),
)
.toFormat(DEFAULT_DATE_FORMAT);
default:
return assertNever(duration);
}
@@ -55,11 +59,13 @@ export function exclusiveEndDateOf(
case Duration.P7D:
case Duration.P30D:
case Duration.P90D:
return moment(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT);
return DateTime.fromISO(inclusiveEndDate)
.plus({ days: 1 })
.toFormat(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(quarterEndDate(inclusiveEndDate))
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
return DateTime.fromISO(quarterEndDate(inclusiveEndDate))
.plus({ days: 1 })
.toFormat(DEFAULT_DATE_FORMAT);
default:
return assertNever(duration);
}
@@ -69,9 +75,9 @@ export function inclusiveEndDateOf(
duration: Duration,
inclusiveEndDate: string,
): string {
return moment(exclusiveEndDateOf(duration, inclusiveEndDate))
.subtract(1, 'day')
.format(DEFAULT_DATE_FORMAT);
return DateTime.fromISO(exclusiveEndDateOf(duration, inclusiveEndDate))
.minus({ days: 1 })
.toFormat(DEFAULT_DATE_FORMAT);
}
// https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals
@@ -87,13 +93,13 @@ export function intervalsOf(
}
export function quarterEndDate(inclusiveEndDate: string): string {
const endDate = moment(inclusiveEndDate);
const endOfQuarter = endDate.endOf('quarter').format(DEFAULT_DATE_FORMAT);
const endDate = DateTime.fromISO(inclusiveEndDate);
const endOfQuarter = endDate.endOf('quarter').toFormat(DEFAULT_DATE_FORMAT);
if (endOfQuarter === inclusiveEndDate) {
return endDate.format(DEFAULT_DATE_FORMAT);
return endDate.toFormat(DEFAULT_DATE_FORMAT);
}
return endDate
.startOf('quarter')
.subtract(1, 'day')
.format(DEFAULT_DATE_FORMAT);
.minus({ days: 1 })
.toFormat(DEFAULT_DATE_FORMAT);
}
+12 -10
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import moment from 'moment';
import { DateTime, Duration as LuxonDuration } from 'luxon';
import pluralize from 'pluralize';
import { ChangeStatistic, Duration } from '../types';
import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration';
@@ -67,9 +67,11 @@ export const monthOf = (date: string): string => {
};
export const quarterOf = (date: string): string => {
// Supports formatting YYYY-MM-DD and YYYY-[Q]Q returned in alerts
const d = moment(date).isValid() ? moment(date) : moment(date, 'YYYY-[Q]Q');
return d.format('[Q]Q YYYY');
// Supports formatting yyyy-LL-dd and yyyy-'Q'q returned in alerts
const d = DateTime.fromISO(date).isValid
? DateTime.fromISO(date)
: DateTime.fromFormat(date, "yyyy-'Q'q");
return d.toFormat("'Q'q yyyy");
};
export function formatCurrency(amount: number, currency?: string): string {
@@ -100,12 +102,12 @@ export function formatPercent(n: number): string {
}
export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string) {
const start = moment(
const start = DateTime.fromISO(
inclusiveStartDateOf(Duration.P3M, inclusiveEndDate),
).format('[Q]Q YYYY');
const end = moment(inclusiveEndDateOf(Duration.P3M, inclusiveEndDate)).format(
'[Q]Q YYYY',
);
).toFormat("'Q'q yyyy");
const end = DateTime.fromISO(
inclusiveEndDateOf(Duration.P3M, inclusiveEndDate),
).toFormat("'Q'q yyyy");
return `${start} vs ${end}`;
}
@@ -116,7 +118,7 @@ const formatRelativePeriod = (
): string => {
const periodStart = isEndDate ? inclusiveStartDateOf(duration, date) : date;
const periodEnd = isEndDate ? date : inclusiveEndDateOf(duration, date);
const days = moment.duration(duration).asDays();
const days = LuxonDuration.fromISO(duration).days;
if (![periodStart, periodEnd].includes(date)) {
throw new Error(`Invalid relative date ${date} for duration ${duration}`);
}