feat(frontend): app on error (#1556)

* feat(frontend): app on error

* feat(frontend): Add default onError errorOverlay + add toggle to optionally append error to the error toast

* feat(frontend): Add default onError errorOverlay + add toggle to optionally append error to the error toast

* feat(frontend): hide bug icon

* feat(frontend): add missing ?

* feat(frontend): simplify the code
This commit is contained in:
Faton Ramadani
2023-05-10 16:32:55 +02:00
committed by GitHub
parent 77c1bc80da
commit 5b6582c673
10 changed files with 152 additions and 43 deletions
+7
View File
@@ -9,6 +9,7 @@
export let toastId: string
export let error: boolean = false
export let actions: ToastAction[] = []
export let errorMessage: string | undefined = undefined
function handleClose() {
toast.pop(toastId)
@@ -36,7 +37,13 @@
</div>
<div class="ml-3 flex-1 w-0">
<p class="text-sm text-gray-500">{message}</p>
{#if errorMessage}
<p class="text-sm text-gray-500 border bg-gray-50 p-2 w-full overflow-auto mt-2">
{errorMessage}
</p>
{/if}
</div>
<div class="ml-4 flex flex-shrink-0">
<button
type="button"
@@ -24,6 +24,7 @@
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
export let controls: { left: () => boolean; right: () => boolean | string } | undefined =
@@ -37,6 +38,7 @@
)
$: initializing = resolvedConfig?.label == undefined
$: errorHandledByComponent = resolvedConfig?.onError?.selected !== 'errorOverlay'
let outputs = initOutput($worldStore, id, {
result: undefined,
@@ -111,6 +113,8 @@
bind:loading
{componentInput}
doOnSuccess={resolvedConfig.onSuccess}
doOnError={resolvedConfig.onError}
{errorHandledByComponent}
{id}
{extraQueryParams}
autoRefresh={false}
@@ -21,6 +21,9 @@
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let customCss: ComponentCustomCSS<'formcomponent'> | undefined = undefined
export let render: boolean
export let errorHandledByComponent: boolean | undefined = false
$: errorHandledByComponent = resolvedConfig?.onError?.selected !== 'errorOverlay'
export const staticOutputs: string[] = ['loading', 'result']
@@ -65,6 +68,8 @@
{componentInput}
{id}
doOnSuccess={resolvedConfig.onSuccess}
doOnError={resolvedConfig.onError}
{errorHandledByComponent}
{extraQueryParams}
autoRefresh={false}
forceSchemaDisplay={true}
@@ -26,6 +26,9 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'formbuttoncomponent'> | undefined = undefined
export let render: boolean
export let errorHandledByComponent: boolean | undefined = false
$: errorHandledByComponent = resolvedConfig?.onError?.selected !== 'errorOverlay'
const { app, worldStore, componentControl } = getContext<AppViewerContext>('AppViewerContext')
@@ -91,6 +94,8 @@
runnableClass="!block"
{outputs}
doOnSuccess={resolvedConfig.onSuccess}
doOnError={resolvedConfig.onError}
{errorHandledByComponent}
triggerable
>
<div class="flex flex-col gap-2 px-4 w-full">
@@ -313,6 +313,7 @@
result = res
if (res?.error) {
recordError(res.error)
dispatch('handleError', res.error.message)
} else {
dispatch('success')
}
@@ -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
)
}
}
</script>
@@ -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}
>
@@ -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 @@
</div>
{/if}
{#if error}
{#if error && !errorHandledByComponent}
{@const json = JSON.parse(JSON.stringify(error))}
<span
title="Error"
@@ -59,6 +59,7 @@
movingcomponents != undefined && $mode == 'dnd' && $movingcomponents?.includes(component.id)
let initializing: boolean | undefined = undefined
let errorHandledByComponent: boolean = false
let componentContainerHeight: number = 0
let inlineEditorOpened: boolean = false
@@ -94,6 +95,7 @@
on:triggerInlineEditor={() => {
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'}
@@ -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',
+4 -2
View File
@@ -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'
},