mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-12-03 02:54:06 +01:00
5496 : fixed UI crash from excessive tick generation with invalid dates/intervals
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
This commit is contained in:
@@ -300,6 +300,16 @@ const getStartDate = function (prevTime, dateFormat, str) {
|
|||||||
} else {
|
} else {
|
||||||
log.debug('Invalid date:' + str);
|
log.debug('Invalid date:' + str);
|
||||||
log.debug('With date format:' + dateFormat.trim());
|
log.debug('With date format:' + dateFormat.trim());
|
||||||
|
|
||||||
|
// Only allow fallback for formats that are simple timestamps (x, X)
|
||||||
|
// which represent Unix timestamps. For all other formats, if dayjs
|
||||||
|
// strict parsing fails - throws an error.
|
||||||
|
const isTimestampFormat = dateFormat.trim() === 'x' || dateFormat.trim() === 'X';
|
||||||
|
|
||||||
|
if (!isTimestampFormat) {
|
||||||
|
throw new Error(`Invalid date: "${str}" does not match format "${dateFormat.trim()}".`);
|
||||||
|
}
|
||||||
|
|
||||||
const d = new Date(str);
|
const d = new Date(str);
|
||||||
if (
|
if (
|
||||||
d === undefined ||
|
d === undefined ||
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ const getMaxIntersections = (tasks, orderOffset) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let w;
|
let w;
|
||||||
|
const MAX_TICK_COUNT = 10000;
|
||||||
export const draw = function (text, id, version, diagObj) {
|
export const draw = function (text, id, version, diagObj) {
|
||||||
const conf = getConfig().gantt;
|
const conf = getConfig().gantt;
|
||||||
|
|
||||||
@@ -602,6 +603,30 @@ export const draw = function (text, id, version, diagObj) {
|
|||||||
.attr('class', 'exclude-range');
|
.attr('class', 'exclude-range');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the estimated number of ticks based on the time domain and tick interval.
|
||||||
|
* Returns the count or Infinity if there would be too many ticks.
|
||||||
|
* @param {Date} minTime - The minimum time in the domain
|
||||||
|
* @param {Date} maxTime - The maximum time in the domain
|
||||||
|
* @param {number} every - The interval count (e.g., 1 for "1second")
|
||||||
|
* @param {string} interval - The interval unit (e.g., "second", "day")
|
||||||
|
* @returns {number} The estimated number of ticks
|
||||||
|
*/
|
||||||
|
function getEstimatedTickCount(minTime, maxTime, every, interval) {
|
||||||
|
const timeDiffMs = maxTime - minTime;
|
||||||
|
const msPerUnit = {
|
||||||
|
millisecond: 1,
|
||||||
|
second: 1000,
|
||||||
|
minute: 60 * 1000,
|
||||||
|
hour: 60 * 60 * 1000,
|
||||||
|
day: 24 * 60 * 60 * 1000,
|
||||||
|
week: 7 * 24 * 60 * 60 * 1000,
|
||||||
|
month: 30 * 24 * 60 * 60 * 1000, // Approximate
|
||||||
|
};
|
||||||
|
const intervalMs = (msPerUnit[interval] || msPerUnit.day) * every;
|
||||||
|
return Math.ceil(timeDiffMs / intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param theSidePad
|
* @param theSidePad
|
||||||
* @param theTopPad
|
* @param theTopPad
|
||||||
@@ -630,32 +655,47 @@ export const draw = function (text, id, version, diagObj) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (resultTickInterval !== null) {
|
if (resultTickInterval !== null) {
|
||||||
const every = resultTickInterval[1];
|
const every = parseInt(resultTickInterval[1], 10);
|
||||||
const interval = resultTickInterval[2];
|
const interval = resultTickInterval[2];
|
||||||
const weekday = diagObj.db.getWeekday() || conf.weekday;
|
const weekday = diagObj.db.getWeekday() || conf.weekday;
|
||||||
|
|
||||||
switch (interval) {
|
// Get the time domain to check tick count
|
||||||
case 'millisecond':
|
const domain = timeScale.domain();
|
||||||
bottomXAxis.ticks(timeMillisecond.every(every));
|
const minTime = domain[0];
|
||||||
break;
|
const maxTime = domain[1];
|
||||||
case 'second':
|
const estimatedTicks = getEstimatedTickCount(minTime, maxTime, every, interval);
|
||||||
bottomXAxis.ticks(timeSecond.every(every));
|
|
||||||
break;
|
if (estimatedTicks > MAX_TICK_COUNT) {
|
||||||
case 'minute':
|
log.warn(
|
||||||
bottomXAxis.ticks(timeMinute.every(every));
|
`The tick interval "${every}${interval}" would generate ${estimatedTicks} ticks, ` +
|
||||||
break;
|
`which exceeds the maximum allowed (${MAX_TICK_COUNT}). ` +
|
||||||
case 'hour':
|
`This may indicate an invalid date or time range. Skipping custom tick interval.`
|
||||||
bottomXAxis.ticks(timeHour.every(every));
|
);
|
||||||
break;
|
// D3 using its default automatic tick generation
|
||||||
case 'day':
|
} else {
|
||||||
bottomXAxis.ticks(timeDay.every(every));
|
switch (interval) {
|
||||||
break;
|
case 'millisecond':
|
||||||
case 'week':
|
bottomXAxis.ticks(timeMillisecond.every(every));
|
||||||
bottomXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every));
|
break;
|
||||||
break;
|
case 'second':
|
||||||
case 'month':
|
bottomXAxis.ticks(timeSecond.every(every));
|
||||||
bottomXAxis.ticks(timeMonth.every(every));
|
break;
|
||||||
break;
|
case 'minute':
|
||||||
|
bottomXAxis.ticks(timeMinute.every(every));
|
||||||
|
break;
|
||||||
|
case 'hour':
|
||||||
|
bottomXAxis.ticks(timeHour.every(every));
|
||||||
|
break;
|
||||||
|
case 'day':
|
||||||
|
bottomXAxis.ticks(timeDay.every(every));
|
||||||
|
break;
|
||||||
|
case 'week':
|
||||||
|
bottomXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every));
|
||||||
|
break;
|
||||||
|
case 'month':
|
||||||
|
bottomXAxis.ticks(timeMonth.every(every));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,32 +717,41 @@ export const draw = function (text, id, version, diagObj) {
|
|||||||
.tickFormat(timeFormat(axisFormat));
|
.tickFormat(timeFormat(axisFormat));
|
||||||
|
|
||||||
if (resultTickInterval !== null) {
|
if (resultTickInterval !== null) {
|
||||||
const every = resultTickInterval[1];
|
const every = parseInt(resultTickInterval[1], 10);
|
||||||
const interval = resultTickInterval[2];
|
const interval = resultTickInterval[2];
|
||||||
const weekday = diagObj.db.getWeekday() || conf.weekday;
|
const weekday = diagObj.db.getWeekday() || conf.weekday;
|
||||||
|
|
||||||
switch (interval) {
|
// Get the time domain to check tick count
|
||||||
case 'millisecond':
|
const domain = timeScale.domain();
|
||||||
topXAxis.ticks(timeMillisecond.every(every));
|
const minTime = domain[0];
|
||||||
break;
|
const maxTime = domain[1];
|
||||||
case 'second':
|
const estimatedTicks = getEstimatedTickCount(minTime, maxTime, every, interval);
|
||||||
topXAxis.ticks(timeSecond.every(every));
|
|
||||||
break;
|
// Only apply custom ticks if the count is reasonable
|
||||||
case 'minute':
|
if (estimatedTicks <= MAX_TICK_COUNT) {
|
||||||
topXAxis.ticks(timeMinute.every(every));
|
switch (interval) {
|
||||||
break;
|
case 'millisecond':
|
||||||
case 'hour':
|
topXAxis.ticks(timeMillisecond.every(every));
|
||||||
topXAxis.ticks(timeHour.every(every));
|
break;
|
||||||
break;
|
case 'second':
|
||||||
case 'day':
|
topXAxis.ticks(timeSecond.every(every));
|
||||||
topXAxis.ticks(timeDay.every(every));
|
break;
|
||||||
break;
|
case 'minute':
|
||||||
case 'week':
|
topXAxis.ticks(timeMinute.every(every));
|
||||||
topXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every));
|
break;
|
||||||
break;
|
case 'hour':
|
||||||
case 'month':
|
topXAxis.ticks(timeHour.every(every));
|
||||||
topXAxis.ticks(timeMonth.every(every));
|
break;
|
||||||
break;
|
case 'day':
|
||||||
|
topXAxis.ticks(timeDay.every(every));
|
||||||
|
break;
|
||||||
|
case 'week':
|
||||||
|
topXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every));
|
||||||
|
break;
|
||||||
|
case 'month':
|
||||||
|
topXAxis.ticks(timeMonth.every(every));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user