Runnable editor
The strength of Windmill's app editor is the ability to connect everything together:
- components can be linked to each other
- components can be directly linked to scripts and trigger them
- background runnables can be that are run in the background on app refresh on when an input changes
- evals can be used to manipulate the client app state
On the bottom of the app editor, you can find the runnable editor. The runnable editor is a code editor that allows to create, edit, or manage the scripts or flows linked to components (runnables) and background runnables.
The panel is structured as follows:
- Runnables list: a list of all the runnables. Click on a runnable to select it.
- Runnable editor: the editor of the selected runnable.
When a runnable is selected, the runnable editor is displayed. It is composed of:
-
Header: The header of the runnable editor. It contains:
- Editable name.
- The status of the runnable can either be valid, invalid depending on the LSP response.
- Cache button.
- Windmill AI button.
- A delete button.
- Expand to Script editor button.
- A format button: Formats the code of the runnable. It's also triggered when you save the runnable.
- A run button: Runs the runnable.
-
Runnable editor: The code editor of the runnable.
Types of runnables
There are 5 types of runnables:
- Inline scripts: Scripts that are defined on the app editor, linked to a component.
- Workspace/Hub: Runnables (scripts, flows) linked to a component, but defined in the workspace or the Hub.
- Background runnables: Runnables that are not linked to a component, but run in the background.
- Frontend scripts: Scripts that can be used to manipulate the client app state. They can either be linked to a component or be a background runnable. If they are a background runnable, they are not executed unless manually set to run when the app starts or reloads.
- Evals: Evals are used to connect data sources to other components or parameters. They are only evaluated on changes to the outputs that have been identified.
They provide interactivity to your app.
Inline scripts
Inline scripts are scripts that are defined on the app editor. They can be either attached to a component or detached.
Frontend scripts
Frontend scripts are JavaScripts that are executed on the browser.
They come with frontend script helpers that are functions and global objects to help you interact with the app.
Frontend scripts can read outputs and ctx of the rest of the app with for example:
// read outputs and ctx
console.log(ctx.email);
// access a global state store
if (!state.foo) {
state.foo = 0;
}
state.foo += 1;
// for reactivity to work, you need to assign a value and not modify it in place
// e.g: state.foo.push(1) will not work but 'state.foo = [...state.foo, 1]' will.
// you may also just reassign as next statement 'state.foo = state.foo'
// you can also navigate (goto), recompute a script (recompute), or set a tab (setTab)
// Inputs and display components support settings their value directly
setValue('a', 'Bar');
// Tables support setting their selected index (setSelectedIndex)
return state.foo;
Background runnables
Background runnables are scripts that are executed in the background. They are executed on the server. They can be triggered on app refresh or when their input changes. They are not attached to any component but their result can be shared among many components.
See Background runnables configurations at Runnable configuration.
Evals
Evals are used to connect data sources to other components or parameters.
Windmill parses your eval and frontend scripts using the swc parser compiled to wasm to extract any references to outputs. It allows Windmill to suggest dependencies for frontend scripts.
Re-evaluated on changes to
Evals are by default evaluated on changes to the outputs that have been identified. The auto-evaluation can be toggled off.
This option is available only when "Recompute on any input changes" is enabled.
This background script's input
message
will be re-evaluated on any change to the value of componentb
(a Text input).
Transform to a frontend script
Clicking on the dedicated link, evals can be "transformed to a frontend script".
Creating a runnable
There are several ways to create runnables:
- Create an inline script from a component.
- Select a script or a flow from the list of detached inline scripts, workspace scripts and flows, or Hub scripts.
- Create a background runnable.
When you create an inline script attached to a component or a background script, the first thing you need to do is to choose the language of the script.
The outputs of your script can be used by other scripts or components:
A component can have runnables attached to it. Depending on the component type, the runnables are executed at different times:
- Data source: A runnable or another component's result is used as the runnable's data source. It executes on app start and app refresh. For instance, if the component is a table, the runnable's result is used as the table's data source.
- Event handler: The runnable executes when the event is triggered. For instance, if the component is a button, the runnable executes when the button is clicked.
- Validations: Only the Stepper component has validations. The runnable executes when the step of the stepper changes.
Runnable configuration
The runnable configuration consists of:
- Transformer: a transformer is a function that alters the output of the runnable, often used to format the output.
- Triggers:
- Hide refresh button: some components have a refresh button. This can be hidden by checking this box.
- Inputs: the runnable inputs are inferred from the inputs of the flow or script parameters this component is attached to.
- Trigger runnables on success: trigger other components to recompute.
Transformer
A transformer is an optional frontend script executed immediately after the component's script. It's used to perform lightweight transformations in the browser. It accepts the previous computation's result as result
.
For instance, if component has a script that returns:
{
data: {
name: 'John',
age: 20
}
}
A transformer could be used to extract the data object:
return result.data;
Here is an example of a transformer used with a background script to automatically download a file upon input change (here the selected row of a table).
Configure triggers
Run on start and app refresh
Two types of runnables can be configured to run on app start and app refresh:
- Background runnables.
- Component runnables used as data sources: Runnables attached to a component and used as the component's data source.
You may want to disable this so that the background runnable is only triggered by changes to other values or triggered by another computation on a button.
Recompute on Any Input Change
Runnables can be configured to recompute whenever an input changes. This is useful for recomputing a component's runnable when an input changes.
Two types of runnables can be configured to recompute on any input change:
- Background runnables.
- Component runnables used as data sources: Runnables attached to a component and used as the component's data source.
Inputs of runnables that are either connected to an output or evaluated can trigger a recompute. These are displayed in the Change on value
section.
When "Recompute on Any Input Change" is enabled, it can be disabled at the input level, toggling off "Re-evaluated on changes to".
Trigger runnables on success
Button & Form components and background scripts can trigger other components to recompute. For example, a button can trigger a table to recompute. When the button is clicked, the table is reloaded.
Static resource select only / Resources from users allowed
Apps are executed on behalf of publishers and by default cannot access viewer's resources.
If the resource passed here as a reference does not come from a static Resource select component (which will be whitelisted by the auto-generated policy), you need to toggle "Resources from users allowed".
The toggle "Static resource select only / Resources from users allowed" can be found for each runnable input when the source is an eval.
Manual dependencies
Frontend scripts don't have any inputs. However, you can manually specify a frontend script's dependencies. This is useful when you want to recompute a frontend script when an input changes.
Manual dependencies are added clicking on Add dependency
button and can be removed by clicking on the x
button.
Cache app inline scripts
Caching an app inline script means caching the results of that script for a certain duration. If the script is triggered with the same inputs during the given duration, it will return the cached result.
You can enable caching for an app inline script directly its editor settings. Here's how you can do it:
- Settings: From the Code editor, go to the top bar and pick the
Cache
tab. - Enable Caching: To enable caching, toggle on "Cache the results for each possible inputs" and specify the desired duration for caching results (in seconds.)
In the above example, the result of step the script will be cached for 5 minutes. If Inline Script 0
is re-triggered with the same input within this period, Windmill will immediately return the cached result.
Frontend scripts helpers
We expose a few functions and global objects to help you interact with the app from a frontend script.
ctx
You can access the context object with the ctx
global variable.
console.log(ctx.email);
Context objects can be seen on the Output menu.
state
The app state is a client-side store that can be used to store data.
You can access the state object with the state
global variable.
console.log(state);
You can update the state directly by manipulating the state
object.
state.foo = 'bar';
goto
Use the goto
function to navigate to a specific URL.
Syntax:
goto(path: string, newTab?: boolean)
Parameter | Type | Description |
---|---|---|
path | string | The URL to navigate to. |
newTab (optional) | boolean | Whether to open the URL in a new tab or not. |
Example:
goto('/apps/1');
goto('https://www.windmill.dev/', true);
setTab
Use the setTab
function to manually set the tab of a Tab component.
This works for all components that have multiple tabs (Tabs, Conditional tabs, Sidebar tabs, Invisible tabs, Stepper, Decision tree).
Syntax:
setTab(id: string, index: string)
Parameter | Type | Description |
---|---|---|
id | string | The id of the component. |
index | string | The index of the tab to set. |
Example:
setTab('a', 1);
Where 'a' is the id of the component and 1 is the index of the tab to set.
recompute
Use the recompute
function to recompute a component.
Syntax:
recompute(id: string)
Parameter | Type | Description |
---|---|---|
id | string | The id of the component to recompute. |
Example:
recompute('a');
getAgGrid
Use the getAgGrid
function to get the ag-grid instance of a table.
Syntax:
getAgGrid(id: string)
setValue
The setValue
function is meant to manually set or force the value of a component. This can be convenient in cases where connecting components is not the easiest pattern.
setValue(id: string, value: any)
Note that it's a bad idea to mix dynamic default value and setValue together.
setSelectedIndex
Use the setSelectedIndex
function to select a row in a table or an AG Grid table.
Syntax:
setSelectedIndex(id: string, index: number)
open
Use the open
function to open a modal or drawer.
Syntax:
open(id: string)
Parameter | Type | Description |
---|---|---|
id | string | The id of the modal or drawer component to open. |
Example:
open('a');
close
Use the close
function to close a modal.
Syntax:
close(id: string)
Parameter | Type | Description |
---|---|---|
id | string | The id of the modal or drawer component to close. |
Example:
close('a');
validate
Make a specific field of a form in a Validate state.
validate(id: string, key: string)
invalidate
Invalidate a specific field of a form.
invalidate(id: string, key: string, error: string)
Parameter | Type | Description |
---|---|---|
id | string | The id of the form component. |
key | string | The key of the field to invalidate. |
error | string | The error message to display. |
validateAll
Make all fields of a form in a Validate state.
validateAll(id: string, key: string)
Parameter | Type | Description |
---|---|---|
id | string | The id of the form component. |
key | string | The key of the field to validate. |
clearFiles
Clear the files of a file input.
clearFiles(id: string)
Parameter | Type | Description |
---|---|---|
id | string | The id of the file input component. |
showToast
Sends a toast notification.
showToast(message: string, error: boolean)
Parameter | Type | Description |
---|---|---|
message | string | The message to display. |
error | boolean | Whether the toast is an error toast or not. |
waitJob
Wait for a job to finish.
waitJob(jobId: string).then(() => {
// do something
})
Parameter | Type | Description |
---|---|---|
jobId | string | The id of the job to wait for. |
Note that the helper returns a promise.
askNewResource
Ask user resource on a UserResourceComponent.
askNewResource(id: string): void
Parameter | Type | Description |
---|---|---|
id | string | The id of the component. |
downloadFile
Download a file from a url, base64 encoded string, dataUrl or S3 object.
downloadFile(input: string | { s3: string; storage?: string; }, fileName?: string): void
Parameter | Type | Description |
---|---|---|
input | string | The url, base64 encoded string, dataUrl or S3 object. |
fileName (optional) | string | The name of the file to download. |
Policy
A viewer of the app will execute the runnables of the app on behalf of the publisher avoiding the risk that a resource or script would not be available to the viewer. To guarantee tight security, a policy is computed at time of saving of the app which only allow the scripts/flows referred to in the app to be called on behalf of. Furthermore, static parameters are not overridable. Hence, users will only be able to use the app as intended by the publisher without risk for leaking resources not used in the app.
To understand how to handle resources in apps, see Static resource select only / Resources from users allowed.