Runnable refactor (#929)

* fix(frontend): Support runnable everywhere

* fix(frontend): Runnable component working
This commit is contained in:
Faton Ramadani
2022-11-22 15:45:10 +01:00
committed by GitHub
parent 21969dd8a4
commit 6cf0bb17a0
10 changed files with 156 additions and 124 deletions
@@ -16,6 +16,9 @@
export let extraLib: string = 'missing extraLib'
export let autofocus = false
export let previousModuleId: string | undefined = undefined
export let shouldHideNoInputs: boolean = false
let clazz: string = ''
export { clazz as class }
@@ -73,7 +76,7 @@
{/if}
</div>
{/each}
{:else}
<p class="italic text-sm">No inputs</p>
{:else if !shouldHideNoInputs}
<div class="text-gray-500 text-sm">No inputs</div>
{/if}
</div>
@@ -13,11 +13,16 @@
ArcElement
} from 'chart.js'
import type { ChartData } from 'chart.js'
import ComponentInputValue from '../helpers/ComponentInputValue.svelte'
import type { ComponentInputsSpec } from '../../types'
import type { InputsSpec } from '../../types'
import RunnableComponent from '../helpers/RunnableComponent.svelte'
export let componentInputs: ComponentInputsSpec
export const staticOutputs: string[] = []
export let id: string
export let inputs: InputsSpec
export let path: string | undefined = undefined
export let runType: 'script' | 'flow' | undefined = undefined
export let inlineScriptName: string | undefined = undefined
export const staticOutputs: string[] = ['loading', 'result']
ChartJS.register(
Title,
@@ -34,33 +39,13 @@
responsive: true
}
let dataSetValue: Record<string, number> | undefined = undefined
let data: ChartData<'pie', number[], unknown> = { datasets: [], labels: [] }
function populateDataSet() {
if (dataSetValue) {
data.datasets = [
{
data: Object.values(dataSetValue).filter((val) => typeof val === 'number'),
backgroundColor: ['#F7464A', '#46BFBD', '#FDB45C', '#949FB1', '#4D5360', '#AC64AD'],
hoverBackgroundColor: ['#FF5A5E', '#5AD3D1', '#FFC870', '#A8B3C5', '#616774', '#DA92DB']
}
]
data.labels = Object.keys(dataSetValue)
.filter((key) => typeof dataSetValue?.[key] === 'number')
.map((s) => s.replace(/([A-Z]+)*([A-Z][a-z])/g, '$1 $2'))
data = data
}
}
$: dataSetValue && populateDataSet()
let result: ChartData<'pie', number[], unknown> | undefined = undefined
</script>
<ComponentInputValue input={componentInputs.dataset} bind:value={dataSetValue} />
{#if data.datasets.length > 0}
<Pie {data} {options} />
{:else}
<span>No dataset</span>
{/if}
<RunnableComponent {id} {path} {runType} {inlineScriptName} {inputs} bind:result>
{#if result}
<Pie data={result} {options} />
{:else}
<span>No dataset</span>
{/if}
</RunnableComponent>
@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores'
import type { Schema } from '$lib/common'
import Alert from '$lib/components/common/alert/Alert.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
@@ -11,7 +12,8 @@
import Icon from 'svelte-awesome'
import type { Output } from '../../rx'
import type { AppEditorContext, InputsSpec } from '../../types'
import { buildArgs, loadSchema, schemaToInputsSpec } from '../../utils'
import { loadSchema, schemaToInputsSpec } from '../../utils'
import RunnableInputValue from './RunnableInputValue.svelte'
// Component props
export let id: string
@@ -20,9 +22,7 @@
export let runType: 'script' | 'flow' | undefined = undefined
export let inlineScriptName: string | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let shouldTick: number | undefined = undefined
export let result: any = undefined
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
@@ -31,71 +31,97 @@
// Local state
let args: Record<string, any> = {}
let schema: Schema | undefined = undefined
let schemaClone: Schema | undefined = undefined
let isValid = true
let testIsLoading = false
let runnableInputValues: Record<string, any> = {}
$: if (outputs) {
outputs.loading.set(testIsLoading)
$: mergedArgs = { ...args, ...extraQueryParams, ...runnableInputValues }
function isMergedArgsValid(mergedArgs: Record<string, any>) {
if (Object.keys(inputs).length !== Object.keys(runnableInputValues).length) {
return false
}
const areAllArgsValid = Object.values(mergedArgs).every(
(arg) => arg !== undefined && arg !== null
)
debugger
if (areAllArgsValid) {
executeComponent()
}
return areAllArgsValid
}
$: isValid = isMergedArgsValid(mergedArgs)
// Test job internal state
let testJob: CompletedJob | undefined = undefined
let testJobLoader: TestJobLoader | undefined = undefined
$: if ($workspaceStore && path && runType) {
loadSchemaFromTriggerable($workspaceStore, path, runType)
$: outputs = $worldStore?.outputsById[id] as {
result: Output<Array<any>>
loading: Output<boolean>
}
$: if (inlineScriptName && $app.inlineScripts[inlineScriptName]) {
schema = $app.inlineScripts[inlineScriptName].schema
/**
* Args are built from 3 sources:
* 1. The inputs spec ( )
* 2. The schema input transform with user submitted values§
* 3. The extra query params
*/
Object.keys(extraQueryParams).forEach((key) => {
if (schema?.properties[key]) {
delete schema.properties[key]
}
})
reloadSchemaAndArgs()
}
$: if (inputs && schema !== undefined) {
if (Object.keys(schema.properties).length !== Object.keys(inputs).length) {
inputs = schemaToInputsSpec(schema)
}
reloadSchemaAndArgs()
}
// Load once
async function loadSchemaFromTriggerable(
workspace: string,
path: string,
runType: 'script' | 'flow'
) {
schema = await loadSchema(workspace, path, runType)
}
Object.keys(extraQueryParams).forEach((key) => {
if (schema?.properties[key]) {
delete schema.properties[key]
// Only loads the schema
$: if ($workspaceStore && path && runType) {
// Remote schema needs to be loaded
loadSchemaFromTriggerable($workspaceStore, path, runType)
} else if (inlineScriptName && $app.inlineScripts[inlineScriptName]) {
// Inline scripts directly provide the schema
schema = $app.inlineScripts[inlineScriptName].schema
}
// When the schema is loaded, we need to update the inputs spec
// in order to render the inputs the component panel
$: if (schema && Object.keys(schema.properties ?? {}).length !== Object.keys(inputs).length) {
inputs = schemaToInputsSpec(schema)
}
let schemaStripped: Schema | undefined = undefined
function stripSchema(schema: Schema) {
schemaStripped = JSON.parse(JSON.stringify(schema))
// Remove hidden static inputs
Object.keys(inputs).forEach((key: string) => {
const input = inputs[key]
if (input.type === 'static' && !input.visible && schemaStripped !== undefined) {
delete schemaStripped.properties[key]
}
if (input.type === 'output' && schemaStripped !== undefined) {
delete schemaStripped.properties[key]
}
})
// Remove extra query params from schema
Object.keys(extraQueryParams).forEach((key: string) => {
if (schemaStripped !== undefined) {
delete schemaStripped.properties[key]
}
})
args = buildArgs(inputs, schema)
}
async function reloadSchemaAndArgs() {
schemaClone = JSON.parse(JSON.stringify(schema))
if (schemaClone !== undefined) {
args = buildArgs(inputs, schemaClone)
Object.keys(schemaClone.properties).forEach((propKey) => {
if (!Object.keys(args).includes(propKey)) {
delete schemaClone!.properties[propKey]
}
})
}
}
$: schema && stripSchema(schema)
$: disabledArgs = Object.keys(inputs).reduce((a: string[], c: string) => {
if (inputs[c].type === 'static') {
@@ -107,10 +133,7 @@
async function executeComponent() {
await testJobLoader?.abstractRun(() => {
const requestBody = {
args: {
...args,
...extraQueryParams
},
args: mergedArgs,
force_viewer_static_fields: {}
}
@@ -131,19 +154,12 @@
})
})
}
$: if (testJobLoader && shouldTick) {
executeComponent()
}
$: extraQueryParams && executeComponent()
$: outputs = $worldStore?.outputsById[id] as {
result: Output<Array<any>>
loading: Output<boolean>
}
</script>
{#each Object.keys(inputs) as key}
<RunnableInputValue input={inputs[key]} bind:value={runnableInputValues[key]} />
{/each}
<TestJobLoader
on:done={() => {
if (testJob) {
@@ -156,18 +172,24 @@
bind:this={testJobLoader}
/>
{#if schemaClone !== undefined}
<SchemaForm schema={schemaClone} bind:args bind:isValid {disabledArgs} />
{#if schemaStripped !== undefined}
<SchemaForm schema={schemaStripped} bind:args {isValid} {disabledArgs} shouldHideNoInputs />
{/if}
{#if shouldTick === undefined}
<Button size="xs" color="dark" on:click={() => executeComponent()} disabled={!isValid}>
<div>
{Object.keys(args).length > 0 ? 'Submit' : 'Refresh'}
{#if testIsLoading}
<Icon data={faArrowsRotate} class="animate-spin ml-2" scale={0.8} />
{/if}
</div>
</Button>
{#if isValid}
<Button size="xs" color="dark" on:click={() => executeComponent()} disabled={!isValid}>
<div>
{Object.keys(args).length > 0 ? 'Submit' : 'Refresh'}
{#if testIsLoading}
<Icon data={faArrowsRotate} class="animate-spin ml-2" scale={0.8} />
{/if}
</div>
</Button>
<slot />
{:else}
<Alert type="warning" size="xs" class="mt-2" title="Missing inputs">
Please fill in all the inputs
</Alert>
{/if}
{/if}
<slot />
@@ -0,0 +1,27 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { StaticInput, DynamicInput, AppEditorContext, UserInput } from '../../types'
export let input: DynamicInput | StaticInput | UserInput
export let value: any
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
$: hasConnection = input.type === 'output' && input.id !== undefined && input.name !== undefined
$: inputResult = hasConnection
? $worldStore?.connect<any>(input, () => updateValue())
: {
peak: () => {
if (input.type === 'static') {
return input.value
}
}
}
function updateValue() {
value = inputResult?.peak()
}
$: !hasConnection && input && input.type === 'static' && updateValue()
</script>
@@ -47,7 +47,11 @@
{:else if component.type === 'barchartcomponent'}
<BarChartComponent {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'piechartcomponent'}
<PieChartComponent {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<PieChartComponent
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
bind:inputs={component.inputs}
/>
{:else if component.type === 'tablecomponent'}
<TableComponent
{...component}
@@ -91,14 +91,7 @@ const chartComponents = {
...defaultProps,
id: 'piechartcomponent',
type: 'piechartcomponent',
componentInputs: {
dataset: {
type: 'static',
visible: true,
value: {},
fieldType: 'textarea'
}
}
runnable: true
},
{
...defaultProps,
@@ -11,7 +11,6 @@
let object = {}
outputs.forEach((output) => {
console.log({ output })
$worldStore?.outputsById[componentId][output].subscribe({
next: (value) => {
object[output] = value
-1
View File
@@ -21,7 +21,6 @@ export type World = {
}
export function buildWorld(components: Record<string, string[]>) {
console.log({ components })
const newWorld = buildObservableWorld()
const outputsById: Record<string, Record<string, Output<any>>> = {}
+1 -1
View File
@@ -59,7 +59,7 @@ export type BarChartComponent = {
type: 'barchartcomponent'
}
export type PieChartComponent = {
export type PieChartComponent = Runnable & {
type: 'piechartcomponent'
}
@@ -21,25 +21,25 @@
const classes: Record<AlertType, Record<string, string>> = {
info: {
bgClass: 'bg-blue-50',
bgClass: 'bg-blue-50 border-blue-200 border',
iconClass: 'text-blue-500',
titleClass: 'text-blue-800',
descriptionClass: 'text-blue-700'
},
warning: {
bgClass: 'bg-yellow-50',
bgClass: 'bg-yellow-50 border-yellow-200 border',
iconClass: 'text-yellow-500',
titleClass: 'text-yellow-800',
descriptionClass: 'text-yellow-700'
},
error: {
bgClass: 'bg-red-50',
bgClass: 'bg-red-50 border-red-200 border',
iconClass: 'text-red-500',
titleClass: 'text-red-800',
descriptionClass: 'text-red-700'
},
success: {
bgClass: 'bg-green-50',
bgClass: 'bg-green-50 border-green-200 border',
iconClass: 'text-green-500',
titleClass: 'text-green-800',
descriptionClass: 'text-green-700'
@@ -47,7 +47,7 @@
}
</script>
<div class={classNames('rounded-md p-4', classes[type].bgClass, $$props.class)}>
<div class={classNames('rounded-md p-4 ', classes[type].bgClass, $$props.class)}>
<div class="flex">
<div class="flex h-8 w-8 items-center justify-center rounded-full">
<Icon data={icons[type]} class={classes[type].iconClass} />