feat(app): Add progress bar app component (#7242)

* Add progress bar app component

- Create AppJobProgressBar component for displaying job progress
- Register jobprogressbarcomponent in component system
- Add component rendering in ComponentInner
- Component accepts jobId configuration parameter
- Similar to jobidlogcomponent and jobidflowstatuscomponent

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>

* feat(app): Add job progress bar to component picker

Add jobprogressbarcomponent to the display component set so it appears
in the component picker UI alongside other job-related components.

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* Add jobprogressbarcomponent to quickStyleProperties

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
This commit is contained in:
Ruben Fiszel
2025-11-27 18:42:23 +01:00
committed by GitHub
parent 6886ba72d1
commit 267171f2c9
5 changed files with 166 additions and 0 deletions
@@ -0,0 +1,129 @@
<script lang="ts">
import { getContext, untrack } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initConfig, initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { initCss } from '../../utils'
import JobLoader from '$lib/components/JobLoader.svelte'
import JobProgressBar from '$lib/components/jobs/JobProgressBar.svelte'
import type { Job } from '$lib/gen'
import { components } from '../../editor/component'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
interface Props {
id: string
initializing?: boolean | undefined
customCss?: ComponentCustomCSS<'jobprogressbarcomponent'> | undefined
configuration: RichConfigurations
render: boolean
}
let {
id,
initializing = $bindable(undefined),
customCss = undefined,
configuration,
render
}: Props = $props()
$effect.pre(() => {
if (initializing) {
initializing = false
}
})
const { app, worldStore, workspace } = getContext<AppViewerContext>('AppViewerContext')
let resolvedConfig = $state(
initConfig(components['jobprogressbarcomponent'].initialData.configuration, configuration)
)
const outputs = initOutput($worldStore, id, {
result: undefined,
loading: false,
jobId: undefined
})
initializing = false
let css = $state(initCss($app.css?.jobprogressbarcomponent, customCss))
let jobLoader: JobLoader | undefined = $state(undefined)
let testIsLoading: boolean = $state(false)
let testJob: Job | undefined = $state(undefined)
let scriptProgress: number | undefined = $state(undefined)
$effect(() => {
if (resolvedConfig.jobId) {
untrack(() => {
outputs.loading.set(true)
const jobId = resolvedConfig?.['jobId']
if (jobId) {
let callbacks = {
done(x) {
outputs.loading.set(false)
outputs.jobId.set(x.id)
outputs.result.set(x.result)
}
}
jobLoader?.watchJob(jobId, callbacks)
}
})
}
})
</script>
{#each Object.keys(components['jobprogressbarcomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
{#each Object.keys(css ?? {}) as key (key)}
<ResolveStyle
{id}
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.jobprogressbarcomponent}
/>
{/each}
<JobLoader
noCode={true}
workspaceOverride={workspace}
bind:this={jobLoader}
bind:isLoading={testIsLoading}
bind:job={testJob}
/>
<InitializeComponent {id} />
{#if render}
<div class="flex flex-col w-full h-full component-wrapper">
<div
class={twMerge(
'w-full border-b p-2 text-xs font-semibold text-primary bg-surface-secondary',
css?.header?.class
)}
style={css?.header?.style}
>
Progress
</div>
<div
style={css?.container?.style}
class={twMerge('p-2 grow overflow-auto', css?.container?.class)}
>
{#if testJob}
<JobProgressBar job={testJob} {scriptProgress} />
{:else}
<span class="text-secondary text-xs">No job</span>
{/if}
</div>
</div>
{/if}
@@ -16,6 +16,7 @@
import AppList from '../../components/layout/AppList.svelte'
import AppJobIdLogComponent from '../../components/display/AppJobIdLogComponent.svelte'
import AppJobIdFlowStatus from '../../components/display/AppJobIdFlowStatus.svelte'
import AppJobProgressBar from '../../components/display/AppJobProgressBar.svelte'
import AppCarouselList from '../../components/display/AppCarouselList.svelte'
import AppAccordionList from '../../components/display/AppAccordionList.svelte'
import AppAggridTableEe from '../../components/display/table/AppAggridTableEe.svelte'
@@ -146,6 +147,7 @@
])
const chunk5Components = new Set([
'jobidlogcomponent',
'jobprogressbarcomponent',
'listcomponent',
'logcomponent',
'mapcomponent',
@@ -605,6 +607,14 @@
configuration={component.configuration}
{render}
/>
{:else if component.type === 'jobprogressbarcomponent'}
<AppJobProgressBar
id={component.id}
customCss={component.customCss}
bind:initializing
configuration={component.configuration}
{render}
/>
{:else if component.type === 'listcomponent'}
<AppList
id={component.id}
@@ -215,6 +215,7 @@ export type LogComponent = BaseComponent<'logcomponent'>
export type JobIdLogComponent = BaseComponent<'jobidlogcomponent'>
export type FlowStatusComponent = BaseComponent<'flowstatuscomponent'>
export type JobIdFlowStatusComponent = BaseComponent<'jobidflowstatuscomponent'>
export type JobProgressBarComponent = BaseComponent<'jobprogressbarcomponent'>
export type ImageComponent = BaseComponent<'imagecomponent'>
export type InputComponent = BaseComponent<'inputcomponent'>
export type SelectComponent = BaseComponent<'selectcomponent'> &
@@ -345,6 +346,7 @@ export type TypedComponent =
| JobIdLogComponent
| FlowStatusComponent
| JobIdFlowStatusComponent
| JobProgressBarComponent
| TextInputComponent
| QuillComponent
| CodeInputComponent
@@ -1276,6 +1278,26 @@ export const components = {
}
}
},
jobprogressbarcomponent: {
name: 'Progress Bar by Job Id',
icon: Clock,
documentationLink: `${documentationBaseUrl}/progress_bar`,
dims: '2:2-6:2' as AppComponentDimensions,
customCss: {
header: { class: '', style: '' },
container: { class: '', style: '' }
},
initialData: {
configuration: {
jobId: {
type: 'static',
fieldType: 'text',
value: '',
tooltip: 'Job id to display progress from'
}
}
}
},
containercomponent: {
name: 'Container',
icon: BoxSelect,
@@ -76,6 +76,7 @@ const display: ComponentSet = {
'chatcomponent',
'displaycomponent',
'jobidlogcomponent',
'jobprogressbarcomponent',
'jobidflowstatuscomponent',
'jobiddisplaycomponent',
'statcomponent',
@@ -665,6 +665,10 @@ export const quickStyleProperties: Record<
header: [...containerDefaultProps, typographyGrouping],
container: containerDefaultProps
},
jobprogressbarcomponent: {
header: [...containerDefaultProps, typographyGrouping],
container: containerDefaultProps
},
accordionlistcomponent: {
container: containerDefaultProps
},