diff --git a/packages/ui-tests/stories/canvas/CanvasSideBar.stories.tsx b/packages/ui-tests/stories/canvas/CanvasSideBar.stories.tsx
index 3c396a3ad..cbc059258 100644
--- a/packages/ui-tests/stories/canvas/CanvasSideBar.stories.tsx
+++ b/packages/ui-tests/stories/canvas/CanvasSideBar.stories.tsx
@@ -1,10 +1,13 @@
import {
+ CamelRouteVisualEntity,
CanvasNode,
CanvasSideBar,
IVisualizationNode,
IVisualizationNodeData,
NodeIconResolver,
+ VisibleFlowsProvider,
VisualComponentSchema,
+ camelRouteJson,
} from '@kaoto-next/ui/testing';
import { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react';
@@ -53,6 +56,7 @@ const selectedNode: CanvasNode = {
},
} as VisualComponentSchema;
},
+ getBaseEntity: () => new CamelRouteVisualEntity(camelRouteJson.route),
} as IVisualizationNode,
},
};
@@ -75,6 +79,7 @@ const unknownSelectedNode: CanvasNode = {
definition: null,
} as VisualComponentSchema;
},
+ getBaseEntity: () => new CamelRouteVisualEntity(camelRouteJson.route),
} as IVisualizationNode,
},
};
@@ -87,7 +92,11 @@ export default {
const Template: StoryFn = (args) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const handleClose = () => setIsModalOpen(!isModalOpen);
- return ;
+ return (
+
+
+
+ );
};
export const ProcessorNode = Template.bind({});
diff --git a/packages/ui/src/components/Visualization/Canvas/CanvasForm.test.tsx b/packages/ui/src/components/Visualization/Canvas/CanvasForm.test.tsx
index 74c4c4d9f..eb91202ad 100644
--- a/packages/ui/src/components/Visualization/Canvas/CanvasForm.test.tsx
+++ b/packages/ui/src/components/Visualization/Canvas/CanvasForm.test.tsx
@@ -8,43 +8,62 @@ import {
CamelCatalogService,
CamelRouteVisualEntity,
CatalogKind,
+ ICamelComponentDefinition,
ICamelDataformatDefinition,
ICamelLanguageDefinition,
ICamelLoadBalancerDefinition,
ICamelProcessorDefinition,
+ IKameletDefinition,
+ KameletVisualEntity,
KaotoSchemaDefinition,
} from '../../../models';
import { IVisualizationNode, VisualComponentSchema } from '../../../models/visualization/base-visual-entity';
-import { EntitiesContext } from '../../../providers/entities.provider';
+import { VisibleFlowsContext, VisibleFlowsProvider } from '../../../providers';
+import { EntitiesContext, EntitiesProvider } from '../../../providers/entities.provider';
+import { camelRouteJson, kameletJson } from '../../../stubs';
import { SchemaService } from '../../Form';
import { CustomAutoFieldDetector } from '../../Form/CustomAutoField';
import { CanvasForm } from './CanvasForm';
import { CanvasNode } from './canvas.models';
+import { CanvasService } from './canvas.service';
+import { VisualFlowsApi } from '../../../models/visualization/flows/support/flows-visibility';
describe('CanvasForm', () => {
+ let camelRouteVisualEntity: CamelRouteVisualEntity;
+ let selectedNode: CanvasNode;
+ let componentCatalogMap: Record;
+ let patternCatalogMap: Record;
+ let kameletCatalogMap: Record;
const schemaService = new SchemaService();
- const schema = {
- type: 'object',
- properties: {
- name: {
- type: 'string',
- },
- },
- } as unknown as KaotoSchemaDefinition['schema'];
-
beforeAll(async () => {
- const patternCatalog = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.patterns.file);
- delete patternCatalog.default;
+ componentCatalogMap = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.components.file);
+ delete componentCatalogMap.default;
+ const modelCatalog = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.models.file);
+ delete modelCatalog.default;
+ patternCatalogMap = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.patterns.file);
+ delete patternCatalogMap.default;
+ kameletCatalogMap = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.kamelets.file);
+ delete kameletCatalogMap.default;
const languageCatalog = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.languages.file);
delete languageCatalog.default;
const dataformatCatalog = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.dataformats.file);
delete dataformatCatalog.default;
const loadbalancerCatalog = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.loadbalancers.file);
delete loadbalancerCatalog.default;
+ const entitiesCatalog = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.entities.file);
+ delete entitiesCatalog.default;
+ CamelCatalogService.setCatalogKey(
+ CatalogKind.Component,
+ componentCatalogMap as unknown as Record,
+ );
CamelCatalogService.setCatalogKey(
CatalogKind.Pattern,
- patternCatalog as unknown as Record,
+ patternCatalogMap as unknown as Record,
+ );
+ CamelCatalogService.setCatalogKey(
+ CatalogKind.Kamelet,
+ kameletCatalogMap as unknown as Record,
);
CamelCatalogService.setCatalogKey(
CatalogKind.Language,
@@ -58,31 +77,29 @@ describe('CanvasForm', () => {
CatalogKind.Loadbalancer,
loadbalancerCatalog as unknown as Record,
);
+ CamelCatalogService.setCatalogKey(
+ CatalogKind.Entity,
+ entitiesCatalog as unknown as Record,
+ );
});
- it('should render', () => {
- const visualComponentSchema: VisualComponentSchema = {
- title: 'My Node',
- schema,
- definition: {
- name: 'my node',
- },
- };
+ beforeEach(() => {
+ camelRouteVisualEntity = new CamelRouteVisualEntity(camelRouteJson.route);
+ const { nodes } = CanvasService.getFlowDiagram(camelRouteVisualEntity.toVizNode());
+ selectedNode = nodes[2]; // choice
+ });
- const selectedNode: CanvasNode = {
- id: '1',
- type: 'node',
- data: {
- vizNode: {
- getComponentSchema: () => visualComponentSchema,
- } as IVisualizationNode,
- },
- };
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+ it('should render', () => {
const { container } = render(
-
-
- ,
+
+
+
+
+ ,
);
expect(container).toMatchSnapshot();
@@ -95,13 +112,16 @@ describe('CanvasForm', () => {
data: {
vizNode: {
getComponentSchema: () => undefined,
- } as IVisualizationNode,
+ getBaseEntity: () => new CamelRouteVisualEntity(camelRouteJson.route),
+ } as unknown as IVisualizationNode,
},
};
const { container } = render(
-
+
+
+
,
);
@@ -121,13 +141,16 @@ describe('CanvasForm', () => {
data: {
vizNode: {
getComponentSchema: () => visualComponentSchema,
- } as IVisualizationNode,
+ getBaseEntity: () => new CamelRouteVisualEntity(camelRouteJson.route),
+ } as unknown as IVisualizationNode,
},
};
const { container } = render(
-
+
+
+
,
);
@@ -149,20 +172,88 @@ describe('CanvasForm', () => {
data: {
vizNode: {
getComponentSchema: () => visualComponentSchema,
- } as IVisualizationNode,
+ getBaseEntity: () => new CamelRouteVisualEntity(camelRouteJson.route),
+ } as unknown as IVisualizationNode,
},
};
render(
-
+
+
+
,
);
expect(visualComponentSchema.definition.parameters).toEqual({});
});
+ it('should allow consumers to update the Camel Route ID', async () => {
+ const flowId = camelRouteVisualEntity.id;
+ const newName = 'MyNewId';
+ const dispatchSpy = jest.fn();
+ const visualFlowsApi = new VisualFlowsApi(dispatchSpy);
+ const { nodes } = CanvasService.getFlowDiagram(camelRouteVisualEntity.toVizNode());
+ selectedNode = nodes[nodes.length - 1];
+
+ render(
+
+
+
+
+ ,
+ );
+
+ const idField = screen.getAllByLabelText('Id', { selector: 'input' })[0];
+ act(() => {
+ fireEvent.change(idField, { target: { value: newName } });
+ });
+
+ const closeSideBarButton = screen.getByTestId('close-side-bar');
+ act(() => {
+ fireEvent.click(closeSideBarButton);
+ });
+
+ expect(camelRouteVisualEntity.id).toEqual(newName);
+ expect(dispatchSpy).toHaveBeenCalledWith({ type: 'renameFlow', flowId, newName });
+ });
+
+ it('should allow consumers to update the Kamelet name', async () => {
+ const kameletVisualEntity = new KameletVisualEntity(kameletJson);
+ const flowId = kameletVisualEntity.id;
+ const newName = 'MyNewName';
+ const dispatchSpy = jest.fn();
+ const visualFlowsApi = new VisualFlowsApi(dispatchSpy);
+ const { nodes } = CanvasService.getFlowDiagram(kameletVisualEntity.toVizNode());
+ selectedNode = nodes[nodes.length - 1];
+
+ render(
+
+
+
+
+ ,
+ );
+
+ const NameField = screen.getByDisplayValue('user-source');
+ act(() => {
+ fireEvent.change(NameField, { target: { value: newName } });
+ });
+
+ const closeSideBarButton = screen.getByTestId('close-side-bar');
+ act(() => {
+ fireEvent.click(closeSideBarButton);
+ });
+
+ expect(kameletVisualEntity.id).toEqual(newName);
+ expect(dispatchSpy).toHaveBeenCalledWith({ type: 'renameFlow', flowId, newName });
+ });
+
describe('should persists changes from both expression editor and main form', () => {
+ beforeEach(() => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ });
+
it('expression => main form', async () => {
const camelRoute = {
from: {
@@ -189,7 +280,9 @@ describe('CanvasForm', () => {
render(
-
+
+
+
,
);
const launchExpression = screen.getByTestId('launch-expression-modal-btn');
@@ -253,7 +346,9 @@ describe('CanvasForm', () => {
render(
-
+
+
+
,
);
const filtered = screen.getAllByRole('textbox').filter((textbox) => textbox.getAttribute('label') === 'Name');
@@ -292,6 +387,10 @@ describe('CanvasForm', () => {
});
describe('should persists changes from both dataformat editor and main form', () => {
+ beforeEach(() => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ });
+
it('dataformat => main form', async () => {
const camelRoute = {
from: {
@@ -318,7 +417,9 @@ describe('CanvasForm', () => {
render(
-
+
+
+
,
);
const button = screen
@@ -368,7 +469,9 @@ describe('CanvasForm', () => {
render(
-
+
+
+
,
);
const idInput = screen.getAllByRole('textbox').filter((textbox) => textbox.getAttribute('label') === 'Id');
@@ -394,6 +497,10 @@ describe('CanvasForm', () => {
});
describe('should persists changes from both loadbalancer editor and main form', () => {
+ beforeEach(() => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ });
+
it('loadbalancer => main form', async () => {
const camelRoute = {
from: {
@@ -420,7 +527,9 @@ describe('CanvasForm', () => {
render(
-
+
+
+
,
);
const button = screen
@@ -470,7 +579,9 @@ describe('CanvasForm', () => {
render(
-
+
+
+
,
);
const idInput = screen.getAllByRole('textbox').filter((textbox) => textbox.getAttribute('label') === 'Id');
@@ -495,76 +606,73 @@ describe('CanvasForm', () => {
});
});
- /*
- * Exhaustive tests
- */
-
- it('should render for all component without an error', async () => {
- jest.spyOn(console, 'error').mockImplementation(() => {});
- const componentCatalogMap = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.components.file);
- Object.entries(componentCatalogMap).forEach(([name, catalog]) => {
- try {
- if (name === 'default') return;
- /* eslint-disable @typescript-eslint/no-explicit-any */
- const schema = schemaService.getSchemaBridge((catalog as any).propertiesSchema);
- render(
-
- {}}>
-
-
- ,
- );
- } catch (e) {
- /* eslint-disable @typescript-eslint/no-explicit-any */
- throw new Error(`Error rendering ${name} component: ${(e as any).message}`);
- }
+ describe('Exhaustive tests', () => {
+ beforeEach(() => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
});
- });
- it('should render for all kamelets without an error', async () => {
- jest.spyOn(console, 'error').mockImplementation(() => {});
- const kameletCatalogMap = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.kamelets.file);
- Object.entries(kameletCatalogMap).forEach(([name, kamelet]) => {
- try {
- if (name === 'default') return;
- expect(kamelet).toBeDefined();
- /* eslint-disable @typescript-eslint/no-explicit-any */
- const schema = (kamelet as any).propertiesSchema;
- const bridge = schemaService.getSchemaBridge(schema);
- render(
-
- {}}>
-
-
- ,
- );
- } catch (e) {
- /* eslint-disable @typescript-eslint/no-explicit-any */
- throw new Error(`Error rendering ${name} component: ${(e as any).message}`);
- }
+ it('should render for all component without an error', async () => {
+ Object.entries(componentCatalogMap).forEach(([name, catalog]) => {
+ try {
+ if (name === 'default') return;
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ const schema = schemaService.getSchemaBridge((catalog as any).propertiesSchema);
+ render(
+
+ {}}>
+
+
+ ,
+ );
+ } catch (e) {
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ throw new Error(`Error rendering ${name} component: ${(e as any).message}`);
+ }
+ });
});
- });
- it('should render for all patterns without an error', async () => {
- const patternCatalogMap = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.patterns.file);
- Object.entries(patternCatalogMap).forEach(([name, pattern]) => {
- try {
- if (name === 'default') return;
- expect(pattern).toBeDefined();
- /* eslint-disable @typescript-eslint/no-explicit-any */
- const schema = (pattern as any).propertiesSchema;
- const bridge = schemaService.getSchemaBridge(schema);
- render(
-
- {}}>
-
-
- ,
- );
- } catch (e) {
- /* eslint-disable @typescript-eslint/no-explicit-any */
- throw new Error(`Error rendering ${name} pattern: ${(e as any).message}`);
- }
+ it('should render for all kamelets without an error', async () => {
+ Object.entries(kameletCatalogMap).forEach(([name, kamelet]) => {
+ try {
+ if (name === 'default') return;
+ expect(kamelet).toBeDefined();
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ const schema = (kamelet as any).propertiesSchema;
+ const bridge = schemaService.getSchemaBridge(schema);
+ render(
+
+ {}}>
+
+
+ ,
+ );
+ } catch (e) {
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ throw new Error(`Error rendering ${name} component: ${(e as any).message}`);
+ }
+ });
+ });
+
+ it('should render for all patterns without an error', async () => {
+ Object.entries(patternCatalogMap).forEach(([name, pattern]) => {
+ try {
+ if (name === 'default') return;
+ expect(pattern).toBeDefined();
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ const schema = (pattern as any).propertiesSchema;
+ const bridge = schemaService.getSchemaBridge(schema);
+ render(
+
+ {}}>
+
+
+ ,
+ );
+ } catch (e) {
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ throw new Error(`Error rendering ${name} pattern: ${(e as any).message}`);
+ }
+ });
});
});
});
diff --git a/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx b/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx
index 3aa90ed29..d1140a5ed 100644
--- a/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx
+++ b/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx
@@ -1,5 +1,6 @@
import { Card, CardBody, CardHeader } from '@patternfly/react-core';
import { FunctionComponent, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
+import { VisibleFlowsContext } from '../../../providers';
import { EntitiesContext } from '../../../providers/entities.provider';
import { SchemaBridgeProvider } from '../../../providers/schema-bridge.provider';
import { isDefined, setValue } from '../../../utils';
@@ -20,9 +21,11 @@ interface CanvasFormProps {
}
export const CanvasForm: FunctionComponent = (props) => {
+ const { visualFlowsApi } = useContext(VisibleFlowsContext)!;
const entitiesContext = useContext(EntitiesContext);
const formRef = useRef(null);
const divRef = useRef(null);
+ const flowIdRef = useRef(undefined);
const visualComponentSchema = useMemo(() => {
const answer = props.selectedNode.data?.vizNode?.getComponentSchema();
@@ -35,6 +38,11 @@ export const CanvasForm: FunctionComponent = (props) => {
const model = visualComponentSchema?.definition;
const title = visualComponentSchema?.title;
+ /** Store the flow's initial Id */
+ useEffect(() => {
+ flowIdRef.current = props.selectedNode.data?.vizNode?.getBaseEntity()?.getId();
+ }, []);
+
useEffect(() => {
formRef.current?.form.reset();
}, [props.selectedNode.data?.vizNode]);
@@ -45,7 +53,7 @@ export const CanvasForm: FunctionComponent = (props) => {
return;
}
- const newModel = props.selectedNode.data?.vizNode?.getComponentSchema()?.definition || {};
+ const newModel = props.selectedNode.data.vizNode.getComponentSchema()?.definition || {};
setValue(newModel, path, value);
props.selectedNode.data.vizNode.updateModel(newModel);
entitiesContext?.updateSourceCodeFromEntities();
@@ -65,6 +73,14 @@ export const CanvasForm: FunctionComponent = (props) => {
return { isExpressionAwareStep, isDataFormatAwareStep, isLoadBalanceAwareStep, isUnknownComponent };
}, [visualComponentSchema]);
+ const onClose = useCallback(() => {
+ props.onClose?.();
+ const newId = props.selectedNode.data?.vizNode?.getBaseEntity()?.getId();
+ if (typeof flowIdRef.current === 'string' && typeof newId === 'string' && flowIdRef.current !== newId) {
+ visualFlowsApi.renameFlow(flowIdRef.current, newId);
+ }
+ }, [props, visualFlowsApi]);
+
return (
This node cannot be configured yet
}>
@@ -72,7 +88,7 @@ export const CanvasForm: FunctionComponent = (props) => {
diff --git a/packages/ui/src/components/Visualization/Canvas/__snapshots__/CanvasForm.test.tsx.snap b/packages/ui/src/components/Visualization/Canvas/__snapshots__/CanvasForm.test.tsx.snap
index 1f30f0e90..1ab441c26 100644
--- a/packages/ui/src/components/Visualization/Canvas/__snapshots__/CanvasForm.test.tsx.snap
+++ b/packages/ui/src/components/Visualization/Canvas/__snapshots__/CanvasForm.test.tsx.snap
@@ -23,7 +23,8 @@ exports[`CanvasForm should render 1`] = `
>
{
+ const visualEntity1 = new CamelRouteVisualEntity({ ...camelRouteJson.route, id: 'entity1' });
+ const visualEntity2 = new CamelRouteVisualEntity({ ...camelRouteJson.route, id: 'entity2' });
+
return {
currentSchemaType: SourceSchemaType.Integration,
- visualEntities: [{ id: 'entity1' } as CamelRouteVisualEntity, { id: 'entity2' } as CamelRouteVisualEntity],
+ visualEntities: [visualEntity1, visualEntity2],
+ updateEntitiesFromCamelResource: jest.fn(),
} as unknown as EntitiesContextResult;
};
const getVisibleFlowsContextValue = () => {
@@ -42,14 +47,14 @@ describe('FlowsList.tsx', () => {
visibleFlowsValue = getVisibleFlowsContextValue();
});
- test('should render the existing flows', async () => {
+ it('should render the existing flows', async () => {
const wrapper = render();
const flows = await wrapper.findAllByTestId(/flows-list-row-*/);
expect(flows).toHaveLength(2);
});
- test('should display an empty state when there is no routes available', async () => {
+ it('should display an empty state when there is no routes available', async () => {
contextValue = { ...contextValue, visualEntities: [] };
visibleFlowsValue = { ...visibleFlowsValue, visibleFlows: {} };
const wrapper = render();
@@ -59,7 +64,7 @@ describe('FlowsList.tsx', () => {
expect(emptyState).toBeInTheDocument();
});
- test('should render the flows ids', async () => {
+ it('should render the flows ids', async () => {
const wrapper = render();
const flow1 = await wrapper.findByText('entity1');
const flow2 = await wrapper.findByText('entity2');
@@ -68,7 +73,7 @@ describe('FlowsList.tsx', () => {
expect(flow2).toBeInTheDocument();
});
- test('should make the selected flow visible by clicking on its ID', async () => {
+ it('should make the selected flow visible by clicking on its ID', async () => {
let resId = '';
const visFlowApi = new VisualFlowsApi(jest.fn);
jest.spyOn(visFlowApi, 'toggleFlowVisible').mockImplementation((id: string) => {
@@ -85,7 +90,7 @@ describe('FlowsList.tsx', () => {
expect(resId).toBe('entity1');
});
- test('should call onClose when clicking on a flow ID', async () => {
+ it('should call onClose when clicking on a flow ID', async () => {
const onCloseSpy = jest.fn();
const wrapper = render(
@@ -101,7 +106,7 @@ describe('FlowsList.tsx', () => {
expect(onCloseSpy).toHaveBeenCalledTimes(1);
});
- test('should toggle the visibility of a flow clicking on the Eye icon', async () => {
+ it('should toggle the visibility of a flow clicking on the Eye icon', async () => {
let resId = '';
const visFlowApi = new VisualFlowsApi(jest.fn);
jest.spyOn(visFlowApi, 'toggleFlowVisible').mockImplementation((id: string) => {
@@ -119,7 +124,7 @@ describe('FlowsList.tsx', () => {
expect(resId).toEqual('entity1');
});
- test('should render the appropriate Eye icon', async () => {
+ it('should render the appropriate Eye icon', async () => {
const wrapper = render();
const flow1 = await wrapper.findByTestId('toggle-btn-entity1-visible');
expect(flow1).toBeInTheDocument();
@@ -128,4 +133,32 @@ describe('FlowsList.tsx', () => {
const flow2 = await wrapper.findByTestId('toggle-btn-entity2-hidden');
expect(flow2).toBeInTheDocument();
});
+
+ it('should rename a flow', async () => {
+ const visualFlowsApi = new VisualFlowsApi(jest.fn);
+ const renameSpy = jest.spyOn(visualFlowsApi, 'renameFlow');
+
+ visibleFlowsValue = { ...getVisibleFlowsContextValue(), visualFlowsApi };
+ const wrapper = render();
+
+ await act(async () => {
+ const entityOnePencilIcon = await wrapper.findByTestId('goto-btn-entity1--edit');
+ fireEvent.click(entityOnePencilIcon);
+ });
+
+ await act(async () => {
+ const input = await wrapper.findByDisplayValue('entity1');
+ fireEvent.change(input, { target: { value: 'new-name' } });
+ fireEvent.blur(input);
+ });
+
+ await act(async () => {
+ const entityOnePencilIcon = await wrapper.findByTestId('goto-btn-entity1--save');
+ fireEvent.click(entityOnePencilIcon);
+ });
+
+ expect(renameSpy).toHaveBeenCalledWith('entity1', 'new-name');
+ expect(contextValue.visualEntities[0].id).toEqual('new-name');
+ expect(contextValue.updateEntitiesFromCamelResource).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/Flows/FlowsList.tsx b/packages/ui/src/components/Visualization/ContextToolbar/Flows/FlowsList.tsx
index e5aa77af3..5c8b0db22 100644
--- a/packages/ui/src/components/Visualization/ContextToolbar/Flows/FlowsList.tsx
+++ b/packages/ui/src/components/Visualization/ContextToolbar/Flows/FlowsList.tsx
@@ -66,6 +66,7 @@ export const FlowsList: FunctionComponent = (props) => {
onSelectFlow(flow.id);
}}
onChange={(name) => {
+ visualFlowsApi.renameFlow(flow.id, name);
flow.setId(name);
updateEntitiesFromCamelResource();
}}
diff --git a/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts b/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts
index d1a7a47a5..236d9e300 100644
--- a/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts
@@ -72,6 +72,8 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity
const updatedValue = CamelComponentSchemaService.getUriSerializedDefinition(path, value);
setValue(this.route, path, updatedValue);
+
+ if (isDefined(this.route.id)) this.id = this.route.id;
}
/**
diff --git a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts
index 52501d025..77b18008f 100644
--- a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts
@@ -44,6 +44,8 @@ export class KameletVisualEntity extends AbstractCamelVisualEntity {
updateModel(path: string | undefined, value: Record): void {
if (path === ROOT_PATH) {
updateKameletFromCustomSchema(this.kamelet, value);
+ this.id = this.kamelet.metadata.name;
+ this.route.id = this.kamelet.metadata.name;
return;
}
diff --git a/packages/ui/src/models/visualization/flows/support/flows-visibility.test.ts b/packages/ui/src/models/visualization/flows/support/flows-visibility.test.ts
new file mode 100644
index 000000000..9616b5335
--- /dev/null
+++ b/packages/ui/src/models/visualization/flows/support/flows-visibility.test.ts
@@ -0,0 +1,59 @@
+import { VisualFlowsApi } from './flows-visibility';
+
+describe('VisualFlowsApi', () => {
+ let dispatch: jest.Mock;
+ let visualFlowsApi: VisualFlowsApi;
+
+ beforeEach(() => {
+ dispatch = jest.fn();
+ visualFlowsApi = new VisualFlowsApi(dispatch);
+ });
+
+ it('should toggle flow visibility', () => {
+ visualFlowsApi.toggleFlowVisible('flowId');
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'toggleFlowVisible', flowId: 'flowId', isVisible: undefined });
+ });
+
+ it('should toggle flow visibility with a given flag', () => {
+ visualFlowsApi.toggleFlowVisible('flowId', true);
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'toggleFlowVisible', flowId: 'flowId', isVisible: true });
+ });
+
+ it('should show all flows', () => {
+ visualFlowsApi.showAllFlows();
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'showAllFlows' });
+ });
+
+ it('should hide all flows', () => {
+ visualFlowsApi.hideAllFlows();
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'hideAllFlows' });
+ });
+
+ it('should set visible flows', () => {
+ visualFlowsApi.setVisibleFlows(['flowId']);
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'setVisibleFlows', flows: ['flowId'] });
+ });
+
+ it('should clear flows', () => {
+ visualFlowsApi.clearFlows();
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'clearFlows' });
+ });
+
+ it('should init visible flows', () => {
+ visualFlowsApi.initVisibleFlows({ flowId: true });
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'initVisibleFlows', visibleFlows: { flowId: true } });
+ });
+
+ it('should rename flow', () => {
+ visualFlowsApi.renameFlow('flowId', 'newName');
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'renameFlow', flowId: 'flowId', newName: 'newName' });
+ });
+});
diff --git a/packages/ui/src/models/visualization/flows/support/flows-visibility.ts b/packages/ui/src/models/visualization/flows/support/flows-visibility.ts
index 83061daf8..2355a2471 100644
--- a/packages/ui/src/models/visualization/flows/support/flows-visibility.ts
+++ b/packages/ui/src/models/visualization/flows/support/flows-visibility.ts
@@ -43,7 +43,8 @@ type VisibleFlowAction =
| { type: 'hideAllFlows' }
| { type: 'clearFlows' }
| { type: 'setVisibleFlows'; flows: string[] }
- | { type: 'initVisibleFlows'; visibleFlows: IVisibleFlows };
+ | { type: 'initVisibleFlows'; visibleFlows: IVisibleFlows }
+ | { type: 'renameFlow'; flowId: string; newName: string };
export function VisibleFlowsReducer(state: IVisibleFlows, action: VisibleFlowAction) {
let visibleFlows;
@@ -84,6 +85,16 @@ export function VisibleFlowsReducer(state: IVisibleFlows, action: VisibleFlowAct
return {};
case 'initVisibleFlows':
return { ...action.visibleFlows };
+
+ case 'renameFlow':
+ // eslint-disable-next-line no-case-declarations
+ const newState = {
+ ...state,
+ [action.newName]: state[action.flowId],
+ };
+ delete newState[action.flowId];
+
+ return newState;
}
}
@@ -117,4 +128,8 @@ export class VisualFlowsApi {
initVisibleFlows(visibleFlows: IVisibleFlows) {
this.dispatch({ type: 'initVisibleFlows', visibleFlows: visibleFlows });
}
+
+ renameFlow(flowId: string, newName: string) {
+ this.dispatch({ type: 'renameFlow', flowId, newName });
+ }
}