Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions GUI/src/components/ExportServicesModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
);

useEffect(() => {
void fetchServices(pagination, sorting, searchQuery);

Check failure on line 69 in GUI/src/components/ExportServicesModal/index.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of the "void" operator.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2zkDTmY6xNq8-7g&open=AZ5CZ2zkDTmY6xNq8-7g&pullRequest=1018
}, [fetchServices, pagination, sorting, searchQuery]);

useEffect(() => {
Expand Down Expand Up @@ -98,8 +98,8 @@
try {
const servicesWithStructure = await Promise.all(
selectedServices.map(async (service) => {
const response = await api.post(getServiceById(), { id: service.serviceId, search: '' });
return { ...service, structure: response.data.structure };
const response = await api.post<Service>(getServiceById(), { id: service.serviceId, search: '' });
return response.data;
}),
);
const success = await exportServices(servicesWithStructure);
Expand Down Expand Up @@ -187,7 +187,7 @@
{loadingState === 'error' && (
<Track direction="vertical" gap={16} justify="center" style={{ padding: '3rem' }}>
<label style={{ color: '#d32f2f', textAlign: 'center' }}>{error}</label>
<Button onClick={() => void fetchServices(pagination, sorting, searchQuery)}>{t('global.retry')}</Button>

Check failure on line 190 in GUI/src/components/ExportServicesModal/index.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of the "void" operator.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2zkDTmY6xNq8-7h&open=AZ5CZ2zkDTmY6xNq8-7h&pullRequest=1018
</Track>
)}
{loadingState === 'success' && (
Expand All @@ -206,11 +206,11 @@
pagination={pagination}
setPagination={(state: PaginationState) => {
setPagination(state);
void fetchServices(state, sorting, searchQuery);

Check failure on line 209 in GUI/src/components/ExportServicesModal/index.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of the "void" operator.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2zkDTmY6xNq8-7i&open=AZ5CZ2zkDTmY6xNq8-7i&pullRequest=1018
}}
setSorting={(state: SortingState) => {
setSorting(state);
void fetchServices(pagination, state, searchQuery);

Check failure on line 213 in GUI/src/components/ExportServicesModal/index.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of the "void" operator.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2zkDTmY6xNq8-7j&open=AZ5CZ2zkDTmY6xNq8-7j&pullRequest=1018
}}
isClientSide={false}
meta={{
Expand Down
79 changes: 54 additions & 25 deletions GUI/src/components/Flow/Controls/ImportExportControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@
import useServiceStore from 'store/new-services.store';
import useToastStore from 'store/toasts.store';
import { FlowData } from 'types/service-flow';
import { appendFlowNodes } from 'utils/append-flow-nodes';
import {
applyServiceSettings,
buildServiceSettingsFromStore,
isValidFlowData,
parseFlowArtifact,
serializeFlowArtifact,
} from 'utils/service-flow-artifact';
import { removeTrailingUnderscores } from 'utils/string-util';

const ImportExportControls: FC = () => {
const { getNodes, getEdges } = useReactFlow();
const { getNodes, getEdges, setNodes, setEdges } = useReactFlow();
const { t } = useTranslation();
const { setHasUnsavedChanges, saveToHistory, setNodes: setStoreNodes, setEdges: setStoreEdges } = useServiceStore();
const fileInputRef = useRef<HTMLInputElement>(null);
Expand All @@ -21,13 +29,13 @@

const handleExport = useCallback(async () => {
try {
const dataString = JSON.stringify({ nodes: getNodes(), edges: getEdges() });
const dataString = serializeFlowArtifact(getNodes(), getEdges(), buildServiceSettingsFromStore());
const fileName = `${serviceName != undefined && serviceName != '' ? serviceName : 'flow'}_${format(new Date(), 'yyyy_MM_dd_HH_mm_ss')}.json`;

if ('showSaveFilePicker' in window) {

Check warning on line 35 in GUI/src/components/Flow/Controls/ImportExportControls.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2zYDTmY6xNq8-7d&open=AZ5CZ2zYDTmY6xNq8-7d&pullRequest=1018
try {
const blob = new Blob([dataString], { type: 'application/json' });
const handle = await (window as any).showSaveFilePicker({

Check warning on line 38 in GUI/src/components/Flow/Controls/ImportExportControls.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2zYDTmY6xNq8-7e&open=AZ5CZ2zYDTmY6xNq8-7e&pullRequest=1018
suggestedName: fileName,
types: [
{
Expand Down Expand Up @@ -56,8 +64,9 @@
}, [getNodes, getEdges, serviceName, t]);

const applyImportedFlow = useCallback(
(flowData: FlowData) => {
(flowData: FlowData, settings?: FlowData['settings']) => {
if (isValidFlowData(flowData)) {
applyServiceSettings(settings);
const nodes = flowData.nodes.map((node: any) => {
if (node.type !== 'custom') return node;
node.data = {
Expand Down Expand Up @@ -90,13 +99,14 @@
reader.onload = (e) => {
try {
const content = e.target?.result as string;
const flowData = JSON.parse(content) as FlowData;
const { nodes, edges, settings } = parseFlowArtifact(content);
const flowData = { nodes, edges } as FlowData;
const currentNodes = getNodes().filter((node) => node.type !== 'ghost');

if (currentNodes.length === 1 && currentNodes[0].type === 'start') {
applyImportedFlow(flowData);
applyImportedFlow(flowData, settings);
} else {
setImportedFlowData(flowData);
setImportedFlowData({ ...flowData, settings });
setIsConfirmImportModalVisible(true);
}
} catch (error) {
Expand All @@ -106,7 +116,7 @@
.error({ title: t('global.notificationError'), message: t('serviceFlow.parseError') });
}
};
reader.readAsText(file);

Check warning on line 119 in GUI/src/components/Flow/Controls/ImportExportControls.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Blob#text()` over `FileReader#readAsText(blob)`.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2zYDTmY6xNq8-7f&open=AZ5CZ2zYDTmY6xNq8-7f&pullRequest=1018

if (fileInputRef.current) {
fileInputRef.current.value = '';
Expand All @@ -115,28 +125,46 @@
[getNodes, applyImportedFlow, t],
);

const closeImportModal = useCallback(() => {
setIsConfirmImportModalVisible(false);
setImportedFlowData(null);
}, []);

const handleConfirmImport = useCallback(() => {
if (importedFlowData) {
applyImportedFlow(importedFlowData);
const { settings, ...flowData } = importedFlowData;
applyImportedFlow(flowData, settings);
}
setIsConfirmImportModalVisible(false);
setImportedFlowData(null);
}, [importedFlowData, applyImportedFlow]);
closeImportModal();
}, [importedFlowData, applyImportedFlow, closeImportModal]);

const handleCancelImport = useCallback(() => {
setIsConfirmImportModalVisible(false);
setImportedFlowData(null);
}, []);
const handleImportFlowOnly = useCallback(() => {
if (!importedFlowData) {
closeImportModal();
return;
}

const isValidFlowData = (data: any): data is FlowData => {
return (
data &&
Array.isArray(data.nodes) &&
Array.isArray(data.edges) &&
data.nodes.every((node: any) => node.id && node.type) &&
data.edges.every((edge: any) => edge.id && edge.source && edge.target)
);
};
const { nodes, edges } = appendFlowNodes(getNodes(), getEdges(), importedFlowData.nodes, importedFlowData.edges);
saveToHistory();
setStoreNodes(nodes);
setStoreEdges(edges);
setNodes(nodes);
setEdges(edges);
saveToHistory({ nodes, edges });
setHasUnsavedChanges(true);
closeImportModal();
}, [
importedFlowData,
getNodes,
getEdges,
setNodes,
setEdges,
setStoreNodes,
setStoreEdges,
setHasUnsavedChanges,
saveToHistory,
closeImportModal,
]);

const triggerFileInput = useCallback(() => {
if (fileInputRef.current) {
Expand All @@ -158,12 +186,13 @@
</Button>
</Track>
{isConfirmImportModalVisible && (
<Modal title={t('serviceFlow.popup.confirmImport')} onClose={handleCancelImport}>
<Modal title={t('serviceFlow.popup.confirmImport')} onClose={closeImportModal}>
<Track justify="end" gap={16}>
<Button appearance="primary" onClick={handleConfirmImport}>
{t('global.proceed')}
</Button>
<Button appearance="secondary" onClick={handleCancelImport}>
<Button onClick={handleImportFlowOnly}>{t('serviceFlow.popup.importFlowOnly')}</Button>
<Button appearance="secondary" onClick={closeImportModal}>
{t('global.cancel')}
</Button>
</Track>
Expand Down
7 changes: 5 additions & 2 deletions GUI/src/components/ServicesTable/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import Label from 'components/Label';
import Tooltip from 'components/Tooltip';
import i18n from 'i18n';
import { AiOutlineExport } from 'react-icons/ai';

Check warning on line 6 in GUI/src/components/ServicesTable/columns.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'react-icons/ai' imported multiple times.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2z1DTmY6xNq8-7k&open=AZ5CZ2z1DTmY6xNq8-7k&pullRequest=1018
import { AiOutlineInfoCircle } from 'react-icons/ai';

Check warning on line 7 in GUI/src/components/ServicesTable/columns.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'react-icons/ai' imported multiple times.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2z1DTmY6xNq8-7l&open=AZ5CZ2z1DTmY6xNq8-7l&pullRequest=1018
import { IoCopyOutline } from 'react-icons/io5';
import { MdDeleteOutline, MdOutlineDescription, MdOutlineEdit } from 'react-icons/md';
import { NavigateFunction } from 'react-router-dom';
Expand Down Expand Up @@ -185,8 +185,11 @@
<Button
appearance="text"
onClick={async () => {
const response = await api.post(getServiceById(), { id: props.row.original.serviceId, search: '' });
await exportServices([{ ...props.row.original, structure: response.data.structure }]);
const response = await api.post<Service>(getServiceById(), {
id: props.row.original.serviceId,
search: '',
});
await exportServices([response.data]);
}}
>
<Icon icon={<AiOutlineExport />} size="medium" />
Expand Down
3 changes: 2 additions & 1 deletion GUI/src/i18n/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@
"removeValueAssignment": "Remove element assigned as value",
"assignManualEdit": "Manually edit variable assignment",
"openObjectEditor": "Open object editor",
"confirmImport": "The current service will be rewritten!"
"confirmImport": "The current service will be rewritten!",
"importFlowOnly": "Import Flow Only"
},
"previousVariables": {
"assignElements": "Assign Elements",
Expand Down
3 changes: 2 additions & 1 deletion GUI/src/i18n/et/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@
"removeValueAssignment": "Eemalda element, mis on määratud väärtuseks",
"assignManualEdit": "Muutke muutuja määramist käsitsi",
"openObjectEditor": "Ava objekti redaktor",
"confirmImport": "Teenus kirjutatakse üle!"
"confirmImport": "Teenus kirjutatakse üle!",
"importFlowOnly": "Ainult voo import"
},
"previousVariables": {
"assignElements": "Määra elemendid",
Expand Down
9 changes: 5 additions & 4 deletions GUI/src/store/new-services.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@
serviceResponse = await api.post<Service>(getServiceById(), { id, search: search ?? '' });

const structure = JSON.parse(serviceResponse.data.structure?.value ?? '{}');
const settings = structure?.settings;
let endpoints = serviceResponse.data.endpoints.map(
(
endpoint: Pick<EndpointData, 'endpointId' | 'name' | 'type' | 'fileName'> & {
Expand Down Expand Up @@ -485,18 +486,18 @@
Therefore ignore sonar issue warning in PR */

const initialHistoryState = {
nodes: JSON.parse(JSON.stringify(nodes)),

Check warning on line 489 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7m&open=AZ5CZ20LDTmY6xNq8-7m&pullRequest=1018
edges: JSON.parse(JSON.stringify(edges)),

Check warning on line 490 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7n&open=AZ5CZ20LDTmY6xNq8-7n&pullRequest=1018
};

set({
serviceId: id,
name: serviceResponse.data.name,
name: settings?.title ?? serviceResponse.data.name,
isCommon: serviceResponse.data.isCommon,
description: serviceResponse.data.description,
description: settings?.description ?? serviceResponse.data.description,
slot: serviceResponse.data.slot,
examples: serviceResponse.data.examples,
entities: serviceResponse.data.entities,
examples: settings?.examples ?? serviceResponse.data.examples,
entities: settings?.keywords ?? serviceResponse.data.entities,
edges,
nodes,
endpoints,
Expand Down Expand Up @@ -854,12 +855,12 @@

const currentState = stateOverride
? {
nodes: JSON.parse(JSON.stringify(stateOverride.nodes)),

Check warning on line 858 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7o&open=AZ5CZ20LDTmY6xNq8-7o&pullRequest=1018
edges: JSON.parse(JSON.stringify(stateOverride.edges)),

Check warning on line 859 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7p&open=AZ5CZ20LDTmY6xNq8-7p&pullRequest=1018
}
: {
nodes: JSON.parse(JSON.stringify(nodes)),

Check warning on line 862 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7q&open=AZ5CZ20LDTmY6xNq8-7q&pullRequest=1018
edges: JSON.parse(JSON.stringify(edges)),

Check warning on line 863 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7r&open=AZ5CZ20LDTmY6xNq8-7r&pullRequest=1018
};

const lastState = history[historyIndex];
Expand Down Expand Up @@ -887,7 +888,7 @@
const { history, historyIndex } = get();
if (historyIndex > 0) {
const previousState = history[historyIndex - 1];
let nodes = JSON.parse(JSON.stringify(previousState.nodes));

Check warning on line 891 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7s&open=AZ5CZ20LDTmY6xNq8-7s&pullRequest=1018

nodes = nodes.map((node: any) => {
if (node.type !== 'custom') return node;
Expand All @@ -905,7 +906,7 @@

set({
nodes: nodes,
edges: JSON.parse(JSON.stringify(previousState.edges)),

Check warning on line 909 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7t&open=AZ5CZ20LDTmY6xNq8-7t&pullRequest=1018
historyIndex: historyIndex - 1,
hasUnsavedChanges: true,
});
Expand All @@ -916,7 +917,7 @@
const { history, historyIndex } = get();
if (historyIndex < history.length - 1) {
const nextState = history[historyIndex + 1];
let nodes = JSON.parse(JSON.stringify(nextState.nodes));

Check warning on line 920 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7u&open=AZ5CZ20LDTmY6xNq8-7u&pullRequest=1018

nodes = nodes.map((node: any) => {
if (node.type !== 'custom') return node;
Expand All @@ -934,7 +935,7 @@

set({
nodes: nodes,
edges: JSON.parse(JSON.stringify(nextState.edges)),

Check warning on line 938 in GUI/src/store/new-services.store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `structuredClone(…)` over `JSON.parse(JSON.stringify(…))` to create a deep clone.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ20LDTmY6xNq8-7v&open=AZ5CZ20LDTmY6xNq8-7v&pullRequest=1018
historyIndex: historyIndex + 1,
hasUnsavedChanges: true,
});
Expand Down
8 changes: 8 additions & 0 deletions GUI/src/types/service-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@ export const EDGE_LENGTH = 5 * GRID_UNIT;
const startNodeId = generateUniqueId();
const ghostNodeId = generateUniqueId();

export interface ServiceExportSettings {
readonly title?: string;
readonly description?: string;
readonly examples?: string[];
readonly keywords?: string[];
}
Comment thread
trevorling marked this conversation as resolved.

export interface FlowData {
nodes: Node<NodeDataProps>[];
edges: Edge[];
readonly settings?: ServiceExportSettings;
}

export type NodeDataProps = {
Expand Down
128 changes: 128 additions & 0 deletions GUI/src/utils/append-flow-nodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Edge, Node } from '@xyflow/react';
import { updateFlowInputRules } from 'services/flow-builder';
import useServiceStore from 'store/new-services.store';
import { StepType } from 'types';
import { generateUniqueId, generateUniqueLabel } from 'utils/flow-utils';

export const appendFlowNodes = (
currentNodes: Node[],
currentEdges: Edge[],
importedNodes: Node[],
importedEdges: Edge[],
): { nodes: Node[]; edges: Edge[] } => {
const nodesToAdd = importedNodes.filter((node) => node.type !== 'start' && node.type !== 'ghost');
const importedIds = new Set(nodesToAdd.map((node) => node.id));
const edgesToAdd = importedEdges.filter((edge) => importedIds.has(edge.source) && importedIds.has(edge.target));

if (nodesToAdd.length === 0) {
return { nodes: currentNodes, edges: currentEdges };
}

const store = useServiceStore.getState();
const idMap = new Map<string, string>();
const processedLabels = new Set<string>();

const newNodes = nodesToAdd.map((node) => {
const newId = generateUniqueId();
idMap.set(node.id, newId);

const labelContext = [...currentNodes];
processedLabels.forEach((label) => {
labelContext.push({ id: 'virtual', data: { label }, position: { x: 0, y: 0 } });
});
const uniqueLabel = generateUniqueLabel(node.data?.label as string, labelContext);
processedLabels.add(uniqueLabel);

return {
...node,
id: newId,
selected: false,
data: {
...node.data,
label: uniqueLabel,
onDelete: store.onDelete,
setClickedNode: store.setClickedNode,
onEdit: store.handleNodeEdit,
update: updateFlowInputRules,
},
};
});

const newEdges = edgesToAdd.map((edge) => ({
...edge,
id: generateUniqueId(),
source: idMap.get(edge.source) ?? edge.source,
target: idMap.get(edge.target) ?? edge.target,
}));

const ghostNodes: Node[] = [];
const ghostEdges: Edge[] = [];

const addGhost = (sourceId: string, x: number, y: number, label: string) => {
const ghostNode: Node = {
id: generateUniqueId(),
type: 'ghost',
position: { x, y },
data: { type: 'ghost' },
className: 'ghost',
selectable: false,
draggable: false,
};
ghostEdges.push({
id: generateUniqueId(),
source: sourceId,
target: ghostNode.id,
type: 'step',
animated: true,
deletable: false,
label,
});
ghostNodes.push(ghostNode);
};

nodesToAdd.forEach((node) => {
const newId = idMap.get(node.id);
if (!newId) return;

const stepType = node.data?.stepType;
if (stepType === StepType.Condition || stepType === StepType.Input) {
['Success', 'Failure'].forEach((label, index) => {
if (!edgesToAdd.some((edge) => edge.source === node.id && edge.label === label)) {
addGhost(newId, node.position.x + 200, node.position.y + index * 100, label);
}
});
} else if (stepType === StepType.MultiChoiceQuestion) {
const buttons = (node.data?.multiChoiceQuestion as { buttons?: { title?: unknown }[] })?.buttons ?? [
{ title: 'Jah' },
{ title: 'Ei' },
];
buttons.forEach((button, index) => {
if (!edgesToAdd.some((edge) => edge.source === node.id && edge.label === button.title)) {
const title = typeof button.title === 'string' ? button.title : String(button.title ?? '');

Check warning on line 101 in GUI/src/utils/append-flow-nodes.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'button.title ?? ''' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ5CZ2wXDTmY6xNq8-7b&open=AZ5CZ2wXDTmY6xNq8-7b&pullRequest=1018
addGhost(newId, node.position.x + 200, node.position.y + index * 100, title);
}
});
}
});

const endNodes = nodesToAdd.filter((node) => !edgesToAdd.some((edge) => edge.source === node.id));
const skipEndGhost = new Set([
StepType.FinishingStepEnd,
StepType.FinishingStepRedirect,
StepType.DynamicChoices,
StepType.Condition,
StepType.Input,
StepType.MultiChoiceQuestion,
]);

endNodes.forEach((node) => {
const newId = idMap.get(node.id);
if (!newId || skipEndGhost.has(node.data?.stepType as StepType)) return;
addGhost(newId, node.position.x, node.position.y, '+');
});

return {
nodes: [...currentNodes, ...newNodes, ...ghostNodes],
edges: [...currentEdges, ...newEdges, ...ghostEdges],
};
};
5 changes: 3 additions & 2 deletions GUI/src/utils/service-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import i18n from 'i18n';
import JSZip from 'jszip';
import useToastStore from 'store/toasts.store';
import { Service } from 'types';
import { buildExportPayloadFromService } from 'utils/service-flow-artifact';

const sanitizeFileName = (name: string): string => name.replaceAll(/[^a-z0-9]/gi, '_');
const getTimestamp = (): string => format(new Date(), 'yyyy_MM_dd_HH_mm_ss');
Expand Down Expand Up @@ -53,15 +54,15 @@ export const exportServices = async (services: Service[]): Promise<boolean> => {

if (services.length === 1) {
const service = services[0];
const dataString = service?.structure?.value ?? '{}';
const dataString = buildExportPayloadFromService(service);
const fileName = `${sanitizeFileName(service.name)}_${timestamp}.json`;
const blob = new Blob([dataString], { type: 'application/json' });
const success = await downloadBlob(blob, fileName, 'application/json', 'json');
return success;
} else {
const zip = new JSZip();
services.forEach((service) => {
const dataString = service?.structure?.value ?? '{}';
const dataString = buildExportPayloadFromService(service);
const fileName = `${sanitizeFileName(service.name)}_${timestamp}.json`;
zip.file(fileName, dataString);
});
Expand Down
Loading
Loading