('AppViewerContext')
@@ -91,6 +94,8 @@
runnableClass="!block"
{outputs}
doOnSuccess={resolvedConfig.onSuccess}
+ doOnError={resolvedConfig.onError}
+ {errorHandledByComponent}
triggerable
>
diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte
index ddc374cf36..c4a5b096f9 100644
--- a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte
+++ b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte
@@ -313,6 +313,7 @@
result = res
if (res?.error) {
recordError(res.error)
+ dispatch('handleError', res.error.message)
} else {
dispatch('success')
}
diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte
index 026660840b..650fbfacf1 100644
--- a/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte
+++ b/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte
@@ -12,6 +12,25 @@
export let componentInput: AppInput | undefined
+ type SideEffectAction =
+ | {
+ selected: 'gotoUrl' | 'none' | 'setTab' | 'sendToast' | 'sendErrorToast' | 'errorOverlay'
+ configuration: {
+ gotoUrl: { url: string | undefined; newTab: boolean | undefined }
+ setTab: {
+ setTab: { id: string; index: number }[] | undefined
+ }
+ sendToast?: {
+ message: string | undefined
+ }
+ sendErrorToast?: {
+ message: string | undefined
+ appendError: boolean | undefined
+ }
+ }
+ }
+ | undefined
+
export let id: string
export let result: any = undefined
export let initializing: boolean = true
@@ -22,20 +41,8 @@
export let forceSchemaDisplay: boolean = false
export let runnableClass = ''
export let runnableStyle = ''
- export let doOnSuccess:
- | {
- selected: 'gotoUrl' | 'none' | 'setTab' | 'sendToast'
- configuration: {
- gotoUrl: { url: string | undefined; newTab: boolean | undefined }
- setTab: {
- setTab: { id: string; index: number }[] | undefined
- }
- sendToast: {
- message: string | undefined
- }
- }
- }
- | undefined = undefined
+ export let doOnSuccess: SideEffectAction = undefined
+ export let doOnError: SideEffectAction = undefined
export let render: boolean
export let recomputeIds: string[] = []
@@ -75,17 +82,19 @@
)
}
- export function onSuccess() {
- if (recomputeIds) {
+ export function handleSideEffect(success: boolean, errorMessage?: string) {
+ const sideEffect = success ? doOnSuccess : doOnError
+
+ if (recomputeIds && success) {
recomputeIds.forEach((id) => $runnableComponents?.[id]?.cb())
}
- if (!doOnSuccess) return
+ if (!sideEffect) return
- if (doOnSuccess.selected == 'none') return
+ if (sideEffect.selected == 'none') return
- if (doOnSuccess.selected == 'setTab') {
- if (Array.isArray(doOnSuccess.configuration.setTab.setTab)) {
- doOnSuccess.configuration.setTab?.setTab?.forEach((tab) => {
+ if (sideEffect.selected == 'setTab') {
+ if (Array.isArray(sideEffect.configuration.setTab.setTab)) {
+ sideEffect.configuration.setTab?.setTab?.forEach((tab) => {
if (tab) {
const { id, index } = tab
$componentControl[id].setTab?.(index)
@@ -93,21 +102,34 @@
})
}
} else if (
- doOnSuccess.selected == 'gotoUrl' &&
- doOnSuccess.configuration.gotoUrl.url &&
- doOnSuccess.configuration.gotoUrl.url != ''
+ sideEffect.selected == 'gotoUrl' &&
+ sideEffect.configuration.gotoUrl.url &&
+ sideEffect.configuration.gotoUrl.url != ''
) {
- if (doOnSuccess.configuration.gotoUrl.newTab) {
- window.open(doOnSuccess.configuration.gotoUrl.url, '_blank')
+ if (sideEffect.configuration.gotoUrl.newTab) {
+ window.open(sideEffect.configuration.gotoUrl.url, '_blank')
} else {
- goto(doOnSuccess.configuration.gotoUrl.url)
+ goto(sideEffect.configuration.gotoUrl.url)
}
} else if (
- doOnSuccess.selected == 'sendToast' &&
- doOnSuccess.configuration.sendToast.message &&
- doOnSuccess.configuration.sendToast.message != ''
+ sideEffect.selected == 'sendToast' &&
+ sideEffect.configuration.sendToast &&
+ sideEffect.configuration.sendToast.message &&
+ sideEffect.configuration.sendToast.message != ''
) {
- sendUserToast(doOnSuccess.configuration.sendToast.message)
+ sendUserToast(sideEffect.configuration.sendToast.message, !success)
+ } else if (
+ sideEffect.selected == 'sendErrorToast' &&
+ sideEffect.configuration.sendErrorToast &&
+ sideEffect.configuration.sendErrorToast.message &&
+ sideEffect.configuration.sendErrorToast.message != ''
+ ) {
+ sendUserToast(
+ sideEffect.configuration.sendErrorToast.message,
+ true,
+ [],
+ sideEffect.configuration.sendErrorToast.appendError ? errorMessage : undefined
+ )
}
}
@@ -136,7 +158,8 @@
wrapperClass={runnableClass}
wrapperStyle={runnableStyle}
{render}
- on:success={onSuccess}
+ on:success={() => handleSideEffect(true)}
+ on:handleError={(e) => handleSideEffect(false, e.detail)}
{outputs}
{errorHandledByComponent}
>
diff --git a/frontend/src/lib/components/apps/editor/ComponentHeader.svelte b/frontend/src/lib/components/apps/editor/ComponentHeader.svelte
index b2484ee7be..d715c205e6 100644
--- a/frontend/src/lib/components/apps/editor/ComponentHeader.svelte
+++ b/frontend/src/lib/components/apps/editor/ComponentHeader.svelte
@@ -18,6 +18,7 @@
export let shouldHideActions: boolean = false
export let hasInlineEditor: boolean = false
export let inlineEditorOpened: boolean = false
+ export let errorHandledByComponent: boolean = false
let isConditionalWrapperManuallySelected: boolean = false
@@ -165,7 +166,7 @@
{/if}
-{#if error}
+{#if error && !errorHandledByComponent}
{@const json = JSON.parse(JSON.stringify(error))}
{
inlineEditorOpened = !inlineEditorOpened
}}
+ {errorHandledByComponent}
/>
{/if}
@@ -234,6 +236,7 @@
componentInput={component.componentInput}
recomputeIds={component.recomputeIds}
bind:initializing
+ bind:errorHandledByComponent
{render}
/>
{:else if component.type === 'selectcomponent' || component.type === 'resourceselectcomponent'}
@@ -262,6 +265,7 @@
customCss={component.customCss}
componentInput={component.componentInput}
recomputeIds={component.recomputeIds}
+ bind:errorHandledByComponent
{render}
/>
{:else if component.type === 'formbuttoncomponent'}
@@ -273,6 +277,7 @@
customCss={component.customCss}
componentInput={component.componentInput}
recomputeIds={component.recomputeIds}
+ bind:errorHandledByComponent
{render}
/>
{:else if component.type === 'checkboxcomponent'}
diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts
index ee0c04b618..5fc67c1009 100644
--- a/frontend/src/lib/components/apps/editor/component/components.ts
+++ b/frontend/src/lib/components/apps/editor/component/components.ts
@@ -236,16 +236,20 @@ export const selectOptions = {
formorientationOptions: ['Horizontal', 'Vertical']
}
+const labels = {
+ none: 'Do nothing',
+ errorOverlay: 'Show error overlay',
+ gotoUrl: 'Go to an url',
+ setTab: 'Set the tab of a tabs component',
+ sendToast: 'Display a toast notification',
+ sendErrorToast: 'Display an error toast notification'
+}
+
const onSuccessClick = {
type: 'oneOf',
tooltip: 'Action to perform on success',
selected: 'none',
- labels: {
- none: 'Do nothing',
- gotoUrl: 'Go to an url',
- setTab: 'Set the tab of a tabs component',
- sendToast: 'Display a toast notification'
- },
+ labels,
configuration: {
none: {},
gotoUrl: {
@@ -284,6 +288,55 @@ const onSuccessClick = {
}
} as const
+const onErrorClick = {
+ type: 'oneOf',
+ tooltip: 'Action to perform on error',
+ selected: 'errorOverlay',
+ labels,
+ configuration: {
+ errorOverlay: {},
+ gotoUrl: {
+ url: {
+ tooltip: 'Go to the given url, absolute or relative',
+ fieldType: 'text',
+ type: 'static',
+ value: '',
+ placeholder: '/apps/get/foo'
+ },
+ newTab: {
+ tooltip: 'Open the url in a new tab',
+ fieldType: 'boolean',
+ type: 'static',
+ value: true
+ }
+ },
+ setTab: {
+ setTab: {
+ type: 'static',
+ value: [] as Array<{ id: string; index: number }>,
+ fieldType: 'array',
+ subFieldType: 'tab-select',
+ tooltip: 'Set the tabs id and index to go to on error'
+ }
+ },
+ sendErrorToast: {
+ message: {
+ tooltip: 'The message of the toast to diplay',
+ fieldType: 'text',
+ type: 'static',
+ value: '',
+ placeholder: 'Hello there'
+ },
+ appendError: {
+ tooltip: 'Append the error message to the toast',
+ fieldType: 'boolean',
+ type: 'static',
+ value: true
+ }
+ }
+ }
+} as const
+
const paginationOneOf = {
type: 'oneOf',
selected: 'auto',
@@ -451,7 +504,8 @@ export const components = {
fieldType: 'boolean',
onlyStatic: true
},
- onSuccess: onSuccessClick
+ onSuccess: onSuccessClick,
+ onError: onErrorClick
}
}
},
@@ -492,7 +546,8 @@ export const components = {
onlyStatic: true,
selectOptions: selectOptions.buttonSizeOptions
},
- onSuccess: onSuccessClick
+ onSuccess: onSuccessClick,
+ onError: onErrorClick
}
}
},
@@ -535,6 +590,7 @@ export const components = {
selectOptions: selectOptions.buttonSizeOptions
},
onSuccess: onSuccessClick,
+ onError: onErrorClick,
disabled: {
fieldType: 'boolean',
type: 'static',
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
index 09e35df283..3edb7d4a88 100644
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -87,7 +87,8 @@ export type ToastAction = {
export function sendUserToast(
message: string,
error: boolean = false,
- actions: ToastAction[] = []
+ actions: ToastAction[] = [],
+ errorMessage: string | undefined = undefined
): void {
toast.push({
component: {
@@ -95,7 +96,8 @@ export function sendUserToast(
props: {
message,
error,
- actions
+ actions,
+ errorMessage
},
sendIdTo: 'toastId'
},