使用:
console.log('script.js loaded');
// Fetch test execution data from the JSON file
fetch('test-execution-data.json')
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch test execution data');
}
return response.json();
})
.then((testExecutionData) => {
// Debug: Log the fetched data
console.log('Fetched test execution data:', testExecutionData);
// Group tests by worker
const workers = {};
testExecutionData.forEach((test) => {
const parallelIndex = test.parallelIndex.toString();
if (!workers[parallelIndex]) {
workers[parallelIndex] = [];
}
workers[parallelIndex].push({
label: test.title,
startTime: new Date(test.startTime),
endTime: new Date(test.endTime),
status: test.status[0], // Get the first status
attempt: test.attempt,
totalAttempts: test.totalAttempts
});
});
// Calculate relative timestamps (in minutes from the earliest test)
const allTimes = testExecutionData.flatMap((test) => [
new Date(test.startTime).getTime(),
new Date(test.endTime).getTime()
]);
const minTimestamp = Math.min(...allTimes);
const maxTimestamp = Math.max(...allTimes);
const timeRangeInMinutes = (maxTimestamp - minTimestamp) / (1000 * 60);
// Create datasets
const datasets = Object.keys(workers).map((worker) => ({
label: `Worker ${worker}`,
data: workers[worker].map((test) => ({
x: [
(test.startTime.getTime() - minTimestamp) / (1000 * 60),
(test.endTime.getTime() - minTimestamp) / (1000 * 60)
],
y: worker,
label: test.label,
start: test.startTime.toLocaleTimeString(),
end: test.endTime.toLocaleTimeString(),
status: test.status,
attempt: test.attempt,
totalAttempts: test.totalAttempts
})),
backgroundColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 0.5)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 0.5)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 0.5)'; // Red for final failure
}
},
borderColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 1)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 1)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 1)'; // Red for final failure
}
},
borderWidth: 1, // Increased from 1 to 3
borderSkipped: false, // This ensures borders are drawn on all sides
borderRadius: 2, // Optional: slightly rounded corners
barThickness: 5
}));
// Render chart
const ctx = document.getElementById('timelineChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: { datasets },
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: {
type: 'linear',
position: 'bottom',
min: 0,
max: Math.ceil(timeRangeInMinutes),
title: {
display: true,
text: 'Time (minutes from start)'
},
ticks: {
stepSize: Math.max(1, Math.ceil(timeRangeInMinutes / 15)), // At most 15 ticks
callback: (value) => {
const date = new Date(value * 60 * 1000 + minTimestamp);
return date.toLocaleTimeString();
}
}
},
y: {
// type: 'category', // Use category scale for discrete worker indices
// labels: Object.keys(workers), // Use worker indices as labels
title: {
display: true,
text: 'Worker'
}
// offset: true // Add some padding to center the bars
}
},
plugins: {
tooltip: {
callbacks: {
title: (context) => context[0].raw.label,
label: (context) => {
const { start, end, status } = context.raw;
return [`Status: ${status}`, `Start: ${start}`, `End: ${end}`];
}
}
},
legend: {
display: false
}
}
}
});
})
.catch((error) => {
// Debug: Log any errors
console.error('Error loading test execution data:', error);
});
样本输入数据:
[
{
"title": "@workflow @tagAdded @AUTOMATIONS_WORKFLOW_SMOKE_TRIGGER DND enabled for all<> All Filter - Verify that when DND enabled trigger should fire",
"parallelIndex": 3,
"startTime": "2025-02-04T10:10:21.503Z",
"endTime": "2025-02-04T10:10:33.748Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
},
{
"title": "@workflow @tagAdded @whatsapp DND enabled specific for Whatsapp - Verify that when DND enabled for Whatsapp trigger should fire",
"parallelIndex": 5,
"startTime": "2025-02-04T10:10:21.546Z",
"endTime": "2025-02-04T10:10:27.457Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
}
]
绘制图像
我不知道这是为什么会发生的,但是我的理论是因为设置grouped
y
let testExecutionData = [
{
"title": "@workflow @tagAdded @AUTOMATIONS_WORKFLOW_SMOKE_TRIGGER DND enabled for all<> All Filter - Verify that when DND enabled trigger should fire",
"parallelIndex": 3,
"startTime": "2025-02-04T10:10:21.503Z",
"endTime": "2025-02-04T10:10:33.748Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
},
{
"title": "@workflow @tagAdded @whatsapp DND enabled specific for Whatsapp - Verify that when DND enabled for Whatsapp trigger should fire",
"parallelIndex": 5,
"startTime": "2025-02-04T10:10:21.546Z",
"endTime": "2025-02-04T10:10:27.457Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
}
];
// Group tests by worker
const workers = {};
testExecutionData.forEach((test) => {
const parallelIndex = test.parallelIndex.toString();
if (!workers[parallelIndex]) {
workers[parallelIndex] = [];
}
workers[parallelIndex].push({
label: test.title,
startTime: new Date(test.startTime),
endTime: new Date(test.endTime),
status: test.status[0], // Get the first status
attempt: test.attempt,
totalAttempts: test.totalAttempts
});
});
// Calculate relative timestamps (in minutes from the earliest test)
const allTimes = testExecutionData.flatMap((test) => [
new Date(test.startTime).getTime(),
new Date(test.endTime).getTime()
]);
const minTimestamp = Math.min(...allTimes);
const maxTimestamp = Math.max(...allTimes);
const timeRangeInMinutes = (maxTimestamp - minTimestamp) / (1000 * 60);
// Create datasets
const datasets = Object.keys(workers).map((worker) => ({
label: `Worker ${worker}`,
data: workers[worker].map((test) => ({
x: [
(test.startTime.getTime() - minTimestamp) / (1000 * 60),
(test.endTime.getTime() - minTimestamp) / (1000 * 60)
],
y: worker,
label: test.label,
start: test.startTime.toLocaleTimeString(),
end: test.endTime.toLocaleTimeString(),
status: test.status,
attempt: test.attempt,
totalAttempts: test.totalAttempts
})),
backgroundColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 0.5)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 0.5)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 0.5)'; // Red for final failure
}
},
borderColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 1)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 1)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 1)'; // Red for final failure
}
},
borderWidth: 3,
borderSkipped: false,
borderRadius: 2,
barThickness: 50
}));
// Render chart
const ctx = document.getElementById('timelineChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {datasets}
,
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: {
type: 'linear',
position: 'bottom',
min: 0,
max: Math.ceil(timeRangeInMinutes),
title: {
display: true,
text: 'Time (minutes from start)'
},
ticks: {
stepSize: Math.max(1, Math.ceil(timeRangeInMinutes / 15)), // At most 15 ticks
callback: (value) => {
const date = new Date(value * 60 * 1000 + minTimestamp);
return date.toLocaleTimeString();
}
}
},
y: {
// type: 'category', // Use category scale for discrete worker indices
// labels: Object.keys(workers), // Use worker indices as labels
title: {
display: true,
text: 'Worker'
},
// offset: true // Add some padding to center the bars
}
},
plugins: {
tooltip: {
callbacks: {
title: (context) => context[0].raw.label,
label: (context) => {
const { start, end, status } = context.raw;
return [`Status: ${status}`, `Start: ${start}`, `End: ${end}`];
}
}
},
legend: {
display: false
}
},
grouped: false
}
});
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas style="display: block; box-sizing: border-box;" height="200" width="500" id="timelineChart"></canvas>
,现在是数据中心的添加option
grouped
如果这不是您要搜索的答案,并且必须有可能它们俩都存在于彼此的行,但如果不存在的中心或所提供答案的其他问题,请发表评论或更改问题。