feat(dates): implement 15-minute interval rounding
Implement time interval rounding to support standardized data queries by rounding dates to the next 15-minute interval. This change: - Adds roundToNextInterval method to Search class - Updates Period.js to round dates to 15-minute intervals - Updates date range calculations to preserve proper time spans - Upgrades Sentry browser dependency from v5 to v8.55.0
This commit is contained in:
parent
83897cb89c
commit
c499114c78
3 changed files with 227 additions and 142 deletions
|
|
@ -143,7 +143,7 @@ export default class Search {
|
|||
return new FilterItem(filter).toJson();
|
||||
});
|
||||
|
||||
const { startDate, endDate } = this.getDateRange(js.rangeValue, js.startDate, js.endDate);
|
||||
const { startDate, endDate } = this.getDateRange(js.rangeValue, js.startDate, js.endDate, 15);
|
||||
js.startDate = startDate;
|
||||
js.endDate = endDate;
|
||||
|
||||
|
|
@ -152,7 +152,38 @@ export default class Search {
|
|||
return js;
|
||||
}
|
||||
|
||||
private getDateRange(rangeName: string, customStartDate: number, customEndDate: number): {
|
||||
private roundToNextInterval(timestamp: number, intervalMinutes: number): number {
|
||||
if (intervalMinutes <= 0) {
|
||||
return timestamp; // No rounding if interval is invalid
|
||||
}
|
||||
|
||||
const date = new Date(timestamp);
|
||||
const minutes = date.getMinutes();
|
||||
const seconds = date.getSeconds();
|
||||
const milliseconds = date.getMilliseconds();
|
||||
|
||||
// Calculate minutes to add to reach next interval slot
|
||||
const minutesToAdd = (intervalMinutes - (minutes % intervalMinutes)) % intervalMinutes;
|
||||
|
||||
// If exactly at interval mark but has seconds/milliseconds, round up to next slot
|
||||
const shouldAddExtra = (minutesToAdd === 0 && (seconds > 0 || milliseconds > 0));
|
||||
const adjustedMinutesToAdd = shouldAddExtra ? intervalMinutes : minutesToAdd;
|
||||
|
||||
// Create a new date with added minutes and zeroed seconds/milliseconds
|
||||
const roundedDate = new Date(timestamp);
|
||||
roundedDate.setMinutes(minutes + adjustedMinutesToAdd);
|
||||
roundedDate.setSeconds(0);
|
||||
roundedDate.setMilliseconds(0);
|
||||
|
||||
return roundedDate.getTime();
|
||||
}
|
||||
|
||||
private getDateRange(
|
||||
rangeName: string,
|
||||
customStartDate: number,
|
||||
customEndDate: number,
|
||||
roundingOption: number | 'none' = 'none'
|
||||
): {
|
||||
startDate: number;
|
||||
endDate: number
|
||||
} {
|
||||
|
|
@ -178,6 +209,29 @@ export default class Search {
|
|||
startDate = endDate - 24 * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
// Apply rounding if specified and it's a number
|
||||
if (roundingOption !== 'none' && typeof roundingOption === 'number') {
|
||||
const intervalMinutes = roundingOption;
|
||||
|
||||
// For CUSTOM_RANGE, do not apply rounding
|
||||
if (rangeName !== CUSTOM_RANGE) {
|
||||
endDate = this.roundToNextInterval(endDate, intervalMinutes);
|
||||
|
||||
// Recalculate the start date based on the rounded end date to maintain the correct time span
|
||||
switch (rangeName) {
|
||||
case LAST_7_DAYS:
|
||||
startDate = endDate - 7 * 24 * 60 * 60 * 1000;
|
||||
break;
|
||||
case LAST_30_DAYS:
|
||||
startDate = endDate - 30 * 24 * 60 * 60 * 1000;
|
||||
break;
|
||||
case LAST_24_HOURS:
|
||||
default:
|
||||
startDate = endDate - 24 * 60 * 60 * 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate
|
||||
|
|
|
|||
|
|
@ -1,109 +1,146 @@
|
|||
import { DateTime, Interval, Settings } from "luxon";
|
||||
import Record from "Types/Record";
|
||||
import { DateTime, Interval, Settings } from 'luxon';
|
||||
import Record from 'Types/Record';
|
||||
|
||||
export const LAST_30_MINUTES = "LAST_30_MINUTES";
|
||||
export const TODAY = "TODAY";
|
||||
export const LAST_24_HOURS = "LAST_24_HOURS";
|
||||
export const YESTERDAY = "YESTERDAY";
|
||||
export const LAST_7_DAYS = "LAST_7_DAYS";
|
||||
export const LAST_30_DAYS = "LAST_30_DAYS";
|
||||
export const THIS_MONTH = "THIS_MONTH";
|
||||
export const LAST_MONTH = "LAST_MONTH";
|
||||
export const THIS_YEAR = "THIS_YEAR";
|
||||
export const CUSTOM_RANGE = "CUSTOM_RANGE";
|
||||
export const LAST_30_MINUTES = 'LAST_30_MINUTES';
|
||||
export const TODAY = 'TODAY';
|
||||
export const LAST_24_HOURS = 'LAST_24_HOURS';
|
||||
export const YESTERDAY = 'YESTERDAY';
|
||||
export const LAST_7_DAYS = 'LAST_7_DAYS';
|
||||
export const LAST_30_DAYS = 'LAST_30_DAYS';
|
||||
export const THIS_MONTH = 'THIS_MONTH';
|
||||
export const LAST_MONTH = 'LAST_MONTH';
|
||||
export const THIS_YEAR = 'THIS_YEAR';
|
||||
export const CUSTOM_RANGE = 'CUSTOM_RANGE';
|
||||
|
||||
function roundToNext15Minutes(dateTime) {
|
||||
const minutes = dateTime.minute;
|
||||
const secondsAndMillis = dateTime.second + (dateTime.millisecond / 1000);
|
||||
|
||||
// Calculate minutes to add to reach next 15-minute slot
|
||||
const minutesToAdd = (15 - (minutes % 15)) % 15;
|
||||
|
||||
// If exactly at 15-minute mark but has seconds/milliseconds, round up to next slot
|
||||
const adjustedMinutesToAdd = (minutesToAdd === 0 && secondsAndMillis > 0) ? 15 : minutesToAdd;
|
||||
|
||||
return dateTime
|
||||
.plus({ minutes: adjustedMinutesToAdd })
|
||||
.set({ second: 0, millisecond: 0 });
|
||||
}
|
||||
|
||||
// Helper function to get rounded now
|
||||
function getRoundedNow(offset) {
|
||||
const now = DateTime.now().setZone(offset);
|
||||
return roundToNext15Minutes(now);
|
||||
}
|
||||
|
||||
function getRange(rangeName, offset) {
|
||||
const now = DateTime.now().setZone(offset);
|
||||
switch (rangeName) {
|
||||
case TODAY:
|
||||
return Interval.fromDateTimes(now.startOf("day"), now.endOf("day"));
|
||||
case YESTERDAY:
|
||||
const yesterday = now.minus({ days: 1 });
|
||||
return Interval.fromDateTimes(
|
||||
yesterday.startOf("day"),
|
||||
yesterday.endOf("day")
|
||||
);
|
||||
case LAST_24_HOURS:
|
||||
return Interval.fromDateTimes(now.minus({ hours: 24 }), now);
|
||||
case LAST_30_MINUTES:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ minutes: 30 }).startOf("minute"),
|
||||
now.startOf("minute")
|
||||
);
|
||||
case LAST_7_DAYS:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ days: 7 }).startOf("day"),
|
||||
now.endOf("day")
|
||||
);
|
||||
case LAST_30_DAYS:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ days: 30 }).startOf("day"),
|
||||
now.endOf("day")
|
||||
);
|
||||
case THIS_MONTH:
|
||||
return Interval.fromDateTimes(now.startOf("month"), now.endOf("month"));
|
||||
case LAST_MONTH:
|
||||
const lastMonth = now.minus({ months: 1 });
|
||||
return Interval.fromDateTimes(lastMonth.startOf("month"), lastMonth.endOf("month"));
|
||||
case THIS_YEAR:
|
||||
return Interval.fromDateTimes(now.startOf("year"), now.endOf("year"));
|
||||
default:
|
||||
return Interval.fromDateTimes(now, now);
|
||||
}
|
||||
const now = getRoundedNow(offset);
|
||||
|
||||
switch (rangeName) {
|
||||
case TODAY:
|
||||
return Interval.fromDateTimes(now.startOf('day'), now.endOf('day'));
|
||||
case YESTERDAY:
|
||||
const yesterday = now.minus({ days: 1 });
|
||||
return Interval.fromDateTimes(
|
||||
yesterday.startOf('day'),
|
||||
yesterday.endOf('day')
|
||||
);
|
||||
case LAST_24_HOURS:
|
||||
return Interval.fromDateTimes(now.minus({ hours: 24 }), now);
|
||||
case LAST_30_MINUTES:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ minutes: 30 }),
|
||||
now
|
||||
);
|
||||
case LAST_7_DAYS:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ days: 7 }).startOf('day'),
|
||||
now.endOf('day')
|
||||
);
|
||||
case LAST_30_DAYS:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ days: 30 }).startOf('day'),
|
||||
now.endOf('day')
|
||||
);
|
||||
case THIS_MONTH:
|
||||
return Interval.fromDateTimes(now.startOf('month'), now.endOf('month'));
|
||||
case LAST_MONTH:
|
||||
const lastMonth = now.minus({ months: 1 });
|
||||
return Interval.fromDateTimes(lastMonth.startOf('month'), lastMonth.endOf('month'));
|
||||
case THIS_YEAR:
|
||||
return Interval.fromDateTimes(now.startOf('year'), now.endOf('year'));
|
||||
default:
|
||||
return Interval.fromDateTimes(now, now);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current rounded time for default Record value
|
||||
const defaultNow = getRoundedNow();
|
||||
|
||||
export default Record(
|
||||
{
|
||||
start: 0,
|
||||
end: 0,
|
||||
rangeName: CUSTOM_RANGE,
|
||||
range: Interval.fromDateTimes(DateTime.now(), DateTime.now()),
|
||||
start: 0,
|
||||
end: 0,
|
||||
rangeName: CUSTOM_RANGE,
|
||||
range: Interval.fromDateTimes(defaultNow, defaultNow)
|
||||
},
|
||||
{
|
||||
fromJS: (period) => {
|
||||
const offset = period.timezoneOffset || DateTime.now().offset;
|
||||
if (!period.rangeName || period.rangeName === CUSTOM_RANGE) {
|
||||
const isLuxon = DateTime.isDateTime(period.start);
|
||||
const start = isLuxon
|
||||
? period.start : DateTime.fromMillis(period.start || 0, { zone: Settings.defaultZone });
|
||||
const end = isLuxon
|
||||
? period.end : DateTime.fromMillis(period.end || 0, { zone: Settings.defaultZone });
|
||||
const range = Interval.fromDateTimes(start, end);
|
||||
return {
|
||||
...period,
|
||||
range,
|
||||
start: range.start.toMillis(),
|
||||
end: range.end.toMillis(),
|
||||
};
|
||||
}
|
||||
const range = getRange(period.rangeName, offset);
|
||||
return {
|
||||
...period,
|
||||
range,
|
||||
start: range.start.toMillis(),
|
||||
end: range.end.toMillis(),
|
||||
};
|
||||
fromJS: (period) => {
|
||||
const offset = period.timezoneOffset || DateTime.now().offset;
|
||||
|
||||
if (!period.rangeName || period.rangeName === CUSTOM_RANGE) {
|
||||
const isLuxon = DateTime.isDateTime(period.start);
|
||||
let start, end;
|
||||
|
||||
if (isLuxon) {
|
||||
start = roundToNext15Minutes(period.start);
|
||||
end = roundToNext15Minutes(period.end);
|
||||
} else {
|
||||
start = roundToNext15Minutes(
|
||||
DateTime.fromMillis(period.start || 0, { zone: Settings.defaultZone })
|
||||
);
|
||||
end = roundToNext15Minutes(
|
||||
DateTime.fromMillis(period.end || 0, { zone: Settings.defaultZone })
|
||||
);
|
||||
}
|
||||
|
||||
const range = Interval.fromDateTimes(start, end);
|
||||
return {
|
||||
...period,
|
||||
range,
|
||||
start: range.start.toMillis(),
|
||||
end: range.end.toMillis()
|
||||
};
|
||||
}
|
||||
|
||||
const range = getRange(period.rangeName, offset);
|
||||
return {
|
||||
...period,
|
||||
range,
|
||||
start: range.start.toMillis(),
|
||||
end: range.end.toMillis()
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toJSON() {
|
||||
return {
|
||||
startDate: this.start,
|
||||
endDate: this.end,
|
||||
rangeName: this.rangeName,
|
||||
rangeValue: this.rangeName
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toJSON() {
|
||||
return {
|
||||
startDate: this.start,
|
||||
endDate: this.end,
|
||||
rangeName: this.rangeName,
|
||||
rangeValue: this.rangeName,
|
||||
};
|
||||
},
|
||||
toTimestamps() {
|
||||
return {
|
||||
startTimestamp: this.start,
|
||||
endTimestamp: this.end,
|
||||
};
|
||||
},
|
||||
rangeFormatted(format = "MMM dd yyyy, HH:mm", tz) {
|
||||
const start = this.range.start.setZone(tz);
|
||||
const end = this.range.end.setZone(tz);
|
||||
return `${start.toFormat(format)} - ${end.toFormat(format)}`;
|
||||
},
|
||||
toTimestamps() {
|
||||
return {
|
||||
startTimestamp: this.start,
|
||||
endTimestamp: this.end
|
||||
};
|
||||
},
|
||||
rangeFormatted(format = 'MMM dd yyyy, HH:mm', tz) {
|
||||
const start = this.range.start.setZone(tz);
|
||||
const end = this.range.end.setZone(tz);
|
||||
return `${start.toFormat(format)} - ${end.toFormat(format)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2952,67 +2952,61 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/browser@npm:^5.21.1":
|
||||
version: 5.30.0
|
||||
resolution: "@sentry/browser@npm:5.30.0"
|
||||
"@sentry-internal/browser-utils@npm:8.55.0":
|
||||
version: 8.55.0
|
||||
resolution: "@sentry-internal/browser-utils@npm:8.55.0"
|
||||
dependencies:
|
||||
"@sentry/core": "npm:5.30.0"
|
||||
"@sentry/types": "npm:5.30.0"
|
||||
"@sentry/utils": "npm:5.30.0"
|
||||
tslib: "npm:^1.9.3"
|
||||
checksum: 10c1/4787cc3ea90600b36b548a8403afb30f13e1e562dd426871879d824536c16005d0734b7406498f1a6dd4fa7e0a49808e17a1c2c24750430ba7f86f909a9eb95a
|
||||
"@sentry/core": "npm:8.55.0"
|
||||
checksum: 10c1/67fdc5ec9c8bc6c8eeda4598332a7937a8c7d6cc1cadb05a886323f3d13c25def7b9258ad4b834919dea5d612010de8900f5cf738e9a577a711c839f285557d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/core@npm:5.30.0":
|
||||
version: 5.30.0
|
||||
resolution: "@sentry/core@npm:5.30.0"
|
||||
"@sentry-internal/feedback@npm:8.55.0":
|
||||
version: 8.55.0
|
||||
resolution: "@sentry-internal/feedback@npm:8.55.0"
|
||||
dependencies:
|
||||
"@sentry/hub": "npm:5.30.0"
|
||||
"@sentry/minimal": "npm:5.30.0"
|
||||
"@sentry/types": "npm:5.30.0"
|
||||
"@sentry/utils": "npm:5.30.0"
|
||||
tslib: "npm:^1.9.3"
|
||||
checksum: 10c1/5c6dcdccc48a9d6957af7745226eacd3d4926574593e852ccbad0fbaa71355879b9c4707c194e3d9b1ef389d98171a3d85d2c75636a5c6d1cc3c9950cd06334a
|
||||
"@sentry/core": "npm:8.55.0"
|
||||
checksum: 10c1/3f6fd3b8c2305b457a5c729c92b2a2335200e5ee0d5a210b513246e00ecda6d2a28940871ed88eee7f7bd8465571388698a7b789c8e0f3d5832ff3a0b040b514
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/hub@npm:5.30.0":
|
||||
version: 5.30.0
|
||||
resolution: "@sentry/hub@npm:5.30.0"
|
||||
"@sentry-internal/replay-canvas@npm:8.55.0":
|
||||
version: 8.55.0
|
||||
resolution: "@sentry-internal/replay-canvas@npm:8.55.0"
|
||||
dependencies:
|
||||
"@sentry/types": "npm:5.30.0"
|
||||
"@sentry/utils": "npm:5.30.0"
|
||||
tslib: "npm:^1.9.3"
|
||||
checksum: 10c1/28b86742c72427b5831ee3077c377d1f305d2eb080f7dc977e81b8f29e8eb0dfa07f129c1f5cda29bda9238fe50e292ab719119c4c5a5b7ef580a24bcb6356a3
|
||||
"@sentry-internal/replay": "npm:8.55.0"
|
||||
"@sentry/core": "npm:8.55.0"
|
||||
checksum: 10c1/48511881330193d754e01b842e3b2b931641d0954bac8a8f01503ff3d2aedc9f1779049be0a7a56ba35583769f0566381853c7656888c42f9f59224c6520e593
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/minimal@npm:5.30.0":
|
||||
version: 5.30.0
|
||||
resolution: "@sentry/minimal@npm:5.30.0"
|
||||
"@sentry-internal/replay@npm:8.55.0":
|
||||
version: 8.55.0
|
||||
resolution: "@sentry-internal/replay@npm:8.55.0"
|
||||
dependencies:
|
||||
"@sentry/hub": "npm:5.30.0"
|
||||
"@sentry/types": "npm:5.30.0"
|
||||
tslib: "npm:^1.9.3"
|
||||
checksum: 10c1/d28ad14e43d3c5c06783288ace1fcf1474437070f04d1476b04d0288656351d9a6285cc66d346e8d84a3e73cf895944c06fa7c82bad93415831e4449e11f2d89
|
||||
"@sentry-internal/browser-utils": "npm:8.55.0"
|
||||
"@sentry/core": "npm:8.55.0"
|
||||
checksum: 10c1/d60b4261df037b4c82dafc6b25695b2be32f95a45cd25fc43c659d65644325238f7152f6222cd5d4f3f52407c3f5ad67ea30b38fea27c9422536f8aaba6b0048
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/types@npm:5.30.0":
|
||||
version: 5.30.0
|
||||
resolution: "@sentry/types@npm:5.30.0"
|
||||
checksum: 10c1/07fe7f04f6aae13f037761fe56a20e06fa4a768bf024fb81970d3087ab9ab5b45bd85b9081945ef5019d93b7de742918374a0e7b70a992dbb29a5078982ddfd9
|
||||
"@sentry/browser@npm:^8.34.0":
|
||||
version: 8.55.0
|
||||
resolution: "@sentry/browser@npm:8.55.0"
|
||||
dependencies:
|
||||
"@sentry-internal/browser-utils": "npm:8.55.0"
|
||||
"@sentry-internal/feedback": "npm:8.55.0"
|
||||
"@sentry-internal/replay": "npm:8.55.0"
|
||||
"@sentry-internal/replay-canvas": "npm:8.55.0"
|
||||
"@sentry/core": "npm:8.55.0"
|
||||
checksum: 10c1/3baf51a0b401bb63b345df480773d49b713dd557e15baf2c98f089612c9497aca6f2c7849b1c4d6ded6229d1de495e3305a0438145333de26c6ba190d261c039
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/utils@npm:5.30.0":
|
||||
version: 5.30.0
|
||||
resolution: "@sentry/utils@npm:5.30.0"
|
||||
dependencies:
|
||||
"@sentry/types": "npm:5.30.0"
|
||||
tslib: "npm:^1.9.3"
|
||||
checksum: 10c1/311ad0be0e40af9f4ab7be2dfb8a782a779fa56700a0662f49ebcbff0dbbe4ea5dff690ad2c0ed4ecb6a6721a3066186b3c8f677fa302c5b606f86dfaa654de3
|
||||
"@sentry/core@npm:8.55.0":
|
||||
version: 8.55.0
|
||||
resolution: "@sentry/core@npm:8.55.0"
|
||||
checksum: 10c1/fbb71058626214674c4b103160fea859ce1fcc83b26533920b2c4fc7d5169bde178b08cd46dad29fabaf616fa465db4274356c500a37f33888bdb8d10fda3d55
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -11574,7 +11568,7 @@ __metadata:
|
|||
"@jest/globals": "npm:^29.7.0"
|
||||
"@medv/finder": "npm:^3.1.0"
|
||||
"@openreplay/sourcemap-uploader": "npm:^3.0.10"
|
||||
"@sentry/browser": "npm:^5.21.1"
|
||||
"@sentry/browser": "npm:^8.34.0"
|
||||
"@svg-maps/world": "npm:^1.0.1"
|
||||
"@tanstack/react-query": "npm:^5.56.2"
|
||||
"@trivago/prettier-plugin-sort-imports": "npm:^4.3.0"
|
||||
|
|
@ -15762,7 +15756,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^1.8.1, tslib@npm:^1.9.3":
|
||||
"tslib@npm:^1.8.1":
|
||||
version: 1.14.1
|
||||
resolution: "tslib@npm:1.14.1"
|
||||
checksum: 10c1/24ee51ea8fb127ca8ad30a25fdac22c5bb11a2b043781757ddde0daf2e03126e1e13e88ab1748d9c50f786a648b5b038e70782063fd15c3ad07ebec039df8f6f
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue