fix: improve flow progress bar

This commit is contained in:
Ruben Fiszel
2023-08-18 14:48:51 +02:00
parent 7c4f568a8f
commit 67cb4516c9
9 changed files with 116 additions and 240 deletions
+13 -8
View File
@@ -269,14 +269,19 @@ await new Command()
const updateState = setInterval(async () => {
const elapsed = start ? Math.ceil((Date.now() - start) / 1000) : 0;
const sum = jobsSent.reduce((a, b) => a + b, 0);
const queue_length = (
await (
await fetch(
host + "/api/w/" + config.workspace_id + "/jobs/queue/count",
{ headers: { ["Authorization"]: "Bearer " + config.token } }
)
).json()
).database_length;
const queue_length = -1;
try {
(
await (
await fetch(
host + "/api/w/" + config.workspace_id + "/jobs/queue/count",
{ headers: { ["Authorization"]: "Bearer " + config.token } }
)
).json()
).database_length;
} catch (e) {
console.error("Error reading queue count: " + e);
}
await Deno.stdout.write(
enc(
`elapsed: ${elapsed}/${seconds} | jobs sent: ${JSON.stringify(
@@ -1,10 +1,14 @@
<script lang="ts">
import { FlowStatusModule, Job } from '$lib/gen'
import { ProgressBar, type GeneralStep, type LoopStep, type ProgressStep } from '../progressBar'
import ProgressBar from '../progressBar/ProgressBar.svelte'
export let job: Job | undefined = undefined
export let duration = 200
let jobProgress: { steps: ProgressStep[]; error: boolean } = { steps: [], error: false }
let error: number | undefined = undefined
let index = 0
let subIndex: number | undefined = undefined
let subLength: number | undefined = undefined
let length = 1
$: if (job) updateJobProgress(job)
@@ -14,51 +18,58 @@
return
}
jobProgress.steps = modules.map((module) => {
let maxDone = 0
let subStepIndex: undefined | number = undefined
let subStepLength: undefined | number = undefined
let newError: number | undefined = undefined
modules.forEach((module, i) => {
if (
module.type === FlowStatusModule.type.FAILURE ||
(module.type === FlowStatusModule.type.SUCCESS && job['success'] === false)
) {
jobProgress.error = true
newError = i
}
let isDone = isJobStepDone(module.type)
if (isDone) {
maxDone = i + 1
return
}
// Loop is still iterating
if (module.iterator) {
return <LoopStep>{
type: 'loop',
isDone: isJobStepDone(module.type),
index: module.iterator.index || 0,
length: module.iterator.itered?.length || 0
const stepIndex = module.iterator.index || 0
const stepLength = module.iterator.itered?.length || 0
if (module.iterator.index != undefined) {
subStepIndex = stepIndex
subStepLength = stepLength
}
}
// Loop is finished
else if (module['flow_jobs']) {
return <LoopStep>{
type: 'loop',
isDone: isJobStepDone(module.type),
index: module['flow_jobs'].length,
length: module['flow_jobs'].length
}
}
// Not a loop
return <GeneralStep>{
type: 'general',
isDone: isJobStepDone(module.type)
} else if (module.branchall) {
subStepIndex = module.branchall.branch
subStepLength = module.branchall.len
}
})
error = newError
subLength = subStepLength ? Math.max(subStepLength, 1) : undefined
subIndex = subStepIndex
length = Math.max(modules.length, 1)
index = maxDone
}
function isJobStepDone(type: FlowStatusModule.type | undefined) {
if (!type) {
return false
}
return type === FlowStatusModule.type.SUCCESS || type === FlowStatusModule.type.FAILURE
}
let resetP: any
export function reset() {
jobProgress.error = false
jobProgress.steps = []
resetP?.()
error = undefined
subIndex = undefined
subLength = undefined
length = 1
index = 0
}
</script>
<ProgressBar {...jobProgress} {duration} class={$$props.class} />
<ProgressBar bind:resetP {length} {index} {subLength} {subIndex} {error} class={$$props.class} />
@@ -1,73 +1,49 @@
<script lang="ts">
import { setContext } from 'svelte'
import { writable } from 'svelte/store'
import {
type ProgressStep,
type ProgressState,
type LoopState,
isLoopStep,
isLoopState,
getTween,
type ProgressStateStoreValue
} from './model'
import ProgressBarGeneralPart from './ProgressBarGeneralPart.svelte'
import ProgressBarLoopPart from './ProgressBarLoopPart.svelte'
import { tweened } from 'svelte/motion'
import { linear } from 'svelte/easing'
export function getTween(initialValue = 0, duration = 200) {
return tweened(initialValue, {
duration,
easing: linear
})
}
export let error: number | undefined = undefined
export let index: number
export let subIndex: number | undefined
export let subLength: number | undefined
export let length: number
let duration = 200
export let steps: ProgressStep[]
export let error = false
export let duration = 100
let percent = getTween(0, duration)
let finished = false
let state: ProgressState[] = []
const stateStore = writable<ProgressStateStoreValue>({
length: state.length,
index: 0,
finished,
error
})
setContext('state', stateStore)
export function resetP() {
percent = getTween(0, duration)
}
$: state = steps.map((step, i) => {
if (isLoopStep(step)) {
return {
type: step.type,
isDone: error ? false : step.isDone,
isDoneChanged: !state[i]?.isDone && step.isDone,
length: step.length,
index: step.index,
indexChanged: (state[i] as LoopState)?.index !== step.index
}
} else {
return {
type: step.type,
isDone: step.isDone,
isDoneChanged: !state[i]?.isDone && step.isDone
}
}
})
$: stateStore.update(({ index }) => ({ length: state.length, index, finished, error }))
$: stepIndex = state.findIndex(({ isDone }) => !isDone)
$: lastStep = state.at(-1)
$: if (typeof lastStep?.isDone === 'boolean') {
finished = lastStep.isDone
if ($percent >= 100 && !lastStep.isDone) {
percent = getTween(0, duration)
$: percent.set(
(length
? index / length + (subIndex && subLength ? subIndex / (subLength ?? 1) / length : 0)
: 0) * 100
)
function getPercent(partIndex: number, _pct: number) {
if (!length) {
return 0
}
const res = Math.min(($percent - (partIndex / length) * 100) * length, 100)
return res
}
$: subStepIndex = lastStep ? lastStep['index'] : undefined
$: length = 100 / (state.length || 1)
$: if (finished) {
percent.set(100)
} else {
const product = length * stepIndex
percent.set(product < 0 ? 0 : product)
}
$: finished = index == length
</script>
<div class={$$props.class}>
<div
class="flex justify-between items-end font-medium mb-1 {error
class="flex justify-between items-end font-medium mb-1 {error != undefined
? 'text-red-700 dark:text-red-200'
: 'text-blue-700 dark:text-blue-200'}"
>
@@ -76,25 +52,37 @@
? 'Error occured'
: finished
? 'Done'
: `Step ${stepIndex + 1}${subStepIndex !== undefined ? `.${subStepIndex + 1}` : ''}`}
: `Step ${index + 1}${subIndex !== undefined ? `.${subIndex + 1}` : ''}`}
</span>
<span class="text-sm">
{state.length ? $percent.toFixed(0) : 0}%
{$percent.toFixed(0)}%
</span>
</div>
<!-- {#each state as step, index}
{index} {JSON.stringify(step)}
{/each} -->
<!-- A: {index}, B: {length}
{#each new Array(length) as _, index (index)}
{index} -
{getPercent(index)}
|
{/each} -->
<div class="flex w-full bg-gray-200 rounded-full h-4 overflow-hidden">
{#each state as step, index}
<div
class="h-full relative border-white {index === 0 ? '' : 'border-l'}"
style="width: {length}%;"
>
{#if isLoopState(step)}
<ProgressBarLoopPart {step} {index} {duration} />
{:else}
<ProgressBarGeneralPart {step} {index} {duration} />
{#each new Array(length) as _, partIndex (partIndex)}
<div class="h-full relative border-white {partIndex === 0 ? '' : 'border-l'} w-full">
{#if partIndex < index - 1}
<div
class="absolute left-0 bottom-0 h-full w-full {error == partIndex
? 'bg-red-400'
: 'bg-blue-400'}"
/>
{:else if partIndex == index - 1 || (partIndex == index && subIndex !== undefined)}
<div
class="absolute left-0 bottom-0 h-full {error == partIndex
? 'bg-red-400'
: 'bg-blue-400'}"
style="width: {getPercent(partIndex, $percent)}%"
/>
{/if}
</div>
{/each}
@@ -1,25 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { Writable } from 'svelte/store'
import { getTween, type GeneralState, type ProgressStateStoreValue } from './model'
export let step: GeneralState
export let index: number
export let duration = 200
const state = getContext<Writable<ProgressStateStoreValue>>('state')
let progress = getTween(0, duration)
$: finishedAndNotLast = $state.finished && $state.length - 1 !== index
$: if (finishedAndNotLast || $state.index > index) progress = getTween(100, duration)
$: if ($state.finished || step.isDone) {
if (index > $state.index) {
state.update((prev) => ({ ...prev, index }))
}
progress.set(100)
}
</script>
<div
class="absolute left-0 bottom-0 h-full {$state.error ? 'bg-red-400' : 'bg-blue-400'}"
style="width: {$progress}%"
/>
@@ -1,20 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { Writable } from 'svelte/store'
import type { Tweened } from 'svelte/motion'
import type { ProgressStateStoreValue } from './model'
export let progress: Tweened<number>
export let index: number
export let length: number
const state = getContext<Writable<ProgressStateStoreValue>>('state')
$: isLast = index === length - 1
</script>
<div
class="h-full absolute left-0 bottom-0 overflow-hidden {$state.error
? 'bg-red-400'
: 'bg-blue-400'}"
style="width: {$progress}%; opacity: {isLast ? 100 : 100 / length}%"
/>
@@ -1,37 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte'
import { get, type Writable } from 'svelte/store'
import ProgressBarLoopAccessor from './ProgressBarLoopAccessor.svelte'
import { getTween, type LoopState, type ProgressStateStoreValue } from './model'
export let step: LoopState
export let index: number
export let duration = 200
const state = getContext<Writable<ProgressStateStoreValue>>('state')
let progresses = getTweenArray()
$: finishedAndNotLast = $state.finished && $state.length - 1 !== index
$: if (!$state.error && ($state.index > index || finishedAndNotLast)) {
progresses = getTweenArray(100)
}
$: if (!$state.error && step.indexChanged) {
progresses.filter((p, i) => get(p) === 0 && i < step.index).forEach((p) => p.set(100))
progresses.forEach((p, i) => {
if (get(p) === 0 && i < step.index) {
p.set(100)
if (index > $state.index) {
state.update((prev) => ({ ...prev, index }))
}
}
})
}
$: if (!$state.error && $state.finished) progresses.forEach((p) => p.set(100))
function getTweenArray(initial = 0) {
return Array.from({ length: step.length }, () => getTween(initial, duration))
}
</script>
{#each progresses as progress, i}
<ProgressBarLoopAccessor {progress} index={i} length={step.length} />
{/each}
@@ -1,3 +0,0 @@
export { default as ProgressBar } from './ProgressBar.svelte'
export * from './model'
@@ -1,45 +0,0 @@
import { tweened } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'
export interface ProgressStateStoreValue {
length: number
index: number
finished: boolean
error: boolean
}
export type StepKind = 'general' | 'loop'
type Step<T extends StepKind> = {
type: T
isDone: boolean
}
export type GeneralStep = Step<'general'>
export type LoopStep = Step<'loop'> & { index: number; length: number }
export type ProgressStep = GeneralStep | LoopStep
export function isLoopStep(step: ProgressStep | undefined): step is LoopStep {
return step?.type === 'loop'
}
type State<T extends StepKind> = {
type: T
isDone: boolean
isDoneChanged: boolean
}
export type GeneralState = State<'general'>
export type LoopState = LoopStep & { index: number; indexChanged: boolean }
export type ProgressState = GeneralState | LoopState
export function isLoopState(state: ProgressState | undefined): state is LoopState {
return state?.type === 'loop'
}
export function getTween(initialValue = 0, duration = 200) {
return tweened(initialValue, {
duration,
easing: cubicOut
})
}
+4 -2
View File
@@ -25,7 +25,8 @@
'Canceled',
'Missing service editorService',
'Unexpected usage',
'NetworkError when attempting to fetch resource.'
'NetworkError when attempting to fetch resource.',
"Client got disposed and can't be restarted."
]
async function setUserWorkspaceStore() {
@@ -162,7 +163,8 @@
interval && clearInterval(interval)
})
const darkMode = window.localStorage.getItem('dark-mode') ??
const darkMode =
window.localStorage.getItem('dark-mode') ??
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
if (darkMode === 'dark') {