feat(frontend): add flowstatus and log component for apps

This commit is contained in:
Ruben Fiszel
2023-05-21 18:36:03 +02:00
parent 6a45b16564
commit badeefe57b
21 changed files with 260 additions and 55 deletions
@@ -1,7 +1,7 @@
<script lang="ts">
import { JobService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { onDestroy } from 'svelte'
import { onDestroy, tick } from 'svelte'
import type { Preview } from '$lib/gen/models/Preview'
import { createEventDispatcher } from 'svelte'
@@ -35,6 +35,7 @@
if (lastStartedAt < startedAt) {
lastStartedAt = startedAt
if (testId) {
dispatch('started', testId)
try {
await watchJob(testId)
} catch {
@@ -149,6 +150,7 @@
isCompleted = true
if (currentId === id) {
job = { ...maybe_job, id }
await tick()
dispatch('done', job)
currentId = undefined
}
@@ -1,7 +1,7 @@
<script lang="ts">
import { Job, JobService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { onDestroy } from 'svelte'
import { onDestroy, tick } from 'svelte'
import type { Preview } from '$lib/gen/models/Preview'
import { createEventDispatcher } from 'svelte'
@@ -166,6 +166,7 @@
//only CompletedJob has success property
isCompleted = true
if (currentId === id) {
await tick()
dispatch('done', job)
currentId = undefined
}
@@ -24,7 +24,6 @@
export let preclickAction: (() => Promise<void>) | undefined = undefined
export let customCss: ComponentCustomCSS<'buttoncomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined = false
export let errorHandledByComponent: boolean | undefined = false
export let extraKey: string | undefined = undefined
@@ -38,7 +37,6 @@
configuration
)
$: initializing = resolvedConfig?.label == undefined
$: errorHandledByComponent = resolvedConfig?.onError?.selected !== 'errorOverlay'
let outputs = initOutput($worldStore, id, {
@@ -0,0 +1,67 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { concatCustomCss } from '../../utils'
import FlowStatusViewer from '$lib/components/FlowStatusViewer.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'flowstatuscomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
const outputs = initOutput($worldStore, id, {
result: undefined,
loading: false
})
$: css = concatCustomCss($app.css?.flowstatuscomponent, customCss)
let jobId: string | undefined
</script>
<RunnableWrapper
on:started={(e) => {
jobId = e.detail
}}
{outputs}
{render}
{componentInput}
{id}
bind:initializing
>
<div class="flex flex-col w-full h-full">
<div
class={twMerge(
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
css?.header?.class
)}
style={css?.header?.style}
>
Flow Status
</div>
<div
style={twMerge(
$app.css?.['flowstatuscomponent']?.['container']?.style,
customCss?.container?.style
)}
class={twMerge(
'p-2 grow overflow-auto',
$app.css?.['flowstatuscomponent']?.['container']?.class,
customCss?.container?.class
)}
>
{#if jobId}
<FlowStatusViewer {jobId} />
{:else}
<span class="text-gray-600 text-xs">No flow</span>
{/if}
</div>
</div>
</RunnableWrapper>
@@ -0,0 +1,66 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { concatCustomCss } from '../../utils'
import LogViewer from '$lib/components/LogViewer.svelte'
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
import type { Job } from '$lib/gen'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'logcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
const outputs = initOutput($worldStore, id, {
result: undefined,
loading: false
})
$: css = concatCustomCss($app.css?.logcomponent, customCss)
let testJobLoader: TestJobLoader | undefined = undefined
let testIsLoading: boolean = false
let testJob: Job | undefined = undefined
</script>
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job={testJob} />
<RunnableWrapper
on:started={(e) => {
testJobLoader?.watchJob(e.detail)
}}
{outputs}
{render}
{componentInput}
{id}
bind:initializing
>
<div class="flex flex-col w-full h-full">
<div
class={twMerge(
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
css?.header?.class
)}
style={css?.header?.style}
>
Logs
</div>
<div
style={twMerge($app.css?.['logcomponent']?.['container']?.style, customCss?.container?.style)}
class={twMerge(
'p-2 grow overflow-auto',
$app.css?.['logcomponent']?.['container']?.class,
customCss?.container?.class
)}
>
<LogViewer content={testJob?.logs} isLoading={testIsLoading} />
</div>
</div>
</RunnableWrapper>
@@ -210,8 +210,6 @@
return
}
loading = true
try {
let njob = await resultJobLoader?.abstractRun(() => {
const nonStaticRunnableInputs = {}
@@ -281,6 +279,7 @@
}
async function setResult(res: any, jobId: string | undefined) {
dispatch('done')
const hasRes = res !== undefined && res !== null
if (transformer) {
@@ -410,6 +409,10 @@
{/if}
<ResultJobLoader
on:started={(e) => {
loading = true
dispatch('started', e.detail)
}}
workspaceOverride={workspace}
on:done={(e) => {
lastJobId = e.detail.id
@@ -53,10 +53,6 @@
const { staticExporter, noBackend, componentControl, runnableComponents } =
getContext<AppViewerContext>('AppViewerContext')
$: if (initializing && result != undefined) {
initializing = false
}
if (noBackend && componentInput?.type == 'runnable') {
result = componentInput?.['value']
}
@@ -156,6 +152,8 @@
wrapperClass={runnableClass}
wrapperStyle={runnableStyle}
{render}
on:started
on:done={() => (initializing = false)}
on:success={() => handleSideEffect(true)}
on:handleError={(e) => handleSideEffect(false, e.detail)}
{outputs}
@@ -14,7 +14,6 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'rangecomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean = true
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
let min = 0
@@ -48,12 +47,7 @@
<InputValue {id} input={configuration.min} bind:value={min} />
<InputValue {id} input={configuration.max} bind:value={max} />
<InputValue {id} input={configuration.defaultLow} bind:value={values[0]} />
<InputValue
on:done={() => (initializing = false)}
{id}
input={configuration.defaultHigh}
bind:value={values[1]}
/>
<InputValue {id} input={configuration.defaultHigh} bind:value={values[1]} />
<InitializeComponent {id} />
@@ -14,7 +14,6 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'slidercomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean = true
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let min = 0
@@ -79,12 +78,7 @@
<InputValue {id} input={configuration.step} bind:value={step} />
<InputValue {id} input={configuration.min} bind:value={min} />
<InputValue {id} input={configuration.max} bind:value={max} />
<InputValue
on:done={() => (initializing = false)}
{id}
input={configuration.defaultValue}
bind:value={values[0]}
/>
<InputValue {id} input={configuration.defaultValue} bind:value={values[0]} />
<InitializeComponent {id} />
<AlignWrapper {render} {verticalAlignment}>
@@ -46,6 +46,8 @@
import AppConditionalWrapper from '../../components/layout/AppConditionalWrapper.svelte'
import AppSelectStep from '../../components/inputs/AppSelectStep.svelte'
import AppDownload from '../../components/display/AppDownload.svelte'
import AppLogsComponent from '../../components/display/AppLogsComponent.svelte'
import AppFlowStatusComponent from '../../components/display/AppFlowStatusComponent.svelte'
export let component: AppComponent
export let selected: boolean
@@ -138,6 +140,22 @@
componentInput={component.componentInput}
{render}
/>
{:else if component.type === 'logcomponent'}
<AppLogsComponent
id={component.id}
customCss={component.customCss}
bind:initializing
componentInput={component.componentInput}
{render}
/>
{:else if component.type === 'flowstatuscomponent'}
<AppFlowStatusComponent
id={component.id}
customCss={component.customCss}
bind:initializing
componentInput={component.componentInput}
{render}
/>
{:else if component.type === 'barchartcomponent'}
<AppBarChart
configuration={component.configuration}
@@ -237,7 +255,6 @@
customCss={component.customCss}
componentInput={component.componentInput}
recomputeIds={component.recomputeIds}
bind:initializing
bind:errorHandledByComponent
{render}
/>
@@ -368,7 +385,6 @@
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:initializing
{render}
/>
{:else if component.type === 'horizontaldividercomponent'}
@@ -398,7 +414,6 @@
id={component.id}
customCss={component.customCss}
{render}
bind:initializing
/>
{:else if component.type === 'tabscomponent' && component.tabs}
<AppTabs
@@ -88,6 +88,8 @@ export type TableComponent = BaseComponent<'tablecomponent'> & {
}
export type AggridComponent = BaseComponent<'aggridcomponent'>
export type DisplayComponent = BaseComponent<'displaycomponent'>
export type LogComponent = BaseComponent<'logcomponent'>
export type FlowStatusComponent = BaseComponent<'flowstatuscomponent'>
export type ImageComponent = BaseComponent<'imagecomponent'>
export type InputComponent = BaseComponent<'inputcomponent'>
export type SelectComponent = BaseComponent<'selectcomponent'> & RecomputeOthersSource
@@ -127,6 +129,8 @@ export type SelectStepComponent = BaseComponent<'selectstepcomponent'>
export type TypedComponent =
| DisplayComponent
| LogComponent
| FlowStatusComponent
| TextInputComponent
| TextareaInputComponent
| PasswordInputComponent
@@ -298,7 +302,8 @@ const onSuccessClick = {
fieldType: 'text',
type: 'static',
value: '',
placeholder: 'Hello there'
placeholder: 'Hello there',
noVariablePicker: true
}
}
}
@@ -399,6 +404,42 @@ export const components = {
configuration: {}
}
},
logcomponent: {
name: 'Log',
icon: Monitor,
dims: '2:8-6:8' as AppComponentDimensions,
customCss: {
header: { class: '', style: '' },
container: { class: '', style: '' }
},
initialData: {
configuration: {},
componentInput: {
type: 'runnable',
fieldType: 'any',
fields: {},
runnable: undefined
}
}
},
flowstatuscomponent: {
name: 'Flow Status',
icon: Monitor,
dims: '2:8-6:8' as AppComponentDimensions,
customCss: {
header: { class: '', style: '' },
container: { class: '', style: '' }
},
initialData: {
configuration: {},
componentInput: {
type: 'runnable',
fieldType: 'any',
fields: {},
runnable: undefined
}
}
},
containercomponent: {
name: 'Container',
icon: BoxSelect,
@@ -57,7 +57,9 @@ const display: ComponentSet = {
'mapcomponent',
'htmlcomponent',
'pdfcomponent',
'displaycomponent'
'displaycomponent',
'logcomponent',
'flowstatuscomponent'
]
} as const
@@ -632,6 +632,14 @@ export const quickStyleProperties: Record<
header: [...containerDefaultProps, typographyGrouping],
container: containerDefaultProps
},
logcomponent: {
header: [...containerDefaultProps, typographyGrouping],
container: containerDefaultProps
},
flowstatuscomponent: {
header: [...containerDefaultProps, typographyGrouping],
container: containerDefaultProps
},
barchartcomponent: {
container: containerDefaultProps
},
@@ -3,7 +3,7 @@
import FlowModuleScript from '$lib/components/flows/content/FlowModuleScript.svelte'
import FlowPathViewer from '$lib/components/flows/content/FlowPathViewer.svelte'
import { emptySchema } from '$lib/utils'
import { getContext } from 'svelte'
import { getContext, tick } from 'svelte'
import type {
ConnectedAppInput,
RowAppInput,
@@ -99,10 +99,11 @@
variant="border"
size="xs"
startIcon={{ icon: faRefresh }}
on:click={() => {
on:click={async () => {
sendUserToast('Refreshing inputs')
refresh()
$stateId = $stateId + 1
await tick()
}}
>
Refresh
@@ -140,7 +140,7 @@
<div class="flex min-h-full flex-col min-w-[150px] w-full divide-y">
{#if component.componentInput}
<PanelSection
title={componentSettings?.item.data.type
title={componentSettings?.item.data.type == 'steppercomponent'
? 'Validations'
: hasInteraction
? 'Event handler'
@@ -205,8 +205,8 @@
>
<svelte:fragment slot="action">
<Tooltip>
The runnable inputs of a button component are not settable by the user. They
must be defined statically or connected.
The runnable inputs are inferred from the inputs of the flow or script
parameters this component is attached to.
</Tooltip>
</svelte:fragment>
@@ -168,6 +168,7 @@
placeholder={condition?.['placeholder']}
customTitle={condition?.['customTitle']}
displayType={false}
noVariablePicker={true}
/>
</div>
@@ -32,6 +32,7 @@
export let placeholder: string | undefined
export let customTitle: string | undefined = undefined
export let displayType: boolean = false
export let noVariablePicker: boolean | undefined = undefined
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
@@ -129,6 +130,7 @@
{selectOptions}
{format}
{placeholder}
noVariablePicker={noVariablePicker ?? false}
bind:componentInput
/>
</div>
@@ -50,6 +50,7 @@
fileUpload={meta?.['fileUpload']}
placeholder={meta?.['placeholder']}
customTitle={meta?.['customTitle']}
noVariablePicker={meta?.['noVariablePicker']}
{displayType}
/>
{/if}
@@ -70,6 +70,7 @@
placeholder={config?.['placeholder']}
onlyStatic={config?.['onlyStatic']}
customTitle={config?.['customTitle']}
noVariablePicker={config?.['noVariablePicker']}
/>
{/if}
{/each}
@@ -32,5 +32,10 @@
{#if isScriptByPathDefined(appInput) || isScriptByNameDefined(appInput)}
<SelectedRunnable {appComponent} bind:appInput />
{:else if appInput !== undefined}
<RunnableSelector {defaultUserInput} on:pick={(e) => onPick(e.detail)} />
<RunnableSelector
hideCreateScript={appComponent.type === 'flowstatuscomponent'}
onlyFlow={appComponent.type === 'flowstatuscomponent'}
{defaultUserInput}
on:pick={(e) => onPick(e.detail)}
/>
{/if}
@@ -17,8 +17,9 @@
export let defaultUserInput = false
export let hideCreateScript = false
export let onlyFlow = false
let tab: Tab = 'inlinescripts'
let tab: Tab = onlyFlow ? 'workspaceflows' : 'inlinescripts'
let filter: string = ''
let picker: Drawer
@@ -132,30 +133,34 @@
<div>
<div class="max-w-6xl">
<Tabs bind:selected={tab}>
<Tab size="sm" value="inlinescripts">
<div class="flex gap-2 items-center my-1">
<Building size={18} />
Detached Inline Scripts
</div>
</Tab>
<Tab size="sm" value="workspacescripts">
<div class="flex gap-2 items-center my-1">
<Building size={18} />
Workspace Scripts
</div>
</Tab>
{#if !onlyFlow}
<Tab size="sm" value="inlinescripts">
<div class="flex gap-2 items-center my-1">
<Building size={18} />
Detached Inline Scripts
</div>
</Tab>
<Tab size="sm" value="workspacescripts">
<div class="flex gap-2 items-center my-1">
<Building size={18} />
Workspace Scripts
</div>
</Tab>
{/if}
<Tab size="sm" value="workspaceflows">
<div class="flex gap-2 items-center my-1">
<Building size={18} />
Workspace Flows
</div>
</Tab>
<Tab size="sm" value="hubscripts">
<div class="flex gap-2 items-center my-1">
<Globe2 size={18} />
Hub Scripts
</div>
</Tab>
{#if !onlyFlow}
<Tab size="sm" value="hubscripts">
<div class="flex gap-2 items-center my-1">
<Globe2 size={18} />
Hub Scripts
</div>
</Tab>
{/if}
</Tabs>
<div class="my-2" />
<div class="flex flex-col gap-y-16">
@@ -202,6 +207,6 @@
startIcon={{ icon: faMousePointer }}
btnClasses="truncate w-full"
>
Select a script or flow
{#if onlyFlow}Select a flow{:else}Select a script or flow{/if}
</Button>
</div>