From 496642fc1e4be38b95a9404f99167998a239bfb9 Mon Sep 17 00:00:00 2001 From: indraraj Date: Thu, 23 Jan 2025 01:06:11 +0530 Subject: [PATCH 1/4] DBZ-8590: Add Predicate support in the Transform configuration and edit page --- .../src/__mocks__/data/Predicates.json | 28 ++ debezium-platform-stage/src/apis/apis.tsx | 7 + .../pipelineDesigner/CreationFlow.tsx | 398 ------------------ .../CreationFlowTransform.tsx | 103 +++-- .../pipelineDesigner/TransformLinkNode.tsx | 30 +- .../pipelineDesigner/UnifiedMultiEdge.tsx | 10 +- .../pipelineDesigner/WelcomeFlow.tsx | 94 +++-- .../src/components/pipelineDesigner/index.ts | 1 - .../src/pages/Pipeline/PipelineDesigner.tsx | 29 +- .../src/pages/Pipeline/PipelineEmpty.tsx | 11 +- .../src/pages/Transforms/CreateTransforms.tsx | 230 +++++++--- .../src/pages/Transforms/EditTransforms.tsx | 265 ++++++++---- 12 files changed, 569 insertions(+), 637 deletions(-) create mode 100644 debezium-platform-stage/src/__mocks__/data/Predicates.json delete mode 100644 debezium-platform-stage/src/components/pipelineDesigner/CreationFlow.tsx diff --git a/debezium-platform-stage/src/__mocks__/data/Predicates.json b/debezium-platform-stage/src/__mocks__/data/Predicates.json new file mode 100644 index 0000000..869a775 --- /dev/null +++ b/debezium-platform-stage/src/__mocks__/data/Predicates.json @@ -0,0 +1,28 @@ +[ + { + "properties": { + "pattern": { + "title": "Pattern", + "description": "A predicate which is true for records with a topic name that matches the configured regular expression.", + "type": "STRING", + "x-name": "pattern" + } + }, + "predicate": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches" + }, + { + "properties": { + "name": { + "title": "Name", + "description": "A predicate which is true for records with at least one header with the configured name.", + "type": "STRING", + "x-name": "name" + } + }, + "predicate": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey" + }, + { + "properties": {}, + "predicate": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone" + } + ] \ No newline at end of file diff --git a/debezium-platform-stage/src/apis/apis.tsx b/debezium-platform-stage/src/apis/apis.tsx index 8b86628..f6ae514 100644 --- a/debezium-platform-stage/src/apis/apis.tsx +++ b/debezium-platform-stage/src/apis/apis.tsx @@ -67,12 +67,19 @@ export type Source = { id: number; }; +export type Predicate = { + type: string; + config: Record; + negate?: boolean; +} + export type TransformData = { type: string; schema: string; vaults: Vault[]; config: SourceConfig; description?: string; + predicate?: Predicate; name: string; id: number; }; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlow.tsx b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlow.tsx deleted file mode 100644 index 05035f7..0000000 --- a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlow.tsx +++ /dev/null @@ -1,398 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useCallback, useMemo, useState } from "react"; -import ReactFlow, { - applyNodeChanges, - applyEdgeChanges, - addEdge, - NodeChange, - Node, - EdgeChange, - Edge, - Connection, - Background, -} from "reactflow"; -import AddTransformationNode from "./TransformAdditionNode"; - -import DataNode from "./DataNode"; - -import { MdLogin, MdLogout } from "react-icons/md"; -import DataNodeSelector from "./DataSelectorNode"; -import { Button, Modal, ModalBody, ModalHeader } from "@patternfly/react-core"; -import { Destination, Source } from "../../apis/apis"; -import { PlusIcon } from "@patternfly/react-icons"; -import "./CreationFlow.css"; -import SourcePipelineModel from "./SourcePipelineModel"; -import DestinationPipelineModel from "./DestinationPipelineModel"; -import CustomEdgeDestination from "./CustomEdgeDestination"; -import CustomEdgeSource from "./CustomEdgeSource"; -import { useData } from "../../appLayout/AppContext"; -import { AppColors } from "@utils/constants"; - -const nodeTypes = { - dataNodeSelector: DataNodeSelector, - addTransformation: AddTransformationNode, - dataNode: DataNode, -}; - -const edgeTypes = { - customEdgeSource: CustomEdgeSource, - customEdgeDestination: CustomEdgeDestination, -}; - -// const defaultEdgeOptions = { -// type: "customEdge", -// markerEnd: "edge-circle", -// }; - -const proOptions = { hideAttribution: true }; - -interface CreationFlowProps { - updateIfSourceConfigured: (isConfigured: boolean) => void; - updateIfDestinationConfigured: (isConfigured: boolean) => void; - isSourceConfigured: boolean; - isDestinationConfigured: boolean; - updateSelectedSource: (source: Source) => void; - updateSelectedDestination: (destination: Destination) => void; -} - -const CreationFlow: React.FC = ({ - updateIfSourceConfigured, - updateIfDestinationConfigured, - isSourceConfigured, - isDestinationConfigured, - updateSelectedSource, - updateSelectedDestination, -}) => { - - const { darkMode } = useData(); - - const [updatedSourceNodes, setUpdatedSourceNodes] = useState(); - const [updatedDestinationNodes, setUpdatedDestinationNodes] = useState(); - - const [isSourceModalOpen, setIsSourceModalOpen] = useState(false); - const [isDestinationModalOpen, setIsDestinationModalOpen] = useState(false); - - const handleSourceModalToggle = useCallback(() => { - setIsSourceModalOpen(!isSourceModalOpen); - }, [isSourceModalOpen]); - - const handleDestinationModalToggle = useCallback(() => { - setIsDestinationModalOpen(!isDestinationModalOpen); - }, [isDestinationModalOpen]); - - const defaultSourceNode = useMemo(() => { - return { - id: "source", - data: { - icon: MdLogout, - label: "Source", - type: "source", - action: ( - - ), - }, - position: { x: 100, y: 150 }, - type: "dataNodeSelector", - // draggable: false, - }; - }, [handleSourceModalToggle]); - - const transformationGroup = useMemo(() => { - return { - id: "transformation_group", - data: { label: "Group B.A" }, - position: { x: 330, y: 100 }, - style: { - boxShadow: - "7px 7px 15px rgba(88, 178, 218, 0.3), -7px -7px 15px rgba(165, 200, 45, 0.3)", - borderRadius: "10px", - border: "0", - width: 210, - height: 150, - }, - type: "group", - // draggable: false, - }; - }, []); - - const defaultTransformationNode = useMemo(() => { - return { - id: "add_transformation", - data: { - label: "Transforms", - sourcePosition: "right", - targetPosition: "left", - }, - position: { x: 65, y: 35 }, - targetPosition: "left", - type: "addTransformation", - parentId: "transformation_group", - extent: "parent", - // draggable: false, - }; - }, []); - - const defaultDestinationNode = useMemo(() => { - return { - id: "destination", - data: { - icon: MdLogin, - label: "Destination", - type: "destination", - action: ( - - ), - }, - position: { x: 650, y: 150 }, - type: "dataNodeSelector", - // draggable: false, - }; - }, [handleDestinationModalToggle]); - - const initialNodes = [ - defaultSourceNode, - transformationGroup, - defaultTransformationNode, - defaultDestinationNode, - ]; - - const initialEdges = [ - { - id: "source-add_transformation", - source: "source", - target: "add_transformation", - // animated: true, - type: "customEdgeSource", - sourceHandle: "a", - }, - { - id: "add_transformation-destination", - source: "add_transformation", - target: "destination", - // animated: true, - type: "customEdgeDestination", - }, - ]; - - const [nodes, setNodes] = useState(initialNodes); - const [edges, setEdges] = useState(initialEdges); - - const onNodesChange = useCallback( - (changes: NodeChange[]) => - setNodes((nds: Node[]) => applyNodeChanges(changes, nds)), - [setNodes] - ); - const onEdgesChange = useCallback( - (changes: EdgeChange[]) => - setEdges((eds: Edge[]) => applyEdgeChanges(changes, eds)), - [setEdges] - ); - const onConnect = useCallback( - (connection: Connection) => { - setEdges((eds: Edge[]) => addEdge(connection, eds)); - }, - [setEdges] - ); - - const onSourceSelection = useCallback( - (source: Source) => { - const selectedSourceNode = { - id: "source", - data: { - connectorType: source.type, - label: source.name, - type: "source", - // draggable: false, - editAction: () => setIsSourceModalOpen(true), - }, - position: { x: 100, y: 160 }, - type: "dataNode", - // draggable: false, - }; - updateIfSourceConfigured(true); - setUpdatedSourceNodes(selectedSourceNode); - updateSelectedSource(source); - - const destinationNode = isDestinationConfigured - ? updatedDestinationNodes - : defaultDestinationNode; - - // Update the nodes - setNodes([ - selectedSourceNode, - transformationGroup, - defaultTransformationNode, - destinationNode, - ]); - - handleSourceModalToggle(); - }, - [ - transformationGroup, - defaultTransformationNode, - defaultDestinationNode, - updateIfSourceConfigured, - isDestinationConfigured, - updatedDestinationNodes, - updateSelectedSource, - handleSourceModalToggle, - ] - ); - - const onDestinationSelection = useCallback( - (destination: Destination) => { - const selectedDestinationNode = { - id: "destination", - data: { - connectorType: destination.type, - label: destination.name, - type: "destination", - // draggable: false, - editAction: () => setIsDestinationModalOpen(true), - }, - position: { x: 650, y: 160 }, - type: "dataNode", - // draggable: false, - }; - - updateIfDestinationConfigured(true); - setUpdatedDestinationNodes(selectedDestinationNode); - updateSelectedDestination(destination); - - const sourceNode = isSourceConfigured - ? updatedSourceNodes - : defaultSourceNode; - - // Update the nodes - setNodes([ - sourceNode, - transformationGroup, - defaultTransformationNode, - selectedDestinationNode, - ]); - - handleDestinationModalToggle(); - }, - [ - transformationGroup, - defaultTransformationNode, - defaultSourceNode, - updateIfDestinationConfigured, - isSourceConfigured, - updatedSourceNodes, - updateSelectedDestination, - handleDestinationModalToggle, - ] - ); - - return ( - <> - - - - - - - - - - - - - - {/* - - */} - - - - - - - - - - - - - - - - - - - - ); -}; - -export default CreationFlow; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx index d964954..6a7dd6f 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx @@ -12,6 +12,7 @@ import ReactFlow, { Background, MiniMap, PanOnScrollMode, + useReactFlow, } from "reactflow"; import TransformAdditionNode from "./TransformAdditionNode"; @@ -76,6 +77,30 @@ const CreationFlowTransform: React.FC = ({ rearrangeTrigger, }) => { const { darkMode } = useData(); + + const reactFlowInstance = useReactFlow(); + + const refitElements = () => { + setTimeout(() => { + reactFlowInstance.fitView({ + padding: 0.2, // 20% padding + duration: 200, // 200ms + }); + }, 50); + }; + + const reactFlowWrapper = useRef(null); + + useEffect(() => { + const handleResize = () => { + refitElements(); + }; + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + const [isSourceModalOpen, setIsSourceModalOpen] = useState(false); const [isTransformModalOpen, setIsTransformModalOpen] = useState(false); const [isDestinationModalOpen, setIsDestinationModalOpen] = useState(false); @@ -252,6 +277,7 @@ const CreationFlowTransform: React.FC = ({ type: "transformLinkNode", parentId: "transform_group", extent: "parent", + draggable: false, }; }; const selectedTransformRef = useRef(selectedTransform); @@ -640,43 +666,48 @@ const CreationFlowTransform: React.FC = ({ ); return ( <> - - - {/* */} - + { + instance.fitView({ padding: 0.2 }); }} - gap={13} - color={darkMode ? AppColors.dark : AppColors.white} - /> - - - - - - - - - - + > + + + + + + + + + + + + + + = ({ data }) => { } > - + + +
+ +
+ = ({ data }) => { >
- +
- = ({ data }) => { overflow: "hidden", textOverflow: "ellipsis", }} - > + > {data.label} - +
+ = ({ }) => { const midY = sourceY - 1; // Slightly above the direct line - const firstTransformX = sourceX + 220; + const firstTransformX = sourceX + 200; const lastTransformX = targetX - 220; // Create a combined path through all three points - const firstPath = getStraightPath({ + const firstPath = getBezierPath({ sourceX, sourceY, targetX: firstTransformX, targetY: midY, }); - const middlePath = getStraightPath({ + const middlePath = getBezierPath({ sourceX: firstTransformX, sourceY: midY, targetX: lastTransformX, targetY: midY, }); - const secondPath = getStraightPath({ + const secondPath = getBezierPath({ sourceX: lastTransformX, sourceY: midY, targetX, diff --git a/debezium-platform-stage/src/components/pipelineDesigner/WelcomeFlow.tsx b/debezium-platform-stage/src/components/pipelineDesigner/WelcomeFlow.tsx index 00b6381..021b929 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/WelcomeFlow.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/WelcomeFlow.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import ReactFlow, { applyNodeChanges, applyEdgeChanges, @@ -10,6 +10,7 @@ import ReactFlow, { Edge, Connection, Background, + useReactFlow, } from "reactflow"; import DataNode from "./DataNode"; import { MdLogin, MdLogout } from "react-icons/md"; @@ -37,6 +38,29 @@ interface WelcomeFlowProps {} const WelcomeFlow: React.FC = () => { const { darkMode } = useData(); + const reactFlowInstance = useReactFlow(); + + const refitElements = () => { + setTimeout(() => { + reactFlowInstance.fitView({ + padding: 0.2, // 20% padding + duration: 200, // 200ms + }); + }, 50); + }; + + const reactFlowWrapper = useRef(null); + + useEffect(() => { + const handleResize = () => { + refitElements(); + }; + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + const defaultSourceNode = useMemo(() => { return { id: "source", @@ -121,39 +145,47 @@ const WelcomeFlow: React.FC = () => { [setEdges] ); + const savedPreference = localStorage.getItem("side-nav-collapsed"); + console.log("side bar", savedPreference); + return ( <> - - + { + instance.fitView({ padding: 0.2 }); }} - gap={15} - color={darkMode ? AppColors.dark : AppColors.white} - /> - - - - - - - - - - + > + + + + + + + + + + + + ); }; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/index.ts b/debezium-platform-stage/src/components/pipelineDesigner/index.ts index 8a10e6b..ae07ad8 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/index.ts +++ b/debezium-platform-stage/src/components/pipelineDesigner/index.ts @@ -1,7 +1,6 @@ export * from './DataNode' export * from './TransformAdditionNode' export * from './CompositionFlow' -export * from './CreationFlow' export * from './CustomEdgeDestination' export * from './CustomEdgeSource' export * from './DataSelectorNode' diff --git a/debezium-platform-stage/src/pages/Pipeline/PipelineDesigner.tsx b/debezium-platform-stage/src/pages/Pipeline/PipelineDesigner.tsx index 1671e02..1f95266 100644 --- a/debezium-platform-stage/src/pages/Pipeline/PipelineDesigner.tsx +++ b/debezium-platform-stage/src/pages/Pipeline/PipelineDesigner.tsx @@ -34,6 +34,7 @@ import { DraggableObject, } from "@patternfly/react-drag-drop"; import { TrashIcon } from "@patternfly/react-icons"; +import { ReactFlowProvider } from "reactflow"; // Define Jotai atoms export const selectedSourceAtom = atom(undefined); @@ -243,19 +244,21 @@ const PipelineDesigner: React.FunctionComponent = () => { - + + + { const navigate = useNavigate(); @@ -24,11 +25,7 @@ const PipelineEmpty: React.FunctionComponent = () => { return ( - + Stage UI provide a visual tool to setup and operate with data @@ -43,7 +40,9 @@ const PipelineEmpty: React.FunctionComponent = () => { - + + + diff --git a/debezium-platform-stage/src/pages/Transforms/CreateTransforms.tsx b/debezium-platform-stage/src/pages/Transforms/CreateTransforms.tsx index 8e284b0..988fca2 100644 --- a/debezium-platform-stage/src/pages/Transforms/CreateTransforms.tsx +++ b/debezium-platform-stage/src/pages/Transforms/CreateTransforms.tsx @@ -7,6 +7,7 @@ import { ButtonType, Card, CardBody, + Checkbox, Form, FormContextProvider, FormFieldGroup, @@ -16,6 +17,7 @@ import { FormHelperText, FormSelect, FormSelectOption, + FormSelectOptionProps, HelperText, HelperTextItem, MenuToggle, @@ -45,6 +47,7 @@ import { } from "@patternfly/react-icons"; import * as React from "react"; import transforms from "../../__mocks__/data/DebeziumTransfroms.json"; +import predicates from "../../__mocks__/data/Predicates.json"; import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { createPost, TransformData } from "src/apis"; @@ -97,6 +100,32 @@ const CreateTransforms: React.FunctionComponent = ({ setSelectOptions(selectOption); }, []); + const [selectedPredicate, setSelectedPredicate] = React.useState(""); + const [selectPredicateOptions, setSelectPredicateOptions] = + React.useState(); + + useEffect(() => { + const selectOption: FormSelectOptionProps[] = predicates.map((item) => { + return { + value: item.predicate, + label: item.predicate, + }; + }); + selectOption.unshift({ + value: "", + label: "Select a predicate type", + isPlaceholder: true, + }); + setSelectPredicateOptions(selectOption); + }, []); + + const onChange = ( + _event: React.FormEvent, + value: string + ) => { + setSelectedPredicate(value); + }; + useEffect(() => { const selectOption: SelectOptionProps[] = transforms.map((item) => { return { @@ -290,7 +319,7 @@ const CreateTransforms: React.FunctionComponent = ({ id="typeahead-select-input" autoComplete="off" innerRef={textInputRef} - placeholder="Select a state" + placeholder="Select a transform" {...(activeItemId && { "aria-activedescendant": activeItemId })} role="combobox" isExpanded={isOpen} @@ -316,12 +345,36 @@ const CreateTransforms: React.FunctionComponent = ({ description: description, ...restValues } = values; + const predicateConfig: Record = {}; + let transformConfig: Record = {}; + + if(selectedPredicate){ + Object.entries(restValues).forEach(([key, value]) => { + if(key.startsWith("predicate")){ + if(key.startsWith("predicateConfig.")){ + predicateConfig[key.replace("predicateConfig.", "")] = value; + } + } + else{ + transformConfig[key] = value} + }); + }else { + transformConfig = restValues; + } + const payload = { description: description, type: selected, schema: "schema321", vaults: [], - config: { ...restValues }, + config: { ...transformConfig }, + ...(selectedPredicate && { + predicate: { + type: selectedPredicate, + config: { ...predicateConfig }, + negate: values["predicate.negate"] === "true", + }, + }), name: transformName, }; const response = await createPost(`${API_URL}/api/transforms`, payload); @@ -346,7 +399,7 @@ const CreateTransforms: React.FunctionComponent = ({ find(transforms, { transform: selected })?.properties || {} ).reduce((acc: Record, [key, value]) => { if (value.defaultValue) { - acc[`debezium.transforms.transform-name.${key}`] = value.defaultValue; + acc[key] = value.defaultValue; } return acc; }, {}); @@ -519,6 +572,107 @@ const CreateTransforms: React.FunctionComponent = ({ /> + + } + > + + + + } + fieldId="predicate-type" + > + + {selectPredicateOptions?.map((option, index) => ( + + ))} + + + {selectedPredicate && + selectedPredicate !== + "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone" && + (() => { + const predicate = predicates.find( + (predicate) => + predicate.predicate === selectedPredicate + ); + const predicateProperties = + predicate?.properties as Record; + const propertyKey = Object.keys( + predicateProperties || {} + )[0]; + const property = predicateProperties?.[propertyKey]; + + return ( + + + + } + fieldId="predicate-config" + > + { + setValue(`predicateConfig.${property["x-name"]}`, value); + // setError("description", undefined); + }} + value={getValue(`predicateConfig.${property["x-name"]}`)} + /> + + ); + })()} + {selectedPredicate && ( + + , checked: boolean) => { + setValue("predicate.negate", ""+checked); + setError("predicate.negate", undefined); + }} + description="Setting this option to True applies the predicate only to records that do not match the defined predicate condition" + /> + + )} + + {selected && ( = ({ @@ -574,44 +728,28 @@ const CreateTransforms: React.FunctionComponent = ({ > {isBoolean ? ( { const value = checked ? "true" : "false"; - setValue( - `debezium.transforms.${transformName}.${key}`, - value - ); - setError( - `debezium.transforms.${transformName}.${key}`, - undefined - ); + setValue(key, value); + setError(key, undefined); }} /> ) : dropDown ? ( { - setValue( - `debezium.transforms.${transformName}.${key}`, - value - ); - setError( - `debezium.transforms.${transformName}.${key}`, - undefined - ); + setValue(key, value); + setError(key, undefined); }} aria-label="FormSelect Input" ouiaId="BasicFormSelect" @@ -631,30 +769,18 @@ const CreateTransforms: React.FunctionComponent = ({ ) : ( { - setValue( - `debezium.transforms.${transformName}.${key}`, - value - ); - setError( - `debezium.transforms.${transformName}.${key}`, - undefined - ); + setValue(key, value); + setError(key, undefined); }} value={ - getValue( - `debezium.transforms.${transformName}.${key}` - ) || property?.defaultValue + getValue(key) || property?.defaultValue } validated={ - errors[ - `debezium.transforms.${transformName}.${key}` - ] - ? "error" - : "default" + errors[key] ? "error" : "default" } /> )} @@ -663,23 +789,13 @@ const CreateTransforms: React.FunctionComponent = ({ , })} > - { - errors[ - `debezium.transforms.${transformName}.${key}` - ] - } + {errors[key]} diff --git a/debezium-platform-stage/src/pages/Transforms/EditTransforms.tsx b/debezium-platform-stage/src/pages/Transforms/EditTransforms.tsx index bdd2855..30b7dde 100644 --- a/debezium-platform-stage/src/pages/Transforms/EditTransforms.tsx +++ b/debezium-platform-stage/src/pages/Transforms/EditTransforms.tsx @@ -7,6 +7,7 @@ import { ButtonType, Card, CardBody, + Checkbox, Form, FormContextProvider, FormFieldGroup, @@ -16,6 +17,7 @@ import { FormHelperText, FormSelect, FormSelectOption, + FormSelectOptionProps, HelperText, HelperTextItem, MenuToggle, @@ -44,6 +46,7 @@ import { } from "@patternfly/react-icons"; import * as React from "react"; import transforms from "../../__mocks__/data/DebeziumTransfroms.json"; +import predicates from "../../__mocks__/data/Predicates.json"; import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { editPut, fetchDataTypeTwo, TransformData } from "src/apis"; @@ -99,6 +102,32 @@ const EditTransforms: React.FunctionComponent = ({ setSelectOptions(selectOption); }, []); + const [selectedPredicate, setSelectedPredicate] = React.useState(""); + const [selectPredicateOptions, setSelectPredicateOptions] = + React.useState(); + + useEffect(() => { + const selectOption: FormSelectOptionProps[] = predicates.map((item) => { + return { + value: item.predicate, + label: item.predicate, + }; + }); + selectOption.unshift({ + value: "", + label: "Select a predicate type", + isPlaceholder: true, + }); + setSelectPredicateOptions(selectOption); + }, []); + + const onChange = ( + _event: React.FormEvent, + value: string + ) => { + setSelectedPredicate(value); + }; + useEffect(() => { const selectOption: SelectOptionProps[] = transforms.map((item) => { return { @@ -285,7 +314,7 @@ const EditTransforms: React.FunctionComponent = ({ id="typeahead-select-input" autoComplete="off" innerRef={textInputRef} - placeholder="Select a state" + placeholder="Select a transform" {...(activeItemId && { "aria-activedescendant": activeItemId })} role="combobox" isExpanded={isOpen} @@ -313,12 +342,21 @@ const EditTransforms: React.FunctionComponent = ({ setError(response.error); } else { setSelected(response.data?.type || ""); + setSelectedPredicate(response.data?.predicate?.type || ""); setInputValue(response.data?.type || ""); setTransformData(response.data as TransformData); setInitialValues({ "transform-name": response.data?.name || "", description: response.data?.description || "", ...response.data?.config, + ...Object.keys(response.data?.predicate?.config || {}).reduce( + (acc: Record, key) => { + acc[`predicateConfig.${key}`] = response.data?.predicate?.config[key]; + return acc; + }, + {} as Record + ), + "predicate.negate": "" + response.data?.predicate?.negate || "false", }); } @@ -336,41 +374,37 @@ const EditTransforms: React.FunctionComponent = ({ ...restValues } = values; const configProperties = transformData?.config || {}; - const oldConfig = Object.keys(configProperties).reduce( - (acc: Record, key) => { - const newKey = key.replace( - /(?<=debezium\.transforms\.)[^.]+/, - transformName - ); - acc[newKey] = configProperties![key]; - return acc; - }, - {} - ); - - const oldTransName = initialValues["transform-name"]; - - const newValues = Object.keys(restValues).reduce>( - (acc, key) => { - if (key.includes(oldTransName)) { - const newKey = key.replace( - /(?<=debezium\.transforms\.)[^.]+/, - transformName - ); - acc[newKey] = restValues[key]; - } else { - acc[key] = restValues[key]; + + const oldConfig = { ...configProperties }; + const oldPredicates = transformData?.predicate || {}; + + const newValues: Record = {}; + const newPredicates: any = { config: {} }; + + Object.entries(restValues).forEach(([key, value]) => { + if(key.startsWith("predicate")){ + if(key.startsWith("predicateConfig.")){ + newPredicates.config[key.replace("predicateConfig.", "")] = value; + }else{ + newPredicates[key.replace("predicate.", "")] = value; } - return acc; - }, - {} - ); + } + else{ + newValues[key] = value} + }); + if (selectedPredicate) { + newPredicates.type = selectedPredicate; + } const updatedConfig = { ...oldConfig, ...newValues }; + const updatedPredicates = Object.keys(newPredicates).length > 1 || !isEmpty(newPredicates.config) + ? { ...oldPredicates, ...newPredicates, config: { ...newPredicates.config } } + : {}; const payload = { description: description, config: { ...updatedConfig }, + predicate: { ...updatedPredicates }, name: transformName, }; @@ -394,7 +428,7 @@ const EditTransforms: React.FunctionComponent = ({ navigateTo("/transform"); } }, - [addNotification, transformData, initialValues, onSelection, selected] + [addNotification, transformData, initialValues, onSelection, selectedPredicate, selected] ); const handleItemClick = ( @@ -568,6 +602,107 @@ const EditTransforms: React.FunctionComponent = ({ /> + + } + > + + + + } + fieldId="predicate-type" + > + + {selectPredicateOptions?.map((option, index) => ( + + ))} + + + {selectedPredicate && + selectedPredicate !== + "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone" && + (() => { + const predicate = predicates.find( + (predicate) => + predicate.predicate === selectedPredicate + ); + const predicateProperties = + predicate?.properties as Record; + const propertyKey = Object.keys( + predicateProperties || {} + )[0]; + const property = predicateProperties?.[propertyKey]; + + return ( + + + + } + fieldId="predicate-config" + > + { + setValue(`predicateConfig.${property["x-name"]}`, value); + // setError("description", undefined); + }} + value={getValue(`predicateConfig.${property["x-name"]}`)} + /> + + ); + })()} + {selectedPredicate && ( + + , checked: boolean) => { + setValue("predicate.negate", ""+checked); + setError("predicate.negate", undefined); + }} + description="Setting this option to True applies the predicate only to records that do not match the defined predicate condition" + /> + + )} + + {selected && ( = ({ @@ -623,46 +758,30 @@ const EditTransforms: React.FunctionComponent = ({ > {isBoolean ? ( { const value = checked ? "true" : "false"; - setValue( - `debezium.transforms.${transformName}.${key}`, - value - ); - setError( - `debezium.transforms.${transformName}.${key}`, - undefined - ); + setValue(key, value); + setError(key, undefined); }} /> ) : dropDown ? ( { - setValue( - `debezium.transforms.${transformName}.${key}`, - value - ); - setError( - `debezium.transforms.${transformName}.${key}`, - undefined - ); + setValue(key, value); + setError(key, undefined); }} aria-label="FormSelect Input" ouiaId="BasicFormSelect" @@ -682,32 +801,20 @@ const EditTransforms: React.FunctionComponent = ({ ) : ( { - setValue( - `debezium.transforms.${transformName}.${key}`, - value - ); - setError( - `debezium.transforms.${transformName}.${key}`, - undefined - ); + setValue(key, value); + setError(key, undefined); }} defaultValue={ (!isEmpty(initialValues) && - getValue( - `debezium.transforms.${transformName}.${key}` - )) || + getValue(key)) || property?.defaultValue } validated={ - errors[ - `debezium.transforms.${transformName}.${key}` - ] - ? "error" - : "default" + errors[key] ? "error" : "default" } /> )} @@ -716,23 +823,13 @@ const EditTransforms: React.FunctionComponent = ({ , })} > - { - errors[ - `debezium.transforms.${transformName}.${key}` - ] - } + {errors[key]} From 05314a2147da78e684896f1348f3c3002cf6349b Mon Sep 17 00:00:00 2001 From: indraraj Date: Thu, 23 Jan 2025 16:25:38 +0530 Subject: [PATCH 2/4] DBZ-8590: Update pipeline designer to show the tranfrom node having predicate --- .../CreationFlowTransform.tsx | 44 ++++++++++++------- ...Model.tsx => PipelineDestinationModel.tsx} | 6 +-- ...elineModel.tsx => PipelineSourceModel.tsx} | 6 +-- ...neModel.tsx => PipelineTransformModel.tsx} | 6 +-- ...tedNode.tsx => TransformCollapsedNode.tsx} | 6 +-- .../pipelineDesigner/TransformLinkNode.tsx | 42 +++++++++++++----- .../pipelineDesigner/UnifiedMultiEdge.tsx | 2 +- .../src/components/pipelineDesigner/index.ts | 4 +- 8 files changed, 74 insertions(+), 42 deletions(-) rename debezium-platform-stage/src/components/pipelineDesigner/{DestinationPipelineModel.tsx => PipelineDestinationModel.tsx} (97%) rename debezium-platform-stage/src/components/pipelineDesigner/{SourcePipelineModel.tsx => PipelineSourceModel.tsx} (97%) rename debezium-platform-stage/src/components/pipelineDesigner/{TransformPipelineModel.tsx => PipelineTransformModel.tsx} (96%) rename debezium-platform-stage/src/components/pipelineDesigner/{TransformSelectedNode.tsx => TransformCollapsedNode.tsx} (96%) diff --git a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx index 6a7dd6f..be13814 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx @@ -21,19 +21,25 @@ import DataNode from "./DataNode"; import { MdLogin, MdLogout } from "react-icons/md"; import DataSelectorNode from "./DataSelectorNode"; import { Button, Modal, ModalBody, ModalHeader } from "@patternfly/react-core"; -import { Destination, Source, Transform, TransformData } from "../../apis/apis"; +import { + Destination, + Predicate, + Source, + Transform, + TransformData, +} from "../../apis/apis"; import { PlusIcon } from "@patternfly/react-icons"; import "./CreationFlow.css"; -import SourcePipelineModel from "./SourcePipelineModel"; -import DestinationPipelineModel from "./DestinationPipelineModel"; +import PipelineSourceModel from "./PipelineSourceModel"; +import PipelineDestinationModel from "./PipelineDestinationModel"; import { useData } from "../../appLayout/AppContext"; import { AppColors } from "@utils/constants"; import TransformLinkNode from "./TransformLinkNode"; -import TransformPipelineModel from "./TransformPipelineModel"; +import PipelineTransformModel from "./PipelineTransformModel"; import TransformGroupNode from "./TransformGroupNode"; import TransformSelectorNode from "./TransformSelectorNode"; -import TransformSelectedNode from "./TransformSelectedNode"; +import TransformCollapsedNode from "./TransformCollapsedNode"; import UnifiedCustomEdge from "./UnifiedCustomEdge"; import UnifiedMultiEdge from "./UnifiedMultiEdge"; @@ -43,7 +49,7 @@ const nodeTypes = { addTransformNode: TransformAdditionNode, transformGroupNode: TransformGroupNode, transformSelectorNode: TransformSelectorNode, - transformSelectedNode: TransformSelectedNode, + transformCollapsedNode: TransformCollapsedNode, dataNode: DataNode, }; @@ -260,7 +266,8 @@ const CreationFlowTransform: React.FC = ({ const createNewTransformNode = ( id: string, xPosition: number, - transformName: string + transformName: string, + transformPredicate?: Predicate ) => { return { id, @@ -268,6 +275,12 @@ const CreationFlowTransform: React.FC = ({ label: transformName, sourcePosition: "left", targetPosition: "right", + ...(transformPredicate && { + predicate: { + label: transformPredicate.type.split(".").pop(), + negate: transformPredicate.negate, + }, + }), }, position: { x: xPosition, y: 31 }, targetPosition: "left", @@ -402,7 +415,7 @@ const CreationFlowTransform: React.FC = ({ setEdges([...newEdge]); }, [cardButtonTransform, onToggleDrawer]); - const TransformSelectedNode = useMemo(() => { + const TransformCollapsedNode = useMemo(() => { return { id: "transform_selected", data: { @@ -414,7 +427,7 @@ const CreationFlowTransform: React.FC = ({ }, position: { x: 270, y: 78 }, targetPosition: "left", - type: "transformSelectedNode", + type: "transformCollapsedNode", draggable: false, }; }, [handleExpand]); @@ -445,7 +458,7 @@ const CreationFlowTransform: React.FC = ({ (node: any) => !node.id.includes("transform") && node.id !== "destination" ), - TransformSelectedNode, + TransformCollapsedNode, updatedDataSelectorDestinationNode, ]; }); @@ -460,7 +473,7 @@ const CreationFlowTransform: React.FC = ({ }, ]); }, [ - TransformSelectedNode, + TransformCollapsedNode, dataSelectorDestinationNode, isDestinationConfiguredRef, ]); @@ -525,7 +538,8 @@ const CreationFlowTransform: React.FC = ({ const newTransformNode = createNewTransformNode( newId, xPosition, - transform.name + transform.name, + transform.predicate ); setNodes((prevNodes: any) => { @@ -721,7 +735,7 @@ const CreationFlowTransform: React.FC = ({ description="Select a source to be used in pipeline from the list of already configured source listed below or configure a new source by selecting create a new source radio card." /> - +
= ({ description="Select a source to be used in pipeline from the list of already configured source listed below or configure a new source by selecting create a new source radio card." /> - + = ({ tabIndex={0} id="modal-box-body-destination-with-description" > - diff --git a/debezium-platform-stage/src/components/pipelineDesigner/DestinationPipelineModel.tsx b/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx similarity index 97% rename from debezium-platform-stage/src/components/pipelineDesigner/DestinationPipelineModel.tsx rename to debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx index b3e49c4..d57941c 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/DestinationPipelineModel.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx @@ -17,11 +17,11 @@ import SourceDestinationSelectionList from "../SourceDestinationSelectionList"; import { CatalogGrid } from "@components/CatalogGrid"; import { CreateDestination } from "@destinationPage/CreateDestination"; -type DestinationPipelineModelProps = { +type PipelineDestinationModelProps = { onDestinationSelection: (destination: Destination) => void; }; -const DestinationPipelineModel: React.FC = ({ +const PipelineDestinationModel: React.FC = ({ onDestinationSelection, }) => { const id1 = "pipeline-destination-select"; @@ -153,4 +153,4 @@ const DestinationPipelineModel: React.FC = ({ ); }; -export default DestinationPipelineModel; +export default PipelineDestinationModel; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/SourcePipelineModel.tsx b/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx similarity index 97% rename from debezium-platform-stage/src/components/pipelineDesigner/SourcePipelineModel.tsx rename to debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx index ad702c5..f936d4b 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/SourcePipelineModel.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx @@ -16,11 +16,11 @@ import SourceDestinationSelectionList from "../SourceDestinationSelectionList"; import { CatalogGrid } from "@components/CatalogGrid"; import { CreateSource } from "@sourcePage/CreateSource"; -type SourcePipelineModelProps = { +type PipelineSourceModelProps = { onSourceSelection: (source: Source) => void; }; -const SourcePipelineModel: React.FC = ({ +const PipelineSourceModel: React.FC = ({ onSourceSelection, }) => { const id1 = "pipeline-source-select"; @@ -147,4 +147,4 @@ const SourcePipelineModel: React.FC = ({ ); }; -export default SourcePipelineModel; +export default PipelineSourceModel; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/TransformPipelineModel.tsx b/debezium-platform-stage/src/components/pipelineDesigner/PipelineTransformModel.tsx similarity index 96% rename from debezium-platform-stage/src/components/pipelineDesigner/TransformPipelineModel.tsx rename to debezium-platform-stage/src/components/pipelineDesigner/PipelineTransformModel.tsx index 20e33ee..0ee71ac 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/TransformPipelineModel.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/PipelineTransformModel.tsx @@ -15,11 +15,11 @@ import { useQuery } from "react-query"; import TransformSelectionList from "@components/TransformSelectionList"; import { CreateTransforms } from "src/pages/Transforms"; -type TransformPipelineModelProps = { +type PipelineTransformModelProps = { onTransformSelection: (source: TransformData) => void; }; -const TransformPipelineModel: React.FC = ({ +const PipelineTransformModel: React.FC = ({ onTransformSelection, }) => { const id1 = "pipeline-transform-select"; @@ -118,4 +118,4 @@ const TransformPipelineModel: React.FC = ({ ); }; -export default TransformPipelineModel; +export default PipelineTransformModel; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/TransformSelectedNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/TransformCollapsedNode.tsx similarity index 96% rename from debezium-platform-stage/src/components/pipelineDesigner/TransformSelectedNode.tsx rename to debezium-platform-stage/src/components/pipelineDesigner/TransformCollapsedNode.tsx index 286b0d6..d679366 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/TransformSelectedNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/TransformCollapsedNode.tsx @@ -17,7 +17,7 @@ import ConnectorImage from "@components/ComponentImage"; import { AngleRightIcon } from "@patternfly/react-icons"; import { Transform } from "src/apis"; -interface TransformSelectedNodeProps { +interface TransformCollapsedNodeProps { data: { label: string; sourcePosition: Position; @@ -27,7 +27,7 @@ interface TransformSelectedNodeProps { }; } -const TransformSelectedNode: React.FC = ({ +const TransformCollapsedNode: React.FC = ({ data, }) => { const { darkMode } = useData(); @@ -165,4 +165,4 @@ const TransformSelectedNode: React.FC = ({ ); }; -export default TransformSelectedNode; +export default TransformCollapsedNode; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/TransformLinkNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/TransformLinkNode.tsx index 7d3bcc6..c33701b 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/TransformLinkNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/TransformLinkNode.tsx @@ -4,6 +4,7 @@ import { Bullseye, CardFooter, Content, + Tooltip, } from "@patternfly/react-core"; import { Handle, Position } from "reactflow"; import "./TransformLinkNode.css"; @@ -12,8 +13,18 @@ import { AutomationIcon, DataProcessorIcon } from "@patternfly/react-icons"; import { useData } from "../../appLayout/AppContext"; import { AppColors } from "@utils/constants"; +type Predicate = { + label: string; + negate: boolean; +}; + interface TransformLinkNodeProps { - data: { label: string; sourcePosition: Position; targetPosition: Position }; + data: { + label: string; + sourcePosition: Position; + targetPosition: Position; + predicate?: Predicate; + }; } const TransformLinkNode: React.FC = ({ data }) => { @@ -31,6 +42,7 @@ const TransformLinkNode: React.FC = ({ data }) => { } : { backgroundColor: AppColors.white, + cursor: "unset", } } > @@ -41,17 +53,21 @@ const TransformLinkNode: React.FC = ({ data }) => { isPlain style={{ position: "relative" }} > - -
- -
+ {data.predicate && ( +
+ + + +
+ )} = ({ data }) => { fontSize: "8px", fontWeight: "bold", maxWidth: "100px", + minWidth: "40px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", + textAlign: "center", }} > {data.label} diff --git a/debezium-platform-stage/src/components/pipelineDesigner/UnifiedMultiEdge.tsx b/debezium-platform-stage/src/components/pipelineDesigner/UnifiedMultiEdge.tsx index 744872d..b0bcf08 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/UnifiedMultiEdge.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/UnifiedMultiEdge.tsx @@ -20,7 +20,7 @@ const UnifiedMultiEdge: React.FC = ({ }) => { const midY = sourceY - 1; // Slightly above the direct line - const firstTransformX = sourceX + 200; + const firstTransformX = sourceX + 180; const lastTransformX = targetX - 220; // Create a combined path through all three points diff --git a/debezium-platform-stage/src/components/pipelineDesigner/index.ts b/debezium-platform-stage/src/components/pipelineDesigner/index.ts index ae07ad8..18c97aa 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/index.ts +++ b/debezium-platform-stage/src/components/pipelineDesigner/index.ts @@ -4,5 +4,5 @@ export * from './CompositionFlow' export * from './CustomEdgeDestination' export * from './CustomEdgeSource' export * from './DataSelectorNode' -export * from './DestinationPipelineModel' -export * from './SourcePipelineModel' \ No newline at end of file +export * from './PipelineDestinationModel' +export * from './PipelineSourceModel' \ No newline at end of file From 47e3986580bb1fa79bdc5fbfdf22dd008459ffdd Mon Sep 17 00:00:00 2001 From: indraraj Date: Fri, 24 Jan 2025 13:27:26 +0530 Subject: [PATCH 3/4] DBZ-8590: Update the pipeline designer ux fixes --- debezium-platform-stage/src/App.tsx | 1 + .../CreationFlowTransform.tsx | 2 +- .../components/pipelineDesigner/DataNode.tsx | 135 +++++----- .../pipelineDesigner/DataSelectorNode.tsx | 75 ++++-- .../pipelineDesigner/TransformLinkNode.tsx | 98 +++++--- .../TransformSelectorNode.tsx | 42 +++- .../src/pages/Transforms/CreateTransforms.tsx | 230 +++++++++--------- 7 files changed, 351 insertions(+), 232 deletions(-) diff --git a/debezium-platform-stage/src/App.tsx b/debezium-platform-stage/src/App.tsx index ad61e61..f66bae2 100644 --- a/debezium-platform-stage/src/App.tsx +++ b/debezium-platform-stage/src/App.tsx @@ -1,5 +1,6 @@ import { BrowserRouter as Router } from "react-router-dom"; import "@patternfly/react-core/dist/styles/base.css"; +import '@patternfly/react-styles/css/utilities/Spacing/spacing.css'; import { AppLayout } from "./appLayout/AppLayout"; import { AppRoutes } from "./AppRoutes"; import { AppContextProvider } from "./appLayout/AppContext"; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx index be13814..33361f2 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx @@ -135,7 +135,7 @@ const CreationFlowTransform: React.FC = ({ ? handleDestinationModalToggle : handleTransformModalToggle } - style={{ paddingRight: 10, paddingLeft: 10, fontSize: ".8em" }} + style={{ paddingRight: 5, paddingLeft: 5, fontSize: ".8em" }} icon={} size="sm" > diff --git a/debezium-platform-stage/src/components/pipelineDesigner/DataNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/DataNode.tsx index 10b2e43..f4bd1a1 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/DataNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/DataNode.tsx @@ -1,11 +1,11 @@ import { Card, CardBody, - Bullseye, Stack, StackItem, Tooltip, Content, + Divider, } from "@patternfly/react-core"; import { Handle, Position } from "reactflow"; import ConnectorImage from "../ComponentImage"; @@ -29,31 +29,33 @@ const DataNode: React.FC = ({ data }) => { return ( <> - {!data.compositionFlow && (
- Change pipeline {data.type}.
}> -
- -
- - )} - + {!data.compositionFlow && ( +
+ Change pipeline {data.type}.
}> +
+ +
+ + + )} +
= ({ data }) => { style={{ cursor: "auto", minWidth: 110 }} > - - - */} + + +
- - - - - +
+
+ + + - - {data.label} - - -
-
+ {data.label} + + + + {/* */}
{data.type === "destination" && ( diff --git a/debezium-platform-stage/src/components/pipelineDesigner/DataSelectorNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/DataSelectorNode.tsx index 9c608e7..bcfcb8d 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/DataSelectorNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/DataSelectorNode.tsx @@ -1,7 +1,6 @@ import { Card, CardBody, - Bullseye, Stack, StackItem, } from "@patternfly/react-core"; @@ -54,30 +53,74 @@ const DataSelectorNode: React.FC = ({ data }) => { style={{ cursor: "auto", width: 110 }} > - - + {/* */} + + +
+ {data.type === AppStrings.source ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+ {/* */} + {data.welcomeFlow ? ( + {data.label} + ) : ( -
- {data.type === AppStrings.source ? ( - - ) : ( - - )} -
+ {data.action}
- {data.welcomeFlow ? ({data.type}): ({data.action})} - -
-
+ )} +
+ {/*
*/}
{data.type === AppStrings.destination && ( diff --git a/debezium-platform-stage/src/components/pipelineDesigner/TransformLinkNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/TransformLinkNode.tsx index c33701b..e3f8cc0 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/TransformLinkNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/TransformLinkNode.tsx @@ -1,15 +1,17 @@ import { Card, CardBody, - Bullseye, - CardFooter, Content, Tooltip, + Icon, + Divider, + Stack, + StackItem, } from "@patternfly/react-core"; import { Handle, Position } from "reactflow"; import "./TransformLinkNode.css"; -import { AutomationIcon, DataProcessorIcon } from "@patternfly/react-icons"; +import { DataProcessorIcon, FilterIcon } from "@patternfly/react-icons"; import { useData } from "../../appLayout/AppContext"; import { AppColors } from "@utils/constants"; @@ -64,44 +66,76 @@ const TransformLinkNode: React.FC = ({ data }) => { }} > - + + +
)} - -
- -
-
+ {/* */} + + + +
+ +
+
+ + + + {data.label} + + +
- - - {data.label} - - = ({ style={{ cursor: "auto", width: 110 }} > - + {/* */} -
- +
+
+ {/*
+ +
*/} - {data.action} + {/* */} + {data.action} - + {/* */} = ({ const predicateConfig: Record = {}; let transformConfig: Record = {}; - if(selectedPredicate){ + if (selectedPredicate) { Object.entries(restValues).forEach(([key, value]) => { - if(key.startsWith("predicate")){ - if(key.startsWith("predicateConfig.")){ + if (key.startsWith("predicate")) { + if (key.startsWith("predicateConfig.")) { predicateConfig[key.replace("predicateConfig.", "")] = value; } + } else { + transformConfig[key] = value; } - else{ - transformConfig[key] = value} }); - }else { + } else { transformConfig = restValues; } @@ -369,11 +369,11 @@ const CreateTransforms: React.FunctionComponent = ({ vaults: [], config: { ...transformConfig }, ...(selectedPredicate && { - predicate: { - type: selectedPredicate, - config: { ...predicateConfig }, - negate: values["predicate.negate"] === "true", - }, + predicate: { + type: selectedPredicate, + config: { ...predicateConfig }, + negate: values["predicate.negate"] === "true", + }, }), name: transformName, }; @@ -572,107 +572,6 @@ const CreateTransforms: React.FunctionComponent = ({ /> - - } - > - - - - } - fieldId="predicate-type" - > - - {selectPredicateOptions?.map((option, index) => ( - - ))} - - - {selectedPredicate && - selectedPredicate !== - "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone" && - (() => { - const predicate = predicates.find( - (predicate) => - predicate.predicate === selectedPredicate - ); - const predicateProperties = - predicate?.properties as Record; - const propertyKey = Object.keys( - predicateProperties || {} - )[0]; - const property = predicateProperties?.[propertyKey]; - - return ( - - - - } - fieldId="predicate-config" - > - { - setValue(`predicateConfig.${property["x-name"]}`, value); - // setError("description", undefined); - }} - value={getValue(`predicateConfig.${property["x-name"]}`)} - /> - - ); - })()} - {selectedPredicate && ( - - , checked: boolean) => { - setValue("predicate.negate", ""+checked); - setError("predicate.negate", undefined); - }} - description="Setting this option to True applies the predicate only to records that do not match the defined predicate condition" - /> - - )} - - {selected && ( = ({ })} )} + + + } + > + + + + } + fieldId="predicate-type" + > + + {selectPredicateOptions?.map((option, index) => ( + + ))} + + + {selectedPredicate && + selectedPredicate !== + "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone" && + (() => { + const predicate = predicates.find( + (predicate) => + predicate.predicate === selectedPredicate + ); + const predicateProperties = + predicate?.properties as Record; + const propertyKey = Object.keys( + predicateProperties || {} + )[0]; + const property = predicateProperties?.[propertyKey]; + + return ( + + + + } + fieldId="predicate-config" + > + { + setValue( + `predicateConfig.${property["x-name"]}`, + value + ); + // setError("description", undefined); + }} + value={getValue( + `predicateConfig.${property["x-name"]}` + )} + /> + + ); + })()} + {selectedPredicate && ( + , + checked: boolean + ) => { + setValue("predicate.negate", "" + checked); + setError("predicate.negate", undefined); + }} + description="Setting this option to True applies the predicate only to records that do not match the defined predicate condition" + /> + )} + From ef792a748b04eba0747bd8fa88943b1c36ff3691 Mon Sep 17 00:00:00 2001 From: indraraj Date: Sun, 26 Jan 2025 21:02:20 +0530 Subject: [PATCH 4/4] DBZ-8590: Update feedback points --- .../CreationFlowTransform.tsx | 2 +- .../components/pipelineDesigner/DataNode.tsx | 6 - .../pipelineDesigner/DataSelectorNode.tsx | 12 +- .../pipelineDesigner/DebeziumNode.tsx | 1 - .../PipelineDestinationModel.tsx | 5 - .../TransformAdditionNode.tsx | 59 +--- .../TransformCollapsedNode.tsx | 1 - .../pipelineDesigner/TransformLinkNode.tsx | 7 - .../TransformSelectorNode.tsx | 75 +++--- .../src/pages/Pipeline/ConfigurePipeline.tsx | 40 ++- .../src/pages/Pipeline/EditPipeline.tsx | 2 - .../src/pages/Pipeline/PipelineDesigner.tsx | 10 - .../src/pages/Pipeline/PipelineLog.tsx | 29 +- .../src/pages/Pipeline/Pipelines.tsx | 10 +- .../src/pages/Transforms/EditTransforms.tsx | 252 ++++++++++-------- 15 files changed, 215 insertions(+), 296 deletions(-) diff --git a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx index 33361f2..1b26b8a 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/CreationFlowTransform.tsx @@ -275,7 +275,7 @@ const CreationFlowTransform: React.FC = ({ label: transformName, sourcePosition: "left", targetPosition: "right", - ...(transformPredicate && { + ...(transformPredicate?.type && { predicate: { label: transformPredicate.type.split(".").pop(), negate: transformPredicate.negate, diff --git a/debezium-platform-stage/src/components/pipelineDesigner/DataNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/DataNode.tsx index f4bd1a1..50e9e06 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/DataNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/DataNode.tsx @@ -86,7 +86,6 @@ const DataNode: React.FC = ({ data }) => { style={{ cursor: "auto", minWidth: 110 }} > - {/* */} = ({ data }) => { = ({ data }) => { - {/* */} {data.type === "destination" && ( diff --git a/debezium-platform-stage/src/components/pipelineDesigner/DataSelectorNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/DataSelectorNode.tsx index bcfcb8d..cc585c4 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/DataSelectorNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/DataSelectorNode.tsx @@ -1,9 +1,4 @@ -import { - Card, - CardBody, - Stack, - StackItem, -} from "@patternfly/react-core"; +import { Card, CardBody, Stack, StackItem } from "@patternfly/react-core"; import { Handle, Position } from "reactflow"; import "./DataNode.css"; import { DataSinkIcon, DataSourceIcon } from "@patternfly/react-icons"; @@ -61,11 +56,9 @@ const DataSelectorNode: React.FC = ({ data }) => { }} className="pf-v5-u-box-shadow-md" > - {/* */} = ({ data }) => { )}
- {/* */} {data.welcomeFlow ? ( {data.label} ) : ( = ({ data }) => { )}
- {/*
*/}
{data.type === AppStrings.destination && ( diff --git a/debezium-platform-stage/src/components/pipelineDesigner/DebeziumNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/DebeziumNode.tsx index ddac885..2fbaf3d 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/DebeziumNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/DebeziumNode.tsx @@ -4,7 +4,6 @@ import "./DebeziumNode.css"; import dbz from "../../assets/debeziumNoText.png"; import { useData } from "../../appLayout/AppContext"; import { AppColors } from "@utils/constants"; -// import { Button } from "@patternfly/react-core"; interface DebeziumNodeProps { data: { diff --git a/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx b/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx index d57941c..5d5c32f 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx @@ -10,7 +10,6 @@ import { } from "@patternfly/react-core"; import React, { useCallback, useEffect, useState } from "react"; import { Destination, fetchData } from "../../apis/apis"; -// import { CreateDestinationForm } from "../../pages/Destination/CreateDestinationForm"; import { useQuery } from "react-query"; import { API_URL } from "../../utils/constants"; import SourceDestinationSelectionList from "../SourceDestinationSelectionList"; @@ -107,7 +106,6 @@ const PipelineDestinationModel: React.FC = ({ {isCreateChecked === id2 && (selectedDestination === "" ? ( - // Select the type of destination you want to connect from the list @@ -116,8 +114,6 @@ const PipelineDestinationModel: React.FC = ({ ) : ( - // - // Fill out the below form or use the smart editor to setup a new @@ -126,7 +122,6 @@ const PipelineDestinationModel: React.FC = ({ smart editor. - // ))} {isCreateChecked === id1 ? ( diff --git a/debezium-platform-stage/src/components/pipelineDesigner/TransformAdditionNode.tsx b/debezium-platform-stage/src/components/pipelineDesigner/TransformAdditionNode.tsx index e476ae0..f2e6d67 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/TransformAdditionNode.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/TransformAdditionNode.tsx @@ -1,6 +1,4 @@ -import { - Button, -} from "@patternfly/react-core"; +import { Button } from "@patternfly/react-core"; import { Handle, Position } from "reactflow"; import "./TransformAdditionNode.css"; @@ -37,50 +35,17 @@ const TransformAdditionNode: React.FC = ({ } > - {/* - - -
- -
-
-
- - {data.action ? ( - data.action - ) : ( - - )} - -
*/} - {data.action ? ( - data.action - ) : ( - @@ -409,12 +413,10 @@ const Pipelines: React.FunctionComponent = () => { {}} - // isReversed isDisabled /> diff --git a/debezium-platform-stage/src/pages/Transforms/EditTransforms.tsx b/debezium-platform-stage/src/pages/Transforms/EditTransforms.tsx index 30b7dde..c1d09c5 100644 --- a/debezium-platform-stage/src/pages/Transforms/EditTransforms.tsx +++ b/debezium-platform-stage/src/pages/Transforms/EditTransforms.tsx @@ -351,7 +351,8 @@ const EditTransforms: React.FunctionComponent = ({ ...response.data?.config, ...Object.keys(response.data?.predicate?.config || {}).reduce( (acc: Record, key) => { - acc[`predicateConfig.${key}`] = response.data?.predicate?.config[key]; + acc[`predicateConfig.${key}`] = + response.data?.predicate?.config[key]; return acc; }, {} as Record @@ -374,32 +375,37 @@ const EditTransforms: React.FunctionComponent = ({ ...restValues } = values; const configProperties = transformData?.config || {}; - + const oldConfig = { ...configProperties }; const oldPredicates = transformData?.predicate || {}; - const newValues: Record = {}; + const newValues: Record = {}; const newPredicates: any = { config: {} }; Object.entries(restValues).forEach(([key, value]) => { - if(key.startsWith("predicate")){ - if(key.startsWith("predicateConfig.")){ + if (key.startsWith("predicate")) { + if (key.startsWith("predicateConfig.")) { newPredicates.config[key.replace("predicateConfig.", "")] = value; - }else{ + } else { newPredicates[key.replace("predicate.", "")] = value; } + } else { + newValues[key] = value; } - else{ - newValues[key] = value} }); if (selectedPredicate) { newPredicates.type = selectedPredicate; } const updatedConfig = { ...oldConfig, ...newValues }; - const updatedPredicates = Object.keys(newPredicates).length > 1 || !isEmpty(newPredicates.config) - ? { ...oldPredicates, ...newPredicates, config: { ...newPredicates.config } } - : {}; + const updatedPredicates = + Object.keys(newPredicates).length > 1 || !isEmpty(newPredicates.config) + ? { + ...oldPredicates, + ...newPredicates, + config: { ...newPredicates.config }, + } + : {}; const payload = { description: description, @@ -428,7 +434,14 @@ const EditTransforms: React.FunctionComponent = ({ navigateTo("/transform"); } }, - [addNotification, transformData, initialValues, onSelection, selectedPredicate, selected] + [ + addNotification, + transformData, + initialValues, + onSelection, + selectedPredicate, + selected, + ] ); const handleItemClick = ( @@ -602,107 +615,6 @@ const EditTransforms: React.FunctionComponent = ({ /> - - } - > - - - - } - fieldId="predicate-type" - > - - {selectPredicateOptions?.map((option, index) => ( - - ))} - - - {selectedPredicate && - selectedPredicate !== - "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone" && - (() => { - const predicate = predicates.find( - (predicate) => - predicate.predicate === selectedPredicate - ); - const predicateProperties = - predicate?.properties as Record; - const propertyKey = Object.keys( - predicateProperties || {} - )[0]; - const property = predicateProperties?.[propertyKey]; - - return ( - - - - } - fieldId="predicate-config" - > - { - setValue(`predicateConfig.${property["x-name"]}`, value); - // setError("description", undefined); - }} - value={getValue(`predicateConfig.${property["x-name"]}`)} - /> - - ); - })()} - {selectedPredicate && ( - - , checked: boolean) => { - setValue("predicate.negate", ""+checked); - setError("predicate.negate", undefined); - }} - description="Setting this option to True applies the predicate only to records that do not match the defined predicate condition" - /> - - )} - - {selected && ( = ({ })} )} + + } + > + + + + } + fieldId="predicate-type" + > + + {selectPredicateOptions?.map((option, index) => ( + + ))} + + + {selectedPredicate && + selectedPredicate !== + "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone" && + (() => { + const predicate = predicates.find( + (predicate) => + predicate.predicate === selectedPredicate + ); + const predicateProperties = + predicate?.properties as Record; + const propertyKey = Object.keys( + predicateProperties || {} + )[0]; + const property = + predicateProperties?.[propertyKey]; + + return ( + + + + } + fieldId="predicate-config" + > + { + setValue( + `predicateConfig.${property["x-name"]}`, + value + ); + // setError("description", undefined); + }} + value={getValue( + `predicateConfig.${property["x-name"]}` + )} + /> + + ); + })()} + {selectedPredicate && ( + + , + checked: boolean + ) => { + setValue("predicate.negate", "" + checked); + setError("predicate.negate", undefined); + }} + description="Setting this option to True applies the predicate only to records that do not match the defined predicate condition" + /> + + )} +