diff --git a/frontend/src/lib/components/apps/components/display/AppDisplayComponent.svelte b/frontend/src/lib/components/apps/components/display/AppDisplayComponent.svelte index 52d390c337..ce589c0849 100644 --- a/frontend/src/lib/components/apps/components/display/AppDisplayComponent.svelte +++ b/frontend/src/lib/components/apps/components/display/AppDisplayComponent.svelte @@ -25,6 +25,7 @@ const requireHtmlApproval = getContext(IS_APP_PUBLIC_CONTEXT_KEY) const { app, worldStore, componentControl } = getContext('AppViewerContext') + let result: any = undefined const resolvedConfig = initConfig( @@ -75,7 +76,7 @@ )} style={css?.header?.style} > - {resolvedConfig?.title ?? 'Result'} + {resolvedConfig?.title ? resolvedConfig?.title : 'Result'}
+ import { getContext } from 'svelte' + import { twMerge } from 'tailwind-merge' + import { initConfig, initOutput } from '../../editor/appUtils' + import { + IS_APP_PUBLIC_CONTEXT_KEY, + type AppViewerContext, + type ComponentCustomCSS, + type RichConfigurations + } from '../../types' + import { initCss } from '../../utils' + import TestJobLoader from '$lib/components/TestJobLoader.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' + import DisplayResult from '$lib/components/DisplayResult.svelte' + + export let id: string + export let initializing: boolean | undefined = false + export let customCss: ComponentCustomCSS<'jobiddisplaycomponent'> | undefined = undefined + export let configuration: RichConfigurations + export let render: boolean + + const { app, worldStore, workspace } = getContext('AppViewerContext') + const requireHtmlApproval = getContext(IS_APP_PUBLIC_CONTEXT_KEY) + + let resolvedConfig = initConfig( + components['jobiddisplaycomponent'].initialData.configuration, + configuration + ) + + const outputs = initOutput($worldStore, id, { + result: undefined, + loading: false, + jobId: undefined + }) + + initializing = false + + let css = initCss($app.css?.jobiddisplaycomponent, customCss) + + let testJobLoader: TestJobLoader | undefined = undefined + let testIsLoading: boolean = false + let testJob: Job | undefined = undefined + + $: if (resolvedConfig.jobId) { + outputs.loading.set(true) + testJobLoader?.watchJob(resolvedConfig?.['jobId']) + } + + let result: any = undefined + + +{#each Object.keys(components['jobiddisplaycomponent'].initialData.configuration) as key (key)} + +{/each} + +{#each Object.keys(css ?? {}) as key (key)} + +{/each} + + { + outputs.loading.set(false) + outputs.jobId.set(e.detail.id) + outputs.result.set(e.detail.result) + result = e.detail.result + }} +/> + + + +{#if render} +
+
+ {resolvedConfig?.title ? resolvedConfig?.title : 'Result'} +
+
+ +
+
+{/if} diff --git a/frontend/src/lib/components/apps/components/helpers/eval.ts b/frontend/src/lib/components/apps/components/helpers/eval.ts index 3659f1c65d..270a3753fc 100644 --- a/frontend/src/lib/components/apps/components/helpers/eval.ts +++ b/frontend/src/lib/components/apps/components/helpers/eval.ts @@ -1,5 +1,6 @@ import type { World } from '../../rx' import { sendUserToast } from '$lib/toast' +import { waitJob } from '$lib/components/waitJob' export function computeGlobalContext(world: World | undefined, extraContext: any = {}) { return { @@ -24,7 +25,7 @@ function create_context_function_template( ) { let hasReturnAsLastLine = noReturn || eval_string.split('\n').some((x) => x.startsWith('return ')) return ` -return async function (context, state, goto, setTab, recompute, getAgGrid, setValue, setSelectedIndex, openModal, closeModal, open, close, validate, invalidate, validateAll, clearFiles, showToast) { +return async function (context, state, goto, setTab, recompute, getAgGrid, setValue, setSelectedIndex, openModal, closeModal, open, close, validate, invalidate, validateAll, clearFiles, showToast, waitJob) { "use strict"; ${ contextKeys && contextKeys.length > 0 @@ -59,7 +60,8 @@ type WmFunctor = ( invalidate, validateAll, clearFiles, - showToast + showToast, + waitJob ) => Promise let functorCache: Record = {} @@ -108,6 +110,7 @@ export async function eval_like( validateAll?: () => void clearFiles?: () => void showToast?: (message: string, error?: boolean) => void + waitJob?: (jobId: string) => void } >, worldStore: World | undefined, @@ -185,6 +188,7 @@ export async function eval_like( }, (message, error) => { sendUserToast(message, error) - } + }, + async (id) => waitJob(id) ) } diff --git a/frontend/src/lib/components/apps/editor/component/Component.svelte b/frontend/src/lib/components/apps/editor/component/Component.svelte index 668e85341b..e595c02568 100644 --- a/frontend/src/lib/components/apps/editor/component/Component.svelte +++ b/frontend/src/lib/components/apps/editor/component/Component.svelte @@ -77,6 +77,7 @@ import AppNumberInput from '../../components/inputs/AppNumberInput.svelte' import AppNavbar from '../../components/display/AppNavbar.svelte' import AppDateSelect from '../../components/inputs/AppDateSelect.svelte' + import AppDisplayComponentByJobId from '../../components/display/AppDisplayComponentByJobId.svelte' export let component: AppComponent export let selected: boolean @@ -847,6 +848,14 @@ verticalAlignment={component.verticalAlignment} {render} /> + {:else if component.type === 'jobiddisplaycomponent'} + {/if}
diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts index d9857908ee..f7e9a8eb95 100644 --- a/frontend/src/lib/components/apps/editor/component/components.ts +++ b/frontend/src/lib/components/apps/editor/component/components.ts @@ -164,6 +164,7 @@ export type AggridInfiniteComponentEe = BaseComponent<'aggridinfinitecomponentee } export type DisplayComponent = BaseComponent<'displaycomponent'> +export type JobIdDisplayComponent = BaseComponent<'jobiddisplaycomponent'> export type LogComponent = BaseComponent<'logcomponent'> export type JobIdLogComponent = BaseComponent<'jobidlogcomponent'> export type FlowStatusComponent = BaseComponent<'flowstatuscomponent'> @@ -354,6 +355,7 @@ export type TypedComponent = | MultiSelectComponentV2 | NavBarComponent | DateSelectComponent + | JobIdDisplayComponent export type AppComponent = BaseAppComponent & TypedComponent @@ -3995,6 +3997,38 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm' } } } + }, + jobiddisplaycomponent: { + name: 'Rich result by Job Id', + icon: Monitor, + documentationLink: `${documentationBaseUrl}/rich_result_by_job_id`, + dims: '2:8-6:8' as AppComponentDimensions, + customCss: { + header: { class: '', style: '' }, + container: { class: '', style: '' } + }, + initialData: { + configuration: { + jobId: { + type: 'static', + fieldType: 'text', + value: '', + tooltip: 'Job id to display logs from' + }, + title: { + type: 'static', + fieldType: 'text', + value: 'Result' + }, + hideDetails: { + type: 'static', + fieldType: 'boolean', + value: false, + tooltip: + 'Hide the details section: the object keys, the clipboard button and the maximise button' + } + } + } } } as const diff --git a/frontend/src/lib/components/apps/editor/component/sets.ts b/frontend/src/lib/components/apps/editor/component/sets.ts index adeedf3cb7..40eb1af993 100644 --- a/frontend/src/lib/components/apps/editor/component/sets.ts +++ b/frontend/src/lib/components/apps/editor/component/sets.ts @@ -71,6 +71,7 @@ const display: ComponentSet = { 'displaycomponent', 'jobidlogcomponent', 'jobidflowstatuscomponent', + 'jobiddisplaycomponent', 'statcomponent', 'menucomponent', 'alertcomponent' diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts index 1669c244d9..963b682355 100644 --- a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts +++ b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts @@ -792,5 +792,9 @@ export const quickStyleProperties: Record< }, dateselectcomponent: { input: inputDefaultProps + }, + jobiddisplaycomponent: { + header: [...containerDefaultProps, typographyGrouping], + container: containerDefaultProps } } diff --git a/frontend/src/lib/components/apps/utils.ts b/frontend/src/lib/components/apps/utils.ts index 031eb924c3..82e6aacc90 100644 --- a/frontend/src/lib/components/apps/utils.ts +++ b/frontend/src/lib/components/apps/utils.ts @@ -258,6 +258,15 @@ declare function clearFiles(id: string): void; * @param message message to display */ declare function showToast(message: string, error?: boolean): void; + +/** + * Wait for a job to finish + * @param id job id + * @returns the result of the job + */ +declare async function waitJob(id: string): Promise; + + ` : '' } diff --git a/frontend/src/lib/components/waitJob.ts b/frontend/src/lib/components/waitJob.ts new file mode 100644 index 0000000000..6788ce6ad0 --- /dev/null +++ b/frontend/src/lib/components/waitJob.ts @@ -0,0 +1,85 @@ +import { JobService } from '$lib/gen' +import { workspaceStore } from '$lib/stores' +import { get } from 'svelte/store' + +const ITERATIONS_BEFORE_SLOW_REFRESH = 10 +const ITERATIONS_BEFORE_SUPER_SLOW_REFRESH = 100 + +export async function waitJob(id: string) { + const workspace = get(workspaceStore) + + if (!id) { + return + } + + if (!workspace) { + throw new Error('Workspace not found') + } + + let syncIteration: number = 0 + let errorIteration: number = 0 + let job: any + + return new Promise((resolve, reject) => { + async function checkJob() { + try { + const maybeJob = await JobService.getCompletedJobResultMaybe({ + workspace: workspace!, + id, + getStarted: false + }) + + if (maybeJob.completed) { + job = { ...maybeJob, id } + + if (!job.success && typeof job.result === 'object' && 'error' in job.result) { + reject(job.result.error) + } else { + resolve(job.result) + } + + return + } + } catch (err) { + errorIteration += 1 + + if (errorIteration === 5) { + try { + await cancelJob(id, workspace!) + } catch (err) { + console.error(err) + } + } + } + + syncIteration++ + + let nextIteration = 50 + + if (syncIteration > ITERATIONS_BEFORE_SLOW_REFRESH) { + nextIteration = 500 + } else if (syncIteration > ITERATIONS_BEFORE_SUPER_SLOW_REFRESH) { + nextIteration = 2000 + } + + setTimeout(checkJob, nextIteration) + } + + job = undefined + checkJob() + }) +} + +async function cancelJob(id: string, workspace: string) { + if (id) { + try { + await JobService.cancelQueuedJob({ + workspace, + id, + requestBody: {} + }) + } catch (err) { + console.error(err) + } + } +} diff --git a/frontend/src/routes/(root)/(logged)/resources/+page.svelte b/frontend/src/routes/(root)/(logged)/resources/+page.svelte index 11c50b4da7..b7cb40be08 100644 --- a/frontend/src/routes/(root)/(logged)/resources/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/resources/+page.svelte @@ -480,14 +480,7 @@ - { - console.log('change') - }} - bind:schema={newResourceType.schema} - fullHeight - /> +