diff --git a/packages/ui/src/components/Visualization/ContextToolbar/ContextToolbar.tsx b/packages/ui/src/components/Visualization/ContextToolbar/ContextToolbar.tsx
index 9adfbe9b2..2fd73c4ed 100644
--- a/packages/ui/src/components/Visualization/ContextToolbar/ContextToolbar.tsx
+++ b/packages/ui/src/components/Visualization/ContextToolbar/ContextToolbar.tsx
@@ -3,29 +3,39 @@ import { FunctionComponent, useContext } from 'react';
import { sourceSchemaConfig } from '../../../models/camel';
import { EntitiesContext } from '../../../providers/entities.provider';
import './ContextToolbar.scss';
+import { DSLSelector } from './DSLSelector/DSLSelector';
import { FlowClipboard } from './FlowClipboard/FlowClipboard';
import { FlowExportImage } from './FlowExportImage/FlowExportImage';
-import { NewFlow } from './FlowType/NewFlow';
import { FlowsMenu } from './Flows/FlowsMenu';
+import { NewEntity } from './NewEntity/NewEntity';
export const ContextToolbar: FunctionComponent = () => {
const { currentSchemaType } = useContext(EntitiesContext)!;
+ const isMultipleRoutes = sourceSchemaConfig.config[currentSchemaType].multipleRoute;
- return [
-
- {sourceSchemaConfig.config[currentSchemaType].name || 'None'}
+ const toolbarItems: JSX.Element[] = [
+
+
,
,
-
-
- ,
+ ];
+
+ if (isMultipleRoutes) {
+ toolbarItems.push(
+
+
+ ,
+ );
+ }
+
+ return toolbarItems.concat([
,
,
- ];
+ ]);
};
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/ChangeDSLModal/ChangeDSLModal.test.tsx b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/ChangeDSLModal/ChangeDSLModal.test.tsx
new file mode 100644
index 000000000..36892c690
--- /dev/null
+++ b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/ChangeDSLModal/ChangeDSLModal.test.tsx
@@ -0,0 +1,43 @@
+import { fireEvent, render } from '@testing-library/react';
+import { ChangeDSLModal } from './ChangeDSLModal';
+
+describe('ChangeDSLModal', () => {
+ it('should be hidden when isOpen is false', () => {
+ const wrapper = render();
+
+ expect(wrapper.queryByTestId('confirmation-modal')).not.toBeInTheDocument();
+ });
+
+ it('should be visible when isOpen is true', () => {
+ const wrapper = render();
+
+ expect(wrapper.queryByTestId('confirmation-modal')).toBeInTheDocument();
+ });
+
+ it('should call onConfirm when confirm button is clicked', () => {
+ const onConfirm = jest.fn();
+ const wrapper = render();
+
+ fireEvent.click(wrapper.getByTestId('confirmation-modal-confirm'));
+
+ expect(onConfirm).toBeCalled();
+ });
+
+ it('should call onCancel when cancel button is clicked', () => {
+ const onCancel = jest.fn();
+ const wrapper = render();
+
+ fireEvent.click(wrapper.getByTestId('confirmation-modal-cancel'));
+
+ expect(onCancel).toBeCalled();
+ });
+
+ it('should call onCancel when close button is clicked', () => {
+ const onCancel = jest.fn();
+ const wrapper = render();
+
+ fireEvent.click(wrapper.getByLabelText('Close'));
+
+ expect(onCancel).toBeCalled();
+ });
+});
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/ChangeDSLModal/ChangeDSLModal.tsx b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/ChangeDSLModal/ChangeDSLModal.tsx
new file mode 100644
index 000000000..321e137d7
--- /dev/null
+++ b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/ChangeDSLModal/ChangeDSLModal.tsx
@@ -0,0 +1,34 @@
+import { Button, Modal, ModalVariant } from '@patternfly/react-core';
+import { FunctionComponent } from 'react';
+
+interface ChangeDSLModalProps {
+ isOpen: boolean;
+ onConfirm: () => void;
+ onCancel: () => void;
+}
+
+export const ChangeDSLModal: FunctionComponent = (props) => {
+ return (
+
+ Confirm
+ ,
+ ,
+ ]}
+ isOpen={props.isOpen}
+ >
+
+ This will remove any existing integration and you will lose your current work. Are you sure you would like to
+ proceed?
+
+
+ );
+};
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelector.test.tsx b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelector.test.tsx
new file mode 100644
index 000000000..5a2528278
--- /dev/null
+++ b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelector.test.tsx
@@ -0,0 +1,81 @@
+import { act, fireEvent, render } from '@testing-library/react';
+import { EntitiesContextResult } from '../../../../hooks';
+import { KaotoSchemaDefinition } from '../../../../models';
+import { SourceSchemaType, sourceSchemaConfig } from '../../../../models/camel';
+import { EntitiesContext } from '../../../../providers/entities.provider';
+import { SourceCodeApiContext } from '../../../../providers/source-code.provider';
+import { DSLSelector } from './DSLSelector';
+
+describe('DSLSelector.tsx', () => {
+ const config = sourceSchemaConfig;
+ config.config[SourceSchemaType.Integration].schema = {
+ schema: { name: 'Integration', description: 'desc' } as KaotoSchemaDefinition['schema'],
+ } as KaotoSchemaDefinition;
+ config.config[SourceSchemaType.Pipe].schema = {
+ schema: { name: 'Pipe', description: 'desc' } as KaotoSchemaDefinition['schema'],
+ } as KaotoSchemaDefinition;
+ config.config[SourceSchemaType.Kamelet].schema = {
+ schema: { name: 'Kamelet', description: 'desc' } as KaotoSchemaDefinition['schema'],
+ } as KaotoSchemaDefinition;
+ config.config[SourceSchemaType.KameletBinding].schema = {
+ name: 'kameletBinding',
+ schema: { description: 'desc' },
+ } as KaotoSchemaDefinition;
+ config.config[SourceSchemaType.Route].schema = {
+ schema: { name: 'route', description: 'desc' } as KaotoSchemaDefinition['schema'],
+ } as KaotoSchemaDefinition;
+
+ const renderWithContext = () => {
+ return render(
+
+
+
+
+ ,
+ );
+ };
+
+ it('should render all of the types', async () => {
+ const wrapper = renderWithContext();
+ const trigger = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Open Select */
+ act(() => {
+ fireEvent.click(trigger);
+ });
+
+ for (const name of ['Pipe', 'Camel Route']) {
+ const element = await wrapper.findByText(name);
+ expect(element).toBeInTheDocument();
+ }
+ });
+
+ it('should warn the user when adding a different type of flow', async () => {
+ const wrapper = renderWithContext();
+ const trigger = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Open Select */
+ act(() => {
+ fireEvent.click(trigger);
+ });
+
+ /** Select an option */
+ act(() => {
+ const element = wrapper.getByText('Pipe');
+ fireEvent.click(element);
+ });
+
+ const modal = await wrapper.findByTestId('confirmation-modal');
+ expect(modal).toBeInTheDocument();
+ });
+});
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelector.tsx b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelector.tsx
new file mode 100644
index 000000000..f5b7cc6fb
--- /dev/null
+++ b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelector.tsx
@@ -0,0 +1,39 @@
+import { FunctionComponent, PropsWithChildren, useCallback, useContext, useState } from 'react';
+import { SourceSchemaType } from '../../../../models/camel';
+import { FlowTemplateService } from '../../../../models/visualization/flows/support/flow-templates-service';
+import { SourceCodeApiContext } from '../../../../providers';
+import { ChangeDSLModal } from './ChangeDSLModal/ChangeDSLModal';
+import { DSLSelectorToggle } from './DSLSelectorToggle/DSLSelectorToggle';
+
+export const DSLSelector: FunctionComponent = () => {
+ const sourceCodeContextApi = useContext(SourceCodeApiContext);
+ const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
+ const [proposedFlowType, setProposedFlowType] = useState();
+
+ const checkBeforeAddNewFlow = useCallback((flowType: SourceSchemaType) => {
+ /**
+ * If it is not the same DSL, this operation might result in
+ * removing the existing flows, so then we warn the user first
+ */
+ setProposedFlowType(flowType);
+ setIsConfirmationModalOpen(true);
+ }, []);
+
+ const onConfirm = useCallback(() => {
+ if (proposedFlowType) {
+ sourceCodeContextApi.setCodeAndNotify(FlowTemplateService.getFlowYamlTemplate(proposedFlowType));
+ setIsConfirmationModalOpen(false);
+ }
+ }, [proposedFlowType, sourceCodeContextApi]);
+
+ const onCancel = useCallback(() => {
+ setIsConfirmationModalOpen(false);
+ }, []);
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelectorToggle/DSLSelectorToggle.test.tsx b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelectorToggle/DSLSelectorToggle.test.tsx
new file mode 100644
index 000000000..d035dd153
--- /dev/null
+++ b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelectorToggle/DSLSelectorToggle.test.tsx
@@ -0,0 +1,177 @@
+import { act, fireEvent, render, waitFor } from '@testing-library/react';
+import { FunctionComponent } from 'react';
+import { EntitiesContextResult } from '../../../../../hooks';
+import { KaotoSchemaDefinition } from '../../../../../models';
+import { SourceSchemaType, sourceSchemaConfig } from '../../../../../models/camel';
+import { EntitiesContext } from '../../../../../providers/entities.provider';
+import { DSLSelectorToggle } from './DSLSelectorToggle';
+
+const config = sourceSchemaConfig;
+config.config[SourceSchemaType.Pipe].schema = {
+ name: 'Pipe',
+ schema: { name: 'Pipe', description: 'desc' } as KaotoSchemaDefinition['schema'],
+} as KaotoSchemaDefinition;
+config.config[SourceSchemaType.Kamelet].schema = {
+ name: 'Kamelet',
+ schema: { name: 'Kamelet', description: 'desc' } as KaotoSchemaDefinition['schema'],
+} as KaotoSchemaDefinition;
+config.config[SourceSchemaType.Route].schema = {
+ name: 'route',
+ schema: { name: 'route', description: 'desc' } as KaotoSchemaDefinition['schema'],
+} as KaotoSchemaDefinition;
+
+describe('FlowTypeSelector.tsx', () => {
+ let onSelect: () => void;
+ beforeEach(() => {
+ onSelect = jest.fn();
+ });
+
+ const FlowTypeSelectorWithContext: FunctionComponent<{ currentSchemaType?: SourceSchemaType }> = (props) => {
+ const currentSchemaType = props.currentSchemaType ?? SourceSchemaType.Route;
+ return (
+
+
+
+ );
+ };
+
+ it('component renders', () => {
+ const wrapper = render();
+ const toggle = wrapper.queryByTestId('dsl-list-dropdown');
+ expect(toggle).toBeInTheDocument();
+ });
+
+ it('should call onSelect when clicking on the MenuToggleAction', async () => {
+ const wrapper = render();
+
+ /** Click on toggle */
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ /** Click on first element */
+ const element = await wrapper.findByText('Pipe');
+ act(() => {
+ fireEvent.click(element);
+ });
+
+ await waitFor(() => {
+ expect(onSelect).toHaveBeenCalled();
+ });
+ });
+
+ it('should disable the MenuToggleAction if the DSL is already selected', async () => {
+ const wrapper = render();
+
+ /** Click on toggle */
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ /** Click on first element */
+ const element = await wrapper.findByText('Camel Route');
+ // act(() => {
+ // fireEvent.click(element);
+ // });
+
+ waitFor(() => {
+ expect(element).toBeDisabled();
+ });
+ });
+
+ it('should toggle list of DSLs', async () => {
+ const wrapper = render();
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Click on toggle */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ const element = await wrapper.findByText('Pipe');
+ expect(element).toBeInTheDocument();
+
+ /** Close Select */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ waitFor(() => {
+ expect(element).not.toBeInTheDocument();
+ });
+ });
+
+ it('should show selected value', async () => {
+ const wrapper = render();
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Open Select */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ /** Click on first element */
+ act(() => {
+ const element = wrapper.getByText('Camel Route');
+ fireEvent.click(element);
+ });
+
+ /** Open Select again */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ const element = await wrapper.findByRole('option', { selected: true });
+ expect(element).toBeInTheDocument();
+ expect(element).toHaveTextContent('Camel Route');
+ });
+
+ it('should have selected DSL if provided', async () => {
+ const wrapper = render();
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Open Select */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ waitFor(() => {
+ const element = wrapper.queryByRole('option', { selected: true });
+ expect(element).toBeInTheDocument();
+ expect(element).toHaveTextContent('Pipe');
+ });
+ });
+
+ it('should close Select when pressing ESC', async () => {
+ const wrapper = render();
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Open Select */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ const menu = await wrapper.findByRole('listbox');
+
+ expect(menu).toBeInTheDocument();
+
+ /** Press Escape key to close the menu */
+ act(() => {
+ fireEvent.focus(menu);
+ fireEvent.keyDown(menu, { key: 'Escape', code: 'Escape', charCode: 27 });
+ });
+
+ waitFor(() => {
+ /** The close panel is an async process */
+ expect(menu).not.toBeInTheDocument();
+ });
+
+ waitFor(() => {
+ const element = wrapper.queryByRole('option', { selected: true });
+ expect(element).toBeInTheDocument();
+ expect(element).toHaveTextContent('Camel Route');
+ });
+ });
+});
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelectorToggle/DSLSelectorToggle.tsx b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelectorToggle/DSLSelectorToggle.tsx
new file mode 100644
index 000000000..01228dbdc
--- /dev/null
+++ b/packages/ui/src/components/Visualization/ContextToolbar/DSLSelector/DSLSelectorToggle/DSLSelectorToggle.tsx
@@ -0,0 +1,82 @@
+import { MenuToggle, Select, SelectList, SelectOption } from '@patternfly/react-core';
+import { FunctionComponent, MouseEvent, RefObject, useCallback, useContext, useRef, useState } from 'react';
+import { ISourceSchema, SourceSchemaType, sourceSchemaConfig } from '../../../../../models/camel';
+import { EntitiesContext } from '../../../../../providers/entities.provider';
+
+interface ISourceTypeSelector {
+ onSelect?: (value: SourceSchemaType) => void;
+}
+
+export const DSLSelectorToggle: FunctionComponent = (props) => {
+ const { currentSchemaType } = useContext(EntitiesContext)!;
+ const currentFlowType: ISourceSchema = sourceSchemaConfig.config[currentSchemaType];
+ const [isOpen, setIsOpen] = useState(false);
+ const dslEntriesRef = useRef>>({
+ [SourceSchemaType.Route]: sourceSchemaConfig.config[SourceSchemaType.Route],
+ [SourceSchemaType.Kamelet]: sourceSchemaConfig.config[SourceSchemaType.Kamelet],
+ [SourceSchemaType.Pipe]: sourceSchemaConfig.config[SourceSchemaType.Pipe],
+ });
+
+ const onSelect = useCallback(
+ (_event: MouseEvent | undefined, flowType: string | number | undefined) => {
+ if (!flowType) {
+ return;
+ }
+ const dsl = sourceSchemaConfig.config[flowType as SourceSchemaType];
+
+ setIsOpen(false);
+ if (dsl !== undefined) {
+ props.onSelect?.(flowType as SourceSchemaType);
+ }
+ },
+ [props],
+ );
+
+ const toggle = (toggleRef: RefObject) => (
+ {
+ setIsOpen(!isOpen);
+ }}
+ isExpanded={isOpen}
+ >
+ {sourceSchemaConfig.config[currentSchemaType].name}
+
+ );
+
+ return (
+
+ );
+};
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/NewEntity/NewEntity.test.tsx b/packages/ui/src/components/Visualization/ContextToolbar/NewEntity/NewEntity.test.tsx
new file mode 100644
index 000000000..7f6b42136
--- /dev/null
+++ b/packages/ui/src/components/Visualization/ContextToolbar/NewEntity/NewEntity.test.tsx
@@ -0,0 +1,203 @@
+import { act, fireEvent, render, waitFor } from '@testing-library/react';
+import { FunctionComponent } from 'react';
+import { EntitiesContextResult } from '../../../../hooks';
+import { KaotoSchemaDefinition } from '../../../../models';
+import { CamelRouteResource, SourceSchemaType, sourceSchemaConfig } from '../../../../models/camel';
+import { EntitiesContext } from '../../../../providers/entities.provider';
+import { VisibleFLowsContextResult, VisibleFlowsContext } from '../../../../providers/visible-flows.provider';
+import { camelRouteJson } from '../../../../stubs';
+import { NewEntity } from './NewEntity';
+
+const config = sourceSchemaConfig;
+config.config[SourceSchemaType.Pipe].schema = {
+ name: 'Pipe',
+ schema: { name: 'Pipe', description: 'desc' } as KaotoSchemaDefinition['schema'],
+} as KaotoSchemaDefinition;
+config.config[SourceSchemaType.Kamelet].schema = {
+ name: 'Kamelet',
+ schema: { name: 'Kamelet', description: 'desc' } as KaotoSchemaDefinition['schema'],
+} as KaotoSchemaDefinition;
+config.config[SourceSchemaType.Route].schema = {
+ name: 'route',
+ schema: { name: 'route', description: 'desc' } as KaotoSchemaDefinition['schema'],
+} as KaotoSchemaDefinition;
+
+describe('FlowTypeSelector.tsx', () => {
+ let setCurrentSchemaTypeSpy: jest.Mock;
+ let updateEntitiesFromCamelResourceSpy: jest.Mock;
+ let updateSourceCodeFromEntitiesSpy: jest.Mock;
+
+ beforeEach(() => {
+ setCurrentSchemaTypeSpy = jest.fn();
+ updateEntitiesFromCamelResourceSpy = jest.fn();
+ updateSourceCodeFromEntitiesSpy = jest.fn();
+ });
+
+ const FlowTypeSelectorWithContext: FunctionComponent<{ currentSchemaType?: SourceSchemaType }> = (props) => {
+ const camelResource = new CamelRouteResource(camelRouteJson);
+ const currentSchemaType = props.currentSchemaType ?? camelResource.getType();
+
+ return (
+
+
+
+
+
+ );
+ };
+
+ it('component renders', () => {
+ const wrapper = render();
+ const toggle = wrapper.queryByTestId('dsl-list-dropdown');
+ expect(toggle).toBeInTheDocument();
+ });
+
+ it('should call `updateEntitiesFromCamelResource` when selecting an item', async () => {
+ const wrapper = render();
+
+ /** Click on toggle */
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ /** Click on first element */
+ const element = await wrapper.findByText('Pipe');
+ act(() => {
+ fireEvent.click(element);
+ });
+
+ await waitFor(() => {
+ expect(updateEntitiesFromCamelResourceSpy).toHaveBeenCalled();
+ });
+ });
+
+ it('should disable the MenuToggleAction if the DSL is already selected', async () => {
+ const wrapper = render();
+
+ /** Click on toggle */
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ /** Click on first element */
+ const element = await wrapper.findByText('Camel Route');
+ // act(() => {
+ // fireEvent.click(element);
+ // });
+
+ waitFor(() => {
+ expect(element).toBeDisabled();
+ });
+ });
+
+ it('should toggle list of DSLs', async () => {
+ const wrapper = render();
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Click on toggle */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ const element = await wrapper.findByText('Pipe');
+ expect(element).toBeInTheDocument();
+
+ /** Close Select */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ waitFor(() => {
+ expect(element).not.toBeInTheDocument();
+ });
+ });
+
+ it('should show selected value', async () => {
+ const wrapper = render();
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Open Select */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ /** Click on first element */
+ act(() => {
+ const element = wrapper.getByText('Camel Route');
+ fireEvent.click(element);
+ });
+
+ /** Open Select again */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ const element = await wrapper.findByRole('option', { selected: true });
+ expect(element).toBeInTheDocument();
+ expect(element).toHaveTextContent('Camel Route');
+ });
+
+ it('should have selected DSL if provided', async () => {
+ const wrapper = render();
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Open Select */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ waitFor(() => {
+ const element = wrapper.queryByRole('option', { selected: true });
+ expect(element).toBeInTheDocument();
+ expect(element).toHaveTextContent('Pipe');
+ });
+ });
+
+ it('should close Select when pressing ESC', async () => {
+ const wrapper = render();
+ const toggle = await wrapper.findByTestId('dsl-list-dropdown');
+
+ /** Open Select */
+ act(() => {
+ fireEvent.click(toggle);
+ });
+
+ const menu = await wrapper.findByRole('listbox');
+
+ expect(menu).toBeInTheDocument();
+
+ /** Press Escape key to close the menu */
+ act(() => {
+ fireEvent.focus(menu);
+ fireEvent.keyDown(menu, { key: 'Escape', code: 'Escape', charCode: 27 });
+ });
+
+ waitFor(() => {
+ /** The close panel is an async process */
+ expect(menu).not.toBeInTheDocument();
+ });
+
+ waitFor(() => {
+ const element = wrapper.queryByRole('option', { selected: true });
+ expect(element).toBeInTheDocument();
+ expect(element).toHaveTextContent('Camel Route');
+ });
+ });
+});
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/NewEntity/NewEntity.tsx b/packages/ui/src/components/Visualization/ContextToolbar/NewEntity/NewEntity.tsx
new file mode 100644
index 000000000..89af17d94
--- /dev/null
+++ b/packages/ui/src/components/Visualization/ContextToolbar/NewEntity/NewEntity.tsx
@@ -0,0 +1,101 @@
+import { Menu, MenuContainer, MenuContent, MenuItem, MenuList, MenuToggle } from '@patternfly/react-core';
+import { PlusIcon } from '@patternfly/react-icons';
+import { FunctionComponent, ReactElement, useCallback, useContext, useRef, useState } from 'react';
+import { BaseVisualCamelEntityDefinition } from '../../../../models/camel/camel-resource';
+import { EntityType } from '../../../../models/camel/entities';
+import { EntitiesContext } from '../../../../providers/entities.provider';
+import { VisibleFlowsContext } from '../../../../providers/visible-flows.provider';
+
+export const NewEntity: FunctionComponent = () => {
+ const { camelResource, updateEntitiesFromCamelResource } = useContext(EntitiesContext)!;
+ const visibleFlowsContext = useContext(VisibleFlowsContext)!;
+ const [isOpen, setIsOpen] = useState(false);
+ const menuRef = useRef(null);
+ const toggleRef = useRef(null);
+ const groupedEntities = useRef(camelResource.getCanvasEntityList());
+
+ const onSelect = useCallback(
+ (_event: unknown, entityType: string | number | undefined) => {
+ if (!entityType) {
+ return;
+ }
+
+ /**
+ * If it's the same DSL as we have in the existing Flows list,
+ * we don't need to do anything special, just add a new flow if
+ * supported
+ */
+ const newId = camelResource.addNewEntity(entityType as EntityType);
+ visibleFlowsContext.visualFlowsApi.hideAllFlows();
+ visibleFlowsContext.visualFlowsApi.setVisibleFlows([newId]);
+ updateEntitiesFromCamelResource();
+ setIsOpen(false);
+ },
+ [camelResource, updateEntitiesFromCamelResource, visibleFlowsContext.visualFlowsApi],
+ );
+
+ const getMenuItem = useCallback(
+ (entity: { name?: EntityType; title: string; description: string }, flyoutMenu?: ReactElement) => {
+ return (
+
+ );
+ },
+ [],
+ );
+
+ return (
+ setIsOpen(isOpen)}
+ menu={
+ // TODO: Workaround for flyout menu being scrollable and packed within the toolbar
+
+ }
+ menuRef={menuRef}
+ toggle={
+ {
+ setIsOpen(!isOpen);
+ }}
+ isExpanded={isOpen}
+ >
+
+ New
+
+ }
+ toggleRef={toggleRef}
+ />
+ );
+};
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/FlowType/FlowTypeSelector.test.tsx b/packages/ui/src/components/Visualization/EmptyState/FlowType/FlowTypeSelector.test.tsx
similarity index 100%
rename from packages/ui/src/components/Visualization/ContextToolbar/FlowType/FlowTypeSelector.test.tsx
rename to packages/ui/src/components/Visualization/EmptyState/FlowType/FlowTypeSelector.test.tsx
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/FlowType/FlowTypeSelector.tsx b/packages/ui/src/components/Visualization/EmptyState/FlowType/FlowTypeSelector.tsx
similarity index 96%
rename from packages/ui/src/components/Visualization/ContextToolbar/FlowType/FlowTypeSelector.tsx
rename to packages/ui/src/components/Visualization/EmptyState/FlowType/FlowTypeSelector.tsx
index c67eaa681..70abc2300 100644
--- a/packages/ui/src/components/Visualization/ContextToolbar/FlowType/FlowTypeSelector.tsx
+++ b/packages/ui/src/components/Visualization/EmptyState/FlowType/FlowTypeSelector.tsx
@@ -1,148 +1,147 @@
-import {
- MenuToggle,
- MenuToggleAction,
- MenuToggleElement,
- Select,
- SelectList,
- SelectOption,
- Tooltip,
-} from '@patternfly/react-core';
-import { FunctionComponent, MouseEvent, PropsWithChildren, Ref, useCallback, useContext, useState } from 'react';
-import { ISourceSchema, SourceSchemaType, sourceSchemaConfig } from '../../../../models/camel';
-import { EntitiesContext } from '../../../../providers/entities.provider';
-
-interface ISourceTypeSelector extends PropsWithChildren {
- isStatic?: boolean;
- onSelect?: (value: SourceSchemaType) => void;
-}
-
-export const FlowTypeSelector: FunctionComponent = (props) => {
- const { currentSchemaType, visualEntities } = useContext(EntitiesContext)!;
- const totalFlowsCount = visualEntities.length;
- const currentFlowType: ISourceSchema = sourceSchemaConfig.config[currentSchemaType];
- const [isOpen, setIsOpen] = useState(false);
-
- /** Toggle the DSL dropdown */
- const onToggleClick = () => {
- setIsOpen(!isOpen);
- };
-
- /** Selecting a DSL checking the the existing flows */
- const onSelect = useCallback(
- (_event: MouseEvent | undefined, flowType: string | number | undefined) => {
- if (flowType) {
- const dsl = sourceSchemaConfig.config[flowType as SourceSchemaType];
-
- setIsOpen(false);
- if (typeof props.onSelect === 'function' && dsl !== undefined) {
- props.onSelect(flowType as SourceSchemaType);
- }
- }
- },
- [props],
- );
-
- /** Selecting the same DSL directly*/
- const onNewSameTypeRoute = useCallback(() => {
- onSelect(undefined, currentSchemaType);
- }, [onSelect, currentSchemaType]);
-
- /** Override function to provide more useful help texts than available via schema */
- const getDescriptionForType = (type: string) => {
- switch (type) {
- case SourceSchemaType.Route:
- return 'Defines an executable integration flow by declaring a source (starter) and followed by a sequence of actions (or steps). Actions can include data manipulations, EIPs (integration patterns) and internal or external calls.';
- case SourceSchemaType.Kamelet:
- return 'Defines a reusable Camel route as a building block. Kamelets can not be executed on their own, they are used as sources, actions or sinks in Camel Routes or Pipes.';
- case SourceSchemaType.Pipe:
- case SourceSchemaType.KameletBinding:
- return 'Defines a sequence of concatenated Kamelets to form start to finish integration flows. Pipes are a more abstract level of defining integration flows, by chosing and configuring Kamelets.';
- case SourceSchemaType.Integration:
- return 'An integration defines a Camel route in a CRD file.';
- default:
- return undefined;
- }
- };
-
- const toggle = (toggleRef: Ref) => (
- Add a new {currentFlowType.name} route
- ) : (
- The {currentFlowType.name} type doesn't support multiple routes
- )
- }
- >
- 0}
- >
- {props.children}
-
- ,
- ],
- }}
- />
- );
-
- return (
-
- );
-};
+import {
+ MenuToggle,
+ MenuToggleAction,
+ MenuToggleElement,
+ Select,
+ SelectList,
+ SelectOption,
+ Tooltip,
+} from '@patternfly/react-core';
+import { FunctionComponent, MouseEvent, PropsWithChildren, Ref, useCallback, useContext, useState } from 'react';
+import { ISourceSchema, SourceSchemaType, sourceSchemaConfig } from '../../../../models/camel';
+import { EntitiesContext } from '../../../../providers/entities.provider';
+
+interface ISourceTypeSelector extends PropsWithChildren {
+ isStatic?: boolean;
+ onSelect?: (value: SourceSchemaType) => void;
+}
+
+export const FlowTypeSelector: FunctionComponent = (props) => {
+ const { currentSchemaType, visualEntities } = useContext(EntitiesContext)!;
+ const totalFlowsCount = visualEntities.length;
+ const currentFlowType: ISourceSchema = sourceSchemaConfig.config[currentSchemaType];
+ const [isOpen, setIsOpen] = useState(false);
+
+ /** Toggle the DSL dropdown */
+ const onToggleClick = () => {
+ setIsOpen(!isOpen);
+ };
+
+ const onSelect = useCallback(
+ (_event: MouseEvent | undefined, flowType: string | number | undefined) => {
+ if (flowType) {
+ const dsl = sourceSchemaConfig.config[flowType as SourceSchemaType];
+
+ setIsOpen(false);
+ if (typeof props.onSelect === 'function' && dsl !== undefined) {
+ props.onSelect(flowType as SourceSchemaType);
+ }
+ }
+ },
+ [props],
+ );
+
+ /** Selecting the same DSL directly*/
+ const onNewSameTypeRoute = useCallback(() => {
+ onSelect(undefined, currentSchemaType);
+ }, [onSelect, currentSchemaType]);
+
+ /** Override function to provide more useful help texts than available via schema */
+ const getDescriptionForType = (type: string) => {
+ switch (type) {
+ case SourceSchemaType.Route:
+ return 'Defines an executable integration flow by declaring a source (starter) and followed by a sequence of actions (or steps). Actions can include data manipulations, EIPs (integration patterns) and internal or external calls.';
+ case SourceSchemaType.Kamelet:
+ return 'Defines a reusable Camel route as a building block. Kamelets can not be executed on their own, they are used as sources, actions or sinks in Camel Routes or Pipes.';
+ case SourceSchemaType.Pipe:
+ case SourceSchemaType.KameletBinding:
+ return 'Defines a sequence of concatenated Kamelets to form start to finish integration flows. Pipes are a more abstract level of defining integration flows, by chosing and configuring Kamelets.';
+ case SourceSchemaType.Integration:
+ return 'An integration defines a Camel route in a CRD file.';
+ default:
+ return undefined;
+ }
+ };
+
+ const toggle = (toggleRef: Ref) => (
+ Add a new {currentFlowType.name} route
+ ) : (
+ The {currentFlowType.name} type doesn't support multiple routes
+ )
+ }
+ >
+ 0}
+ >
+ {props.children}
+
+ ,
+ ],
+ }}
+ />
+ );
+
+ return (
+
+ );
+};
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/FlowType/NewFlow.test.tsx b/packages/ui/src/components/Visualization/EmptyState/FlowType/NewFlow.test.tsx
similarity index 100%
rename from packages/ui/src/components/Visualization/ContextToolbar/FlowType/NewFlow.test.tsx
rename to packages/ui/src/components/Visualization/EmptyState/FlowType/NewFlow.test.tsx
diff --git a/packages/ui/src/components/Visualization/ContextToolbar/FlowType/NewFlow.tsx b/packages/ui/src/components/Visualization/EmptyState/FlowType/NewFlow.tsx
similarity index 97%
rename from packages/ui/src/components/Visualization/ContextToolbar/FlowType/NewFlow.tsx
rename to packages/ui/src/components/Visualization/EmptyState/FlowType/NewFlow.tsx
index 66c935355..c0d6adba8 100644
--- a/packages/ui/src/components/Visualization/ContextToolbar/FlowType/NewFlow.tsx
+++ b/packages/ui/src/components/Visualization/EmptyState/FlowType/NewFlow.tsx
@@ -1,92 +1,92 @@
-import { Button, Modal, ModalVariant } from '@patternfly/react-core';
-import { PlusIcon } from '@patternfly/react-icons';
-import { FunctionComponent, PropsWithChildren, useCallback, useContext, useState } from 'react';
-import { useEntityContext } from '../../../../hooks/useEntityContext/useEntityContext';
-import { SourceSchemaType } from '../../../../models/camel';
-import { FlowTemplateService } from '../../../../models/visualization/flows/support/flow-templates-service';
-import { SourceCodeApiContext } from '../../../../providers';
-import { VisibleFlowsContext } from '../../../../providers/visible-flows.provider';
-import { FlowTypeSelector } from './FlowTypeSelector';
-
-export const NewFlow: FunctionComponent = () => {
- const sourceCodeContextApi = useContext(SourceCodeApiContext);
- const entitiesContext = useEntityContext();
- const visibleFlowsContext = useContext(VisibleFlowsContext)!;
- const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
- const [proposedFlowType, setProposedFlowType] = useState();
-
- const checkBeforeAddNewFlow = useCallback(
- (flowType: SourceSchemaType) => {
- const isSameSourceType = entitiesContext.currentSchemaType === flowType;
-
- if (isSameSourceType) {
- /**
- * If it's the same DSL as we have in the existing Flows list,
- * we don't need to do anything special, just add a new flow if
- * supported
- */
- const newId = entitiesContext.camelResource.addNewEntity();
- visibleFlowsContext.visualFlowsApi.hideAllFlows();
- visibleFlowsContext.visualFlowsApi.setVisibleFlows([newId]);
- entitiesContext.updateEntitiesFromCamelResource();
- } else {
- /**
- * If it is not the same DSL, this operation might result in
- * removing the existing flows, so then we warn the user first
- */
- setProposedFlowType(flowType);
- setIsConfirmationModalOpen(true);
- }
- },
- [entitiesContext, visibleFlowsContext.visualFlowsApi],
- );
-
- return (
- <>
-
-
- New
-
- {
- setIsConfirmationModalOpen(false);
- }}
- actions={[
- ,
- ,
- ]}
- isOpen={isConfirmationModalOpen}
- >
-
- This will remove any existing integration and you will lose your current work. Are you sure you would like to
- proceed?
-
-
- >
- );
-};
+import { Button, Modal, ModalVariant } from '@patternfly/react-core';
+import { PlusIcon } from '@patternfly/react-icons';
+import { FunctionComponent, PropsWithChildren, useCallback, useContext, useState } from 'react';
+import { useEntityContext } from '../../../../hooks/useEntityContext/useEntityContext';
+import { SourceSchemaType } from '../../../../models/camel';
+import { FlowTemplateService } from '../../../../models/visualization/flows/support/flow-templates-service';
+import { SourceCodeApiContext } from '../../../../providers';
+import { VisibleFlowsContext } from '../../../../providers/visible-flows.provider';
+import { FlowTypeSelector } from './FlowTypeSelector';
+
+export const NewFlow: FunctionComponent = () => {
+ const sourceCodeContextApi = useContext(SourceCodeApiContext);
+ const entitiesContext = useEntityContext();
+ const visibleFlowsContext = useContext(VisibleFlowsContext)!;
+ const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
+ const [proposedFlowType, setProposedFlowType] = useState();
+
+ const checkBeforeAddNewFlow = useCallback(
+ (flowType: SourceSchemaType) => {
+ const isSameSourceType = entitiesContext.currentSchemaType === flowType;
+
+ if (isSameSourceType) {
+ /**
+ * If it's the same DSL as we have in the existing Flows list,
+ * we don't need to do anything special, just add a new flow if
+ * supported
+ */
+ const newId = entitiesContext.camelResource.addNewEntity();
+ visibleFlowsContext.visualFlowsApi.hideAllFlows();
+ visibleFlowsContext.visualFlowsApi.setVisibleFlows([newId]);
+ entitiesContext.updateEntitiesFromCamelResource();
+ } else {
+ /**
+ * If it is not the same DSL, this operation might result in
+ * removing the existing flows, so then we warn the user first
+ */
+ setProposedFlowType(flowType);
+ setIsConfirmationModalOpen(true);
+ }
+ },
+ [entitiesContext, visibleFlowsContext.visualFlowsApi],
+ );
+
+ return (
+ <>
+
+
+ New
+
+ {
+ setIsConfirmationModalOpen(false);
+ }}
+ actions={[
+ ,
+ ,
+ ]}
+ isOpen={isConfirmationModalOpen}
+ >
+
+ This will remove any existing integration and you will lose your current work. Are you sure you would like to
+ proceed?
+
+
+ >
+ );
+};
diff --git a/packages/ui/src/components/Visualization/EmptyState/VisualizationEmptyState.tsx b/packages/ui/src/components/Visualization/EmptyState/VisualizationEmptyState.tsx
index 8f4e2ecc1..cca4d3604 100644
--- a/packages/ui/src/components/Visualization/EmptyState/VisualizationEmptyState.tsx
+++ b/packages/ui/src/components/Visualization/EmptyState/VisualizationEmptyState.tsx
@@ -11,7 +11,7 @@ import {
import { CubesIcon as PatternFlyCubesIcon, EyeSlashIcon as PatternFlyEyeSlashIcon } from '@patternfly/react-icons';
import { FunctionComponent, useMemo } from 'react';
import { IDataTestID } from '../../../models';
-import { NewFlow } from '../ContextToolbar/FlowType/NewFlow';
+import { NewFlow } from './FlowType/NewFlow';
const CubesIcon: FunctionComponent = (props) => ;
const EyeSlashIcon: FunctionComponent = (props) => ;
diff --git a/packages/ui/src/models/camel/camel-k-resource.ts b/packages/ui/src/models/camel/camel-k-resource.ts
index edeee8dc3..25f78088c 100644
--- a/packages/ui/src/models/camel/camel-k-resource.ts
+++ b/packages/ui/src/models/camel/camel-k-resource.ts
@@ -4,13 +4,13 @@ import {
Pipe as PipeType,
} from '@kaoto-next/camel-catalog/types';
import { TileFilter } from '../../components/Catalog';
+import { createCamelPropertiesSorter } from '../../utils';
import { IKameletDefinition } from '../kamelets-catalog';
import { AddStepMode, BaseVisualCamelEntity, IVisualizationNodeData } from '../visualization/base-visual-entity';
import { MetadataEntity } from '../visualization/metadata';
-import { CamelResource } from './camel-resource';
+import { BaseVisualCamelEntityDefinition, CamelResource } from './camel-resource';
import { BaseCamelEntity } from './entities';
import { SourceSchemaType } from './source-schema-type';
-import { createCamelPropertiesSorter } from '../../utils';
export type CamelKType = IntegrationType | IKameletDefinition | KameletBindingType | PipeType;
@@ -41,6 +41,13 @@ export abstract class CamelKResource implements CamelResource {
this.metadata = this.resource.metadata && new MetadataEntity(this.resource);
}
+ getCanvasEntityList(): BaseVisualCamelEntityDefinition {
+ return {
+ common: [],
+ groups: {},
+ };
+ }
+
removeEntity(_id?: string) {}
refreshVisualMetadata() {}
createMetadataEntity() {
diff --git a/packages/ui/src/models/camel/camel-resource.ts b/packages/ui/src/models/camel/camel-resource.ts
index 95f56389b..1b487f434 100644
--- a/packages/ui/src/models/camel/camel-resource.ts
+++ b/packages/ui/src/models/camel/camel-resource.ts
@@ -7,23 +7,24 @@ import { TileFilter } from '../../components/Catalog';
import { IKameletDefinition } from '../kamelets-catalog';
import { AddStepMode, BaseVisualCamelEntity, IVisualizationNodeData } from '../visualization/base-visual-entity';
import { BeansEntity } from '../visualization/metadata';
+import { RouteTemplateBeansEntity } from '../visualization/metadata/routeTemplateBeansEntity';
import { CamelRouteResource } from './camel-route-resource';
-import { BaseCamelEntity } from './entities';
+import { BaseCamelEntity, EntityType } from './entities';
import { IntegrationResource } from './integration-resource';
import { KameletBindingResource } from './kamelet-binding-resource';
import { KameletResource } from './kamelet-resource';
import { PipeResource } from './pipe-resource';
import { SourceSchemaType } from './source-schema-type';
-import { RouteTemplateBeansEntity } from '../visualization/metadata/routeTemplateBeansEntity';
export interface CamelResource {
getVisualEntities(): BaseVisualCamelEntity[];
getEntities(): BaseCamelEntity[];
- addNewEntity(entity?: unknown): string;
+ addNewEntity(entityType?: EntityType): string;
removeEntity(id?: string): void;
supportsMultipleVisualEntities(): boolean;
toJSON(): unknown;
getType(): SourceSchemaType;
+ getCanvasEntityList(): BaseVisualCamelEntityDefinition;
/** Components Catalog related methods */
getCompatibleComponents(
@@ -36,6 +37,17 @@ export interface CamelResource {
sortFn?: (a: unknown, b: unknown) => number;
}
+export interface BaseVisualCamelEntityDefinition {
+ common: BaseVisualCamelEntityDefinitionItem[];
+ groups: Record;
+}
+
+export interface BaseVisualCamelEntityDefinitionItem {
+ name: EntityType;
+ title: string;
+ description: string;
+}
+
export interface BeansAwareResource {
createBeansEntity(): BeansEntity;
deleteBeansEntity(entity: BeansEntity): void;
diff --git a/packages/ui/src/models/camel/camel-route-resource.ts b/packages/ui/src/models/camel/camel-route-resource.ts
index f79299914..21c77f440 100644
--- a/packages/ui/src/models/camel/camel-route-resource.ts
+++ b/packages/ui/src/models/camel/camel-route-resource.ts
@@ -1,8 +1,9 @@
import { RouteDefinition } from '@kaoto-next/camel-catalog/types';
import { TileFilter } from '../../components/Catalog';
import { createCamelPropertiesSorter, isDefined } from '../../utils';
-import { AddStepMode } from '../visualization/base-visual-entity';
-import { CamelRouteVisualEntity, isCamelFrom, isCamelRoute } from '../visualization/flows';
+import { CatalogKind } from '../catalog-kind';
+import { AddStepMode, BaseVisualCamelEntityConstructor } from '../visualization/base-visual-entity';
+import { CamelCatalogService, CamelRouteVisualEntity } from '../visualization/flows';
import { CamelErrorHandlerVisualEntity } from '../visualization/flows/camel-error-handler-visual-entity';
import { CamelInterceptFromVisualEntity } from '../visualization/flows/camel-intercept-from-visual-entity';
import { CamelInterceptSendToEndpointVisualEntity } from '../visualization/flows/camel-intercept-send-to-endpoint-visual-entity';
@@ -16,27 +17,34 @@ import { CamelComponentFilterService } from '../visualization/flows/support/came
import { CamelRouteVisualEntityData } from '../visualization/flows/support/camel-component-types';
import { FlowTemplateService } from '../visualization/flows/support/flow-templates-service';
import { BeansEntity, isBeans } from '../visualization/metadata';
-import { BeansAwareResource, CamelResource } from './camel-resource';
-import { BaseCamelEntity } from './entities';
+import { BaseVisualCamelEntityDefinition, BeansAwareResource, CamelResource } from './camel-resource';
+import { BaseCamelEntity, EntityType } from './entities';
import { SourceSchemaType } from './source-schema-type';
export class CamelRouteResource implements CamelResource, BeansAwareResource {
- static readonly SUPPORTED_ENTITIES = [
- CamelOnExceptionVisualEntity,
- CamelOnCompletionVisualEntity,
- CamelErrorHandlerVisualEntity,
- CamelRestConfigurationVisualEntity,
- CamelRouteConfigurationVisualEntity,
- CamelInterceptVisualEntity,
- CamelInterceptFromVisualEntity,
- CamelInterceptSendToEndpointVisualEntity,
- ] as const;
+ static readonly SUPPORTED_ENTITIES: { type: EntityType; group: string; Entity: BaseVisualCamelEntityConstructor }[] =
+ [
+ { type: EntityType.Route, group: '', Entity: CamelRouteVisualEntity },
+ { type: EntityType.RouteConfiguration, group: 'Configuration', Entity: CamelRouteConfigurationVisualEntity },
+ { type: EntityType.Intercept, group: 'Configuration', Entity: CamelInterceptVisualEntity },
+ { type: EntityType.InterceptFrom, group: 'Configuration', Entity: CamelInterceptFromVisualEntity },
+ {
+ type: EntityType.InterceptSendToEndpoint,
+ group: 'Configuration',
+ Entity: CamelInterceptSendToEndpointVisualEntity,
+ },
+ { type: EntityType.OnCompletion, group: 'Configuration', Entity: CamelOnCompletionVisualEntity },
+ { type: EntityType.OnException, group: 'Error Handling', Entity: CamelOnExceptionVisualEntity },
+ { type: EntityType.ErrorHandler, group: 'Error Handling', Entity: CamelErrorHandlerVisualEntity },
+ { type: EntityType.RestConfiguration, group: 'Rest', Entity: CamelRestConfigurationVisualEntity },
+ ];
static readonly PARAMETERS_ORDER = ['id', 'description', 'uri', 'parameters', 'steps'];
readonly sortFn = createCamelPropertiesSorter(CamelRouteResource.PARAMETERS_ORDER) as (
a: unknown,
b: unknown,
) => number;
private entities: BaseCamelEntity[] = [];
+ private resolvedEntities: BaseVisualCamelEntityDefinition | undefined;
constructor(json?: unknown) {
if (!json) return;
@@ -50,13 +58,51 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource {
}, [] as BaseCamelEntity[]);
}
- addNewEntity(): string {
+ getCanvasEntityList(): BaseVisualCamelEntityDefinition {
+ if (isDefined(this.resolvedEntities)) {
+ return this.resolvedEntities;
+ }
+
+ this.resolvedEntities = CamelRouteResource.SUPPORTED_ENTITIES.reduce(
+ (acc, { type, group }) => {
+ const catalogEntity = CamelCatalogService.getComponent(CatalogKind.Entity, type);
+ const entityDefinition = {
+ name: type,
+ title: catalogEntity?.model.title || type,
+ description: catalogEntity?.model.description || '',
+ };
+
+ if (group === '') {
+ acc.common.push(entityDefinition);
+ return acc;
+ }
+
+ acc.groups[group] ??= [];
+ acc.groups[group].push(entityDefinition);
+ return acc;
+ },
+ { common: [], groups: {} } as BaseVisualCamelEntityDefinition,
+ );
+
+ return this.resolvedEntities;
+ }
+
+ addNewEntity(entityType?: EntityType): string {
+ if (entityType) {
+ const supportedEntity = CamelRouteResource.SUPPORTED_ENTITIES.find(({ type }) => type === entityType);
+ if (supportedEntity) {
+ const entity = new supportedEntity.Entity();
+ this.entities.push(entity);
+ return entity.id;
+ }
+ }
+
const template = FlowTemplateService.getFlowTemplate(this.getType());
const route = template[0].route as RouteDefinition;
- const visualEntity = new CamelRouteVisualEntity(route);
- this.entities.push(visualEntity);
+ const entity = new CamelRouteVisualEntity(route);
+ this.entities.push(entity);
- return visualEntity.id;
+ return entity.id;
}
getType(): SourceSchemaType {
@@ -71,7 +117,7 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource {
return this.entities.filter(
(entity) =>
entity instanceof CamelRouteVisualEntity ||
- CamelRouteResource.SUPPORTED_ENTITIES.some((SupportedEntity) => entity instanceof SupportedEntity),
+ CamelRouteResource.SUPPORTED_ENTITIES.some(({ Entity }) => entity instanceof Entity),
) as CamelRouteVisualEntity[];
}
@@ -104,10 +150,6 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource {
if (index !== -1) {
this.entities.splice(index, 1);
}
- // we don't want to end up with clean entities, so we're adding default one if the list if empty
- if (this.entities.length === 0) {
- this.addNewEntity();
- }
}
/** Components Catalog related methods */
@@ -125,17 +167,12 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource {
return undefined;
}
- if (isCamelRoute(rawItem)) {
- return new CamelRouteVisualEntity(rawItem.route);
- } else if (isCamelFrom(rawItem)) {
- return new CamelRouteVisualEntity({ from: rawItem.from });
- } else if (isBeans(rawItem)) {
+ if (isBeans(rawItem)) {
return new BeansEntity(rawItem);
}
- for (const Entity of CamelRouteResource.SUPPORTED_ENTITIES) {
+ for (const { Entity } of CamelRouteResource.SUPPORTED_ENTITIES) {
if (Entity.isApplicable(rawItem)) {
- // @ts-expect-error When iterating over the entities, we know that the entity is applicable but TS doesn't, hence causing an error
return new Entity(rawItem);
}
}
diff --git a/packages/ui/src/models/camel/source-schema-config.ts b/packages/ui/src/models/camel/source-schema-config.ts
index d2f7e2a0b..3064d5949 100644
--- a/packages/ui/src/models/camel/source-schema-config.ts
+++ b/packages/ui/src/models/camel/source-schema-config.ts
@@ -6,6 +6,7 @@ export interface ISourceSchema {
schema: KaotoSchemaDefinition | undefined;
name: string;
multipleRoute: boolean;
+ description?: string;
}
interface IEntitySchemaConfig {
@@ -22,26 +23,35 @@ class SourceSchemaConfig {
name: 'Camel Route',
schema: undefined,
multipleRoute: true,
+ description:
+ 'Defines an executable integration flow by declaring a source (starter) and followed by a sequence of actions (or steps). Actions can include data manipulations, EIPs (integration patterns) and internal or external calls.',
},
[SourceSchemaType.Kamelet]: {
name: 'Kamelet',
schema: undefined,
multipleRoute: false,
+ description:
+ 'Defines a reusable Camel route as a building block. Kamelets can not be executed on their own, they are used as sources, actions or sinks in Camel Routes or Pipes.',
},
[SourceSchemaType.Pipe]: {
name: 'Pipe',
schema: undefined,
multipleRoute: false,
+ description:
+ 'Defines a sequence of concatenated Kamelets to form start to finish integration flows. Pipes are a more abstract level of defining integration flows, by chosing and configuring Kamelets.',
},
[SourceSchemaType.KameletBinding]: {
name: 'Kamelet Binding',
schema: undefined,
multipleRoute: false,
+ description:
+ 'Defines a sequence of concatenated Kamelets to form start to finish integration flows. Pipes are a more abstract level of defining integration flows, by chosing and configuring Kamelets.',
},
[SourceSchemaType.Integration]: {
name: 'Integration',
schema: undefined,
multipleRoute: true,
+ description: 'An integration defines a Camel route in a CRD file.',
},
};
diff --git a/packages/ui/src/models/visualization/base-visual-entity.ts b/packages/ui/src/models/visualization/base-visual-entity.ts
index 095e3f935..b25bc7ec3 100644
--- a/packages/ui/src/models/visualization/base-visual-entity.ts
+++ b/packages/ui/src/models/visualization/base-visual-entity.ts
@@ -53,6 +53,12 @@ export interface BaseVisualCamelEntity extends BaseCamelEntity {
toVizNode: () => IVisualizationNode;
}
+export interface BaseVisualCamelEntityConstructor {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ new (...args: any[]): BaseVisualCamelEntity;
+ isApplicable: (entity: unknown) => boolean;
+}
+
/**
* IVisualizationNode
*
diff --git a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts
index 84d4a166c..63923616a 100644
--- a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts
@@ -17,7 +17,7 @@ export class CamelErrorHandlerVisualEntity implements BaseVisualCamelEntity {
id: string;
readonly type = EntityType.ErrorHandler;
- constructor(public errorHandlerDef: { errorHandler: ErrorHandlerBuilderDeserializer }) {
+ constructor(public errorHandlerDef: { errorHandler: ErrorHandlerBuilderDeserializer } = { errorHandler: {} }) {
const id = getCamelRandomId('errorHandler');
this.id = id;
}
diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts
index 52176a558..6615e1ebf 100644
--- a/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts
@@ -23,7 +23,7 @@ export class CamelInterceptFromVisualEntity
readonly type = EntityType.InterceptFrom;
static readonly ROOT_PATH = 'interceptFrom';
- constructor(interceptFromRaw: { interceptFrom: InterceptFrom }) {
+ constructor(interceptFromRaw: { interceptFrom: InterceptFrom } = { interceptFrom: {} }) {
let interceptFromDef: { interceptFrom: Exclude };
if (typeof interceptFromRaw.interceptFrom === 'string') {
interceptFromDef = {
diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts
index 064fb64f4..4218caa74 100644
--- a/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts
@@ -23,7 +23,9 @@ export class CamelInterceptSendToEndpointVisualEntity
readonly type = EntityType.InterceptSendToEndpoint;
static readonly ROOT_PATH = 'interceptSendToEndpoint';
- constructor(interceptSendToEndpointRaw: { interceptSendToEndpoint: InterceptSendToEndpoint }) {
+ constructor(
+ interceptSendToEndpointRaw: { interceptSendToEndpoint: InterceptSendToEndpoint } = { interceptSendToEndpoint: {} },
+ ) {
let interceptSendToEndpointDef: { interceptSendToEndpoint: Exclude };
if (typeof interceptSendToEndpointRaw.interceptSendToEndpoint === 'string') {
interceptSendToEndpointDef = {
diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts
index b27f63ef7..f2ad3ad41 100644
--- a/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts
@@ -22,7 +22,7 @@ export class CamelInterceptVisualEntity
readonly type = EntityType.Intercept;
static readonly ROOT_PATH = 'intercept';
- constructor(public interceptDef: { intercept: Intercept }) {
+ constructor(public interceptDef: { intercept: Intercept } = { intercept: {} }) {
super(interceptDef);
const id = interceptDef.intercept.id ?? getCamelRandomId(CamelInterceptVisualEntity.ROOT_PATH);
this.id = id;
diff --git a/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts
index bc52aa6b2..d9192393c 100644
--- a/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts
@@ -22,7 +22,7 @@ export class CamelOnCompletionVisualEntity
readonly type = EntityType.OnCompletion;
static readonly ROOT_PATH = 'onCompletion';
- constructor(public onCompletionDef: { onCompletion: OnCompletion }) {
+ constructor(public onCompletionDef: { onCompletion: OnCompletion } = { onCompletion: {} }) {
super(onCompletionDef);
const id = onCompletionDef.onCompletion.id ?? getCamelRandomId(CamelOnCompletionVisualEntity.ROOT_PATH);
this.id = id;
diff --git a/packages/ui/src/models/visualization/flows/camel-on-exception-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-on-exception-visual-entity.ts
index f8619bc8e..484588095 100644
--- a/packages/ui/src/models/visualization/flows/camel-on-exception-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-on-exception-visual-entity.ts
@@ -22,7 +22,7 @@ export class CamelOnExceptionVisualEntity
readonly type = EntityType.OnException;
private static readonly ROOT_PATH = 'onException';
- constructor(public onExceptionDef: { onException: OnException }) {
+ constructor(public onExceptionDef: { onException: OnException } = { onException: {} }) {
super(onExceptionDef);
const id = onExceptionDef.onException.id ?? getCamelRandomId(CamelOnExceptionVisualEntity.ROOT_PATH);
this.id = id;
diff --git a/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts
index 864a76c6d..c3e37fd6a 100644
--- a/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts
@@ -21,7 +21,7 @@ export class CamelRestConfigurationVisualEntity implements BaseVisualCamelEntity
readonly type = EntityType.RestConfiguration;
private schemaValidator: ValidateFunction | undefined;
- constructor(public restConfigurationDef: { restConfiguration: RestConfiguration }) {
+ constructor(public restConfigurationDef: { restConfiguration: RestConfiguration } = { restConfiguration: {} }) {
const id = getCamelRandomId('restConfiguration');
this.id = id;
}
diff --git a/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts
index 49140e912..8662e81a0 100644
--- a/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts
@@ -37,7 +37,9 @@ export class CamelRouteConfigurationVisualEntity
'onCompletion',
];
- constructor(public routeConfigurationDef: { routeConfiguration: RouteConfigurationDefinition }) {
+ constructor(
+ public routeConfigurationDef: { routeConfiguration: RouteConfigurationDefinition } = { routeConfiguration: {} },
+ ) {
super(routeConfigurationDef);
const id =
routeConfigurationDef.routeConfiguration.id ?? getCamelRandomId(CamelRouteConfigurationVisualEntity.ROOT_PATH);
diff --git a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts
index 2fdb39436..e3c697eb1 100644
--- a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts
+++ b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts
@@ -38,14 +38,37 @@ export const isCamelFrom = (rawEntity: unknown): rawEntity is { from: FromDefini
export class CamelRouteVisualEntity extends AbstractCamelVisualEntity {
id: string;
+ route: RouteDefinition;
readonly type = EntityType.Route;
- constructor(public route: RouteDefinition) {
+ constructor(
+ routeRaw: RouteDefinition = {
+ from: {
+ uri: '',
+ steps: [],
+ },
+ },
+ ) {
+ let route: RouteDefinition;
+ if (isCamelFrom(routeRaw)) {
+ route = {
+ from: routeRaw.from,
+ };
+ } else {
+ route = routeRaw;
+ }
+
super(route);
- this.id = route.id ?? getCamelRandomId('route');
+ this.route = route;
+ const id = routeRaw.id ?? getCamelRandomId('route');
+ this.id = id;
this.route.id = this.id;
}
+ static isApplicable(routeDef: unknown): routeDef is { route: RouteDefinition } | { from: FromDefinition } {
+ return isCamelRoute(routeDef) || isCamelFrom(routeDef);
+ }
+
/** Internal API methods */
setId(routeId: string): void {
this.id = routeId;