From e19c4f1c8fded770ce5f11226c5c0d35fdb608da Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Tue, 19 May 2026 17:10:11 +0200 Subject: [PATCH 1/7] Improve Api handling --- .../directory-picker/directory-picker.tsx | 6 ++-- src/main/frontend/app/components/toast.tsx | 6 ++-- .../routes/projectlanding/project-landing.tsx | 5 +++ src/main/frontend/app/utils/api.ts | 32 +++++++------------ 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/main/frontend/app/components/directory-picker/directory-picker.tsx b/src/main/frontend/app/components/directory-picker/directory-picker.tsx index 0a7f93f3..1b963b9b 100644 --- a/src/main/frontend/app/components/directory-picker/directory-picker.tsx +++ b/src/main/frontend/app/components/directory-picker/directory-picker.tsx @@ -36,11 +36,11 @@ export default function DirectoryPicker({ setEntries(result.entries) setCurrentPath(result.resolvedPath) setParentPath(result.parentPath) - } catch (error_) { - if (error_ instanceof ApiError && error_.status === 403) { + } catch (error) { + if (error instanceof ApiError && error.httpCode === 403) { setError('Access denied') } else { - setError(error_ instanceof Error ? error_.message : 'Failed to load directories') + setError(error instanceof Error ? error.message : 'Failed to load directories') } } finally { setLoading(false) diff --git a/src/main/frontend/app/components/toast.tsx b/src/main/frontend/app/components/toast.tsx index 129b2178..3561ad9c 100644 --- a/src/main/frontend/app/components/toast.tsx +++ b/src/main/frontend/app/components/toast.tsx @@ -18,13 +18,13 @@ const toastStyles = { container: defaultStyle, card: `${toastBaseCard} bg-error`, icon: '', - defaultDuration: 2000, + defaultDuration: 5000, }, WARNING: { container: defaultStyle, card: `${toastBaseCard} bg-warning`, icon: '⚠️', - defaultDuration: 3000, + defaultDuration: 5000, }, INFO: { container: defaultStyle, @@ -36,7 +36,7 @@ const toastStyles = { container: defaultStyle, card: `${toastBaseCard} bg-success`, icon: '✅', - defaultDuration: 3000, + defaultDuration: 2000, }, } as const diff --git a/src/main/frontend/app/routes/projectlanding/project-landing.tsx b/src/main/frontend/app/routes/projectlanding/project-landing.tsx index 626547b3..2700f2d9 100644 --- a/src/main/frontend/app/routes/projectlanding/project-landing.tsx +++ b/src/main/frontend/app/routes/projectlanding/project-landing.tsx @@ -4,6 +4,7 @@ import FfIcon from '/icons/custom/ff!-icon.svg?react' import ArchiveIcon from '/icons/solar/Archive.svg?react' import { fetchInstanceConfigurations, type FFConfiguration } from '~/services/frank-framework-service' import { useProjectStore } from '~/stores/project-store' +import { ApiError } from '~/utils/api' import ConfigurationRow from './configuration-row' import Search from '~/components/search/search' @@ -81,6 +82,10 @@ export default function ProjectLanding() { setFFInstanceName(ffInstance.name) setFFConfiguration(ffInstance.configurations) }) + .catch((error) => { + if (error instanceof ApiError && error.httpCode === 404) return + showErrorToast(error.message) + }) .finally(() => setIsDiscovering(false)) } diff --git a/src/main/frontend/app/utils/api.ts b/src/main/frontend/app/utils/api.ts index 2008af12..68aa9b54 100644 --- a/src/main/frontend/app/utils/api.ts +++ b/src/main/frontend/app/utils/api.ts @@ -19,18 +19,17 @@ const getAuthToken = () => { } interface BackendErrorResponse { - httpStatus: number - messages: string[] - errorCode: string + status: string + error: string } export class ApiError extends Error { constructor( - public status: number, - public messages?: string[], - public errorCode?: string, + public status: string, + public error: string, + public httpCode: number, ) { - super(messages ? messages.join(', ') : status.toString()) + super(error) this.name = 'ApiError' } } @@ -38,14 +37,11 @@ export class ApiError extends Error { export async function apiFetch(path: string, options?: RequestInit): Promise { const isFormData = options?.body instanceof FormData - const defaultHeaders: Record = - options?.body && !isFormData ? { 'Content-Type': 'application/json' } : {} - const headers: Record = { - ...defaultHeaders, - 'X-Session-ID': getAnonymousSessionId(), ...(options?.headers as Record), + 'X-Session-ID': getAnonymousSessionId(), } + if (options?.body && !isFormData) headers['Content-Type'] = 'application/json' const token = getAuthToken() if (token) { @@ -61,15 +57,11 @@ export async function apiFetch(path: string, options?: RequestInit): Promise< const contentType = response.headers.get('content-type') if (contentType?.includes('application/json')) { const error: BackendErrorResponse = await response.json() - throw new ApiError(error.httpStatus, error.messages, error.errorCode) + throw new ApiError(error.status, error.error, response.status) } - throw new ApiError(response.status, [response.statusText]) - } - - const contentType = response.headers.get('content-type') - if (contentType?.includes('application/json')) { - return response.json() + throw new ApiError('Server Error', `HTTP ${response.status} - ${response.statusText}`, response.status) } - return undefined as T + // assume the response is in json as our API should always do, errors can be caught with .catch() + return response.json() } From 235823ae7d5841c30bc237db1466b0e25f96dc06 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Fri, 22 May 2026 16:58:26 +0200 Subject: [PATCH 2/7] Wrap API error / warning logging for userfriend and advanced outputs --- .../file-structure/editor-data-provider.ts | 9 ++++---- .../studio-files-data-provider.ts | 7 +++--- .../use-file-tree-context-menu.ts | 11 +++++----- .../file-structure/use-studio-context-menu.ts | 12 +++++----- .../frontend/app/components/git/git-panel.tsx | 13 ++++++----- src/main/frontend/app/components/toast.tsx | 5 ----- .../providers/frankconfig-xsd-provider.tsx | 3 ++- .../app/routes/datamapper/property-list.tsx | 22 ++++++++----------- .../frontend/app/routes/datamapper/root.tsx | 4 ++-- .../frontend/app/routes/editor/editor.tsx | 14 ++++++------ .../routes/projectlanding/project-landing.tsx | 17 +++++++------- .../app/routes/studio/canvas/flow.tsx | 8 +++---- .../routes/studio/context/sorted-elements.tsx | 5 +++-- src/main/frontend/app/utils/logger.ts | 11 ++++++++++ 14 files changed, 74 insertions(+), 67 deletions(-) create mode 100644 src/main/frontend/app/utils/logger.ts diff --git a/src/main/frontend/app/components/file-structure/editor-data-provider.ts b/src/main/frontend/app/components/file-structure/editor-data-provider.ts index 7e028cb1..7988f179 100644 --- a/src/main/frontend/app/components/file-structure/editor-data-provider.ts +++ b/src/main/frontend/app/components/file-structure/editor-data-provider.ts @@ -1,6 +1,7 @@ import type { TreeItem, TreeItemIndex } from 'react-complex-tree' import type { FileTreeNode } from '~/types/filesystem.types' import { fetchProjectRootTree, fetchDirectoryByPath } from '~/services/file-tree-service' +import { logApiWarning } from '~/utils/logger'; import { sortChildren } from './tree-utilities' import type { DataProviderLike } from './use-file-tree-context-menu' import { BaseFilesDataProvider } from './base-files-data-provider' @@ -50,7 +51,7 @@ export default class EditorFilesDataProvider extends BaseFilesDataProvider(null) const theme = useTheme() diff --git a/src/main/frontend/app/providers/frankconfig-xsd-provider.tsx b/src/main/frontend/app/providers/frankconfig-xsd-provider.tsx index e17759cb..8396e1c2 100644 --- a/src/main/frontend/app/providers/frankconfig-xsd-provider.tsx +++ b/src/main/frontend/app/providers/frankconfig-xsd-provider.tsx @@ -1,5 +1,6 @@ import { createContext, useContext, useEffect, useState, type ReactNode } from 'react' import { fetchFrankConfigXsd } from '~/services/xsd-service' +import {logApiWarning} from "~/utils/logger"; interface FrankConfigXsdContextValue { xsdContent: string | null @@ -13,7 +14,7 @@ export function FrankConfigXsdProvider({ children }: { children: ReactNode }) { useEffect(() => { fetchFrankConfigXsd() .then(setXsdContent) - .catch((error) => console.error('Failed to load FrankConfig XSD:', error)) + .catch((error) => logApiWarning('Failed to load FrankConfig XSD:', error as Error)) }, []) return {children} diff --git a/src/main/frontend/app/routes/datamapper/property-list.tsx b/src/main/frontend/app/routes/datamapper/property-list.tsx index 0523cfb6..ad212005 100644 --- a/src/main/frontend/app/routes/datamapper/property-list.tsx +++ b/src/main/frontend/app/routes/datamapper/property-list.tsx @@ -156,9 +156,10 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { setEditingMapping, openMapping, }) - //Don't add flow as dependancy here, it'll become an infinite loop flow changes every rerender --> updates the memo --> the memo updates the nodetypes --> updating the nodetypes causes react to trigger a rerender resulting in a infinite loop + //UseMemo is used here to ensure nodetype is not changed throughout rerenders. If the variable is updated reactflow throws a warning in the console; + //Don't add flow as dependency here, it'll become an infinite loop flow changes every rerender --> updates the memo --> the memo updates the nodetypes --> updating the nodetypes causes react to trigger a rerender resulting in a infinite loop // eslint-disable-next-line react-hooks/exhaustive-deps - }, [openMapping]) //UseMemo is used here to ensure nodetype is not changed throughout rerenders. If the variable is updated reactflow throws a warning in the console; + }, [openMapping]) useEffect(() => { if (!reactFlowInstance) return @@ -171,7 +172,6 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { window.addEventListener('resize', updateSize) - // delay initial run requestAnimationFrame(updateSize) return () => { @@ -189,14 +189,13 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { }) }, [reactFlowNodes, edges, reactFlowInstance, configDispatch]) - //Updates the outer canvas whenever something is added useEffect(() => { setCanvasSize((size) => updateCanvasSize(reactFlowNodes, size)) }, [reactFlowNodes]) const onRestore = useCallback(() => { const restoreFlow = async () => { - if (config.propertyData) await flow.importJsonConfiguration(JSON.stringify(config.propertyData)) + if (config.propertyData) flow.importJsonConfiguration(JSON.stringify(config.propertyData)) } restoreFlow().then(() => { @@ -258,8 +257,8 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { ) function openMappingModal(sources: NodeLabels[], targets: NodeLabels[]) { - setMappingSources(sources.filter((s) => s.id?.includes('item'))) - setMappingTargets(targets.filter((t) => t.id?.includes('item'))) + setMappingSources(sources.filter((source) => source.id?.includes('item'))) + setMappingTargets(targets.filter((target) => target.id?.includes('item'))) setAddMappingModal(true) } @@ -286,7 +285,7 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { } setEditingNode(null) setAddFieldModal(false) - showSuccessToast('Added property succesfully!') + showSuccessToast('Added property successfully') } catch (error) { if (error instanceof Error) { showErrorToast(error.message) @@ -307,7 +306,7 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { setEdges(updatedEdges) setEditingMapping(null) setAddMappingModal(false) - showSuccessToast('Added mapping succesfully!') + showSuccessToast('Added mapping successfully') setReactFlowNodes((previous) => previous.map((node) => ({ ...node, @@ -343,10 +342,7 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) {
-
+
({ diff --git a/src/main/frontend/app/routes/datamapper/root.tsx b/src/main/frontend/app/routes/datamapper/root.tsx index c43a041f..32f4bb9b 100644 --- a/src/main/frontend/app/routes/datamapper/root.tsx +++ b/src/main/frontend/app/routes/datamapper/root.tsx @@ -75,8 +75,8 @@ export default function Root() {