{
onValueChange(row.id, e.target.value);
setInputValue(e.target.value);
+ updateRowVariable(row.id, e.target.value);
}}
value={inputValue}
placeholder={t("newService.endpoint.variable") + ".."}
/>
) : (
-
- {rowData.variable}
- {rowData.type && `, (${rowData.type})`}
- {rowData.description && `, (${rowData.description})`}
+
+ {row.original.variable}
+ {row.original.type && `, (${row.original.type})`}
+ {row.original.description && `, (${row.original.description})`}
);
};
diff --git a/GUI/src/components/ApiEndpointCard/Endpoints/RequestVariables/columns.tsx b/GUI/src/components/ApiEndpointCard/Endpoints/RequestVariables/columns.tsx
index c91b58c3f..ff8605463 100644
--- a/GUI/src/components/ApiEndpointCard/Endpoints/RequestVariables/columns.tsx
+++ b/GUI/src/components/ApiEndpointCard/Endpoints/RequestVariables/columns.tsx
@@ -10,16 +10,15 @@ import { PreDefinedEndpointEnvVariables } from "types/endpoint";
import { RequestTab } from "types";
interface GetColumnsConfig {
- rowsData: RequestVariablesTabsRowsData,
- updateParams: (isValue: boolean, rowId: string, value: string) => void,
- requestTab: RequestTab,
- deleteVariable: (rowData: RequestVariablesRowData) => void,
- setRowsData: React.Dispatch>,
- updateRowVariable: (id: string, variable: string) => void,
- requestValues: PreDefinedEndpointEnvVariables,
- isLive: boolean,
- updateRowValue: (id: string, value: string) => void,
- getTabsRowsData: () => RequestVariablesTabsRowsData,
+ rowsData: RequestVariablesTabsRowsData;
+ updateParams: (isValue: boolean, rowId: string, value: string) => void;
+ requestTab: RequestTab;
+ deleteVariable: (rowData: RequestVariablesRowData) => void;
+ setRowsData: React.Dispatch>;
+ requestValues: PreDefinedEndpointEnvVariables;
+ isLive: boolean;
+ updateRowField: (id: string, field: "variable" | "value", value: string) => void;
+ getTabsRowsData: () => RequestVariablesTabsRowsData;
}
export const getColumns = ({
@@ -28,10 +27,7 @@ export const getColumns = ({
requestTab,
deleteVariable,
setRowsData,
- updateRowVariable,
- requestValues,
- isLive,
- updateRowValue,
+ updateRowField,
getTabsRowsData,
}: GetColumnsConfig) => {
const columnHelper = createColumnHelper();
@@ -63,8 +59,9 @@ export const getColumns = ({
r.id === props.row.id)?.variable ?? ""}
- updateRowVariable={updateRowVariable}
- rowData={rowsData[requestTab.tab]![+props.row.id]}
+ updateRowVariable={(id, variable) => {
+ updateRowField(id, "variable", variable);
+ }}
onValueChange={(rowId, value) => {
updateParams(false, rowId, value);
}}
@@ -82,11 +79,10 @@ export const getColumns = ({
cell: (props) => (
r.id === props.row.id)?.value ?? ""}
- updateRowValue={updateRowValue}
+ updateRowValue={(id, value) => {
+ updateRowField(id, "value", value);
+ }}
onValueChange={(rowId, value) => {
updateParams(true, rowId, value);
}}
@@ -118,5 +114,5 @@ export const getColumns = ({
);
},
}),
- ]
+ ];
}
diff --git a/GUI/src/components/ApiEndpointCard/Endpoints/RequestVariables/index.tsx b/GUI/src/components/ApiEndpointCard/Endpoints/RequestVariables/index.tsx
index 5f4a4d976..42637fd80 100644
--- a/GUI/src/components/ApiEndpointCard/Endpoints/RequestVariables/index.tsx
+++ b/GUI/src/components/ApiEndpointCard/Endpoints/RequestVariables/index.tsx
@@ -5,7 +5,6 @@ import * as Tabs from "@radix-ui/react-tabs";
import DataTable from "../../../DataTable";
import { RequestTab } from "../../../../types";
import {
- EndpointDefinition,
EndpointData,
EndpointTab,
EndpointVariableData,
@@ -22,7 +21,7 @@ import { PaginationState, SortingState } from "@tanstack/react-table";
type RequestVariablesProps = {
disableRawData?: boolean;
- endpointData: EndpointDefinition;
+ endpoint: EndpointData;
parentEndpointId?: string;
isLive: boolean;
requestValues: PreDefinedEndpointEnvVariables;
@@ -33,25 +32,25 @@ type RequestVariablesProps = {
const RequestVariables: React.FC = ({
disableRawData,
- endpointData,
+ endpoint,
isLive,
requestValues,
requestTab,
setRequestTab,
- parentEndpointId,
onParametersChange,
}) => {
const { t } = useTranslation();
const tabs: EndpointTab[] = [EndpointTab.Params, EndpointTab.Headers, EndpointTab.Body];
const [jsonError, setJsonError] = useState();
const [key, setKey] = useState(0);
- const { setEndpoints, updateEndpointRawData, updateEndpointData } = useServiceStore();
+ const { updateEndpointRawData, updateEndpointData } = useServiceStore();
const [pagination, setPagination] = useState({
pageIndex: 0,
- pageSize: 10,
+ pageSize: 5,
});
const [sorting, setSorting] = useState([]);
+ const [deletedVariable, setDeletedVariable] = useState(undefined);
const constructRow = (id: number, data: EndpointVariableData, nestedLevel: number): RequestVariablesRowData => {
const value = isLive ? data.value : data.testValue;
@@ -72,10 +71,11 @@ const RequestVariables: React.FC = ({
const getTabsRowsData = (): RequestVariablesTabsRowsData => {
return tabs.reduce((tabsRowsData, tab) => {
const rows: RequestVariablesRowData[] = [];
+ const endpointData = endpoint.definitions[0];
if (endpointData) {
if (!endpointData[tab]) return tabsRowsData;
let rowIdx = 0;
- endpointData[tab]!.variables.forEach((variable) => {
+ endpointData[tab].variables.forEach((variable) => {
rows.push(constructRow(rowIdx, variable, 0));
if (["schema", "array"].includes(variable.type)) {
rowIdx = getRowsFromNestedSchema(variable, rowIdx, rows, 1);
@@ -126,6 +126,7 @@ const RequestVariables: React.FC = ({
const getInitialTabsRawData = (): RequestVariablesTabsRawData => {
return tabs.reduce((tabsRawData, tab) => {
+ const endpointData = endpoint.definitions[0];
return { ...tabsRawData, [tab]: endpointData[tab]?.rawData[isLive ? "value" : "testValue"] ?? "" };
}, {});
};
@@ -135,35 +136,51 @@ const RequestVariables: React.FC = ({
const getTabTriggerClasses = (tab: EndpointTab) =>
`endpoint-tab-group__tab-btn ${requestTab.tab === tab ? "active" : ""}`;
- const updateRowVariable = (id: string, variable: string) => {
+ const maintainSingleEmptyRow = (rows: RequestVariablesRowData[]) => {
+ const emptyRow = rows.find(row => row.value === undefined && row.variable === undefined);
+ const nonEmptyRows = rows.filter(row => row.value !== undefined || row.variable !== undefined);
+
+ const baseEmptyRow: RequestVariablesRowData = {
+ id: nonEmptyRows.length.toString(),
+ required: false,
+ isNameEditable: true,
+ nestedLevel: 0,
+ };
+
+ return [
+ ...nonEmptyRows,
+ emptyRow
+ ? { ...baseEmptyRow, ...emptyRow }
+ : baseEmptyRow,
+ ];
+ };
+
+ const updateRowField = (id: string, field: "variable" | "value", newValue: string) => {
setRowsData((prevRowsData) => {
- prevRowsData[requestTab.tab]!.map((row) => {
- if (row.id !== id) return row;
- row.variable = variable;
- return row;
- });
- // if last row name is edited, add a new row
- if (!rowsData[requestTab.tab] || id !== `${rowsData[requestTab.tab]!.length - 1}`) return prevRowsData;
- prevRowsData[requestTab.tab]!.push({
- id: `${rowsData[requestTab.tab]!.length}`,
- required: false,
- isNameEditable: true,
- nestedLevel: 0,
+ const newRowsData = { ...prevRowsData };
+ newRowsData[requestTab.tab] = [...(newRowsData[requestTab.tab] || [])];
+
+ newRowsData[requestTab.tab]!.forEach((row) => {
+ if (row.id !== id) return;
+ row[field] = newValue;
});
- return prevRowsData;
- });
- updateEndpointData(rowsData, endpointData?.id);
- };
- const updateRowValue = (id: string, value: string) => {
- if (!rowsData[requestTab.tab]) return;
- rowsData[requestTab.tab]!.map((row) => {
- if (row.id !== id) return row;
- row.value = value;
- return row;
+ newRowsData[requestTab.tab] = maintainSingleEmptyRow(newRowsData[requestTab.tab] || []);
+ updateEndpointData(newRowsData, endpoint);
+ if (requestTab.tab === "params")
+ onParametersChange(
+ newRowsData[requestTab.tab]
+ ?.filter((row) => row.value && row.variable)
+ .map((row) => ({
+ id: row.endpointVariableId ?? row.id,
+ name: row.variable!,
+ type: row.type ?? "custom",
+ required: row.required ?? false,
+ value: row.value!,
+ })) ?? []
+ );
+ return newRowsData;
});
- updateEndpointData(rowsData, endpointData?.id);
- setKey(key + 1);
};
const checkNestedVariables = (rowVariableId: string, variable: EndpointVariableData) => {
@@ -184,55 +201,65 @@ const RequestVariables: React.FC = ({
};
const deleteVariable = (rowData: RequestVariablesRowData) => {
- setEndpoints((prevEndpoints: EndpointData[]) => {
- const newEndpoints: EndpointData[] = [];
- for (const prevEndpoint of prevEndpoints) {
- const defEndpoint = prevEndpoint.definitions.find((x) => x.id === endpointData.id);
- const endpoint = defEndpoint?.[requestTab.tab];
-
- if (defEndpoint && endpoint) {
- if (rowData.endpointVariableId && endpoint.variables.map((v) => v.id).includes(rowData.endpointVariableId)) {
- endpoint.variables = endpoint.variables.filter((v) => v.id !== rowData.endpointVariableId);
- } else {
- endpoint.variables
- .filter((variable) => ["schema", "array"].includes(variable.type))
- .forEach((variable) => checkNestedVariables(rowData.endpointVariableId!, variable));
- }
- }
+ if (rowData.variable === undefined || rowData.value === undefined) return;
+ const endpointData = endpoint.definitions[0];
+ const defEndpoint = endpoint.definitions.find((x) => x.id === endpointData.id);
+ const endpointTab = defEndpoint?.[requestTab.tab];
- newEndpoints.push(prevEndpoint);
+ if (defEndpoint && endpointTab) {
+ if (rowData.endpointVariableId && endpointTab.variables.map((v) => v.id).includes(rowData.endpointVariableId)) {
+ endpointTab.variables = endpointTab.variables.filter((v) => v.id !== rowData.endpointVariableId);
+ } else {
+ endpointTab.variables
+ .filter((variable) => ["schema", "array"].includes(variable.type))
+ .forEach((variable) => checkNestedVariables(rowData.endpointVariableId!, variable));
}
- return newEndpoints;
- });
+ }
+
+ if (requestTab.tab === "params") {
+ onParametersChange(endpointTab?.variables ?? []);
+ }
+ setDeletedVariable(rowData);
};
const updateParams = (isValue: boolean, rowId: string, value: string) => {
- if (requestTab.tab === "params") {
- if (!rowsData[requestTab.tab]) return;
- const newData = rowsData[requestTab.tab]!.map((row) => {
- if (row.id !== rowId) return row;
- if (isValue) {
- row.value = value;
- } else {
- row.variable = value;
- }
- return row;
- });
+ if (!rowsData[requestTab.tab]) return;
+ const newData = rowsData[requestTab.tab]!.map((row) => {
+ if (row.id !== rowId) return row;
+ if (isValue) {
+ row.value = value;
+ } else {
+ row.variable = value;
+ }
+ return row;
+ });
- const parameters: EndpointVariableData[] = [];
- newData.forEach((row) => {
- if (!row.value || !row.variable) return;
+ const variables: EndpointVariableData[] = [];
+ newData.forEach((row) => {
+ if (!row.value || !row.variable) return;
- parameters.push({
- id: row.endpointVariableId !== undefined ? row.endpointVariableId : row.id,
- name: row.variable,
- type: row.type ?? "custom",
- required: row.required ?? false,
- value: row.value,
- });
- });
+ const newVariable: EndpointVariableData = {
+ id: row.endpointVariableId ?? row.id,
+ name: row.variable,
+ type: row.type ?? "custom",
+ required: row.required ?? false,
+ value: row.value,
+ };
+ variables.push(newVariable);
+ });
- onParametersChange(parameters);
+ if (requestTab.tab === "params") {
+ onParametersChange(variables);
+ } else if (requestTab.tab === "body") {
+ endpoint.definitions[0].body = {
+ variables: variables,
+ rawData: {},
+ };
+ } else if (requestTab.tab === "headers") {
+ endpoint.definitions[0].headers = {
+ variables: variables,
+ rawData: {},
+ };
}
};
@@ -244,13 +271,12 @@ const RequestVariables: React.FC = ({
requestTab,
deleteVariable,
setRowsData,
- updateRowVariable,
+ updateRowField,
requestValues,
isLive,
- updateRowValue,
getTabsRowsData,
}),
- []
+ [deletedVariable]
);
const buildRawDataView = (): JSX.Element => {
@@ -273,7 +299,7 @@ const RequestVariables: React.FC = ({
setKey(key + 1);
}}
>
- Format JSON
+ {t("newService.endpoint.formatJson")}
= ({
name={`${requestTab.tab}-raw-data`}
label={""}
defaultValue={tabRawData[requestTab.tab]}
- onBlur={() => updateEndpointRawData(tabRawData, endpointData.id, parentEndpointId)}
+ onBlur={() => updateEndpointRawData(tabRawData, endpoint)}
onChange={(event) => {
setJsonError(undefined);
tabRawData[requestTab.tab] = event.target.value;
@@ -341,7 +367,7 @@ const RequestVariables: React.FC = ({
<>
= ({
onNameChange,
onCommonChange,
}) => {
- const { deleteEndpoint, changeServiceEndpointType, getAvailableRequestValues } = useServiceStore();
+ const { changeServiceEndpointType, getAvailableRequestValues } = useServiceStore();
const [selectedTab, setSelectedTab] = useState(EndpointEnv.Live);
const [endpointName, setEndpointName] = useState(endpoint.name);
const [isCommonEndpoint, setIsCommonEndpoint] = useState(endpoint.isCommon ?? false);
@@ -44,7 +43,7 @@ const ApiEndpointCard: FC = ({
const getTabTriggerClasses = (tab: EndpointEnv) => `tab-group__tab-btn ${selectedTab === tab ? "active" : ""}`;
- const requestValues = useMemo(() => getAvailableRequestValues(endpoint.endpointId), []);
+ const requestValues = useMemo(() => getAvailableRequestValues(endpoint), []);
return (
= ({
{t("newService.endpoint.single")}
- {isDeletable && (
-
- )}
{[EndpointEnv.Live, EndpointEnv.Test].map((env) => {
return (
@@ -83,7 +76,7 @@ const ApiEndpointCard: FC = ({
onSelectionChange={(selection) => {
setOption(selection);
endpoint.type = selection?.value as EndpointType;
- changeServiceEndpointType(endpoint.endpointId, (selection?.value ?? "custom") as EndpointType);
+ changeServiceEndpointType(endpoint, (selection?.value ?? "custom") as EndpointType);
}}
defaultValue={option?.value}
/>
diff --git a/GUI/src/components/Dropdown/Dropdown.scss b/GUI/src/components/Dropdown/Dropdown.scss
index d383e23fd..48aec92ae 100644
--- a/GUI/src/components/Dropdown/Dropdown.scss
+++ b/GUI/src/components/Dropdown/Dropdown.scss
@@ -1,36 +1,45 @@
-@import 'src/styles/tools/spacing';
-@import 'src/styles/tools/color';
-@import 'src/styles/settings/variables/other';
-@import 'src/styles/settings/variables/typography';
+@import "src/styles/tools/spacing";
+@import "src/styles/tools/color";
+@import "src/styles/settings/variables/other";
+@import "src/styles/settings/variables/typography";
.dropdown {
- background-color: white;
- border-radius: 6px;
- box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35);
- z-index: 1000;
- resize: both;
- overflow: auto;
- width: 400px;
- min-width: 400px;
- max-width: 600px;
- min-height: 500px;
- max-height: 550px;
+ background-color: white;
+ border-radius: 6px;
+ box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35);
+ z-index: 1000;
+ resize: both;
+ overflow: auto;
+ width: 400px;
+ min-width: 400px;
+ max-width: 600px;
+ min-height: 235px;
+ max-height: 75vh;
+ height: auto;
+ display: flex;
+ flex-direction: column;
- &__header {
- padding: get-spacing(haapsalu);
- margin-bottom: 12px;
- display: flex;
- align-items: center;
- border-radius: $veera-radius-m $veera-radius-m 0 0;
- background-color: get-color(black-coral-0);
- border-bottom: 1px solid get-color(black-coral-2);
- }
-
- &__title {
- font-size: large;
- }
-
- &__content {
- padding: 10px;
- }
+ &__header {
+ padding: get-spacing(haapsalu);
+ margin-bottom: 12px;
+ display: flex;
+ align-items: center;
+ border-radius: $veera-radius-m $veera-radius-m 0 0;
+ background-color: get-color(black-coral-0);
+ border-bottom: 1px solid get-color(black-coral-2);
+ flex-shrink: 0;
}
+
+ &__title {
+ font-size: large;
+ }
+
+ &__content {
+ padding: 10px;
+ flex: 1;
+ overflow-y: auto;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ }
+}
diff --git a/GUI/src/components/Dropdown/index.tsx b/GUI/src/components/Dropdown/index.tsx
index fb5d1a25b..e748925fa 100644
--- a/GUI/src/components/Dropdown/index.tsx
+++ b/GUI/src/components/Dropdown/index.tsx
@@ -18,14 +18,14 @@ const Dropdown: FC = ({ open, onOpenChange, trigger, title, child
{trigger}
-
-
- {children}
+
+
+ {children}
diff --git a/GUI/src/components/Flow/EdgeTypes/AddEndpointModal.tsx b/GUI/src/components/Flow/EdgeTypes/AddEndpointModal.tsx
new file mode 100644
index 000000000..6839122fa
--- /dev/null
+++ b/GUI/src/components/Flow/EdgeTypes/AddEndpointModal.tsx
@@ -0,0 +1,90 @@
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { v4 as uuid } from "uuid";
+import { ApiEndpointCard, Button, Modal, Track } from "components";
+import { EndpointData } from "../../../types/endpoint/endpoint-data";
+import useToastStore from "store/toasts.store";
+import useServiceStore from "store/new-services.store";
+import { saveEndpoints } from "../../../services/service-builder";
+
+interface AddEndpointModalProps {
+ onClose: () => void;
+ onUpdatePreferences: (endpointIds: string[]) => void;
+ currentEndpointIds: string[];
+}
+
+const AddEndpointModal: React.FC = ({ onClose, onUpdatePreferences, currentEndpointIds }) => {
+ const { t } = useTranslation();
+ const [endpoint, setEndpoint] = useState({
+ endpointId: uuid(),
+ name: "",
+ definitions: [],
+ isNew: true,
+ });
+ const [endpointName, setEndpointName] = useState("");
+ const [endpointNameExists, setEndpointNameExists] = useState(false);
+ const [isCommonEndpoint, setIsCommonEndpoint] = useState(false);
+ const [isCreatingEndpoint, setIsCreatingEndpoint] = useState(false);
+
+ const handleClose = () => {
+ setEndpoint({ endpointId: uuid(), name: "", definitions: [], isNew: true });
+ setEndpointName("");
+ setIsCommonEndpoint(false);
+ setIsCreatingEndpoint(false);
+ onClose();
+ };
+
+ const handleCreate = () => {
+ const passedEndpoint = endpoint;
+ passedEndpoint.name = endpointName;
+ passedEndpoint.isCommon = isCommonEndpoint;
+ setIsCreatingEndpoint(true);
+
+ saveEndpoints(
+ [passedEndpoint],
+ () => {
+ useServiceStore.getState().addEndpoint(passedEndpoint);
+ // Add the new endpoint to user preferences
+ const newEndpointIds = [...currentEndpointIds, passedEndpoint.endpointId];
+ onUpdatePreferences(newEndpointIds);
+
+ handleClose();
+ useToastStore.getState().success({ title: t("serviceFlow.apiElements.createSuccess") });
+ setIsCreatingEndpoint(false);
+ },
+ (error) => {
+ console.error(`Error creating API endpoint: ${error}`);
+ useToastStore.getState().error({ title: t("serviceFlow.apiElements.createError") });
+ setIsCreatingEndpoint(false);
+ }
+ );
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default AddEndpointModal;
diff --git a/GUI/src/components/Flow/EdgeTypes/CustomEdge.tsx b/GUI/src/components/Flow/EdgeTypes/CustomEdge.tsx
index 68eaf9970..7efd0340c 100644
--- a/GUI/src/components/Flow/EdgeTypes/CustomEdge.tsx
+++ b/GUI/src/components/Flow/EdgeTypes/CustomEdge.tsx
@@ -1,11 +1,11 @@
import { BaseEdge, EdgeLabelRenderer, EdgeProps, getBezierPath } from "@xyflow/react";
import { CSSProperties, memo, useEffect, useState } from "react";
-import { ApiEndpointCard, Button, Collapsible, Dropdown, Modal, StepElement, Track } from "components";
+import { Collapsible, Dropdown, StepElement, Track } from "components";
import useServiceStore from "store/new-services.store";
import ApiEndpoint from "components/ApiEndpoint";
+import AddEndpointModal from "./AddEndpointModal";
import { useTranslation } from "react-i18next";
import { Step, stepsLabels, StepType } from "types";
-import { v4 as uuid } from "uuid";
import {
arrayMove,
SortableContext,
@@ -25,11 +25,19 @@ import {
import { userStepPreferences } from "resources/api-constants";
import api from "services/api";
import useEdgeAdd from "hooks/flow/useEdgeAdd";
-import { EndpointData } from "types/endpoint";
-import { saveEndpoints } from "services/service-builder";
import useToastStore from "store/toasts.store";
import { useParams } from "react-router-dom";
+function reorderElements(elements: T[], activeId: string | number, overId: string | number): T[] {
+ const oldIndex = elements.findIndex((item: any) => item.id === activeId);
+ const newIndex = elements.findIndex((item: any) => item.id === overId);
+ return arrayMove(elements, oldIndex, newIndex);
+}
+
+function getEndpointIds(elements: Step[]): string[] {
+ return elements.map((e) => e.data!.endpointId);
+}
+
function CustomEdge({
id,
label,
@@ -42,7 +50,6 @@ function CustomEdge({
style,
markerEnd,
}: EdgeProps) {
-
const [edgePath, edgeCenterX, edgeCenterY] = getBezierPath({
sourceX,
sourceY,
@@ -54,53 +61,76 @@ function CustomEdge({
const { t } = useTranslation();
const [allElements, setAllElements] = useState([]);
+ const [apiElements, setApiElements] = useState([]);
const [dropdownOpen, setDropdownOpen] = useState(false);
- const steps = useServiceStore((state) => state.mapEndpointsToSteps());
- const contentStyle: CSSProperties = { overflowY: "auto", maxHeight: "245px" };
+ const contentStyle: CSSProperties = {
+ overflowY: "auto",
+ maxHeight: "calc(30vh - 42px)",
+ minHeight: "80px",
+ };
const [isAddEndpointModalVisible, setIsAddEndpointModalVisible] = useState(false);
- const [isCreatingEndpoint, setIsCreatingEndpoint] = useState(false);
- const [endpointNameExists, setEndpointNameExists] = useState(false);
- const [endpoint, setEndpoint] = useState({
- endpointId: uuid(),
- name: "",
- definitions: [],
- isNew: true,
- });
const { id: idParam } = useParams();
- const [endpointName, setEndpointName] = useState(endpoint.name ?? "");
- const [isCommonEndpoint, setIsCommonEndpoint] = useState(endpoint.isCommon ?? false);
const { setHasUnsavedChanges } = useServiceStore();
const stepPreferences = useServiceStore((state) => state.stepPreferences);
+ const mapEndpointsToSteps = useServiceStore((state) => state.mapEndpointsToSteps);
+ const endpoints = useServiceStore((state) => state.endpoints);
const onEdgeAdd = useEdgeAdd(id);
useEffect(() => {
const elements: Step[] = [];
stepPreferences.forEach((preference, index) => {
- elements.push({
- id: index,
- label: t(`${stepsLabels[preference as StepType]}`),
- type: preference as StepType,
- });
+ // Add more steps when they are ready
+ const allowedSteps = [
+ StepType.Condition,
+ StepType.Assign,
+ StepType.MultiChoiceQuestion,
+ StepType.FinishingStepRedirect,
+ StepType.FinishingStepEnd,
+ ];
+
+ if (allowedSteps.includes(preference as StepType)) {
+ elements.push({
+ id: index,
+ label: t(`${stepsLabels[preference as StepType]}`),
+ type: preference as StepType,
+ });
+ }
});
setAllElements(elements);
}, [stepPreferences]);
+ useEffect(() => {
+ const steps = mapEndpointsToSteps();
+ setApiElements(steps);
+ // endpoints in the dependency array below needed to re-run when new endpoints are added
+ }, [mapEndpointsToSteps, endpoints]);
+
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (over && active.id !== over.id) {
setAllElements((elements) => {
- const oldIndex = elements.findIndex((item) => item.id === active.id);
- const newIndex = elements.findIndex((item) => item.id === over.id);
- const newElements = arrayMove(elements, oldIndex, newIndex);
+ const newElements = reorderElements(elements, active.id, over.id);
updateStepPreference(newElements);
return newElements;
});
}
}
+ function handleApiDragEnd(event: DragEndEvent) {
+ const { active, over } = event;
+
+ if (over && active.id !== over.id) {
+ const currentElements = apiElements;
+ const newElements = reorderElements(currentElements, active.id, over.id);
+ setApiElements(newElements);
+ const endpointIds = getEndpointIds(newElements);
+ updateEndpointPreference(endpointIds);
+ }
+ }
+
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
@@ -111,6 +141,14 @@ function CustomEdge({
function updateStepPreference(steps: Step[]) {
api.post(userStepPreferences(), {
steps: steps.map((e) => e.type),
+ endpoints: getEndpointIds(apiElements),
+ });
+ }
+
+ function updateEndpointPreference(endpointIds: string[]) {
+ api.post(userStepPreferences(), {
+ steps: stepPreferences,
+ endpoints: endpointIds,
});
}
@@ -135,41 +173,8 @@ function CustomEdge({
}
>
+ {/* All elements */}
- {
- if (!idParam) {
- useToastStore.getState().error({
- title: t("newService.toast.servieNotFound"),
- message: t("newService.toast.serviceNotFoundEndpointsMessage"),
- });
- } else {
- setIsAddEndpointModalVisible(true);
- }
- }}
- >
- {steps.length > 0 && (
-
- {steps.map((step) => (
- {
- onEdgeAdd(step).then(() => {
- useServiceStore.getState().loadEndpointsResponseVariables();
- });
- setDropdownOpen(false);
- setHasUnsavedChanges(true);
- }}
- />
- ))}
-
- )}
-
-
+
+ {/* API elements */}
+
+ {
+ if (!idParam) {
+ useToastStore.getState().error({
+ title: t("newService.toast.serviceNotFound"),
+ message: t("newService.toast.serviceNotFoundEndpointsMessage"),
+ });
+ } else {
+ setIsAddEndpointModalVisible(true);
+ }
+ }}
+ >
+ {apiElements.length > 0 && (
+
+
+ {apiElements.map((step) => (
+ {
+ onEdgeAdd(step).then(() => {
+ useServiceStore.getState().loadEndpointsResponseVariables();
+ });
+ setDropdownOpen(false);
+ setHasUnsavedChanges(true);
+ }}
+ />
+ ))}
+
+
+ )}
+
+
+
+ {/* Add endpoint modal */}
{isAddEndpointModalVisible && (
- {
- setEndpoint({ endpointId: uuid(), name: "", definitions: [], isNew: true });
- setIsAddEndpointModalVisible(false);
- }}
- >
-
-
-
-
-
-
-
-
+ setIsAddEndpointModalVisible(false)}
+ onUpdatePreferences={updateEndpointPreference}
+ currentEndpointIds={getEndpointIds(apiElements)}
+ />
)}
>
diff --git a/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx b/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx
index 8e30315b4..d84494be0 100644
--- a/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx
+++ b/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx
@@ -12,7 +12,7 @@ import { getTypeColor, isObject } from "utils/object-util";
import Tooltip from "../Tooltip";
import { v4 } from "uuid";
import { getHelperTooltips } from "utils/constants";
-import { datesVariables, helperVariables } from "resources/variables-constants";
+import { datesVariables, environmentVariables, helperVariables } from "resources/variables-constants";
import { Node, Edge } from "@xyflow/react";
import { NodeDataProps } from "types/service-flow";
@@ -20,9 +20,9 @@ type PreviousVariablesProps = {
readonly nodeId: string;
};
-// Unique key for input element, used below to identify it
+// Unique key for predefined elements, used below to identify it
// All other assign element keys are UUIDs
-const INPUT_ELEMENT_KEY = "-1";
+const predefinedInputKeys = ['-1', '-2'];
const PreviousVariables: FC = ({ nodeId }) => {
const { t } = useTranslation();
@@ -68,16 +68,26 @@ const PreviousVariables: FC = ({ nodeId }) => {
// Get Assign variables
const assignNodes: Node[] = previousNodes.filter((node) => node.data.stepType === StepType.Assign) as Node[] ?? [];
const assignElements = assignNodes.map((node) => node.data.assignElements).flat();
- const inputElement: Assign = {
- id: INPUT_ELEMENT_KEY,
- key: "input",
- value: stringToTemplate("incoming.body.input"),
- // Can only be a string array, see trigger-service.yaml in Buerokratt-Chatbot
- // Value is not known at this point, so passing a dummy to correctly infer type
- data: [],
- };
+ const predefinedInputElements: Assign[] = [
+ {
+ id: predefinedInputKeys[0],
+ key: "input",
+ value: stringToTemplate("incoming.body.input"),
+ // Can only be a string array, see trigger-service.yaml in Buerokratt-Chatbot
+ // Value is not known at this point, so passing a dummy to correctly infer type
+ data: [],
+ },
+ {
+ id: predefinedInputKeys[1],
+ key: "Empty Content Type",
+ value: stringToTemplate(""),
+ // Can only be a string array, see trigger-service.yaml in Buerokratt-Chatbot
+ // Value is not known at this point, so passing a dummy to correctly infer type
+ data: [],
+ },
+ ];
- setAssignedVariables([...assignElements, inputElement, ...newAssignElements]);
+ setAssignedVariables([...assignElements, ...predefinedInputElements, ...newAssignElements]);
}, [endpointsVariables, newAssignElements]);
function getCurrentBranchNodesUp(nodes: Node[], edges: Edge[], currentNode: Node) {
@@ -121,6 +131,15 @@ const PreviousVariables: FC = ({ nodeId }) => {
/>
)}
+
+
{
const typeColor = getTypeColor(variable?.value);
- return isObject(variable.data) && variable.id !== INPUT_ELEMENT_KEY ? (
+ return isObject(variable.data) && !predefinedInputKeys.includes(variable.id) ? (
) : (
-
+
= ({ isCommon = false }) => {
.deleteSelectedService(
() => setIsDeletePopupVisible(false),
t("overview.service.toast.deleted"),
- t("overview.service.toast.failed.delete")
+ t("overview.service.toast.failed.delete"),
+ pagination,
+ sorting
);
};
diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json
index f95cc2474..8e555764b 100644
--- a/GUI/src/i18n/en/common.json
+++ b/GUI/src/i18n/en/common.json
@@ -232,7 +232,8 @@
"unsupported": "Sorry, we currently only support GET and POST requests.",
"insertName": "Insert endpoint name...",
"type": "Endpoint type",
- "nameAlreadyExists": "API title must be unique"
+ "nameAlreadyExists": "API title must be unique",
+ "formatJson": "Format JSON"
},
"trainingModuleSetup": "Training module setup",
"serviceSetup": "Service setup",
@@ -257,7 +258,7 @@
"saveConfigFailed": "Failed to save config",
"testResultSuccess": "Test result- success",
"testResultError": "Test result - error",
- "servieNotFound": "Service not found",
+ "serviceNotFound": "Service not found",
"serviceNotFoundEndpointsMessage": "Please save the service first to be able to add endpoints",
"serviceNameAlreadyExists": "Service name already exists",
"elementNameAlreadyExists": "Element name already exists",
@@ -280,7 +281,7 @@
"openNewWebpage": "Open new webpage",
"fileGeneration": "File generation",
"fileSigning": "File signing",
- "conversationEnd": "End conversation",
+ "serviceEnd": "End service",
"redirectConversationToSupport": "Direct to Customer Support",
"rules": "Rules",
"rasaRules": "Rasa Rules",
@@ -342,6 +343,9 @@
"customFormat": "Custom format"
},
"noName": "",
+ "environmentVariables": {
+ "title": "Environment Variables"
+ },
"helpers": {
"title": "Tools",
"map": "Change items",
@@ -387,7 +391,8 @@
"deleteSuccess": "API element deleted successfully",
"deleteError": "Failed to delete API element",
"editSuccess": "API element edited successfully",
- "editError": "Failed to edit API element"
+ "editError": "Failed to edit API element",
+ "public": "Public"
},
"multiChoiceQuestion": {
"questionPlaceholder": "Enter a question",
diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json
index fe5e4e355..5eb608e44 100644
--- a/GUI/src/i18n/et/common.json
+++ b/GUI/src/i18n/et/common.json
@@ -232,7 +232,8 @@
"unsupported": "Vabandust, praegu toetame ainult GET ja POST päringud",
"insertName": "Sisesta otspunkti nimetus...",
"type": "Otspunkti tüüp",
- "nameAlreadyExists": "API pealkiri peab olema unikaalne"
+ "nameAlreadyExists": "API pealkiri peab olema unikaalne",
+ "formatJson": "Formateeri JSON"
},
"trainingModuleSetup": "Avaleht",
"serviceSetup": "Teenuse seadistamine",
@@ -257,7 +258,7 @@
"saveConfigFailed": "Seadete salvestamine ebaõnnestus",
"testResultSuccess": "Testi tulemus - edu",
"testResultError": "Testi tulemus - viga",
- "servieNotFound": "Teenust ei leitud",
+ "serviceNotFound": "Teenust ei leitud",
"serviceNotFoundEndpointsMessage": "Salvesta teenus kõigepealt, et saaksid lisada otspunkte",
"serviceNameAlreadyExists": "Teenuse nimi on juba olemas",
"elementNameAlreadyExists": "Elementi nimi on juba olemas",
@@ -280,7 +281,7 @@
"openNewWebpage": "Uue veebilehe avamine",
"fileGeneration": "Faili genereerimine",
"fileSigning": "Faili allkirjastamine",
- "conversationEnd": "Vestluse lõpetamine",
+ "serviceEnd": "Teenuse lõpetamine",
"redirectConversationToSupport": "Klienditeenindusse suunamine",
"rules": "Reeglid",
"rasaRules": "Rasa reeglid",
@@ -330,6 +331,9 @@
"previousVariables": {
"assignElements": "Määra elemendid",
"noName": "",
+ "environmentVariables": {
+ "title": "Keskkonnamuutujad"
+ },
"dates": {
"title": "Kuupäevad",
"currentDate": "Praegune kuupäev",
@@ -387,7 +391,8 @@
"deleteSuccess": "API element kustutatud",
"deleteError": "API elementi kustutamine ebaõnnestus",
"editSuccess": "API element edukalt muudetud",
- "editError": "API elemendi muutmine ebaõnnestus"
+ "editError": "API elemendi muutmine ebaõnnestus",
+ "public": "Avalik"
},
"multiChoiceQuestion": {
"title": "Mitmevalikuline küsimus",
diff --git a/GUI/src/resources/variables-constants.ts b/GUI/src/resources/variables-constants.ts
index 8de7b4314..b7cde650a 100644
--- a/GUI/src/resources/variables-constants.ts
+++ b/GUI/src/resources/variables-constants.ts
@@ -3,10 +3,16 @@ import { DATE_CONSTANTS, HELPERS_CONSTANTS } from "utils/constants";
import { stringToTemplate } from "utils/string-util";
import { v4 } from "uuid";
-const createTemplate = (id: string, key: string, value: string, tooltip: string | undefined = undefined): Assign => ({
+const createTemplate = (
+ id: string,
+ key: string,
+ value: string,
+ tooltip: string | undefined = undefined,
+ valueFormat: "plain" | "formatted" = "formatted"
+): Assign => ({
id,
key,
- value: stringToTemplate(value),
+ value: valueFormat === 'formatted' ? stringToTemplate(value) : value,
tooltip,
});
@@ -37,3 +43,8 @@ export const helperVariables: Assign[] = [
createTemplate(v4(), `${helpersTrPath}.reduce`, HELPERS_CONSTANTS.REDUCE),
createTemplate(v4(), `${helpersTrPath}.mapAndJoin`, HELPERS_CONSTANTS.MAP_AND_JOIN),
];
+
+export const environmentVariables: Assign[] = [
+ createTemplate(v4(), "XTR", "[#XTR]", undefined, 'plain'),
+ createTemplate(v4(), "Open Search", "[#OPENSEARCH]", undefined, 'plain'),
+];
diff --git a/GUI/src/services/service-builder.ts b/GUI/src/services/service-builder.ts
index c6ffeee5c..dd4588dd0 100644
--- a/GUI/src/services/service-builder.ts
+++ b/GUI/src/services/service-builder.ts
@@ -3,16 +3,10 @@ import { Group, Rule } from "components/FlowElementsPopup/RuleBuilder/types";
import i18next, { t } from "i18next";
import { NodeHtmlMarkdown } from "node-html-markdown";
import { Edge, Node } from "@xyflow/react";
-import {
- createEndpoint,
- createNewService,
- editService,
- testService,
- updateEndpoint,
-} from "resources/api-constants";
+import { createEndpoint, createNewService, editService, testService, updateEndpoint } from "resources/api-constants";
import useServiceStore from "store/new-services.store";
import useToastStore from "store/toasts.store";
-import { Step, StepType } from "types";
+import { StepType } from "types";
import { EndpointData, EndpointVariableData } from "types/endpoint";
import api from "../services/api-dev";
import { NodeDataProps } from "types/service-flow";
@@ -24,7 +18,6 @@ export async function saveEndpoints(endpoints: EndpointData[], onSuccess?: () =>
const tasks: Promise[] = [];
const serviceId = useServiceStore.getState().serviceId;
-
for (const endpoint of endpoints) {
const selectedEndpointType = endpoint.definitions.find((e) => e.isSelected);
if (!selectedEndpointType) continue;
@@ -60,7 +53,6 @@ async function createEndpointAndUpdateState(endpoint: EndpointData): Promise {
};
export const saveFlow = async ({
- steps,
name,
edges,
nodes,
@@ -140,7 +131,7 @@ export const saveFlow = async ({
status = "ready",
}: SaveFlowConfig) => {
try {
- let yamlContent = getYamlContent(nodes, edges, steps, name, description);
+ let yamlContent = getYamlContent(nodes, edges, name, description);
const mcqNodes = nodes.filter(
(node) => node.data?.stepType === StepType.MultiChoiceQuestion
@@ -151,7 +142,7 @@ export const saveFlow = async ({
0,
nodes.findIndex((node) => node.data?.stepType === StepType.MultiChoiceQuestion) + 1
);
- yamlContent = getYamlContent(nodesUpToFirstMcq, edges, steps, name, description);
+ yamlContent = getYamlContent(nodesUpToFirstMcq, edges, name, description);
}
await saveService(
@@ -179,7 +170,7 @@ export const saveFlow = async ({
);
await saveService(
- getYamlContent(branchNodes, branchEdges, steps, serviceName, description),
+ getYamlContent(branchNodes, branchEdges, serviceName, description),
{ name: serviceName, serviceId, description, slot, isCommon, nodes, edges, isNewService } as SaveFlowConfig,
false,
status
@@ -232,7 +223,7 @@ async function saveService(
.catch(onError);
}
-function getYamlContent(nodes: Node[], edges: Edge[], steps: Step[], name: string, description: string): any {
+function getYamlContent(nodes: Node[], edges: Edge[], name: string, description: string): any {
const allRelations: any[] = [];
nodes.forEach((node) => {
@@ -292,8 +283,28 @@ function getYamlContent(nodes: Node[], edges: Edge[], steps: Step[], name: strin
accepts: "json",
returns: "json",
namespace: "service",
+ allowList: {
+ body: [
+ {
+ field: "chatId",
+ type: "string",
+ description: "The chat ID for the message",
+ },
+ {
+ field: "authorId",
+ type: "string",
+ description: "The author ID for the message",
+ },
+ {
+ field: "input",
+ type: "object",
+ description: "The Input from the user",
+ },
+ ],
+ },
});
+ const firstNode = nodes.find((node) => node.type === "custom");
finishedFlow.set("prepare", {
assign: {
chatId: "${incoming.body.chatId}",
@@ -304,18 +315,9 @@ function getYamlContent(nodes: Node[], edges: Edge[], steps: Step[], name: strin
result: "",
},
},
- next: "get_secrets",
- });
-
- const firstNode = nodes.find((node) => node.type === "custom");
- finishedFlow.set("get_secrets", {
- call: "http.get",
- args: {
- url: `${import.meta.env.REACT_APP_API_URL}/secrets-with-priority`,
- },
- result: "secrets",
next: firstNode ? toSnakeCase(firstNode.data.label?.toString() ?? "format_messages") : "format_messages",
});
+
try {
allRelations.forEach((r) => {
const [parentNodeId, childNodeId] = r.split(",");
@@ -356,7 +358,7 @@ function getYamlContent(nodes: Node[], edges: Edge[], steps: Step[], name: strin
}
const nextStep = childNode ? toSnakeCase(childNode.data.label ?? "format_messages") : "format_messages";
- const template = getTemplate(steps, parentNode, parentStepName, nextStep);
+ const template = getTemplate(parentNode, parentStepName, nextStep);
finishedFlow.set(parentStepName, template);
});
@@ -432,7 +434,7 @@ function handleTextField(
finishedFlow: Map,
parentStepName: string,
parentNode: Node,
- childNode: Node | undefined,
+ childNode: Node | undefined
) {
const htmlToMarkdown = new NodeHtmlMarkdown({
textReplace: [
@@ -493,7 +495,7 @@ function handleAssignStep(
parentNode: Node,
finishedFlow: Map,
parentStepName: string,
- childNode: Node | undefined,
+ childNode: Node | undefined
) {
const invalidElementsExist = hasInvalidElements(parentNode.data.assignElements ?? []);
const isInvalid =
@@ -518,7 +520,7 @@ function handleEndpointStep(
parentNode: Node,
finishedFlow: Map,
parentStepName: string,
- childNode: Node | undefined,
+ childNode: Node | undefined
) {
const endpointDefinition = parentNode.data.endpoint?.definitions[0];
const paramsVariables = endpointDefinition?.params?.variables;
@@ -531,6 +533,7 @@ function handleEndpointStep(
args: {
url: endpointDefinition?.url?.split("?")[0] ?? "",
},
+ result: `${parentNode.data.endpoint?.name.replaceAll(" ", "_")}_res`,
next: childNode ? toSnakeCase(childNode.data.label ?? "format_messages") : "format_messages",
};
@@ -562,7 +565,7 @@ function handleMultiChoiceQuestion(
finishedFlow: Map,
parentStepName: string,
parentNode: Node,
- childNode: Node | undefined,
+ childNode: Node | undefined
) {
return finishedFlow.set(parentStepName, {
assign: {
@@ -610,7 +613,7 @@ const getNestedPreDefinedEndpointVariables = (variable: EndpointVariableData, re
}
};
-const getTemplate = (steps: Step[], node: Node, stepName: string, nextStep?: string) => {
+const getTemplate = (node: Node, stepName: string, nextStep?: string) => {
const data = getTemplateDataFromNode(node);
return {
@@ -687,13 +690,11 @@ export const saveFlowClick = async (status: "draft" | "ready" = "ready") => {
const description = useServiceStore.getState().description;
const slot = useServiceStore.getState().slot;
const isCommon = useServiceStore.getState().isCommon;
- const steps = useServiceStore.getState().mapEndpointsToSteps();
const isNewService = useServiceStore.getState().isNewService;
const edges = useServiceStore.getState().edges;
const nodes = useServiceStore.getState().nodes;
await saveFlow({
- steps,
name: !name
? `${t("newService.defaultServiceName").toString()}_${format(new Date(), "dd_MM_yyyy_HH_mm_ss")}`
: name,
diff --git a/GUI/src/store/new-services.store.ts b/GUI/src/store/new-services.store.ts
index 58ab34872..3c6c1f69f 100644
--- a/GUI/src/store/new-services.store.ts
+++ b/GUI/src/store/new-services.store.ts
@@ -1,6 +1,16 @@
import { create } from "zustand";
import { v4 as uuid } from "uuid";
-import { Edge, EdgeChange, Node, NodeChange, ReactFlowInstance, applyEdgeChanges, applyNodeChanges, getIncomers, getOutgoers } from "@xyflow/react";
+import {
+ Edge,
+ EdgeChange,
+ Node,
+ NodeChange,
+ ReactFlowInstance,
+ applyEdgeChanges,
+ applyNodeChanges,
+ getIncomers,
+ getOutgoers,
+} from "@xyflow/react";
import { EndpointData, EndpointEnv, EndpointTab, PreDefinedEndpointEnvVariables } from "types/endpoint";
import {
getCommonEndpoints,
@@ -77,19 +87,15 @@ interface ServiceStoreState {
loadService: (id?: string, resetState?: boolean) => Promise | undefined>;
loadCommonEndpoints: () => Promise;
loadStepPreferences: () => Promise;
- getAvailableRequestValues: (endpointId: string) => PreDefinedEndpointEnvVariables;
+ getAvailableRequestValues: (endpoint: EndpointData) => PreDefinedEndpointEnvVariables;
onNameChange: (endpointId: string, oldName: string, newName: string) => void;
- changeServiceEndpointType: (id: string, type: EndpointType) => void;
+ changeServiceEndpointType: (endpoint: EndpointData, type: EndpointType) => void;
mapEndpointsToSteps: () => Step[];
selectedTab: EndpointEnv;
setSelectedTab: (tab: EndpointEnv) => void;
isLive: () => boolean;
- updateEndpointRawData: (
- rawData: RequestVariablesTabsRawData,
- endpointDataId?: string,
- parentEndpointId?: string
- ) => void;
- updateEndpointData: (data: RequestVariablesTabsRowsData, endpointDataId?: string, parentEndpointId?: string) => void;
+ updateEndpointRawData: (rawData: RequestVariablesTabsRawData, endpoint?: EndpointData) => void;
+ updateEndpointData: (data: RequestVariablesTabsRowsData, endpoint?: EndpointData) => void;
resetState: () => void;
resetAssign: () => void;
resetRules: () => void;
@@ -248,12 +254,14 @@ const useServiceStore = create((set, get, store) => ({
try {
const instance = get().reactFlowInstance;
if (!instance) return;
- const endpointNodes = instance.getNodes().filter((node) => node.data.stepType === StepType.UserDefined) as Node[];
+ const endpointNodes = instance
+ .getNodes()
+ .filter((node) => node.data.stepType === StepType.UserDefined) as Node[];
if (endpointNodes.length === 0) {
set({ endpointsResponseVariables: [] });
return;
- };
-
+ }
+
const endpointsFromNodes = endpointNodes.map((node) => node.data.endpoint);
const requests = endpointsFromNodes.flatMap((e) =>
e?.definitions.map((endpoint) => ({
@@ -283,8 +291,14 @@ const useServiceStore = create((set, get, store) => ({
});
}
+ chips.push({
+ name: "Status Code",
+ value: `${endpoint?.name.replaceAll(" ", "_")}_res.response.statusCodeValue`,
+ data: `${endpoint?.name.replaceAll(" ", "_")}_res.response.statusCodeValue`,
+ });
+
const variable: EndpointResponseVariable = {
- name: endpoint?.name ?? '',
+ name: endpoint?.name ?? "",
chips: chips,
};
@@ -472,8 +486,10 @@ const useServiceStore = create((set, get, store) => ({
},
loadStepPreferences: async () => {
try {
- const response = await api.get<{ response: string[] }>(userStepPreferences());
- set({ stepPreferences: response.data.response });
+ const response = await api.get<{ response: { steps: string[]; endpoints: string[] } }>(userStepPreferences());
+ set({
+ stepPreferences: response.data.response.steps ?? [],
+ });
} catch (error) {
console.error("Failed to load step preferences:", error);
}
@@ -499,15 +515,13 @@ const useServiceStore = create((set, get, store) => ({
const taraVariables = Object.keys(data).map((key) => `{{TARA.${key}}}`);
get().addProductionVariables(taraVariables);
},
- getAvailableRequestValues: (endpointId: string) => {
- const variables = get()
- .endpoints.filter((endpoint) => endpoint.endpointId !== endpointId)
- .map((endpoint) => ({
- id: endpoint.endpointId,
- name: endpoint.name,
- response: endpoint.definitions.find((x) => x.isSelected)?.response ?? [],
- }))
- .flatMap(({ id, name, response }) => response?.map((x) => `{{${name === "" ? id : name}.${x.name}}}`));
+ getAvailableRequestValues: (endpoint: EndpointData) => {
+ const selectedDefinition = endpoint.definitions.find((x) => x.isSelected);
+ const responseVariables = selectedDefinition?.response ?? [];
+
+ const variables = responseVariables.map(
+ (x) => `{{${endpoint.name === "" ? endpoint.endpointId : endpoint.name}.${x.name}}}`
+ );
return {
prod: [...variables, ...get().availableVariables.prod],
@@ -539,17 +553,8 @@ const useServiceStore = create((set, get, store) => ({
},
}));
},
- changeServiceEndpointType: (id: string, type: EndpointType) => {
- const endpoints = get().endpoints.map((x) => {
- if (x.endpointId !== id) return x;
- return {
- ...x,
- type,
- definitions: [],
- };
- });
-
- set({ endpoints });
+ changeServiceEndpointType: (endpoint: EndpointData, type: EndpointType) => {
+ endpoint.type = type;
},
mapEndpointsToSteps: (): Step[] => {
return get()
@@ -566,22 +571,15 @@ const useServiceStore = create((set, get, store) => ({
data: endpoint,
}));
},
- setEndpoints: (callback) => {
- set((state) => ({
- endpoints: callback(state.endpoints),
- }));
- },
+ setEndpoints: () => {},
selectedTab: EndpointEnv.Live,
setSelectedTab: (tab: EndpointEnv) => set({ selectedTab: tab }),
isLive: () => get().selectedTab === EndpointEnv.Live,
- updateEndpointRawData: (data: RequestVariablesTabsRawData, endpointId?: string, parentEndpointId?: string) => {
- if (!endpointId) return;
- const live = get().isLive() ? "value" : "testValue";
+ updateEndpointRawData: (data: RequestVariablesTabsRawData, endpoint?: EndpointData) => {
+ if (!endpoint) return;
+ const live = "value";
- const endpoints = JSON.parse(JSON.stringify(get().endpoints)) as EndpointData[];
- const defEndpoint = endpoints
- .find((x) => x.endpointId === parentEndpointId)
- ?.definitions.find((x) => x.id === endpointId);
+ const defEndpoint = endpoint.definitions[0];
for (const key in data) {
if (defEndpoint?.[key as EndpointTab]) {
@@ -589,18 +587,13 @@ const useServiceStore = create((set, get, store) => ({
}
}
- set({
- endpoints,
- });
+ endpoint.definitions[0] = defEndpoint;
+ return endpoint;
},
- updateEndpointData: (data: RequestVariablesTabsRowsData, endpointId?: string, parentEndpointId?: string) => {
- if (!endpointId) return;
+ updateEndpointData: (data: RequestVariablesTabsRowsData, endpoint?: EndpointData) => {
+ if (!endpoint) return;
- const live = get().isLive() ? "value" : "testValue";
- const endpoints = JSON.parse(JSON.stringify(get().endpoints)) as EndpointData[];
- const defEndpoint = endpoints
- .find((x) => x.endpointId === parentEndpointId)
- ?.definitions.find((x) => x.id === endpointId);
+ const defEndpoint = endpoint.definitions[0];
if (!defEndpoint) return;
@@ -617,21 +610,20 @@ const useServiceStore = create((set, get, store) => ({
name: row.variable,
type: "custom",
required: false,
- [live]: row.value,
+ value: row.value,
});
}
}
for (const variable of keyedDefEndpoint?.variables ?? []) {
const updatedVariable = data[key as EndpointTab]!.find((updated) => updated.endpointVariableId === variable.id);
- variable[live] = updatedVariable?.value;
variable.name = updatedVariable?.variable ?? variable.name;
+ variable.value = updatedVariable?.value ?? variable.value;
}
}
- set({
- endpoints,
- });
+ endpoint.definitions[0] = defEndpoint;
+ return endpoint;
},
reactFlowInstance: null,
setReactFlowInstance: (reactFlowInstance) => set({ reactFlowInstance }),
diff --git a/GUI/src/store/services.store.ts b/GUI/src/store/services.store.ts
index 795af56df..5d362524d 100644
--- a/GUI/src/store/services.store.ts
+++ b/GUI/src/store/services.store.ts
@@ -37,7 +37,13 @@ interface ServiceStoreState {
sorting: SortingState
) => Promise;
checkServiceIntentConnection: (onConnected: (response: Trigger) => void, onNotConnected: () => void) => Promise;
- deleteSelectedService: (onEnd: () => void, successMessage: string, errorMessage: string) => Promise;
+ deleteSelectedService: (
+ onEnd: () => void,
+ successMessage: string,
+ errorMessage: string,
+ pagination: PaginationState,
+ sorting: SortingState
+ ) => Promise;
requestServiceIntentConnection: (
onEnd: () => void,
successMessage: string,
@@ -212,7 +218,7 @@ const useServiceListStore = create((set, get, store) => ({
onNotConnected();
}
},
- deleteSelectedService: async (onEnd, successMessage, errorMessage) => {
+ deleteSelectedService: async (onEnd, successMessage, errorMessage, pagination, sorting) => {
const selectedService = get().selectedService;
if (!selectedService) return;
@@ -222,8 +228,8 @@ const useServiceListStore = create((set, get, store) => ({
type: selectedService?.type,
});
useToastStore.getState().success({ title: successMessage });
- await useServiceListStore.getState().loadServicesList({ pageIndex: 0, pageSize: 10 }, []);
- await useServiceListStore.getState().loadCommonServicesList({ pageIndex: 0, pageSize: 10 }, []);
+ await useServiceListStore.getState().loadServicesList(pagination, sorting);
+ await useServiceListStore.getState().loadCommonServicesList(pagination, sorting);
} catch (error) {
console.error(error);
useToastStore.getState().error({ title: errorMessage });
diff --git a/GUI/src/types/request-variables/request-variables-table-columns.ts b/GUI/src/types/request-variables/request-variables-table-columns.ts
index 3f04629fe..1b41b7366 100644
--- a/GUI/src/types/request-variables/request-variables-table-columns.ts
+++ b/GUI/src/types/request-variables/request-variables-table-columns.ts
@@ -1,5 +1,12 @@
export type RequestVariablesTableColumns = {
+ id: string;
+ isNameEditable: boolean;
required: boolean;
- value: any;
- variable: string;
+ nestedLevel: number;
+ arrayType?: string;
+ description?: string;
+ endpointVariableId?: string;
+ type?: string;
+ value?: string;
+ variable?: string;
};
diff --git a/GUI/src/types/step.ts b/GUI/src/types/step.ts
index b08adc129..e7c5504bc 100644
--- a/GUI/src/types/step.ts
+++ b/GUI/src/types/step.ts
@@ -22,10 +22,9 @@ export const stepsLabels: Record = {
[StepType.FileSign]: "serviceFlow.element.fileSigning",
[StepType.Step]: "serviceFlow.element.step",
[StepType.Rule]: "serviceFlow.element.rule",
- [StepType.FinishingStepEnd]: "serviceFlow.element.conversationEnd",
+ [StepType.FinishingStepEnd]: "serviceFlow.element.serviceEnd",
[StepType.FinishingStepRedirect]: "serviceFlow.element.redirectConversationToSupport",
[StepType.UserDefined]: "serviceFlow.element.userDefined",
[StepType.RasaRules]: "serviceFlow.element.rasaRules",
[StepType.SiGa]: "serviceFlow.element.siga",
};
-
diff --git a/GUI/src/utils/string-util.ts b/GUI/src/utils/string-util.ts
index 4305f3c92..23e3a1098 100644
--- a/GUI/src/utils/string-util.ts
+++ b/GUI/src/utils/string-util.ts
@@ -3,7 +3,7 @@ export const isTemplate = (value: string | number) => {
};
export const stringToTemplate = (value: string | number) => {
- return "${" + value + "}";
+ return value ? "${" + value + "}" : '${""}';
};
export const templateToString = (value: string | number) => {