Add fiscal years and search to time picker (#39073)

* Add search to time picker

* implement fiscal datemath

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
Oscar Kilhed
2021-09-30 09:40:02 +02:00
committed by GitHub
co-authored by Dominik Prokop
parent 787e5e78dd
commit 738d5e499e
30 changed files with 457 additions and 164 deletions
@@ -135,4 +135,71 @@ describe('DateMath', () => {
expect(date!.valueOf()).toEqual(dateTime([2014, 1, 3]).valueOf());
});
});
describe('Round to fiscal start/end', () => {
it('Should round to start of fiscal year when datetime is the same year as the start of the fiscal year', () => {
let date = dateMath.roundToFiscal(1, dateTime([2021, 3, 5]), 'y', false);
let expected = dateTime([2021, 1, 1]);
expect(date!.valueOf()).toEqual(expected.valueOf());
});
it('Should round to start of fiscal year when datetime is the next year from the start of the fiscal year', () => {
let date = dateMath.roundToFiscal(1, dateTime([2022, 0, 2]), 'y', false);
let expected = dateTime([2021, 1, 1]);
expect(date!.valueOf()).toEqual(expected.valueOf());
});
it('Should round to start of fiscal year when datetime is on a leap day', () => {
let date = dateMath.roundToFiscal(1, dateTime([2020, 1, 29]), 'y', false);
let expected = dateTime([2020, 1, 1]);
expect(date!.valueOf()).toEqual(expected.valueOf());
});
it('Should round to end of fiscal year when datetime is the same year as the start of the fiscal year', () => {
let date = dateMath.roundToFiscal(1, dateTime([2021, 5, 2]), 'y', true);
let expected = dateTime([2022, 0, 1]).endOf('M');
expect(date!.valueOf()).toEqual(expected.valueOf());
});
it('Should round to end of fiscal year when datetime is the next year from the start of the fiscal year', () => {
let date = dateMath.roundToFiscal(1, dateTime([2022, 0, 1]), 'y', true);
let expected = dateTime([2022, 0, 1]).endOf('M');
expect(date!.valueOf()).toEqual(expected.valueOf());
});
it('Should round to end of fiscal year when datetime is on a leap day', () => {
let date = dateMath.roundToFiscal(1, dateTime([2020, 1, 29]), 'y', true);
let expected = dateTime([2021, 0, 1]).endOf('M');
expect(date!.valueOf()).toEqual(expected.valueOf());
});
//fq1 = 2021-02-01 - 2021-04-30
//fq2 = 2021-05-01 - 2021-07-31
//fq4 = 2021-08-01 - 2021-10-31
//fq5 = 2021-11-01 - 2022-01-31
it('Should round to start of q2 when one month into q2', () => {
let date = dateMath.roundToFiscal(1, dateTime([2021, 6, 1]), 'Q', false);
let expected = dateTime([2021, 4, 1]);
expect(date!.valueOf()).toEqual(expected.valueOf());
});
it('Should round to start of q4 when datetime is in next year from fiscal year start', () => {
let date = dateMath.roundToFiscal(1, dateTime([2022, 0, 1]), 'Q', false);
let expected = dateTime([2021, 10, 1]);
expect(date!.valueOf()).toEqual(expected.valueOf());
});
it('Should round to end of q2 when one month into q2', () => {
let date = dateMath.roundToFiscal(1, dateTime([2021, 6, 1]), 'Q', true);
let expected = dateTime([2021, 6, 1]).endOf('M');
expect(date!.valueOf()).toEqual(expected.valueOf());
});
it('Should round to end of q4 when datetime is in next year from fiscal year start', () => {
let date = dateMath.roundToFiscal(1, dateTime([2022, 0, 1]), 'Q', true);
let expected = dateTime([2022, 0, 31]).endOf('M');
expect(date!.valueOf()).toEqual(expected.valueOf());
});
});
});
+48 -7
View File
@@ -2,7 +2,7 @@ import { includes, isDate } from 'lodash';
import { DateTime, dateTime, dateTimeForTimeZone, ISO_8601, isDateTime, DurationUnit } from './moment_wrapper';
import { TimeZone } from '../types/index';
const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's'];
const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's', 'Q'];
export function isMathString(text: string | DateTime | Date): boolean {
if (!text) {
@@ -26,7 +26,8 @@ export function isMathString(text: string | DateTime | Date): boolean {
export function parse(
text?: string | DateTime | Date | null,
roundUp?: boolean,
timezone?: TimeZone
timezone?: TimeZone,
fiscalYearStartMonth?: number
): DateTime | undefined {
if (!text) {
return undefined;
@@ -67,7 +68,7 @@ export function parse(
return time;
}
return parseDateMath(mathString, time, roundUp);
return parseDateMath(mathString, time, roundUp, fiscalYearStartMonth);
}
}
@@ -96,7 +97,12 @@ export function isValid(text: string | DateTime): boolean {
* @param roundUp If true it will round the time to endOf time unit, otherwise to startOf time unit.
*/
// TODO: Had to revert Andrejs `time: moment.Moment` to `time: any`
export function parseDateMath(mathString: string, time: any, roundUp?: boolean): DateTime | undefined {
export function parseDateMath(
mathString: string,
time: any,
roundUp?: boolean,
fiscalYearStartMonth = 0
): DateTime | undefined {
const strippedMathString = mathString.replace(/\s/g, '');
const dateTime = time;
let i = 0;
@@ -107,6 +113,7 @@ export function parseDateMath(mathString: string, time: any, roundUp?: boolean):
let type;
let num;
let unit;
let isFiscal = false;
if (c === '/') {
type = 0;
@@ -121,7 +128,7 @@ export function parseDateMath(mathString: string, time: any, roundUp?: boolean):
if (isNaN(parseInt(strippedMathString.charAt(i), 10))) {
num = 1;
} else if (strippedMathString.length === 2) {
num = strippedMathString.charAt(i);
num = parseInt(strippedMathString.charAt(i), 10);
} else {
const numFrom = i;
while (!isNaN(parseInt(strippedMathString.charAt(i), 10))) {
@@ -141,14 +148,27 @@ export function parseDateMath(mathString: string, time: any, roundUp?: boolean):
}
unit = strippedMathString.charAt(i++);
if (unit === 'f') {
unit = strippedMathString.charAt(i++);
isFiscal = true;
}
if (!includes(units, unit)) {
return undefined;
} else {
if (type === 0) {
if (roundUp) {
dateTime.endOf(unit);
if (isFiscal) {
roundToFiscal(fiscalYearStartMonth, dateTime, unit, roundUp);
} else {
dateTime.endOf(unit);
}
} else {
dateTime.startOf(unit);
if (isFiscal) {
roundToFiscal(fiscalYearStartMonth, dateTime, unit, roundUp);
} else {
dateTime.startOf(unit);
}
}
} else if (type === 1) {
dateTime.add(num, unit);
@@ -159,3 +179,24 @@ export function parseDateMath(mathString: string, time: any, roundUp?: boolean):
}
return dateTime;
}
export function roundToFiscal(fyStartMonth: number, dateTime: any, unit: string, roundUp: boolean | undefined) {
switch (unit) {
case 'y':
if (roundUp) {
roundToFiscal(fyStartMonth, dateTime, unit, false).add(11, 'M').endOf('M');
} else {
dateTime.subtract((dateTime.month() - fyStartMonth + 12) % 12, 'M').startOf('M');
}
return dateTime;
case 'Q':
if (roundUp) {
roundToFiscal(fyStartMonth, dateTime, unit, false).add(2, 'M').endOf('M');
} else {
dateTime.subtract((dateTime.month() - fyStartMonth + 3) % 3, 'M').startOf('M');
}
return dateTime;
default:
return undefined;
}
}
+2 -1
View File
@@ -19,6 +19,7 @@ export interface DateTimeOptionsWhenParsing extends DateTimeOptions {
* the returned DateTime value will be 06:00:00.
*/
roundUp?: boolean;
fiscalYearStartMonth?: number;
}
type DateTimeParser<T extends DateTimeOptions = DateTimeOptions> = (value: DateTimeInput, options?: T) => DateTime;
@@ -56,7 +57,7 @@ const parseString = (value: string, options?: DateTimeOptionsWhenParsing): DateT
return moment() as DateTime;
}
const parsed = parse(value, options?.roundUp, options?.timeZone);
const parsed = parse(value, options?.roundUp, options?.timeZone, options?.fiscalYearStartMonth);
return parsed || (moment() as DateTime);
}
@@ -40,7 +40,9 @@ const rangeOptions: TimeOption[] = [
},
{ from: 'now-1w/w', to: 'now-1w/w', display: 'Previous week' },
{ from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month' },
{ from: 'now-1Q/fQ', to: 'now-1Q/fQ', display: 'Previous fiscal quarter' },
{ from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year' },
{ from: 'now-1y/fy', to: 'now-1y/fy', display: 'Previous fiscal year' },
{ from: 'now-5m', to: 'now', display: 'Last 5 minutes' },
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes' },
@@ -58,6 +60,10 @@ const rangeOptions: TimeOption[] = [
{ from: 'now-1y', to: 'now', display: 'Last 1 year' },
{ from: 'now-2y', to: 'now', display: 'Last 2 years' },
{ from: 'now-5y', to: 'now', display: 'Last 5 years' },
{ from: 'now/fQ', to: 'now', display: 'This fiscal quarter so far' },
{ from: 'now/fQ', to: 'now/fQ', display: 'This fiscal quarter' },
{ from: 'now/fy', to: 'now', display: 'This fiscal year so far' },
{ from: 'now/fy', to: 'now/fy', display: 'This fiscal year' },
];
const hiddenRangeOptions: TimeOption[] = [
@@ -192,9 +198,9 @@ export const describeTimeRangeAbbreviation = (range: TimeRange, timeZone?: TimeZ
return parsed ? timeZoneAbbrevation(parsed, { timeZone }) : '';
};
export const convertRawToRange = (raw: RawTimeRange, timeZone?: TimeZone): TimeRange => {
const from = dateTimeParse(raw.from, { roundUp: false, timeZone });
const to = dateTimeParse(raw.to, { roundUp: true, timeZone });
export const convertRawToRange = (raw: RawTimeRange, timeZone?: TimeZone, fiscalYearStartMonth?: number): TimeRange => {
const from = dateTimeParse(raw.from, { roundUp: false, timeZone, fiscalYearStartMonth });
const to = dateTimeParse(raw.to, { roundUp: true, timeZone, fiscalYearStartMonth });
if (dateMath.isMathString(raw.from) || dateMath.isMathString(raw.to)) {
return { from, to, raw };
@@ -210,6 +216,15 @@ function isRelativeTime(v: DateTime | string) {
return false;
}
export function isFiscal(timeRange: TimeRange) {
if (typeof timeRange.raw.from === 'string' && timeRange.raw.from.indexOf('f') > 0) {
return true;
} else if (typeof timeRange.raw.to === 'string' && timeRange.raw.to.indexOf('f') > 0) {
return true;
}
return false;
}
export function isRelativeTimeRange(raw: RawTimeRange): boolean {
return isRelativeTime(raw.from) || isRelativeTime(raw.to);
}