Files
windmill/frontend/src/lib/components/QueueMetricsDrawerInner.svelte
Ruben Fiszel 310f37a682 feat: migrate to svelte5 + vite6 (#4813)
* runs on svelte 5

* Line component from svelte-chartjs

* Replaced all svelte-chartjs occurrences with custom wrapper

* Fix props mistake

* Fix illegal table structures

* self-closing-tags fix

* aria labels

* Fixed trivial warnings and errors

* @tanstack/svelte-table fix

* upgrade to vite 6

* svelte-kit sync before running svelte-check

* Remove on:clear which is actually on:removeAll and already handled by on:change

* fix worker tags not displaying in Autoscaling

* Try to fix svelte-kit sync not working during CI

* remove warnings

* Fix add flow page crashing

* access worldStore before assignment fix

* fix infinite recursions in App Editor

* Replaced JSON.stringify with proper deepEqual

* component mount api changed (no longer classes)

* fix ci errors

* Fix infinite loops in background runnable panel

* factored effect on deep equal logic in onObjChange

* fix "Add" not working in AgGrid Table

* Replaced legacy component.$set api

* Fix multiselect infinite value reaction

* Fix flow input fields resetting when opening their edit tab

* fix date input resetting when typing year

* Remove !p-0 affecting subgrid dotted borders

* fix missing debounceTemplate causing hundreds of updates

* Fix AgGrid action refreshes and disppearing

* resolve getItems generating random ids every rerun

* fix cannot access items before init

* fix sort lambda arguments being undefined

* Revert "Remove !p-0 affecting subgrid dotted borders"

This reverts commit c62809bb45d682a48376b071680645ed4e1c601b.

* fix input not updating in decision tree editor

* Update frontend/src/lib/components/schema/EditableSchemaWrapper.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Re-added padding affecting subgrid dotted borders (#5479)

* remove !p-0 in preset components

* removed extra padding on accordion tabs subgrid

* Fix non-reactive SchemaForm

* dirty fix for the oneOf bug

* Fix warnings and update svelte-exmarkdown for svelte 5

* fix dnd not working

* don't mount component like objects

---------

Co-authored-by: Diego Imbert <diegoimbert@protonmail.com>
Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-02 21:33:50 +00:00

266 lines
6.1 KiB
Svelte

<script lang="ts">
import 'chartjs-adapter-date-fns'
import { Line } from '$lib/components/chartjs-wrappers/chartJs'
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
CategoryScale,
LinearScale,
PointElement,
LogarithmicScale,
TimeScale,
type ChartData,
type Point
} from 'chart.js'
import { WorkerService } from '$lib/gen'
import Skeleton from './common/skeleton/Skeleton.svelte'
import DarkModeObserver from './DarkModeObserver.svelte'
import Alert from './common/alert/Alert.svelte'
let loading: boolean = true
const colorTuples = [
['#7EB26D', 'rgba(126, 178, 109, 0.2)'],
['#EAB839', 'rgba(234, 184, 57, 0.2)'],
['#6ED0E0', 'rgba(110, 208, 224, 0.2)'],
['#EF843C', 'rgba(239, 132, 60, 0.2)'],
['#E24D42', 'rgba(226, 77, 66, 0.2)'],
['#1F78C1', 'rgba(31, 120, 193, 0.2)'],
['#BA43A9', 'rgba(186, 67, 169, 0.2)'],
['#705DA0', 'rgba(112, 93, 160, 0.2)'],
['#508642', 'rgba(80, 134, 66, 0.2)'],
['#CCA300', 'rgba(204, 163, 0, 0.2)'],
['#447EBC', 'rgba(68, 126, 188, 0.2)'],
['#C15C17', 'rgba(193, 92, 23, 0.2)'],
['#890F02', 'rgba(137, 15, 2, 0.2)'],
['#666666', 'rgba(102, 102, 102, 0.2)'],
['#44AA99', 'rgba(68, 170, 153, 0.2)'],
['#6D8764', 'rgba(109, 135, 100, 0.2)'],
['#555555', 'rgba(85, 85, 85, 0.2)'],
['#B3B3B3', 'rgba(179, 179, 179, 0.2)'],
['#008C9E', 'rgba(0, 140, 158, 0.2)'],
['#6BBA70', 'rgba(107, 186, 112, 0.2)']
]
function getColors(labels: string[]) {
const colors = labels.map((_, i) => colorTuples[i % colorTuples.length])
return Object.fromEntries(colors.map((c, i) => [labels[i], c]))
}
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
PointElement,
CategoryScale,
TimeScale,
LogarithmicScale
)
let countData: ChartData<'line', Point[], undefined> | undefined = undefined
let delayData: ChartData<'line', Point[], undefined> | undefined = undefined
let minDate = new Date()
let noMetrics = false
function fillData(
data: {
created_at: string
value: number
}[],
zero = 0
) {
// fill holes with 0
const sorted: typeof data = []
for (const el of [
...data,
{
created_at: new Date().toISOString(),
value: zero
}
]) {
const last =
sorted.length > 0 ? new Date(sorted[sorted.length - 1].created_at).getTime() : undefined
const currentTs = new Date(el.created_at).getTime()
if (last && currentTs - last > 1000 * 60 * 2) {
const numElements = Math.floor((currentTs - last) / (1000 * 30))
for (let i = 1; i < numElements; i++) {
sorted.push({
created_at: new Date(last + i * (1000 * 30)).toISOString(),
value: zero
})
}
}
sorted.push(el)
}
// remove high frequency data points for similar values
const light: typeof sorted = []
for (const el of sorted) {
const last = light.length > 0 ? light[light.length - 1] : undefined
if (
!last ||
Math.abs((el.value - last.value) / last.value) > 0.1 ||
new Date(el.created_at).getTime() - new Date(last.created_at).getTime() > 1000 * 60 * 15
) {
light.push(el)
}
}
return light
}
async function loadMetrics() {
loading = true
let metrics = await WorkerService.getQueueMetrics()
if (metrics.length == 0) {
noMetrics = true
loading = false
return
}
const labels = metrics
.map((m) => m.id.slice(12))
.filter((v, i, a) => a.indexOf(v) === i)
.sort()
const labelColors = getColors(labels)
countData = {
datasets: metrics
.filter((m) => m.id.startsWith('queue_count_'))
.map((m) => {
const [color, bgColor] = labelColors[m.id.slice(12)]
return {
label: m.id.slice(12),
backgroundColor: bgColor,
borderColor: color,
data: fillData(m.values).map((v) => ({ x: v.created_at as any, y: v.value }))
}
})
}
delayData = {
datasets: metrics
.filter((m) => m.id.startsWith('queue_delay_'))
.map((m) => {
const [color, bgColor] = labelColors[m.id.slice(12)]
return {
label: m.id.slice(12),
borderColor: color,
backgroundColor: bgColor,
data: fillData(m.values, 1).map((v) => ({
x: v.created_at as any,
y: v.value
}))
}
})
}
minDate = new Date(Math.min(...countData.datasets.map((d) => new Date(d.data[0].x).getTime())))
loading = false
}
loadMetrics()
let darkMode = false
$: ChartJS.defaults.color = darkMode ? '#ccc' : '#666'
$: ChartJS.defaults.borderColor = darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'
</script>
<DarkModeObserver bind:darkMode />
{#if loading}
<Skeleton layout={[[20]]} />
{:else if noMetrics}
<p class="text-secondary">No jobs delayed by more than 3 seconds in the last 14 days</p>
{:else}
<div class="flex flex-col gap-4">
{#if countData}
<Line
data={countData}
options={{
animation: false,
plugins: {
title: {
display: true,
text: 'Number of delayed jobs per tag (> 3s)'
}
},
scales: {
x: {
type: 'time',
min: minDate.toISOString(),
max: new Date().toISOString()
},
y: {
title: {
display: true,
text: 'count'
}
}
}
}}
/>
{/if}
{#if delayData}
<Line
data={delayData}
options={{
animation: false,
plugins: {
title: {
display: true,
text: 'Queue delay per tag (> 3s)'
},
tooltip: {
callbacks: {
label: function (context) {
// @ts-ignore
if (context.raw.y === 1) {
return context.dataset.label + ': 0'
} else {
// @ts-ignore
return context.dataset.label + ': ' + context.raw.y
}
}
}
}
},
scales: {
x: {
type: 'time',
min: minDate.toISOString(),
max: new Date().toISOString()
},
y: {
type: 'logarithmic',
title: {
display: true,
text: 'delay (s)'
},
ticks: {
callback: (value, _) => (value === 1 ? '0' : value)
}
}
}
}}
/>
{/if}
<Alert title="Info">
Only tags for jobs that have been delayed by more than 3 seconds in the last 14 days are
included in the graph.
</Alert>
</div>
{/if}