From 3bbd1a81c788d0ebe9b24a44ee1502ee251a5262 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Wed, 31 Dec 2025 01:28:38 +0200 Subject: [PATCH 1/9] chore(873): Added Import Button --- .../Flow/Controls/ImportExportControls.tsx | 6 +----- GUI/src/i18n/en/common.json | 1 + GUI/src/i18n/et/common.json | 1 + GUI/src/pages/OverviewPage.tsx | 16 ++++++++++++++-- GUI/src/types/service-flow.ts | 5 +++++ GUI/src/utils/service-import.ts | 9 +++++++++ 6 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 GUI/src/utils/service-import.ts diff --git a/GUI/src/components/Flow/Controls/ImportExportControls.tsx b/GUI/src/components/Flow/Controls/ImportExportControls.tsx index 34ad3c884..0d7e903d1 100644 --- a/GUI/src/components/Flow/Controls/ImportExportControls.tsx +++ b/GUI/src/components/Flow/Controls/ImportExportControls.tsx @@ -7,13 +7,9 @@ import { AiOutlineExport, AiOutlineImport } from 'react-icons/ai'; import { updateFlowInputRules } from 'services/flow-builder'; import useServiceStore from 'store/new-services.store'; import useToastStore from 'store/toasts.store'; +import { FlowData } from 'types/service-flow'; import { removeTrailingUnderscores } from 'utils/string-util'; -interface FlowData { - nodes: any[]; - edges: any[]; -} - const ImportExportControls: FC = () => { const { getNodes, getEdges, setNodes, setEdges } = useReactFlow(); const { t } = useTranslation(); diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 06b26397a..66a431fe9 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -145,6 +145,7 @@ "edit": "Edit", "delete": "Delete", "cancel": "Cancel", + "importMany": "Import many", "trainingModuleLink": { "text": "For the service to be accessible to users a new model needs to be trained. Model training happens on the Training Module page", "train": "Train new model" diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 77b202a58..a0c1437c6 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -145,6 +145,7 @@ "edit": "Muuda", "delete": "Kustuta", "cancel": "Tühista", + "importMany": "Impordi mitu", "trainingModuleLink": { "text": "Uute teenuste lõppkasutajatele kättesaadavaks tegemiseks tuleb treenida uus mudel. Mudeli treenimine toimub treeningmooduli alamlehel", "train": "Treeni uus mudel" diff --git a/GUI/src/pages/OverviewPage.tsx b/GUI/src/pages/OverviewPage.tsx index f4a575185..626ffd239 100644 --- a/GUI/src/pages/OverviewPage.tsx +++ b/GUI/src/pages/OverviewPage.tsx @@ -1,5 +1,5 @@ import withAuthorization, { ROLES } from 'hoc/with-authorization'; -import React from 'react'; +import React, { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; @@ -7,16 +7,28 @@ import { Button, Track } from '../components'; import ServicesTable from '../components/ServicesTable'; import { trainingModuleTraining } from '../resources/api-constants'; import { ROUTES } from '../resources/routes-constants'; +import { importServices } from 'utils/service-import'; const OverviewPage: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); + const fileInputRef = useRef(null); + + const triggerFileInput = useCallback(() => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }, []); return ( <>

{t('overview.services')}

- + + + + + diff --git a/GUI/src/types/service-flow.ts b/GUI/src/types/service-flow.ts index b29c943b3..101139bea 100644 --- a/GUI/src/types/service-flow.ts +++ b/GUI/src/types/service-flow.ts @@ -14,6 +14,11 @@ export const EDGE_LENGTH = 5 * GRID_UNIT; const startNodeId = generateUniqueId(); const ghostNodeId = generateUniqueId(); +export interface FlowData { + nodes: any[]; + edges: any[]; +} + export type NodeDataProps = { label: string; onDelete: (id: string) => void; diff --git a/GUI/src/utils/service-import.ts b/GUI/src/utils/service-import.ts new file mode 100644 index 000000000..1ddb6cebe --- /dev/null +++ b/GUI/src/utils/service-import.ts @@ -0,0 +1,9 @@ +import { t } from "i18next"; +import { useCallback, ChangeEvent } from "react"; +import useToastStore from "store/toasts.store"; +import { FlowData } from "types/service-flow"; + +export const importServices = async (event: ChangeEvent) => { + const files = event.target.files; + if (!files) return; +} From 2ba0d1cc9adadfa5e2d4bd6c33438f7195ec205d Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:38:59 +0200 Subject: [PATCH 2/9] chore(873): Added Missing Translations --- GUI/src/i18n/en/common.json | 3 +++ GUI/src/i18n/et/common.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 66a431fe9..ab3a35d56 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -522,6 +522,9 @@ "ROLE_UNAUTHENTICATED": "Unauthenticated" }, "chat": { + "unanswered": "Unanswered", + "forwarded": "Forwarded", + "pending": "Pending", "service-test-error": { "title": "Service test error", "dslName": "Service file", diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index a0c1437c6..8b4a97dff 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -523,6 +523,9 @@ "ROLE_UNAUTHENTICATED": "Autentimata" }, "chat": { + "unanswered": "Vastamata", + "forwarded": "Suunatud", + "pending": "Ootel", "service-test-error": { "title": "Teenuse testimise viga", "dslName": "Teenuse fail", From e23d147e5c2de75e6dd64081668efc9182ab0b08 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:43:29 +0200 Subject: [PATCH 3/9] chore(873): Added handleImportServices function --- GUI/src/i18n/en/common.json | 4 ++ GUI/src/i18n/et/common.json | 4 ++ GUI/src/utils/service-import.ts | 75 +++++++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index ab3a35d56..6f74257c3 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -146,6 +146,10 @@ "delete": "Delete", "cancel": "Cancel", "importMany": "Import many", + "import": { + "importSuccess": "{{count}} service{{lengthCheck}} imported successfully", + "importFailure": "Could not import the following files (Wrong format or corrupted): {{files}}" + }, "trainingModuleLink": { "text": "For the service to be accessible to users a new model needs to be trained. Model training happens on the Training Module page", "train": "Train new model" diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 8b4a97dff..7214c445c 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -146,6 +146,10 @@ "delete": "Kustuta", "cancel": "Tühista", "importMany": "Impordi mitu", + "import": { + "importSuccess": "{{count}} teenus{{lengthCheck}} edukalt imporditud", + "importFailure": "Järgmisi faile ei õnnestunud importida (vale vorming või rikutud): {{files}}" + }, "trainingModuleLink": { "text": "Uute teenuste lõppkasutajatele kättesaadavaks tegemiseks tuleb treenida uus mudel. Mudeli treenimine toimub treeningmooduli alamlehel", "train": "Treeni uus mudel" diff --git a/GUI/src/utils/service-import.ts b/GUI/src/utils/service-import.ts index 1ddb6cebe..d8ad1079c 100644 --- a/GUI/src/utils/service-import.ts +++ b/GUI/src/utils/service-import.ts @@ -1,9 +1,76 @@ +import i18n from "i18n"; import { t } from "i18next"; -import { useCallback, ChangeEvent } from "react"; +import { ChangeEvent } from "react"; import useToastStore from "store/toasts.store"; import { FlowData } from "types/service-flow"; +const isValidFlowData = (data: any): data is FlowData => + data?.nodes && data?.edges && Array.isArray(data.nodes) && Array.isArray(data.edges); + +const handleImportServices = async ( + event: ChangeEvent, +): Promise<{ + validFiles: Array<{ fileName: string; flowData: FlowData }>; + corruptedFiles: string[]; +}> => { + const files = event.target.files; + if (!files) return { validFiles: [], corruptedFiles: [] }; + + const validFiles: Array<{ fileName: string; flowData: FlowData }> = []; + const corruptedFiles: string[] = []; + + const fileProcessingPromises = Array.from(files).map(async (file) => { + const name = file.name.replaceAll(/\s+/g, '_'); + try { + const content = await file.text(); + const flowData = JSON.parse(content) as FlowData; + + if (!isValidFlowData(flowData)) { + throw new Error('Invalid flow data structure'); + } + + validFiles.push({ + fileName: name, + flowData, + }); + } catch (error) { + corruptedFiles.push(name); + console.error(`Error processing file ${name}:`, error); + } + }); + + await Promise.all(fileProcessingPromises); + + return { validFiles, corruptedFiles }; +}; + export const importServices = async (event: ChangeEvent) => { - const files = event.target.files; - if (!files) return; -} + const { validFiles, corruptedFiles } = await handleImportServices(event); + + if (corruptedFiles.length > 0) { + useToastStore.getState().error({ + title: t('global.notificationError'), + message: t('overview.import.importFailure', { files: corruptedFiles.join(', ') }), + }); + } + + if (validFiles.length > 0) { + validFiles.forEach(({ fileName, flowData }) => { + console.log(`Successfully imported ${fileName}`, flowData); + // Add your logic to handle each valid flowData + }); + + if (validFiles.length > 0) { + const lengthCheck = i18n.language === 'en' ? 's' : 'ed'; + useToastStore.getState().success({ + title: t('newService.toast.success'), + message: t('overview.import.importSuccess', { + count: validFiles.length, + lengthCheck: validFiles.length === 1 ? '' : lengthCheck, + }), + }); + } + } + + event.target.value = ''; +}; From 7387df7d58af5d20224d59f1f78df11d16117d58 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:38:52 +0200 Subject: [PATCH 4/9] chore(873): Added Import Services Endpoint --- DSL/Resql/services/POST/add-services.sql | 11 +++++ DSL/Resql/services/POST/get-import-names.sql | 19 +++++++ .../POST/services/import-services.yml | 49 +++++++++++++++++++ GUI/src/resources/api-constants.ts | 1 + GUI/src/services/service-builder.ts | 4 +- GUI/src/utils/service-import.ts | 46 +++++++++-------- 6 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 DSL/Resql/services/POST/add-services.sql create mode 100644 DSL/Resql/services/POST/get-import-names.sql create mode 100644 DSL/Ruuter/services/POST/services/import-services.yml diff --git a/DSL/Resql/services/POST/add-services.sql b/DSL/Resql/services/POST/add-services.sql new file mode 100644 index 000000000..055970c7f --- /dev/null +++ b/DSL/Resql/services/POST/add-services.sql @@ -0,0 +1,11 @@ +INSERT INTO services (name, description, service_id, ruuter_type, structure) +SELECT + name, + '', + gen_random_uuid(), + 'POST'::ruuter_request_type, + structure +FROM UNNEST( + ARRAY[:names]::text[], + ARRAY[:structures]::json[] +) AS t(name, structure); diff --git a/DSL/Resql/services/POST/get-import-names.sql b/DSL/Resql/services/POST/get-import-names.sql new file mode 100644 index 000000000..0e12c7a6e --- /dev/null +++ b/DSL/Resql/services/POST/get-import-names.sql @@ -0,0 +1,19 @@ +WITH input_names AS ( + SELECT TRIM(UNNEST(string_to_array(:names, ','))) AS name +), +processed_names AS ( + SELECT + CASE + WHEN EXISTS ( + SELECT 1 + FROM services s + WHERE s.name = iname.name + AND NOT s.deleted + ) + THEN iname.name || '_' || to_char(NOW() AT TIME ZONE :timezone, 'YYYY_MM_DD_HH24_MI_SS') + ELSE iname.name + END AS processed_name + FROM input_names iname +) +SELECT string_agg(processed_name, ',') AS names +FROM processed_names; diff --git a/DSL/Ruuter/services/POST/services/import-services.yml b/DSL/Ruuter/services/POST/services/import-services.yml new file mode 100644 index 000000000..41b21c1f6 --- /dev/null +++ b/DSL/Ruuter/services/POST/services/import-services.yml @@ -0,0 +1,49 @@ +declaration: + call: declare + version: 0.1 + description: "Decription placeholder for 'IMPORT-SERVICES'" + method: post + accepts: json + returns: json + namespace: service + allowlist: + body: + - field: services + type: object + description: "Body field 'services'" + - field: timezone + type: string + description: "Body field 'timezone'" + + +extract_request_data: + assign: + services: ${incoming.body.services ?? []} + names: ${services.map(s => s.fileName).join(",") ?? []} + timezone: ${incoming.body.timezone} + +get_import_names: + call: http.post + args: + url: "[#SERVICE_RESQL]/get-import-names" + body: + names: ${names} + timezone: ${timezone} + result: import_names_res + +assign_imported_names: + assign: + imported_names: ${import_names_res.response.body[0].names.split(",")} + services: "$=services.map((s, i) => ({ ...s, fileName: imported_names[i] }))=" + +insert_services: + call: http.post + args: + url: "[#SERVICE_RESQL]/add-services" + body: + names: ${services.map(s => s.fileName)} + structures: ${services.map(s => s.flowData)} + result: insert_services_res + +return_result: + return: ${services} diff --git a/GUI/src/resources/api-constants.ts b/GUI/src/resources/api-constants.ts index 4ba1445c6..1017f3eee 100644 --- a/GUI/src/resources/api-constants.ts +++ b/GUI/src/resources/api-constants.ts @@ -36,3 +36,4 @@ export const deleteEndpoint = (): string => `${baseUrl}/services/delete-endpoint export const getSlots = (): string => `${baseUrl}/slots`; export const userStepPreferences = (): string => `${baseUrl}/steps/preferences`; export const getCommonEndpoints = (): string => `${baseUrl}/endpoints/common`; +export const importMultipleServices = (): string => `${baseUrl}/services/import-services`; diff --git a/GUI/src/services/service-builder.ts b/GUI/src/services/service-builder.ts index 379af6fe3..2b528e7ff 100644 --- a/GUI/src/services/service-builder.ts +++ b/GUI/src/services/service-builder.ts @@ -288,7 +288,7 @@ export const validateCondition = (node: NodeDataProps | undefined) => { return isInvalid ? (i18next.t('toast.missing-condition-rules') ?? 'Error') : null; }; -function getYamlContent( +export function getYamlContent( nodes: Node[], edges: Edge[], name: string, @@ -353,7 +353,7 @@ function getYamlContent( finishedFlow.set('declaration', { call: 'declare', version: 0.1, - description: description ?? `Description placeholder for '${name ?? ''}'`, + description: description && description.trim().length > 0 ? description : `Description placeholder for '${name ?? ''}'`, method: 'post', accepts: 'json', returns: 'json', diff --git a/GUI/src/utils/service-import.ts b/GUI/src/utils/service-import.ts index d8ad1079c..0d04dab87 100644 --- a/GUI/src/utils/service-import.ts +++ b/GUI/src/utils/service-import.ts @@ -1,8 +1,11 @@ -import i18n from "i18n"; -import { t } from "i18next"; -import { ChangeEvent } from "react"; -import useToastStore from "store/toasts.store"; -import { FlowData } from "types/service-flow"; +import i18n from 'i18n'; +import { t } from 'i18next'; +import { ChangeEvent } from 'react'; +import { importMultipleServices } from 'resources/api-constants'; +import api from 'services/api'; +import { getYamlContent } from 'services/service-builder'; +import useToastStore from 'store/toasts.store'; +import { FlowData } from 'types/service-flow'; const isValidFlowData = (data: any): data is FlowData => data?.nodes && data?.edges && Array.isArray(data.nodes) && Array.isArray(data.edges); @@ -16,11 +19,11 @@ const handleImportServices = async ( const files = event.target.files; if (!files) return { validFiles: [], corruptedFiles: [] }; - const validFiles: Array<{ fileName: string; flowData: FlowData }> = []; + const validFiles: Array<{ fileName: string; flowData: FlowData; content: any }> = []; const corruptedFiles: string[] = []; const fileProcessingPromises = Array.from(files).map(async (file) => { - const name = file.name.replaceAll(/\s+/g, '_'); + const name = file.name.replaceAll(/\s+/g, '_').replace(/\.[^/.]+$/, ''); try { const content = await file.text(); const flowData = JSON.parse(content) as FlowData; @@ -32,6 +35,7 @@ const handleImportServices = async ( validFiles.push({ fileName: name, flowData, + content: getYamlContent(flowData.nodes, flowData.edges, name, '', false), }); } catch (error) { corruptedFiles.push(name); @@ -55,21 +59,21 @@ export const importServices = async (event: ChangeEvent) => { } if (validFiles.length > 0) { - validFiles.forEach(({ fileName, flowData }) => { - console.log(`Successfully imported ${fileName}`, flowData); - // Add your logic to handle each valid flowData - }); - - if (validFiles.length > 0) { - const lengthCheck = i18n.language === 'en' ? 's' : 'ed'; - useToastStore.getState().success({ - title: t('newService.toast.success'), - message: t('overview.import.importSuccess', { - count: validFiles.length, - lengthCheck: validFiles.length === 1 ? '' : lengthCheck, - }), + api + .post(importMultipleServices(), { + services: validFiles, + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }) + .then(() => { + const lengthCheck = i18n.language === 'en' ? 's' : 'ed'; + useToastStore.getState().success({ + title: t('newService.toast.success'), + message: t('overview.import.importSuccess', { + count: validFiles.length, + lengthCheck: validFiles.length === 1 ? '' : lengthCheck, + }), + }); }); - } } event.target.value = ''; From 3e74392617d016a308dde7eb6924965edb20f2d5 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sat, 3 Jan 2026 23:17:24 +0200 Subject: [PATCH 5/9] chore(873): Fixed Passed Structure --- .../services/POST/services/import-services.yml | 2 +- GUI/src/utils/service-import.ts | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/DSL/Ruuter/services/POST/services/import-services.yml b/DSL/Ruuter/services/POST/services/import-services.yml index 41b21c1f6..893ecdc6c 100644 --- a/DSL/Ruuter/services/POST/services/import-services.yml +++ b/DSL/Ruuter/services/POST/services/import-services.yml @@ -46,4 +46,4 @@ insert_services: result: insert_services_res return_result: - return: ${services} + return: "Services imported successfully" diff --git a/GUI/src/utils/service-import.ts b/GUI/src/utils/service-import.ts index 0d04dab87..220d81512 100644 --- a/GUI/src/utils/service-import.ts +++ b/GUI/src/utils/service-import.ts @@ -4,6 +4,7 @@ import { ChangeEvent } from 'react'; import { importMultipleServices } from 'resources/api-constants'; import api from 'services/api'; import { getYamlContent } from 'services/service-builder'; +import useServiceListStore from 'store/services.store'; import useToastStore from 'store/toasts.store'; import { FlowData } from 'types/service-flow'; @@ -13,13 +14,13 @@ const isValidFlowData = (data: any): data is FlowData => const handleImportServices = async ( event: ChangeEvent, ): Promise<{ - validFiles: Array<{ fileName: string; flowData: FlowData }>; + validFiles: Array<{ fileName: string; flowData: string }>; corruptedFiles: string[]; }> => { const files = event.target.files; if (!files) return { validFiles: [], corruptedFiles: [] }; - const validFiles: Array<{ fileName: string; flowData: FlowData; content: any }> = []; + const validFiles: Array<{ fileName: string; flowData: string; content: any }> = []; const corruptedFiles: string[] = []; const fileProcessingPromises = Array.from(files).map(async (file) => { @@ -34,7 +35,7 @@ const handleImportServices = async ( validFiles.push({ fileName: name, - flowData, + flowData: JSON.stringify({ nodes: flowData.nodes, edges: flowData.edges }), content: getYamlContent(flowData.nodes, flowData.edges, name, '', false), }); } catch (error) { @@ -64,7 +65,7 @@ export const importServices = async (event: ChangeEvent) => { services: validFiles, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, }) - .then(() => { + .then(async () => { const lengthCheck = i18n.language === 'en' ? 's' : 'ed'; useToastStore.getState().success({ title: t('newService.toast.success'), @@ -73,6 +74,10 @@ export const importServices = async (event: ChangeEvent) => { lengthCheck: validFiles.length === 1 ? '' : lengthCheck, }), }); + const pagination = { pageIndex: 0, pageSize: 10 }; + const sorting = [{ id: 'name', desc: false }]; + await useServiceListStore.getState().loadServicesList(pagination, sorting); + await useServiceListStore.getState().loadCommonServicesList(pagination, sorting); }); } From a3a836294de56f13606b902235cb26b4783b707b Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sun, 4 Jan 2026 00:37:11 +0200 Subject: [PATCH 6/9] chore(873): Added Dsls Adding --- .../POST/services/import-services.yml | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/DSL/Ruuter/services/POST/services/import-services.yml b/DSL/Ruuter/services/POST/services/import-services.yml index 893ecdc6c..89a51644b 100644 --- a/DSL/Ruuter/services/POST/services/import-services.yml +++ b/DSL/Ruuter/services/POST/services/import-services.yml @@ -15,7 +15,6 @@ declaration: type: string description: "Body field 'timezone'" - extract_request_data: assign: services: ${incoming.body.services ?? []} @@ -35,15 +34,38 @@ assign_imported_names: assign: imported_names: ${import_names_res.response.body[0].names.split(",")} services: "$=services.map((s, i) => ({ ...s, fileName: imported_names[i] }))=" + file_names: ${services.map(s => s.fileName)} insert_services: call: http.post args: url: "[#SERVICE_RESQL]/add-services" body: - names: ${services.map(s => s.fileName)} + names: ${file_names} structures: ${services.map(s => s.flowData)} - result: insert_services_res + result: insert_services_res + +convert_json_content_to_yml: + call: http.post + args: + url: "[#SERVICE_DMAPPER]/conversion/json_to_yaml_data_multiple" + body: + data: ${services.map(s => s.content)} + result: ymls_res + +prepare_files: + assign: + file_paths: "$=file_names.map(name => `[#RUUTER_SERVICES_POST_PATH]/draft/${name}.tmp`)=" + yaml_contents: ${ymls_res.response.body.yamls} + +add_dsls: + call: http.post + args: + url: "[#SERVICE_DMAPPER]/file-manager/create_multiple" + body: + file_paths: ${file_paths} + contents: ${yaml_contents} + result: add_dsls_res return_result: return: "Services imported successfully" From c070937705f49f775e7f67d42e25e93d46faf4be Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sun, 4 Jan 2026 00:39:38 +0200 Subject: [PATCH 7/9] fix(873): Fixed Format --- GUI/src/pages/OverviewPage.tsx | 17 ++++++++++++----- GUI/src/services/service-builder.ts | 3 ++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/GUI/src/pages/OverviewPage.tsx b/GUI/src/pages/OverviewPage.tsx index 626ffd239..d95ea9132 100644 --- a/GUI/src/pages/OverviewPage.tsx +++ b/GUI/src/pages/OverviewPage.tsx @@ -15,17 +15,24 @@ const OverviewPage: React.FC = () => { const fileInputRef = useRef(null); const triggerFileInput = useCallback(() => { - if (fileInputRef.current) { - fileInputRef.current.click(); - } - }, []); + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }, []); return ( <>

{t('overview.services')}

- + diff --git a/GUI/src/services/service-builder.ts b/GUI/src/services/service-builder.ts index 2b528e7ff..8c341b955 100644 --- a/GUI/src/services/service-builder.ts +++ b/GUI/src/services/service-builder.ts @@ -353,7 +353,8 @@ export function getYamlContent( finishedFlow.set('declaration', { call: 'declare', version: 0.1, - description: description && description.trim().length > 0 ? description : `Description placeholder for '${name ?? ''}'`, + description: + description && description.trim().length > 0 ? description : `Description placeholder for '${name ?? ''}'`, method: 'post', accepts: 'json', returns: 'json', From ad446939cdfff410d01671f537940cafb9356670 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sun, 4 Jan 2026 00:44:05 +0200 Subject: [PATCH 8/9] fix(873): Fixed Lint --- GUI/src/i18n/en/common.json | 3 ++- GUI/src/i18n/et/common.json | 3 ++- GUI/src/pages/OverviewPage.tsx | 2 +- GUI/src/types/service-flow.ts | 4 ++-- GUI/src/utils/service-import.ts | 7 +++++++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 6f74257c3..bf99cce4a 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -148,7 +148,8 @@ "importMany": "Import many", "import": { "importSuccess": "{{count}} service{{lengthCheck}} imported successfully", - "importFailure": "Could not import the following files (Wrong format or corrupted): {{files}}" + "importFailure": "Could not import the following files (Wrong format or corrupted): {{files}}", + "failedToImport": "Failed to import services" }, "trainingModuleLink": { "text": "For the service to be accessible to users a new model needs to be trained. Model training happens on the Training Module page", diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 7214c445c..617619444 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -148,7 +148,8 @@ "importMany": "Impordi mitu", "import": { "importSuccess": "{{count}} teenus{{lengthCheck}} edukalt imporditud", - "importFailure": "Järgmisi faile ei õnnestunud importida (vale vorming või rikutud): {{files}}" + "importFailure": "Järgmisi faile ei õnnestunud importida (vale vorming või rikutud): {{files}}", + "failedToImport": "Teenuste importimine ebaõnnestus" }, "trainingModuleLink": { "text": "Uute teenuste lõppkasutajatele kättesaadavaks tegemiseks tuleb treenida uus mudel. Mudeli treenimine toimub treeningmooduli alamlehel", diff --git a/GUI/src/pages/OverviewPage.tsx b/GUI/src/pages/OverviewPage.tsx index d95ea9132..78f5af73a 100644 --- a/GUI/src/pages/OverviewPage.tsx +++ b/GUI/src/pages/OverviewPage.tsx @@ -2,12 +2,12 @@ import withAuthorization, { ROLES } from 'hoc/with-authorization'; import React, { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; +import { importServices } from 'utils/service-import'; import { Button, Track } from '../components'; import ServicesTable from '../components/ServicesTable'; import { trainingModuleTraining } from '../resources/api-constants'; import { ROUTES } from '../resources/routes-constants'; -import { importServices } from 'utils/service-import'; const OverviewPage: React.FC = () => { const { t } = useTranslation(); diff --git a/GUI/src/types/service-flow.ts b/GUI/src/types/service-flow.ts index 101139bea..cbca36261 100644 --- a/GUI/src/types/service-flow.ts +++ b/GUI/src/types/service-flow.ts @@ -15,8 +15,8 @@ const startNodeId = generateUniqueId(); const ghostNodeId = generateUniqueId(); export interface FlowData { - nodes: any[]; - edges: any[]; + nodes: Node[]; + edges: Edge[]; } export type NodeDataProps = { diff --git a/GUI/src/utils/service-import.ts b/GUI/src/utils/service-import.ts index 220d81512..237acc457 100644 --- a/GUI/src/utils/service-import.ts +++ b/GUI/src/utils/service-import.ts @@ -78,6 +78,13 @@ export const importServices = async (event: ChangeEvent) => { const sorting = [{ id: 'name', desc: false }]; await useServiceListStore.getState().loadServicesList(pagination, sorting); await useServiceListStore.getState().loadCommonServicesList(pagination, sorting); + }) + .catch((error) => { + console.error('Error importing services:', error); + useToastStore.getState().error({ + title: t('global.notificationError'), + message: t('overview.import.failedToImport'), + }); }); } From 8bdcf3cb54b7bda1a7d7b6f0f9aaa1e1e53bfdb1 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:20:53 +0200 Subject: [PATCH 9/9] chore(873): Enhanced handleServicesImport --- GUI/src/utils/service-import.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUI/src/utils/service-import.ts b/GUI/src/utils/service-import.ts index 237acc457..58f310238 100644 --- a/GUI/src/utils/service-import.ts +++ b/GUI/src/utils/service-import.ts @@ -14,7 +14,7 @@ const isValidFlowData = (data: any): data is FlowData => const handleImportServices = async ( event: ChangeEvent, ): Promise<{ - validFiles: Array<{ fileName: string; flowData: string }>; + validFiles: Array<{ fileName: string; flowData: string; content: any }>; corruptedFiles: string[]; }> => { const files = event.target.files;