Merge pull request #6159 from vannarath-poeu/master
Replaced moment and dayjs with luxon
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-cost-insights': patch
|
||||
---
|
||||
|
||||
Replaced moment and dayjs with Luxon
|
||||
@@ -52,6 +52,7 @@ cookiecutter
|
||||
css
|
||||
Datadog
|
||||
dataflow
|
||||
dayjs
|
||||
deadnaming
|
||||
debounce
|
||||
Debounce
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
+6
-5
@@ -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 => ({
|
||||
|
||||
+7
-3
@@ -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 (
|
||||
|
||||
+2
-2
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user