From 750b33bdcd40665f86d2f13910310a98be4f87c1 Mon Sep 17 00:00:00 2001 From: indraraj Date: Wed, 16 Oct 2024 16:50:41 +0530 Subject: [PATCH 1/3] DBZ-8328: Add Transform initial work --- .DS_Store | Bin 8196 -> 8196 bytes package.json | 1 + src/__mocks__/data/DebeziumTransfroms.json | 673 +++ src/__mocks__/data/transforms.json | 5358 +++++++++++++++++ src/apis/apis.tsx | 12 + src/appLayout/AppSideNavigation.test.tsx | 2 +- src/components/TransformSelectionList.tsx | 95 + src/components/dataFlow/CreationFlow.tsx | 8 +- .../dataFlow/CreationFlowTransform.tsx | 597 ++ ...aNodeSelector.tsx => DataSelectorNode.tsx} | 11 +- src/components/dataFlow/DebeziumNode.tsx | 14 +- .../dataFlow/TransformAdditionNode.css | 56 + .../dataFlow/TransformAdditionNode.tsx | 95 + .../dataFlow/TransformGroupNode.css | 37 + .../dataFlow/TransformGroupNode.tsx | 84 + ...ormationNode.css => TransformLinkNode.css} | 2 +- ...ormationNode.tsx => TransformLinkNode.tsx} | 47 +- .../dataFlow/TransformPipelineModel.tsx | 128 + src/components/dataFlow/WelcomeFlow.tsx | 2 +- src/components/dataFlow/index.ts | 4 +- src/pages/Destination/Destinations.tsx | 2 +- src/pages/Pipeline/PipelineDesigner.css | 10 + src/pages/Pipeline/PipelineDesigner.tsx | 223 +- src/pages/Pipeline/Pipelines.tsx | 4 +- src/pages/Transformation/Transformation.tsx | 48 - src/pages/Transformation/index.ts | 1 - src/pages/Transforms/CreateTransforms.tsx | 752 +++ .../Teansformations.test.tsx | 12 +- .../Transforms.css} | 4 + src/pages/Transforms/Transforms.tsx | 426 ++ src/pages/Transforms/index.ts | 1 + src/pages/Vault/Vaults.tsx | 1 - src/route.tsx | 19 +- yarn.lock | 81 +- 34 files changed, 8661 insertions(+), 149 deletions(-) create mode 100644 src/__mocks__/data/DebeziumTransfroms.json create mode 100644 src/__mocks__/data/transforms.json create mode 100644 src/components/TransformSelectionList.tsx create mode 100644 src/components/dataFlow/CreationFlowTransform.tsx rename src/components/dataFlow/{DataNodeSelector.tsx => DataSelectorNode.tsx} (89%) create mode 100644 src/components/dataFlow/TransformAdditionNode.css create mode 100644 src/components/dataFlow/TransformAdditionNode.tsx create mode 100644 src/components/dataFlow/TransformGroupNode.css create mode 100644 src/components/dataFlow/TransformGroupNode.tsx rename src/components/dataFlow/{AddTransformationNode.css => TransformLinkNode.css} (98%) rename src/components/dataFlow/{AddTransformationNode.tsx => TransformLinkNode.tsx} (58%) create mode 100644 src/components/dataFlow/TransformPipelineModel.tsx delete mode 100644 src/pages/Transformation/Transformation.tsx delete mode 100644 src/pages/Transformation/index.ts create mode 100644 src/pages/Transforms/CreateTransforms.tsx rename src/pages/{Transformation => Transforms}/Teansformations.test.tsx (74%) rename src/pages/{Transformation/Transformation.css => Transforms/Transforms.css} (89%) create mode 100644 src/pages/Transforms/Transforms.tsx create mode 100644 src/pages/Transforms/index.ts diff --git a/.DS_Store b/.DS_Store index 9e27401b88c3f2b8100fd80ac607349eca1e0d0a..5f1603b6a26cc7fa9795b3c23cb5ae29f62f3582 100644 GIT binary patch delta 40 wcmZp1XmOa}&nUDpU^hRb&}JTiYL>~%MNK9jk+IxZAl{E<+YWv1d+xa#Buy z5(5K+00RT#Pax*|4+cOH2B5Aopp7L!oq7zD!$kyBh;ReM6$U_ijDh-f87vu+fcBaI r*~viLQ-Pu>K%Nm0<8Z^|3K8dx6*^3t*(JWQNaJxFC{*a^B26X$ZWU6e diff --git a/package.json b/package.json index 73feebe33..bdd70879e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@patternfly/react-charts": "^8.0.0-prerelease.10", "@patternfly/react-code-editor": "^6.0.0-prerelease.13", "@patternfly/react-core": "^6.0.0-prerelease.13", + "@patternfly/react-drag-drop": "6.0.0-prerelease.21", "@patternfly/react-icons": "^6.0.0-prerelease.4", "@patternfly/react-log-viewer": "^6.0.0-alpha.5", "@patternfly/react-styles": "^6.0.0-prerelease.3", diff --git a/src/__mocks__/data/DebeziumTransfroms.json b/src/__mocks__/data/DebeziumTransfroms.json new file mode 100644 index 000000000..53c980c61 --- /dev/null +++ b/src/__mocks__/data/DebeziumTransfroms.json @@ -0,0 +1,673 @@ +[ + { + "properties": { + "serializer.type": { + "title": "Specifies a serialization type a provided CloudEvent was serialized and deserialized with", + "description": "Specifies a serialization type a provided CloudEvent was serialized and deserialized with", + "type": "STRING", + "x-name": "serializer.type" + }, + "fields.mapping": { + "title": "Specifies a list of pairs with mappings between a CloudEvent's fields and names of database columns", + "description": "Specifies a list of pairs with mappings between a CloudEvent's fields and names of database columns", + "type": "STRING", + "format": "list,regex", + "x-name": "fields.mapping" + } + }, + "transform": "io.debezium.connector.jdbc.transforms.ConvertCloudEventToSaveableForm" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": ["drop", "rewrite", "none"] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "flatten.struct.delimiter": { + "title": "Delimiter for flattened struct", + "description": "Delimiter to concat between field names from the input record when generating field names for theoutput record.", + "type": "STRING", + "defaultValue": "_", + "x-name": "flatten.struct.delimiter" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or __\u003Cstruct\u003E_ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "flatten.struct": { + "title": "Flatten struct", + "description": "Flattening structs by concatenating the fields into plain properties, using a (configurable) delimiter.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "flatten.struct" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or __\u003Cstruct\u003E_ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + }, + "array.encoding": { + "title": "Array encoding", + "description": "The arrays can be encoded using 'array' schema type (the default) or as a 'document' (similar to how BSON encodes arrays). 'array' is easier to consume but requires all elements in the array to be of the same type. Use 'document' if the arrays in data source mix different types together.", + "type": "STRING", + "defaultValue": "array", + "x-name": "array.encoding", + "enum": ["array", "document"] + } + }, + "transform": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState" + }, + { + "properties": { + "collection.field.event.payload": { + "title": "Event Payload Field", + "description": "The field which contains the event payload within the outbox collection", + "type": "STRING", + "defaultValue": "payload", + "x-name": "collection.field.event.payload" + }, + "collection.field.event.key": { + "title": "Event Key Field", + "description": "The field which contains the event key within the outbox collection", + "type": "STRING", + "x-name": "collection.field.event.key" + }, + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "collection.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen field, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "collection.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?\u003CroutedByValue\u003E.*)", + "x-name": "route.topic.regex" + }, + "collection.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. \u003Ccode\u003Eid:header,field_name:envelope:alias\u003C/code\u003E ", + "type": "STRING", + "format": "list,regex", + "x-name": "collection.fields.additional.placement" + }, + "collection.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The field which contains the event schema version within the outbox collection", + "type": "STRING", + "x-name": "collection.field.event.schema.version" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "collection.field.event.type": { + "title": "Event Type Field", + "description": "The field which contains the event type within the outbox collection", + "type": "STRING", + "defaultValue": "type", + "x-name": "collection.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The field which determines how the events will be routed within the outbox collection. The value will become a part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "collection.field.event.id": { + "title": "Event ID Field", + "description": "The field which contains the event ID within the outbox collection", + "type": "STRING", + "defaultValue": "_id", + "x-name": "collection.field.event.id" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is monitoring the collection, it's expecting only to see 'create' document events, in case something else is processed this transform can log it as warning, error or stop the process", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "collection.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "collection.expand.json.payload" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of the field configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + } + }, + "transform": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter" + }, + { + "properties": { + "target.topic.prefix": { + "title": "The prefix of TimescaleDB topic names", + "description": "The namespace (prefix) of topics where TimescaleDB events will be routed, defaults to: '_timescaledb_internal'", + "type": "STRING", + "defaultValue": "timescaledb", + "x-name": "target.topic.prefix" + }, + "schema.list": { + "title": "The list of TimescaleDB data schemas", + "description": "Comma-separated list schema names that contain TimescaleDB data tables, defaults to: '_timescaledb_internal'", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[_timescaledb_internal]", + "x-name": "schema.list" + } + }, + "transform": "io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": ["drop", "rewrite", "none"] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + } +] diff --git a/src/__mocks__/data/transforms.json b/src/__mocks__/data/transforms.json new file mode 100644 index 000000000..29d1b7295 --- /dev/null +++ b/src/__mocks__/data/transforms.json @@ -0,0 +1,5358 @@ +[ + { + "properties": { + "serializer.type": { + "title": "Specifies a serialization type a provided CloudEvent was serialized and deserialized with", + "description": "Specifies a serialization type a provided CloudEvent was serialized and deserialized with", + "type": "STRING", + "x-name": "serializer.type" + }, + "fields.mapping": { + "title": "Specifies a list of pairs with mappings between a CloudEvent's fields and names of database columns", + "description": "Specifies a list of pairs with mappings between a CloudEvent's fields and names of database columns", + "type": "STRING", + "format": "list,regex", + "x-name": "fields.mapping" + } + }, + "transform": "io.debezium.connector.jdbc.transforms.ConvertCloudEventToSaveableForm" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "flatten.struct.delimiter": { + "title": "Delimiter for flattened struct", + "description": "Delimiter to concat between field names from the input record when generating field names for theoutput record.", + "type": "STRING", + "defaultValue": "_", + "x-name": "flatten.struct.delimiter" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "flatten.struct": { + "title": "Flatten struct", + "description": "Flattening structs by concatenating the fields into plain properties, using a (configurable) delimiter.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "flatten.struct" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + }, + "array.encoding": { + "title": "Array encoding", + "description": "The arrays can be encoded using 'array' schema type (the default) or as a 'document' (similar to how BSON encodes arrays). 'array' is easier to consume but requires all elements in the array to be of the same type. Use 'document' if the arrays in data source mix different types together.", + "type": "STRING", + "defaultValue": "array", + "x-name": "array.encoding", + "enum": [ + "array", + "document" + ] + } + }, + "transform": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState" + }, + { + "properties": { + "collection.field.event.payload": { + "title": "Event Payload Field", + "description": "The field which contains the event payload within the outbox collection", + "type": "STRING", + "defaultValue": "payload", + "x-name": "collection.field.event.payload" + }, + "collection.field.event.key": { + "title": "Event Key Field", + "description": "The field which contains the event key within the outbox collection", + "type": "STRING", + "x-name": "collection.field.event.key" + }, + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "collection.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen field, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "collection.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "collection.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias ", + "type": "STRING", + "format": "list,regex", + "x-name": "collection.fields.additional.placement" + }, + "collection.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The field which contains the event schema version within the outbox collection", + "type": "STRING", + "x-name": "collection.field.event.schema.version" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "collection.field.event.type": { + "title": "Event Type Field", + "description": "The field which contains the event type within the outbox collection", + "type": "STRING", + "defaultValue": "type", + "x-name": "collection.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The field which determines how the events will be routed within the outbox collection. The value will become a part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "collection.field.event.id": { + "title": "Event ID Field", + "description": "The field which contains the event ID within the outbox collection", + "type": "STRING", + "defaultValue": "_id", + "x-name": "collection.field.event.id" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is monitoring the collection, it's expecting only to see 'create' document events, in case something else is processed this transform can log it as warning, error or stop the process", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "collection.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "collection.expand.json.payload" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of the field configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + } + }, + "transform": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter" + }, + { + "properties": {}, + "transform": "io.debezium.connector.mysql.transforms.ReadToInsertEvent" + }, + { + "properties": { + "target.topic.prefix": { + "title": "The prefix of TimescaleDB topic names", + "description": "The namespace (prefix) of topics where TimescaleDB events will be routed, defaults to: '_timescaledb_internal'", + "type": "STRING", + "defaultValue": "timescaledb", + "x-name": "target.topic.prefix" + }, + "schema.list": { + "title": "The list of TimescaleDB data schemas", + "description": "Comma-separated list schema names that contain TimescaleDB data tables, defaults to: '_timescaledb_internal'", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[_timescaledb_internal]", + "x-name": "schema.list" + } + }, + "transform": "io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "key.enforce.uniqueness": { + "title": "Add source topic name into key", + "description": "Augment each record's key with a field denoting the source topic. This field distinguishes records coming from different physical tables which may otherwise have primary/unique key conflicts. If the source tables are guaranteed to have globally unique keys then this may be set to false to disable key rewriting.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "key.enforce.uniqueness" + }, + "key.field.regex": { + "title": "Key field regex", + "description": "The regex used for extracting the physical table identifier from the original topic name. Now that multiple physical tables can share a topic, the event's key may need to be augmented to include fields other than just those for the record's primary/unique key, since these are not guaranteed to be unique across tables. We need some identifier added to the key that distinguishes the different physical tables.", + "type": "STRING", + "x-name": "key.field.regex" + }, + "topic.replacement": { + "title": "Topic replacement", + "description": "The replacement string used in conjunction with topic.regex. This will be used to create the new topic name.", + "type": "STRING", + "x-name": "topic.replacement" + }, + "logical.table.cache.size": { + "title": "Logical table cache size", + "description": "The size used for holding the max entries in LRUCache. The cache will keep the old/new schema for logical table key and value, also cache the derived key and topic regex result for improving the source record transformation.", + "type": "INTEGER", + "format": "int32", + "defaultValue": "16", + "x-name": "logical.table.cache.size" + }, + "topic.regex": { + "title": "Topic regex", + "description": "The regex used for extracting the name of the logical table from the original topic name.", + "type": "STRING", + "x-name": "topic.regex" + }, + "key.field.replacement": { + "title": "Key field replacement", + "description": "The replacement string used in conjunction with key.field.regex. This will be used to create the physical table identifier in the record's key.", + "type": "STRING", + "x-name": "key.field.replacement" + } + }, + "transform": "io.debezium.transforms.ByLogicalTableRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + }, + "topic.expression": { + "title": "Topic name expression", + "description": "An expression determining the new name of the topic the record should use. When null the record is delivered to the original topic.", + "type": "STRING", + "x-name": "topic.expression" + } + }, + "transform": "io.debezium.transforms.ContentBasedRouter" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "header.unchanged.name": { + "title": "Header unchanged name.", + "description": "Specify the header unchanged name of schema, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.unchanged.name" + }, + "header.changed.name": { + "title": "Header change name.", + "description": "Specify the header changed name, default is null which means not send changes to header.", + "type": "STRING", + "x-name": "header.changed.name" + } + }, + "transform": "io.debezium.transforms.ExtractChangedRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "delete.handling.mode": { + "title": "Handle delete records", + "description": "How to handle delete records. Options are: none - records are passed,drop - records are removed (the default),rewrite - __deleted field is added to records.Note: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "STRING", + "defaultValue": "drop", + "x-name": "delete.handling.mode", + "enum": [ + "drop", + "rewrite", + "none" + ] + }, + "add.headers.prefix": { + "title": "Header prefix to be added to each header.", + "description": "Adds this prefix listed to each header.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.headers.prefix" + }, + "route.by.field": { + "title": "Route by field name", + "description": "The column which determines how the events will be routed, the value will replace the topic name.", + "type": "STRING", + "defaultValue": "", + "x-name": "route.by.field" + }, + "drop.fields.from.key": { + "title": "Specifies whether the fields to be dropped should also be omitted from the key", + "description": "Specifies whether to apply the drop fields behavior to the event key as well as the value. Default behavior is to only remove fields from the event value, not the key.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "drop.fields.from.key" + }, + "drop.fields.header.name": { + "title": "Specifies a header that contains a list of field names to be removed", + "description": "Specifies the name of a header that contains a list of fields to be removed from the event value.", + "type": "STRING", + "x-name": "drop.fields.header.name" + }, + "add.headers": { + "title": "Adds the specified fields to the header if they exist.", + "description": "Adds each field listed to the header, __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.headers" + }, + "drop.fields.keep.schema.compatible": { + "title": "Specifies if fields are dropped, will the event's schemas be compatible", + "description": "Controls the output event's schema compatibility when using the drop fields feature. `true`: dropped fields are removed if the schema indicates its optional leaving the schemas unchanged, `false`: dropped fields are removed from the key/value schemas, regardless of optionality.", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.fields.keep.schema.compatible" + }, + "drop.tombstones": { + "title": "Drop tombstones", + "description": "Debezium by default generates a tombstone record to enable Kafka compaction after a delete record was generated. This record is usually filtered out to avoid duplicates as a delete record is converted to a tombstone record, tooNote: This option is scheduled for removal in a future release, use \"delete.tombstone.handling.mode\" instead", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "drop.tombstones" + }, + "add.fields": { + "title": "Adds the specified field(s) to the message if they exist.", + "description": "Adds each field listed, prefixed with __ (or ___ if the struct is specified). Example: 'version,connector,source.ts_ms' would add __version, __connector and __source_ts_ms fields. Optionally one can also map new field name like version:VERSION,connector:CONNECTOR,source.ts_ms:EVENT_TIMESTAMP.Please note that the new field name is case-sensitive.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "add.fields" + }, + "add.fields.prefix": { + "title": "Field prefix to be added to each field.", + "description": "Adds this prefix to each field listed.", + "type": "STRING", + "defaultValue": "__", + "x-name": "add.fields.prefix" + } + }, + "transform": "io.debezium.transforms.ExtractNewRecordState" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "condition": { + "title": "Filtering condition", + "description": "An expression determining whether the record should be filtered out. When evaluated to true the record is removed.", + "type": "STRING", + "x-name": "condition" + }, + "topic.regex": { + "title": "Topic regex", + "description": "A regex used for selecting the topic(s) to which this transformation should be applied.", + "type": "STRING", + "x-name": "topic.regex" + }, + "language": { + "title": "Expression language", + "description": "An expression language used to evaluate the expression. Must begin with 'jsr223.', e.g. 'jsr223.groovy' or 'jsr223.graal.js'.", + "type": "STRING", + "x-name": "language" + }, + "null.handling.mode": { + "title": "Handle null records", + "description": "How to handle records with null value. Options are: keep - records are passed (the default),drop - records are removed,evaluate - the null records are passed for evaluation.", + "type": "STRING", + "defaultValue": "keep", + "x-name": "null.handling.mode" + } + }, + "transform": "io.debezium.transforms.Filter" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "headers": { + "title": "Header names list", + "description": "Header names in the record whose values are to be copied or moved to record value.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "Field names list", + "description": "Field names, in the same order as the header names listed in the headers configuration property. Supports Struct nesting using dot notation.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "Operation: mover or copy", + "description": "Either move if the fields are to be moved to the value (removed from the headers), or copy if the fields are to be copied to the value (retained in the headers).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "io.debezium.transforms.HeaderToValue" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "schema.change.event.exclude.list": { + "title": "Schema change event exclude list", + "description": "Support filtering during DDL synchronization", + "type": "STRING", + "x-name": "schema.change.event.exclude.list" + } + }, + "transform": "io.debezium.transforms.SchemaChangeEventFilter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "include.list": { + "title": "Include List", + "description": "A comma-separated list of rules that specify what events should be evaluated for timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be converted. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be converted. `topic:`:: Matches the specified topic name, converting all time-based fields. `topic::`:: Matches the specified topic name, converting only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "include.list" + }, + "converted.timezone": { + "title": "Converted Timezone", + "description": "A string that represents the timezone to which the time-based fields should be converted.The format can be geographic (e.g. America/Los_Angeles), or it can be a UTC offset in the format of +/-hh:mm, (e.g. +08:00).", + "type": "STRING", + "x-name": "converted.timezone" + }, + "exclude.list": { + "title": "Exclude List", + "description": "A comma-separated list of rules that specify what events should be excluded from timezone conversion, using one of the following formats: `source:`:: Matches only Debezium change events with a source information block with the specified table name. All time-based fields will be excluded. `source::`:: Matches only Debezium change events with a source information with the specified table name. Only the specified field name will be excluded. `topic:`:: Matches the specified topic name, excluding all time-based fields. `topic::`:: Matches the specified topic name, excluding only the specified field name. `:`:: Uses a heuristic matching algorithm to matches the source information block table name if the source information block exists, otherwise matches against the topic name. The conversion is applied only to to the specified field name. ", + "type": "STRING", + "format": "list,regex", + "x-name": "exclude.list" + } + }, + "transform": "io.debezium.transforms.TimezoneConverter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "table.json.payload.null.behavior": { + "title": "Behavior when json payload including null value", + "description": "Behavior when json payload including null value, the default will ignore null, optional_bytes will keep the null value, and treat null as optional bytes of connect.", + "type": "STRING", + "defaultValue": "ignore", + "x-name": "table.json.payload.null.behavior" + }, + "table.field.event.schema.version": { + "title": "Event Schema Version Field", + "description": "The column which contains the event schema version within the outbox table", + "type": "STRING", + "x-name": "table.field.event.schema.version" + }, + "table.field.event.timestamp": { + "title": "Event Timestamp Field", + "description": "Optionally you can override the Kafka message timestamp with a value from a chosen column, otherwise it'll be the Debezium event processed timestamp.", + "type": "STRING", + "x-name": "table.field.event.timestamp" + }, + "route.topic.regex": { + "title": "The name of the routed topic", + "description": "The default regex to use within the RegexRouter, the default capture will allow to replace the routed field into a new topic name defined in 'route.topic.replacement'", + "type": "STRING", + "defaultValue": "(?.*)", + "x-name": "route.topic.regex" + }, + "table.field.event.key": { + "title": "Event Key Field", + "description": "The column which contains the event key within the outbox table", + "type": "STRING", + "defaultValue": "aggregateid", + "x-name": "table.field.event.key" + }, + "table.fields.additional.placement": { + "title": "Settings for each additional column in the outbox table", + "description": "Extra fields can be added as part of the event envelope or a message header, format is a list of colon-delimited pairs or trios when you desire to have aliases, e.g. id:header,field_name:envelope:alias,field_name:partition ", + "type": "STRING", + "format": "list,regex", + "x-name": "table.fields.additional.placement" + }, + "table.expand.json.payload": { + "title": "Expand Payload escaped string as real JSON", + "description": "Whether or not to try unescaping a JSON string and make it real JSON. It will infer schema information from payload and update the record schema accordingly. If content is not JSON, it just produces a warning and emits the record unchanged", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "table.expand.json.payload" + }, + "route.tombstone.on.empty.payload": { + "title": "Empty payloads cause a tombstone message", + "description": "Whether or not an empty payload should cause a tombstone event.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "route.tombstone.on.empty.payload" + }, + "table.field.event.payload": { + "title": "Event Payload Field", + "description": "The column which contains the event payload within the outbox table", + "type": "STRING", + "defaultValue": "payload", + "x-name": "table.field.event.payload" + }, + "table.field.event.type": { + "title": "Event Type Field", + "description": "The column which contains the event type within the outbox table", + "type": "STRING", + "defaultValue": "type", + "x-name": "table.field.event.type" + }, + "route.by.field": { + "title": "Field to route events by", + "description": "The column which determines how the events will be routed, the value will become part of the topic name", + "type": "STRING", + "defaultValue": "aggregatetype", + "x-name": "route.by.field" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + }, + "table.op.invalid.behavior": { + "title": "Behavior when capturing an unexpected outbox event", + "description": "While Debezium is capturing changes from the outbox table, it is expecting only to process 'create' or 'delete' row events; in case something else is processed this transform can log it as warning, error or stop the process.", + "type": "STRING", + "defaultValue": "warn", + "x-name": "table.op.invalid.behavior" + }, + "table.fields.additional.error.on.missing": { + "title": "Should the transform error if an additional field is missing in the change data", + "description": "When transforming the 'table.fields.additional.placement' fields, should the transform throw an exception if one of the fields is missing in the change data", + "type": "BOOLEAN", + "defaultValue": "true", + "x-name": "table.fields.additional.error.on.missing" + }, + "route.topic.replacement": { + "title": "The name of the routed topic", + "description": "The name of the topic in which the events will be routed, a replacement '${routedByValue}' is available which is the value of The column configured via 'route.by.field'", + "type": "STRING", + "defaultValue": "outbox.event.${routedByValue}", + "x-name": "route.topic.replacement" + }, + "table.field.event.id": { + "title": "Event ID Field", + "description": "The column which contains the event ID within the outbox table", + "type": "STRING", + "defaultValue": "id", + "x-name": "table.field.event.id" + } + }, + "transform": "io.debezium.transforms.outbox.EventRouter" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "partition.hash.function": { + "title": "Hash function", + "description": "Hash function to be used when computing hash of the fields which would determine number of the destination partition.", + "type": "STRING", + "defaultValue": "java", + "x-name": "partition.hash.function" + }, + "partition.topic.num": { + "title": "Number of partition configured for topic", + "description": "Number of partition for the topic on which this SMT act. Use TopicNameMatches predicate to filter records by topic", + "type": "INTEGER", + "format": "int32", + "x-name": "partition.topic.num" + }, + "partition.payload.fields": { + "title": "List of payload fields to use for compute partition.", + "description": "Payload fields to use to calculate the partition. Supports Struct nesting using dot notation.To access fields related to data collections, you can use: after, before or change, where 'change' is a special field that will automatically choose, based on operation, the 'after' or 'before'. If a field not exist for the current record it will simply not usede.g. after.name,source.table,change.name", + "type": "STRING", + "format": "list,regex", + "x-name": "partition.payload.fields" + } + }, + "transform": "io.debezium.transforms.partitions.PartitionRouting" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "tracing.operation.name": { + "title": "Tracing operation name", + "description": "The operation name representing Debezium processing span. Default is 'debezium-read'", + "type": "STRING", + "defaultValue": "debezium-read", + "x-name": "tracing.operation.name" + }, + "tracing.span.context.field": { + "title": "Serialized tracing span context field", + "description": "The name of the field containing java.util.Properties representation of serialized span context. Defaults to 'tracingspancontext'", + "type": "STRING", + "defaultValue": "tracingspancontext", + "x-name": "tracing.span.context.field" + }, + "tracing.with.context.field.only": { + "title": "Trace only events with context field present", + "description": "Set to `true` when only events that have serialized context field should be traced.", + "type": "BOOLEAN", + "defaultValue": "false", + "x-name": "tracing.with.context.field.only" + } + }, + "transform": "io.debezium.transforms.tracing.ActivateTracingSpan" + }, + { + "properties": { + "spec": { + "title": "spec", + "description": "List of fields and the type to cast them to of the form field1:type,field2:type to cast fields of Maps or Structs. A single type to cast the entire value. Valid types are int8, int16, int32, int64, float32, float64, boolean, and string. Note that binary fields can only be cast to string.", + "type": "STRING", + "format": "list,regex", + "x-name": "spec" + } + }, + "transform": "org.apache.kafka.connect.transforms.Cast" + }, + { + "properties": { + "headers": { + "title": "headers", + "description": "The name of the headers to be removed.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + } + }, + "transform": "org.apache.kafka.connect.transforms.DropHeaders" + }, + { + "properties": { + "field": { + "title": "field", + "description": "Field name to extract.", + "type": "STRING", + "x-name": "field" + } + }, + "transform": "org.apache.kafka.connect.transforms.ExtractField" + }, + { + "properties": {}, + "transform": "org.apache.kafka.connect.transforms.Filter" + }, + { + "properties": { + "delimiter": { + "title": "delimiter", + "description": "Delimiter to insert between field names from the input record when generating field names for the output record", + "type": "STRING", + "defaultValue": ".", + "x-name": "delimiter" + } + }, + "transform": "org.apache.kafka.connect.transforms.Flatten" + }, + { + "properties": { + "headers": { + "title": "headers", + "description": "Header names, in the same order as the field names listed in the fields configuration property.", + "type": "STRING", + "format": "list,regex", + "x-name": "headers" + }, + "fields": { + "title": "fields", + "description": "Field names in the record whose values are to be copied or moved to headers.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "operation": { + "title": "operation", + "description": "Either move if the fields are to be moved to the headers (removed from the key/value), or copy if the fields are to be copied to the headers (retained in the key/value).", + "type": "STRING", + "x-name": "operation" + } + }, + "transform": "org.apache.kafka.connect.transforms.HeaderFrom" + }, + { + "properties": { + "field": { + "title": "field", + "description": "Field name for the single field that will be created in the resulting Struct or Map.", + "type": "STRING", + "x-name": "field" + } + }, + "transform": "org.apache.kafka.connect.transforms.HoistField" + }, + { + "properties": { + "topic.field": { + "title": "topic.field", + "description": "Field name for Kafka topic. Suffix with ! to make this a required field, or ? to keep it optional (the default).", + "type": "STRING", + "x-name": "topic.field" + }, + "offset.field": { + "title": "offset.field", + "description": "Field name for Kafka offset - only applicable to sink connectors.
Suffix with ! to make this a required field, or ? to keep it optional (the default).", + "type": "STRING", + "x-name": "offset.field" + }, + "partition.field": { + "title": "partition.field", + "description": "Field name for Kafka partition. Suffix with ! to make this a required field, or ? to keep it optional (the default).", + "type": "STRING", + "x-name": "partition.field" + }, + "static.value": { + "title": "static.value", + "description": "Static field value, if field name configured.", + "type": "STRING", + "x-name": "static.value" + }, + "static.field": { + "title": "static.field", + "description": "Field name for static data field. Suffix with ! to make this a required field, or ? to keep it optional (the default).", + "type": "STRING", + "x-name": "static.field" + }, + "timestamp.field": { + "title": "timestamp.field", + "description": "Field name for record timestamp. Suffix with ! to make this a required field, or ? to keep it optional (the default).", + "type": "STRING", + "x-name": "timestamp.field" + } + }, + "transform": "org.apache.kafka.connect.transforms.InsertField" + }, + { + "properties": { + "value.literal": { + "title": "value.literal", + "description": "The literal value that is to be set as the header value on all records.", + "type": "STRING", + "x-name": "value.literal" + }, + "header": { + "title": "header", + "description": "The name of the header.", + "type": "STRING", + "x-name": "header" + } + }, + "transform": "org.apache.kafka.connect.transforms.InsertHeader" + }, + { + "properties": { + "fields": { + "title": "fields", + "description": "Names of fields to mask.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + }, + "replacement": { + "title": "replacement", + "description": "Custom value replacement, that will be applied to all 'fields' values (numeric or non-empty string values only).", + "type": "STRING", + "x-name": "replacement" + } + }, + "transform": "org.apache.kafka.connect.transforms.MaskField" + }, + { + "properties": { + "regex": { + "title": "regex", + "description": "Regular expression to use for matching.", + "type": "STRING", + "x-name": "regex" + }, + "replacement": { + "title": "replacement", + "description": "Replacement string.", + "type": "STRING", + "x-name": "replacement" + } + }, + "transform": "org.apache.kafka.connect.transforms.RegexRouter" + }, + { + "properties": { + "include": { + "title": "include", + "description": "Fields to include. If specified, only these fields will be used.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "include" + }, + "exclude": { + "title": "exclude", + "description": "Fields to exclude. This takes precedence over the fields to include.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "exclude" + }, + "renames": { + "title": "renames", + "description": "Field rename mappings.", + "type": "STRING", + "format": "list,regex", + "defaultValue": "[]", + "x-name": "renames" + } + }, + "transform": "org.apache.kafka.connect.transforms.ReplaceField" + }, + { + "properties": { + "schema.name": { + "title": "schema.name", + "description": "Schema name to set.", + "type": "STRING", + "x-name": "schema.name" + }, + "schema.version": { + "title": "schema.version", + "description": "Schema version to set.", + "type": "INTEGER", + "format": "int32", + "x-name": "schema.version" + } + }, + "transform": "org.apache.kafka.connect.transforms.SetSchemaMetadata" + }, + { + "properties": { + "field": { + "title": "field", + "description": "The field containing the timestamp, or empty if the entire value is a timestamp", + "type": "STRING", + "defaultValue": "", + "x-name": "field" + }, + "format": { + "title": "format", + "description": "A SimpleDateFormat-compatible format for the timestamp. Used to generate the output when type=string or used to parse the input if the input is a string.", + "type": "STRING", + "defaultValue": "", + "x-name": "format" + }, + "target.type": { + "title": "target.type", + "description": "The desired timestamp representation: string, unix, Date, Time, or Timestamp", + "type": "STRING", + "x-name": "target.type" + }, + "unix.precision": { + "title": "unix.precision", + "description": "The desired Unix precision for the timestamp: seconds, milliseconds, microseconds, or nanoseconds. Used to generate the output when type=unix or used to parse the input if the input is a Long.Note: This SMT will cause precision loss during conversions from, and to, values with sub-millisecond components.", + "type": "STRING", + "defaultValue": "milliseconds", + "x-name": "unix.precision" + } + }, + "transform": "org.apache.kafka.connect.transforms.TimestampConverter" + }, + { + "properties": { + "timestamp.format": { + "title": "timestamp.format", + "description": "Format string for the timestamp that is compatible with java.text.SimpleDateFormat.", + "type": "STRING", + "defaultValue": "yyyyMMdd", + "x-name": "timestamp.format" + }, + "topic.format": { + "title": "topic.format", + "description": "Format string which can contain ${topic} and ${timestamp} as placeholders for the topic and timestamp, respectively.", + "type": "STRING", + "defaultValue": "${topic}-${timestamp}", + "x-name": "topic.format" + } + }, + "transform": "org.apache.kafka.connect.transforms.TimestampRouter" + }, + { + "properties": { + "fields": { + "title": "fields", + "description": "Field names on the record value to extract as the record key.", + "type": "STRING", + "format": "list,regex", + "x-name": "fields" + } + }, + "transform": "org.apache.kafka.connect.transforms.ValueToKey" + } + ] \ No newline at end of file diff --git a/src/apis/apis.tsx b/src/apis/apis.tsx index 3776542e5..8b86628c6 100644 --- a/src/apis/apis.tsx +++ b/src/apis/apis.tsx @@ -67,6 +67,18 @@ export type Source = { id: number; }; +export type TransformData = { + type: string; + schema: string; + vaults: Vault[]; + config: SourceConfig; + description?: string; + name: string; + id: number; +}; + +export type TransformApiResponse = TransformData[]; + export type SourceApiResponse = Source[]; export type PipelineApiResponse = Pipeline[]; diff --git a/src/appLayout/AppSideNavigation.test.tsx b/src/appLayout/AppSideNavigation.test.tsx index 70e828584..75262934d 100644 --- a/src/appLayout/AppSideNavigation.test.tsx +++ b/src/appLayout/AppSideNavigation.test.tsx @@ -31,7 +31,7 @@ test("renders the side navigation Expanded", () => { const expectedTexts = [ "Pipeline", "Source", - "Transformation", + "Transforms", "Destination", "Vaults", ]; diff --git a/src/components/TransformSelectionList.tsx b/src/components/TransformSelectionList.tsx new file mode 100644 index 000000000..6ef3b2736 --- /dev/null +++ b/src/components/TransformSelectionList.tsx @@ -0,0 +1,95 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + EmptyState, + EmptyStateBody, + EmptyStateVariant, + Label, +} from "@patternfly/react-core"; +import { DataProcessorIcon, TagIcon } from "@patternfly/react-icons"; +import { Table, Thead, Tr, Th, Tbody, Td } from "@patternfly/react-table"; +import React from "react"; +import { + Pipeline, + TransformApiResponse, + TransformData, + fetchData, +} from "../apis/apis"; +import { API_URL } from "../utils/constants"; +import { useQuery } from "react-query"; + +interface ITransformSelectionListProps { + data: TransformApiResponse; + onSelection: (selection: TransformData) => void; +} + +const TransformSelectionList: React.FunctionComponent< + ITransformSelectionListProps +> = ({ data, onSelection }) => { + const { + data: pipelineList = [], + error: _pipelineError, + isLoading: _isPipelineLoading, + } = useQuery( + "pipelines", + () => fetchData(`${API_URL}/api/pipelines`), + { + refetchInterval: 7000, + } + ); + + return ( + <> + {data.length > 0 ? ( + + + + + + + + + + {data.length > 0 && + data.map((instance) => ( + onSelection(instance)} + isSelectable + isClickable + > + + + + + ))} + +
NameTypeActive
{instance.name} + {instance.type} + + +
+ ) : ( + + + No transform is configure for this cluster yet. Configure a one by + selecting "Create a transform" option above. + + + )} + + ); +}; + +export default TransformSelectionList; diff --git a/src/components/dataFlow/CreationFlow.tsx b/src/components/dataFlow/CreationFlow.tsx index 1f5aaaa7a..05035f7f3 100644 --- a/src/components/dataFlow/CreationFlow.tsx +++ b/src/components/dataFlow/CreationFlow.tsx @@ -11,12 +11,12 @@ import ReactFlow, { Connection, Background, } from "reactflow"; -import AddTransformationNode from "./AddTransformationNode"; +import AddTransformationNode from "./TransformAdditionNode"; import DataNode from "./DataNode"; import { MdLogin, MdLogout } from "react-icons/md"; -import DataNodeSelector from "./DataNodeSelector"; +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"; @@ -127,11 +127,11 @@ const CreationFlow: React.FC = ({ return { id: "add_transformation", data: { - label: "Transformation", + label: "Transforms", sourcePosition: "right", targetPosition: "left", }, - position: { x: 55, y: 35 }, + position: { x: 65, y: 35 }, targetPosition: "left", type: "addTransformation", parentId: "transformation_group", diff --git a/src/components/dataFlow/CreationFlowTransform.tsx b/src/components/dataFlow/CreationFlowTransform.tsx new file mode 100644 index 000000000..5b9222602 --- /dev/null +++ b/src/components/dataFlow/CreationFlowTransform.tsx @@ -0,0 +1,597 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useCallback, useMemo, useState } from "react"; +import ReactFlow, { + applyNodeChanges, + applyEdgeChanges, + addEdge, + NodeChange, + Node, + EdgeChange, + Edge, + Connection, + Background, + MiniMap, + PanOnScrollMode, +} from "reactflow"; +import TransformAdditionNode from "./TransformAdditionNode"; + +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 { 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"; +import TransformLinkNode from "./TransformLinkNode"; +import TransformPipelineModel from "./TransformPipelineModel"; +import TransformGroupNode from "./TransformGroupNode"; +import DebeziumNode from "./DebeziumNode"; + +const nodeTypes = { + dataSelectorNode: DataSelectorNode, + transformLinkNode: TransformLinkNode, + addTransformNode: TransformAdditionNode, + transformGroupNode: TransformGroupNode, + debeziumNode: DebeziumNode, + dataNode: DataNode, +}; + +const edgeTypes = { + customEdgeSource: CustomEdgeSource, + customEdgeDestination: CustomEdgeDestination, +}; + +const proOptions = { hideAttribution: true }; +interface CreationFlowTransformProps { + updateIfSourceConfigured: (isConfigured: boolean) => void; + updateIfDestinationConfigured: (isConfigured: boolean) => void; + updateSelectedSource: (source: Source) => void; + updateSelectedDestination: (destination: Destination) => void; + updateSelectedTransform: (transform: Transform) => void; + onToggleDrawer: () => void; +} + +const CreationFlowTransform: React.FC = ({ + updateIfSourceConfigured, + updateIfDestinationConfigured, + updateSelectedSource, + updateSelectedDestination, + updateSelectedTransform, + onToggleDrawer, +}) => { + const { darkMode } = useData(); + const [isSourceModalOpen, setIsSourceModalOpen] = useState(false); + const [isTransformModalOpen, setIsTransformModalOpen] = useState(false); + const [isDestinationModalOpen, setIsDestinationModalOpen] = useState(false); + + const handleSourceModalToggle = useCallback(() => { + setIsSourceModalOpen(!isSourceModalOpen); + }, [isSourceModalOpen]); + + const handleTransformModalToggle = useCallback(() => { + setIsTransformModalOpen(true); + }, []); + + const handleDestinationModalToggle = useCallback(() => { + setIsDestinationModalOpen(!isDestinationModalOpen); + }, [isDestinationModalOpen]); + + const cardButton = useCallback( + (buttonText: string): JSX.Element => { + return ( + + ); + }, + [handleSourceModalToggle, handleDestinationModalToggle] + ); + + const cardButtonTransform = useCallback((): JSX.Element => { + return ( + + + */} ); }; diff --git a/src/components/dataFlow/TransformAdditionNode.css b/src/components/dataFlow/TransformAdditionNode.css new file mode 100644 index 000000000..37b4981a9 --- /dev/null +++ b/src/components/dataFlow/TransformAdditionNode.css @@ -0,0 +1,56 @@ +.transformationAdditionWrapper { + overflow: hidden; + display: flex; + padding: 1px; + position: relative; + border-radius: 17px; + flex-grow: 1; + box-shadow: var(--brand-shadow--light); +} + +.transformationGradient:before { + content: ""; + position: absolute; + padding-bottom: calc(100% * 1.41421356237); + width: calc(100% * 1.41421356237); + background: conic-gradient( + from -45deg at 50% 50%, + var(--brand-color--green), + var(--brand-color--green-light) 120deg, + var(--brand-color--teal) 200deg, + var(--brand-color--green) 360deg + ); + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border-radius: 100%; +} + +.transformationAdditionWrapper.transformationGradient:before { + content: ""; + background: conic-gradient( + from -45deg at 50% 50%, + var(--brand-color--green), + var(--brand-color--green-light) 120deg, + var(--brand-color--teal) 200deg, + var(--brand-color--green) 360deg + ); + /* animation: spinner 4s linear infinite; */ + transform: translate(-50%, -50%) rotate(0deg); + z-index: -1; +} + +@keyframes spinner { + 100% { + transform: translate(-50%, -50%) rotate(-360deg); + } +} + +.transformationAdditionInner { + border-radius: 17px; + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + position: relative; +} diff --git a/src/components/dataFlow/TransformAdditionNode.tsx b/src/components/dataFlow/TransformAdditionNode.tsx new file mode 100644 index 000000000..e476ae0e3 --- /dev/null +++ b/src/components/dataFlow/TransformAdditionNode.tsx @@ -0,0 +1,95 @@ +import { + Button, +} from "@patternfly/react-core"; +import { Handle, Position } from "reactflow"; +import "./TransformAdditionNode.css"; + +import { PlusIcon } from "@patternfly/react-icons"; +import { useData } from "../../appLayout/AppContext"; +import { AppColors } from "@utils/constants"; + +interface TransformAdditionNodeProps { + data: { + label: string; + sourcePosition: Position; + targetPosition: Position; + action: React.ReactNode; + }; +} + +const TransformAdditionNode: React.FC = ({ + data, +}) => { + const { darkMode } = useData(); + return ( + <> +
+
+ + {/* + + +
+ +
+
+
+ + {data.action ? ( + data.action + ) : ( + + )} + +
*/} + {data.action ? ( + data.action + ) : ( +
+
+ + ); +}; + +export default TransformAdditionNode; diff --git a/src/components/dataFlow/TransformGroupNode.css b/src/components/dataFlow/TransformGroupNode.css new file mode 100644 index 000000000..b59241279 --- /dev/null +++ b/src/components/dataFlow/TransformGroupNode.css @@ -0,0 +1,37 @@ +.transformationWrapper { + border-radius: 10px; + box-shadow: var(--brand-shadow); +} + +.editDataNodeDestination { + border-radius: 100%; + background-color: #fff; + width: 22px; + height: 22px; + right: 0; + position: absolute; + top: 0; + transform: translate(40%, -40%); + display: flex; + transform-origin: center center; + padding: 1px; + overflow: hidden; + box-shadow: var(--brand-shadow--teal-large); + z-index: 1; +} +.collapsedDataNodeDestination { + border-radius: 100%; + background-color: #fff; + width: 25px; + height: 25px; + left: 5; + position: absolute; + top: 0; + /* transform: translate(40%, -40%); */ + display: flex; + /* transform-origin: center center; */ + padding: 3px; + overflow: hidden; + /* box-shadow: var(--brand-shadow--teal-large); */ + z-index: 1; +} diff --git a/src/components/dataFlow/TransformGroupNode.tsx b/src/components/dataFlow/TransformGroupNode.tsx new file mode 100644 index 000000000..0a3054c0e --- /dev/null +++ b/src/components/dataFlow/TransformGroupNode.tsx @@ -0,0 +1,84 @@ +import { Content, Tooltip } from "@patternfly/react-core"; +import { Position } from "reactflow"; +import "./TransformGroupNode.css"; +import { AngleRightIcon, PencilAltIcon } from "@patternfly/react-icons"; +import { AppColors } from "@utils/constants"; +import { useData } from "@appContext/AppContext"; + +interface TransformGroupNodeProps { + data: { + label: string; + sourcePosition: Position; + targetPosition: Position; + onToggleDrawer: () => void; + }; +} + +const TransformGroupNode: React.FC = ({ data }) => { + const { darkMode } = useData(); + + return ( + <> +
{ + console.log("expand/collapsed clicked"); + }} + className={"collapsedDataNodeDestination"} + > + Collapsed
}> +
+ +
+ + +
+ Edit transform
}> +
+ +
+ + + +
+ {/* */} + + + {data.label} + + + {/* */} +
+ + ); +}; + +export default TransformGroupNode; diff --git a/src/components/dataFlow/AddTransformationNode.css b/src/components/dataFlow/TransformLinkNode.css similarity index 98% rename from src/components/dataFlow/AddTransformationNode.css rename to src/components/dataFlow/TransformLinkNode.css index 4e447238d..1963c6be6 100644 --- a/src/components/dataFlow/AddTransformationNode.css +++ b/src/components/dataFlow/TransformLinkNode.css @@ -3,7 +3,7 @@ display: flex; padding: 1px; position: relative; - border-radius: 10px; + border-radius: 17px; flex-grow: 1; box-shadow: var(--brand-shadow--light); } diff --git a/src/components/dataFlow/AddTransformationNode.tsx b/src/components/dataFlow/TransformLinkNode.tsx similarity index 58% rename from src/components/dataFlow/AddTransformationNode.tsx rename to src/components/dataFlow/TransformLinkNode.tsx index 9f426438e..5c01dad3d 100644 --- a/src/components/dataFlow/AddTransformationNode.tsx +++ b/src/components/dataFlow/TransformLinkNode.tsx @@ -3,23 +3,22 @@ import { CardBody, Bullseye, CardFooter, - Button, + Content, } from "@patternfly/react-core"; import { Handle, Position } from "reactflow"; -import "./AddTransformationNode.css"; +import "./TransformLinkNode.css"; -import { OptimizeIcon, PlusIcon } from "@patternfly/react-icons"; +import { OptimizeIcon } from "@patternfly/react-icons"; import { useData } from "../../appLayout/AppContext"; import { AppColors } from "@utils/constants"; -interface AddTransformationNodeProps { +interface TransformLinkNodeProps { data: { label: string; sourcePosition: Position; targetPosition: Position }; } -const AddTransformationNode: React.FC = ({ - data, -}) => { +const TransformLinkNode: React.FC = ({ data }) => { const { darkMode } = useData(); + return ( <>
@@ -35,13 +34,16 @@ const AddTransformationNode: React.FC = ({ } } > - - - + + +
@@ -49,24 +51,17 @@ const AddTransformationNode: React.FC = ({ - +
@@ -74,4 +69,4 @@ const AddTransformationNode: React.FC = ({ ); }; -export default AddTransformationNode; +export default TransformLinkNode; diff --git a/src/components/dataFlow/TransformPipelineModel.tsx b/src/components/dataFlow/TransformPipelineModel.tsx new file mode 100644 index 000000000..f322c3316 --- /dev/null +++ b/src/components/dataFlow/TransformPipelineModel.tsx @@ -0,0 +1,128 @@ +import { + Card, + CardHeader, + CardTitle, + CardBody, + Divider, + Flex, + FlexItem, + Content, +} from "@patternfly/react-core"; +import React, { useEffect, useState } from "react"; +import { fetchData, Source, TransformData } from "../../apis/apis"; +import { API_URL } from "../../utils/constants"; +import { useQuery } from "react-query"; +// import SourceDestinationSelectionList from "../SourceDestinationSelectionList"; +// import { CatalogGrid } from "@components/CatalogGrid"; +import { CreateSource } from "@sourcePage/CreateSource"; +import TransformSelectionList from "@components/TransformSelectionList"; + +type TransformPipelineModelProps = { + onTransformSelection: (source: Source) => void; +}; + +const TransformPipelineModel: React.FC = ({ + onTransformSelection, +}) => { + const id1 = "pipeline-transform-select"; + const id2 = "pipeline-transform-create"; + const [isCreateChecked, setIsCreateChecked] = useState(id1); + // const [selectedTransform, setSelectedTransform] = useState(""); + + const { + data: transformList = [], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + error: _transformError, + isLoading: isTransformLoading, + } = useQuery("transform", () => + fetchData(`${API_URL}/api/transforms`) + ); + + useEffect(() => { + if (transformList.length === 0 && isTransformLoading === false) { + setIsCreateChecked(id2); + } + }, [transformList, isTransformLoading]); + + // const selectTransform = useCallback( + // (transformId: string) => { + // setSelectedTransform(transformId); + // }, + // [setSelectedTransform] + // ); + + const onChange = (event: React.FormEvent) => { + setIsCreateChecked(event.currentTarget.id); + }; + + return ( + <> + + + + + Select a transform + + + Select a already configured transform from the list below + + + + + + + + Create a transform + + Create a new transform for your data pipeline. + + + + + {isCreateChecked === id2 && ( + + + Fill out the below form or use the smart editor to setup a new + source connector. If you already have a configuration file, you can + setup a new source connector by uploading it in the smart editor. + + + )} + + {isCreateChecked === id1 ? ( + + ) : ( + + )} + + ); +}; + +export default TransformPipelineModel; diff --git a/src/components/dataFlow/WelcomeFlow.tsx b/src/components/dataFlow/WelcomeFlow.tsx index edb79f799..4d536c79e 100644 --- a/src/components/dataFlow/WelcomeFlow.tsx +++ b/src/components/dataFlow/WelcomeFlow.tsx @@ -16,7 +16,7 @@ import ReactFlow, { import DataNode from "./DataNode"; import { MdLogin, MdLogout } from "react-icons/md"; -import DataNodeSelector from "./DataNodeSelector"; +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"; diff --git a/src/components/dataFlow/index.ts b/src/components/dataFlow/index.ts index 860aff5a6..8a10e6bf3 100644 --- a/src/components/dataFlow/index.ts +++ b/src/components/dataFlow/index.ts @@ -1,9 +1,9 @@ export * from './DataNode' -export * from './AddTransformationNode' +export * from './TransformAdditionNode' export * from './CompositionFlow' export * from './CreationFlow' export * from './CustomEdgeDestination' export * from './CustomEdgeSource' -export * from './DataNodeSelector' +export * from './DataSelectorNode' export * from './DestinationPipelineModel' export * from './SourcePipelineModel' \ No newline at end of file diff --git a/src/pages/Destination/Destinations.tsx b/src/pages/Destination/Destinations.tsx index 0e9e956a9..51bd5e322 100644 --- a/src/pages/Destination/Destinations.tsx +++ b/src/pages/Destination/Destinations.tsx @@ -191,7 +191,7 @@ const Destinations: React.FunctionComponent = () => { diff --git a/src/pages/Pipeline/PipelineDesigner.css b/src/pages/Pipeline/PipelineDesigner.css index a4f209075..3051fe5d4 100644 --- a/src/pages/Pipeline/PipelineDesigner.css +++ b/src/pages/Pipeline/PipelineDesigner.css @@ -10,3 +10,13 @@ .custom-card-footer .pf-m-action { padding: var(--pf-t--global--spacer--md) !important; } + +.pipeline_designer{ + padding: 0 !important; + gap: 0 !important; + width: 100% !important; +} + +.pipeline_designer .pf-v6-c-page__main-body{ + display: contents !important; +} \ No newline at end of file diff --git a/src/pages/Pipeline/PipelineDesigner.tsx b/src/pages/Pipeline/PipelineDesigner.tsx index 5d100c539..ace9a9199 100644 --- a/src/pages/Pipeline/PipelineDesigner.tsx +++ b/src/pages/Pipeline/PipelineDesigner.tsx @@ -6,17 +6,69 @@ import { CardBody, CardFooter, Content, + DataList, + DataListAction, + DataListCell, + DataListItemCells, + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelBody, + DrawerPanelContent, + DrawerPanelDescription, PageSection, + Tooltip, } from "@patternfly/react-core"; import { useNavigate } from "react-router-dom"; import "./PipelineDesigner.css"; -import CreationFlow from "../../components/dataFlow/CreationFlow"; -import { Destination, Source } from "../../apis/apis"; +// import CreationFlow from "../../components/dataFlow/CreationFlow"; +import { Destination, Source, Transform } from "../../apis/apis"; +import CreationFlowTransform from "@components/dataFlow/CreationFlowTransform"; +import { DragDropSort, DraggableObject } from "@patternfly/react-drag-drop"; +import { TrashIcon } from "@patternfly/react-icons"; + +const getItems = (selectedTransform: Transform[]): DraggableObject[] => + selectedTransform.map((transform, idx) => ({ + id: `data-list-${transform.id}-item-${idx}`, + content: ( + <> + + {transform.name} + , + ]} + /> + + + } + + + + ); + return ( <> - - Pipeline designer - - Configure the pipeline by adding an existing source and destination or - create a new one as per you need. Optionally you can also any number - of transformation as needed. - - - - - - - - - - - - - - - - + + + + + + + Pipeline designer + + + Configure the pipeline by adding an existing source and + destination or create a new one as per you need. Optionally + you can also any number of transformation as needed. + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/src/pages/Pipeline/Pipelines.tsx b/src/pages/Pipeline/Pipelines.tsx index c5f120a96..41c812ea8 100644 --- a/src/pages/Pipeline/Pipelines.tsx +++ b/src/pages/Pipeline/Pipelines.tsx @@ -57,12 +57,12 @@ import { useDeleteData } from "src/apis"; import PageHeader from "@components/PageHeader"; import "./Pipelines.css"; -type DeleteInstance = { +export type DeleteInstance = { id: number; name: string; }; -type ActionData = { +export type ActionData = { id: number; name: string; }; diff --git a/src/pages/Transformation/Transformation.tsx b/src/pages/Transformation/Transformation.tsx deleted file mode 100644 index 51ab0ebfd..000000000 --- a/src/pages/Transformation/Transformation.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from "react"; -import { Button, PageSection } from "@patternfly/react-core"; -import { PlusIcon } from "@patternfly/react-icons"; -import EmptyStatus from "../../components/EmptyStatus"; -import comingSoonImage from "../../assets/comingSoon.png"; -import "./Transformation.css"; -import { useData } from "../../appLayout/AppContext"; - -export interface ITransformationProps { - sampleProp?: string; -} - -const Transformation: React.FunctionComponent = () => { - const { darkMode } = useData(); - return ( - <> - -
- Coming Soon -
- }> - Add Transformation - - } - secondaryActions={ - <> - - - - - } - /> -
- - ); -}; - -export { Transformation }; diff --git a/src/pages/Transformation/index.ts b/src/pages/Transformation/index.ts deleted file mode 100644 index a599607f8..000000000 --- a/src/pages/Transformation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Transformation'; \ No newline at end of file diff --git a/src/pages/Transforms/CreateTransforms.tsx b/src/pages/Transforms/CreateTransforms.tsx new file mode 100644 index 000000000..73c702509 --- /dev/null +++ b/src/pages/Transforms/CreateTransforms.tsx @@ -0,0 +1,752 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import PageHeader from "@components/PageHeader"; +import { CodeEditor, Language } from "@patternfly/react-code-editor"; +import { + ActionGroup, + Button, + ButtonType, + Card, + CardBody, + Form, + FormContextProvider, + FormFieldGroup, + FormFieldGroupHeader, + FormGroup, + FormGroupLabelHelp, + FormHelperText, + FormSelect, + FormSelectOption, + HelperText, + HelperTextItem, + MenuToggle, + MenuToggleElement, + PageSection, + Popover, + Select, + SelectList, + SelectOption, + SelectOptionProps, + Switch, + TextInput, + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, + ToggleGroup, + ToggleGroupItem, + Toolbar, + ToolbarContent, + ToolbarItem, +} from "@patternfly/react-core"; +import { + CodeIcon, + ExclamationCircleIcon, + PencilAltIcon, + // PlusIcon, + TimesIcon, +} from "@patternfly/react-icons"; +// import { selectOptions } from "@testing-library/user-event/dist/cjs/utility/selectOptions.js"; +import * as React from "react"; +import transforms from "../../__mocks__/data/DebeziumTransfroms.json"; +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { createPost } from "src/apis"; +import { API_URL } from "@utils/constants"; +import { useNotification } from "@appContext/AppNotificationContext"; + +export interface ICreateTransformsProps { + sampleProp?: string; +} + +const CreateTransforms: React.FunctionComponent< + ICreateTransformsProps +> = () => { + // const { darkMode } = useData(); + + const navigate = useNavigate(); + + const navigateTo = (url: string) => { + navigate(url); + }; + + const { addNotification } = useNotification(); + + const [editorSelected, setEditorSelected] = React.useState("form-editor"); + + const [isLoading] = React.useState(false); + + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState(""); + const [inputValue, setInputValue] = React.useState(""); + const [filterValue, setFilterValue] = React.useState(""); + const [selectOptions, setSelectOptions] = + React.useState(); + const [focusedItemIndex, setFocusedItemIndex] = React.useState( + null + ); + const [activeItemId, setActiveItemId] = React.useState(null); + const textInputRef = React.useRef(); + + const NO_RESULTS = "no results"; + + useEffect(() => { + const selectOption: SelectOptionProps[] = transforms.map((item) => { + return { + value: item.transform, + children: item.transform, + }; + }); + setSelectOptions(selectOption); + }, []); + + useEffect(() => { + const selectOption: SelectOptionProps[] = transforms.map((item) => { + return { + value: item.transform, + children: item.transform, + }; + }); + let newSelectOptions: SelectOptionProps[] = selectOption; + + // Filter menu items based on the text input value when one exists + if (filterValue) { + newSelectOptions = selectOption.filter((menuItem) => + String(menuItem.children) + .toLowerCase() + .includes(filterValue.toLowerCase()) + ); + + // When no options are found after filtering, display 'No results found' + if (!newSelectOptions.length) { + newSelectOptions = [ + { + isAriaDisabled: true, + children: `No results found for "${filterValue}"`, + value: NO_RESULTS, + }, + ]; + } + + // Open the menu when the input value changes and the new value is not empty + if (!isOpen) { + setIsOpen(true); + } + } + + setSelectOptions(newSelectOptions); + }, [filterValue, isOpen]); + + const createItemId = (value: any) => + `select-typeahead-${value.replace(" ", "-")}`; + + const setActiveAndFocusedItem = (itemIndex: number) => { + setFocusedItemIndex(itemIndex); + const focusedItem = selectOptions![itemIndex]; + setActiveItemId(createItemId(focusedItem.value)); + }; + + const resetActiveAndFocusedItem = () => { + setFocusedItemIndex(null); + setActiveItemId(null); + }; + + const closeMenu = () => { + setIsOpen(false); + resetActiveAndFocusedItem(); + }; + + const onInputClick = () => { + if (!isOpen) { + setIsOpen(true); + } else if (!inputValue) { + closeMenu(); + } + }; + + const selectOption = (value: string | number, content: string | number) => { + // eslint-disable-next-line no-console + console.log("selected", content); + + setInputValue(String(content)); + setFilterValue(""); + setSelected(String(value)); + + closeMenu(); + }; + + const onSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined + ) => { + if (value && value !== NO_RESULTS) { + const optionText = selectOptions!.find( + (option) => option.value === value + )?.children; + selectOption(value, optionText as string); + } + }; + + const onTextInputChange = ( + _event: React.FormEvent, + value: string + ) => { + setInputValue(value); + setFilterValue(value); + + resetActiveAndFocusedItem(); + + if (value !== selected) { + setSelected(""); + } + }; + + const handleMenuArrowKeys = (key: string) => { + let indexToFocus = 0; + + if (!isOpen) { + setIsOpen(true); + } + + if (selectOptions!.every((option) => option.isDisabled)) { + return; + } + + if (key === "ArrowUp") { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions!.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } + + // Skip disabled options + while (selectOptions![indexToFocus].isDisabled) { + indexToFocus--; + if (indexToFocus === -1) { + indexToFocus = selectOptions!.length - 1; + } + } + } + + if (key === "ArrowDown") { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if ( + focusedItemIndex === null || + focusedItemIndex === selectOptions!.length - 1 + ) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } + + // Skip disabled options + while (selectOptions![indexToFocus].isDisabled) { + indexToFocus++; + if (indexToFocus === selectOptions!.length) { + indexToFocus = 0; + } + } + } + + setActiveAndFocusedItem(indexToFocus); + }; + + const onInputKeyDown = (event: React.KeyboardEvent) => { + const focusedItem = + focusedItemIndex !== null ? selectOptions![focusedItemIndex] : null; + + switch (event.key) { + case "Enter": + if ( + isOpen && + focusedItem && + focusedItem.value !== NO_RESULTS && + !focusedItem.isAriaDisabled + ) { + selectOption(focusedItem.value, focusedItem.children as string); + } + + if (!isOpen) { + setIsOpen(true); + } + + break; + case "ArrowUp": + case "ArrowDown": + event.preventDefault(); + handleMenuArrowKeys(event.key); + break; + } + }; + + const onToggleClick = () => { + setIsOpen(!isOpen); + textInputRef?.current?.focus(); + }; + + const onClearButtonClick = () => { + setSelected(""); + setInputValue(""); + setFilterValue(""); + resetActiveAndFocusedItem(); + textInputRef?.current?.focus(); + }; + + const toggle = (toggleRef: React.Ref) => ( + + + + + + + + + + + )} + + + ); +}; + +export { CreateTransforms }; diff --git a/src/pages/Transformation/Teansformations.test.tsx b/src/pages/Transforms/Teansformations.test.tsx similarity index 74% rename from src/pages/Transformation/Teansformations.test.tsx rename to src/pages/Transforms/Teansformations.test.tsx index 0f4de362d..a88f83f92 100644 --- a/src/pages/Transformation/Teansformations.test.tsx +++ b/src/pages/Transforms/Teansformations.test.tsx @@ -1,6 +1,6 @@ import { render, screen } from "@testing-library/react"; import { describe, it, expect, vi, beforeEach } from "vitest"; -import { Transformation } from "./Transformation"; +import { Transforms } from "./Transforms"; import { MemoryRouter } from "react-router-dom"; import { useData } from "../../appLayout/AppContext"; @@ -16,7 +16,7 @@ vi.mock("../../appLayout/AppContext", () => ({ useData: vi.fn(), })); -describe("Transformation Component", () => { +describe("Transforms Component", () => { beforeEach(() => { vi.mocked(useData).mockReturnValue({ darkMode: false, @@ -26,16 +26,16 @@ describe("Transformation Component", () => { }); }); - it("renders the Transformation component with correct content", () => { + it("renders the Transforms component with correct content", () => { render( - + ); expect(screen.getByAltText("Coming Soon")).toBeInTheDocument(); - expect(screen.getByText("No transformation available")).toBeInTheDocument(); - expect(screen.getByText("Add Transformation")).toBeInTheDocument(); + expect(screen.getByText("No transforms available")).toBeInTheDocument(); + expect(screen.getByText("Add Transforms")).toBeInTheDocument(); expect(screen.getByText("Go to source")).toBeInTheDocument(); expect(screen.getByText("Go to destination")).toBeInTheDocument(); expect(screen.getByText("Go to pipeline")).toBeInTheDocument(); diff --git a/src/pages/Transformation/Transformation.css b/src/pages/Transforms/Transforms.css similarity index 89% rename from src/pages/Transformation/Transformation.css rename to src/pages/Transforms/Transforms.css index 3670554a6..52d93f30f 100644 --- a/src/pages/Transformation/Transformation.css +++ b/src/pages/Transforms/Transforms.css @@ -15,4 +15,8 @@ .transformation_overlay img { max-width: 20%; max-height: 20%; + } + + .transform-card { + padding-top: 10px; } \ No newline at end of file diff --git a/src/pages/Transforms/Transforms.tsx b/src/pages/Transforms/Transforms.tsx new file mode 100644 index 000000000..38680038e --- /dev/null +++ b/src/pages/Transforms/Transforms.tsx @@ -0,0 +1,426 @@ +import * as React from "react"; +import { + Bullseye, + Button, + Card, + Content, + ContentVariants, + EmptyState, + EmptyStateActions, + EmptyStateBody, + EmptyStateFooter, + EmptyStateVariant, + Form, + FormGroup, + Modal, + ModalBody, + ModalFooter, + ModalHeader, + PageSection, + SearchInput, + Spinner, + TextInput, + ToggleGroup, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, +} from "@patternfly/react-core"; +import { PlusIcon, SearchIcon } from "@patternfly/react-icons"; +import EmptyStatus from "../../components/EmptyStatus"; +// import comingSoonImage from "../../assets/comingSoon.png"; +import "./Transforms.css"; +import { useNavigate } from "react-router-dom"; +import { fetchData, TransformData, useDeleteData } from "src/apis"; +import { API_URL } from "@utils/constants"; +import { useQuery } from "react-query"; +import _, { debounce } from "lodash"; +import { useCallback, useState } from "react"; +import ApiError from "@components/ApiError"; +import PageHeader from "@components/PageHeader"; +import { + ActionsColumn, + IAction, + Table, + Tbody, + Td, + Th, + Thead, + Tr, +} from "@patternfly/react-table"; +import { ActionData, DeleteInstance } from "@pipelinePage/index"; +import { useNotification } from "@appContext/index"; +// import { useData } from "../../appLayout/AppContext"; + +export interface ITransformsProps { + sampleProp?: string; +} + +const Transforms: React.FunctionComponent = () => { + // const { darkMode } = useData(); + const navigate = useNavigate(); + + const navigateTo = (url: string) => { + navigate(url); + }; + + const { addNotification } = useNotification(); + + const [isOpen, setIsOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [deleteInstance, setDeleteInstance] = useState({ + id: 0, + name: "", + }); + const [deleteInstanceName, setDeleteInstanceName] = useState(""); + + const [searchResult, setSearchResult] = React.useState([]); + const [searchQuery, setSearchQuery] = React.useState(""); + + const onClear = () => { + onSearch?.(""); + }; + + const { + data: transformsList = [], + error, + isLoading: isTransformsLoading, + } = useQuery( + "destinations", + () => fetchData(`${API_URL}/api/transforms`), + { + refetchInterval: 7000, + onSuccess: (data) => { + if (searchQuery.length > 0) { + const filteredSource = _.filter(data, function (o) { + return o.name.toLowerCase().includes(searchQuery.toLowerCase()); + }); + setSearchResult(filteredSource); + } else { + setSearchResult(data); + } + }, + } + ); + + const { mutate: deleteData } = useDeleteData({ + onSuccess: () => { + modalToggle(false); + setIsLoading(false); + addNotification( + "success", + `Delete successful`, + `Transform deleted successfully` + ); + }, + onError: (error) => { + modalToggle(false); + setIsLoading(false); + addNotification( + "danger", + `Delete failed`, + `Failed to delete transform: ${error}` + ); + }, + }); + + const handleDelete = async (id: number) => { + setIsLoading(true); + const url = `${API_URL}/api/transforms/${id}`; + + deleteData(url); + }; + + const modalToggle = (toggleValue: boolean) => { + setDeleteInstanceName(""); + setIsOpen(toggleValue); + }; + + const debouncedSearch = useCallback( + debounce((searchQuery: string) => { + const filteredSource = _.filter(transformsList, function (o) { + return o.name.toLowerCase().includes(searchQuery.toLowerCase()); + }); + setSearchResult(filteredSource); + }, 700), + [transformsList] + ); + + const onSearch = React.useCallback( + (value: string) => { + setSearchQuery(value); + debouncedSearch(value); + }, + [debouncedSearch] + ); + + const onDeleteHandler = (id: number, name: string) => { + setIsOpen(true); + setDeleteInstance({ id: id, name: name }); + }; + + const rowActions = (actionData: ActionData): IAction[] => [ + { + title: "Edit", + onClick: () => { + console.log("Edit transform", actionData.name); + }, + }, + + { + title: "Delete", + onClick: () => onDeleteHandler(actionData.id, actionData.name), + }, + ]; + + return ( + <> + {/* + + */} + + <> + {error ? ( + + + + + + } + /> + + ) : ( + <> + {isTransformsLoading ? ( + + ) : ( + <> + {transformsList.length > 0 ? ( + <> + + + + + + + onSearch(value)} + onClear={onClear} + /> + + + + + + + + + + { + (searchQuery.length > 0 + ? searchResult + : transformsList + ).length + }{" "} + Items + + + + + + + + + + + + + + + + + + {(searchQuery.length > 0 + ? searchResult + : transformsList + ).length > 0 ? ( + (searchQuery.length > 0 + ? searchResult + : transformsList + ).map((instance: TransformData) => ( + + + + + + + + )) + ) : ( + + + + )} + +
NameTypeActiveActions
{instance.name}{instance.type}0 + +
+ + + + Clear search and try again. + + + + + + + + +
+
+
+ + ) : ( +
+ } + onClick={() => + navigateTo("/transform/create_transform") + } + > + Add Transform + + } + secondaryActions={ + <> + + + + + } + /> +
+ )} + + )} + + modalToggle(false)} + aria-labelledby={`delete transform model`} + aria-describedby="modal-box-body-variant" + > + + {" "} + Enter "{`${deleteInstance.name}`}" to delete + transform +

+ } + titleIconVariant="warning" + labelId="delete-modal-title" + /> + +
+ + setDeleteInstanceName(value)} + value={deleteInstanceName} + /> + +
+
+ + + + +
+ + )} + + + ); +}; + +export { Transforms }; diff --git a/src/pages/Transforms/index.ts b/src/pages/Transforms/index.ts new file mode 100644 index 000000000..6549e76fd --- /dev/null +++ b/src/pages/Transforms/index.ts @@ -0,0 +1 @@ +export * from './Transforms'; \ No newline at end of file diff --git a/src/pages/Vault/Vaults.tsx b/src/pages/Vault/Vaults.tsx index 57bd71a83..64e916f66 100644 --- a/src/pages/Vault/Vaults.tsx +++ b/src/pages/Vault/Vaults.tsx @@ -4,7 +4,6 @@ import { PlusIcon } from "@patternfly/react-icons"; import EmptyStatus from "../../components/EmptyStatus"; import { useNavigate } from "react-router-dom"; import comingSoonImage from "../../assets/comingSoon.png"; -import "../Transformation/Transformation.css"; import { useData } from "../../appLayout/AppContext"; export interface IVaultsProps { diff --git a/src/route.tsx b/src/route.tsx index 895665c94..dd4907c60 100644 --- a/src/route.tsx +++ b/src/route.tsx @@ -25,8 +25,9 @@ import { PipelineDetails, Pipelines, } from "./pages/Pipeline"; -import { Transformation } from "./pages/Transformation"; +import { Transforms } from "./pages/Transforms"; import { Vaults } from "./pages/Vault"; +import { CreateTransforms } from "./pages/Transforms/CreateTransforms"; export interface IAppRoute { label?: string; // Excluding the label will exclude the route from the nav sidebar in AppLayout @@ -120,12 +121,18 @@ const routes: AppRouteConfig[] = [ title: `${AppBranding} | Source`, }, { - component: Transformation, - label: "Transformation", + component: CreateTransforms, + path: "/transform/create_transform", + navSection: "transform", + title: `${AppBranding} | Transform`, + }, + { + component: Transforms, + label: "Transform", icon: , - path: "/transformation", - navSection: "transformation", - title: `${AppBranding} | Transformation`, + path: "/transform", + navSection: "transform", + title: `${AppBranding} | Transform`, }, { component: Destinations, diff --git a/yarn.lock b/yarn.lock index 426c4d5f5..810e2f5bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1110,6 +1110,45 @@ resolved "https://registry.yarnpkg.com/@dagrejs/graphlib/-/graphlib-2.2.2.tgz#74154d5cb880a23b4fae71034a09b4b5aef06feb" integrity sha512-CbyGpCDKsiTg/wuk79S7Muoj8mghDGAESWGxcSyhHX5jD35vYMBZochYVFzlHxynpE9unpu6O+4ZuhrLxASsOg== +"@dnd-kit/accessibility@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz#1054e19be276b5f1154ced7947fc0cb5d99192e0" + integrity sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ== + dependencies: + tslib "^2.0.0" + +"@dnd-kit/core@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.1.0.tgz#e81a3d10d9eca5d3b01cbf054171273a3fe01def" + integrity sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg== + dependencies: + "@dnd-kit/accessibility" "^3.1.0" + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/modifiers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz#9e39b25fd6e323659604cc74488fe044d33188c8" + integrity sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A== + dependencies: + "@dnd-kit/utilities" "^3.2.1" + tslib "^2.0.0" + +"@dnd-kit/sortable@^7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-7.0.2.tgz#791d550872457f3f3c843e00d159b640f982011c" + integrity sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA== + dependencies: + "@dnd-kit/utilities" "^3.2.0" + tslib "^2.0.0" + +"@dnd-kit/utilities@^3.2.0", "@dnd-kit/utilities@^3.2.1", "@dnd-kit/utilities@^3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b" + integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== + dependencies: + tslib "^2.0.0" + "@esbuild/aix-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" @@ -1527,6 +1566,31 @@ react-dropzone "^14.2.3" tslib "^2.7.0" +"@patternfly/react-core@^6.0.0-prerelease.21": + version "6.0.0-prerelease.21" + resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-6.0.0-prerelease.21.tgz#78dc72a3bcf4f0bf886a5dc6edd765b6bfc0db74" + integrity sha512-EaGcKUPeeR253vY4N0Ahm9oOVtltoI6JycfclwmzjevOzpYvuLj1jcsVwL8wqgWYQVpURoBm1yxIdx34fo5UHA== + dependencies: + "@patternfly/react-icons" "^6.0.0-prerelease.7" + "@patternfly/react-styles" "^6.0.0-prerelease.6" + "@patternfly/react-tokens" "^6.0.0-prerelease.7" + focus-trap "7.6.0" + react-dropzone "^14.2.3" + tslib "^2.7.0" + +"@patternfly/react-drag-drop@6.0.0-prerelease.21": + version "6.0.0-prerelease.21" + resolved "https://registry.yarnpkg.com/@patternfly/react-drag-drop/-/react-drag-drop-6.0.0-prerelease.21.tgz#d07b40d61d07c38f7948d2460a02217ba3eef2f9" + integrity sha512-neFAU4UgFpnGEhKdi4MahbI7GMvq2oN6F9Qdg8nO5Ac+mgTPq+sGSk3918B32xk29mgqsACRy0ziyHru0/TBlA== + dependencies: + "@dnd-kit/core" "^6.1.0" + "@dnd-kit/modifiers" "^6.0.1" + "@dnd-kit/sortable" "^7.0.2" + "@patternfly/react-core" "^6.0.0-prerelease.21" + "@patternfly/react-icons" "^6.0.0-prerelease.7" + "@patternfly/react-styles" "^6.0.0-prerelease.6" + resize-observer-polyfill "^1.5.1" + "@patternfly/react-icons@6.0.0-alpha.35": version "6.0.0-alpha.35" resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-6.0.0-alpha.35.tgz#03caf5362d6f08cb4dd18e618aaeaa44212af478" @@ -1542,6 +1606,11 @@ resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-6.0.0-prerelease.4.tgz#21ddc2a6c66adb6d4160e043a167d750841a8af0" integrity sha512-KHo0v4XG4vS5wSZ76EUOrXDM636/ikXe6lNYqbAL/KRfqhfvXHEESZnK+0p1tpoBwwEUivAmJNSdIjppBPhACg== +"@patternfly/react-icons@^6.0.0-prerelease.7": + version "6.0.0-prerelease.7" + resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-6.0.0-prerelease.7.tgz#44218ac52e5a8440bd0bb96eceef3df4c9239b86" + integrity sha512-DQmecVgXRIiD3ww4KUuJ0qO76TmYMDEJ1ao1+DYuTSP+FzeJLJKuE9QxvL8qn3anyKtuORBuHdTIjM52mVq5Vg== + "@patternfly/react-log-viewer@^6.0.0-alpha.5": version "6.0.0-alpha.5" resolved "https://registry.yarnpkg.com/@patternfly/react-log-viewer/-/react-log-viewer-6.0.0-alpha.5.tgz#5daf98ed71e250d82df7473ded1e1cdbfdc84f9b" @@ -1567,6 +1636,11 @@ resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-6.0.0-prerelease.3.tgz#16142ad8f3dbec9cc69c2b54690dba02f86e67fa" integrity sha512-VyAODCKA/PkyGMVT0A2G2TVVx1H1QKBrmXBwY11Ba3ggvuLZ2zWu+vU9LyM/HhmefOwy+5/P8bmRtLM+37D/CA== +"@patternfly/react-styles@^6.0.0-prerelease.6": + version "6.0.0-prerelease.6" + resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-6.0.0-prerelease.6.tgz#e46acc02c8bc2464846544ebf55bbc4a646824b9" + integrity sha512-tI28gIJFgbgVQs7Xj705csfl6T92dr5Bh7ynR5gN4+QdTWCUWmSctp46G2ZewXdrIN+C+2zUPE86o77aFp4CWw== + "@patternfly/react-table@^6.0.0-prerelease.13": version "6.0.0-prerelease.13" resolved "https://registry.yarnpkg.com/@patternfly/react-table/-/react-table-6.0.0-prerelease.13.tgz#0569a3e8396755e51e1c3421c8e8fa56e2eae97b" @@ -1594,6 +1668,11 @@ resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-6.0.0-prerelease.4.tgz#0b0e0eaa271847cf71f6828b0ad048b5efa783d9" integrity sha512-T1/C6nj78zvk4zLUp3VvNI3hChPR2vEy0BasIG3AYakWoLJsdOY6qS3PlLulawNZvlK7KH0X7VRfT1Zk073R6A== +"@patternfly/react-tokens@^6.0.0-prerelease.7": + version "6.0.0-prerelease.7" + resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-6.0.0-prerelease.7.tgz#68525ffcf08aebe2436d77af168a5fb4b3478b17" + integrity sha512-SLgVwbIgVx26LCjaXkpNlPIZYqWpHJkw3QX/n3URLmIcRlCw536/rKO1PzXaeuCCqhuSq66J6R125zM2eJjM6A== + "@patternfly/react-topology@^6.0.0-alpha.3": version "6.0.0-prerelease.1" resolved "https://registry.yarnpkg.com/@patternfly/react-topology/-/react-topology-6.0.0-prerelease.1.tgz#12915fc87b0f7fa0d33eff434cedc611c86f16cd" @@ -6846,7 +6925,7 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -resize-observer-polyfill@^1.5.0: +resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== From 2d305a0a43085654a9958b1835807a4118daca83 Mon Sep 17 00:00:00 2001 From: indraraj Date: Mon, 4 Nov 2024 11:18:16 +0530 Subject: [PATCH 2/3] DBZ-8328: Add Tranfromation in pipeline flow process --- package.json | 1 + src/assets/dbz_logo.png | Bin 0 -> 9758 bytes src/components/ComponentImage.tsx | 16 +- src/components/dataFlow/CompositionFlow.tsx | 8 +- .../dataFlow/CreationFlowTransform copy.tsx | 729 ++++++++++++++++++ .../dataFlow/CreationFlowTransform.tsx | 445 ++++++++--- src/components/dataFlow/DebeziumNode.css | 2 +- src/components/dataFlow/DebeziumNode.tsx | 11 +- .../dataFlow/TransformGroupNode.css | 6 +- .../dataFlow/TransformGroupNode.tsx | 14 +- src/components/dataFlow/TransformLinkNode.css | 7 +- src/components/dataFlow/TransformLinkNode.tsx | 14 +- src/components/dataFlow/TransformNode.css | 40 + .../dataFlow/TransformSelectedNode.tsx | 168 ++++ .../dataFlow/TransformSelectorNode.tsx | 86 +++ src/pages/Pipeline/ConfigurePipeline.tsx | 10 +- src/pages/Pipeline/PipelineDesigner.tsx | 139 +++- yarn.lock | 5 + 18 files changed, 1501 insertions(+), 200 deletions(-) create mode 100644 src/assets/dbz_logo.png create mode 100644 src/components/dataFlow/CreationFlowTransform copy.tsx create mode 100644 src/components/dataFlow/TransformNode.css create mode 100644 src/components/dataFlow/TransformSelectedNode.tsx create mode 100644 src/components/dataFlow/TransformSelectorNode.tsx diff --git a/package.json b/package.json index bdd70879e..bf5285a33 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@patternfly/react-tokens": "^6.0.0-prerelease.4", "@patternfly/react-topology": "^6.0.0-alpha.3", "axios": "^1.6.8", + "jotai": "^2.10.1", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/assets/dbz_logo.png b/src/assets/dbz_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fb862ad0e753228ea8b6a5d21840443dd1149454 GIT binary patch literal 9758 zcmXAvc|26#|Ht3=&S1u%u|*+c7m`wTH6lx7ja1Z>L@*?e`30{nC56 z?z<1>1qy9(crU^^mfT;#rg|zV=vK6cY^YO{X`o{1^c~^FIYM578+N_fa4ffg!1Ixc+32B| zPl2ZEI=%n>GcI_11rwY7*7E52KK}v929;%j-fae-hJwcfeevjs1AxnqF2S z?jJNDDoVWYvsl71fg!I6dlMzP&OS`)SeaLw;WB(tyIJ^GoDUg-{{_Hsa#%a-q=9RfazkG79a`Plk`GPeqWu)9ChK`JJ;l@ zAjq7xFg#ixnHyOjscr$br0ij<7wZ~Voc>PbjK39N?pjGvCKF?`neq;b`qxnJ-ofp- z?jNH)iPjYI)n$od7G#VaYaqZxcWI*W$v8jS+^EX4nZgkjUbt0#8BbfcZ;%#aMPCIT z0v0Xeo*EQoDymWH;qag;^y2)3er*3kS~e7N0`qF+>=QD^kcRKy-yiXbN46N9rTV2WIi_C0@?Kr~vvga6mR0z1snqR*9td2Pk z^d{VFusmQCfS(iO{qs&D(PShO2}5XVyP4EEq2?QO75v*Bcg|fPVASN*GYXt>eC-?N zfM-(ybYen|c&b4TiC?ucg7qatxsF1{0UD&LKc1s^FQ=ksGgV*%TE!W?1vULN}gLC=7X6DVeDWMtAiWet>ykzD`OP)TNF0DRfA}ZZIJtXp%A!}lZK15RNR+ zk6O-sN+FmZ{UjFZ;Kio9@taS=C;u~vWdhT zy}`ivLWF|dbu_?89Jq&5fpVCk{%jKy$SXvY4|<~sooQT!GnzSRsz4ze$=Re_(sgjQ zr>a-_NWf|gFgieWg?M`^5P;T{jM-8K%&ZSZ6>XP03zS7*4T)r)`bL&Hu*kez5|Ehy z24B*7eS+CtwHA>0P@M^dGtuY;4>{+ibikTpv*@U1{#8E{ZZ)uR0)9Ay!7Ez=l9z$Q zo*k0#I#5ytJly1*pLYPpAi-3S6;EfWWBf(% zU8Lmtinh%-LkIb3L5xo$UWUn@hfyIGshE1u0m`}hlE4+l=v=+zQIHq49jT4J_=0K zT*o+QO4l0G5*09DRBt%Mfl>2F@}(rdBRFq1nEfcw$O$5Y3}-vq&d?s6`-WV%0#6>} z$BrgvNTf#8u<$e`*gk)3G5P6fK@(x*W>zbw^fE8OXrpk|*vld}Dkm^g13c|Jqs*BX z&^RAnl}_QMv~;TT)T!{#Ek0%VraG|-&;Iqem?9EbQA|RX>hUGgb20$I{g zy>C_qf2=0XnejG}nOtG3BZd9{+igSQ&+HoY=le-F4ZMjYGrOYP-mHAA{QDZl4w!eR zLcn~avflk~N1rX5@}LrzhA8Hy7|-roKE^V{;9-KJf0WEoF}cu9!4wk|mV{`Hg##}ke2R9zI% zvxNHxZ6<#dG>2*maj$5S=;c}^F(286826r~v1i+rcPtM<^3D2-g&&NmL}lQ2v6YY0 z>0Pbo%~d{1Q*H?H@?B3qvZQZ=j5b$m(qi)EQvd1-wMyiiygZ_u#(VE+zvucR50^Uj z;ahlSWy~3T;K*d;Qw+K7Ue8VE#~U|(ZqFj=i64TW^Ve!j+GcMozgcvAao#=1y-8+f zuf-;wZMbChWT^O@0P>tQu1B?$M6qvsFkY8@;U>^yr<|#B`YW;@%0imzT`<;b>EvT& z(L0xP{H_q@ir@DNJ|zYG)@0l5FYYTf2S)m-hymm*S~0suMI0x2Y&ic)6S%qk%!cTn zDJAm{f+`e zLQDiXL-SL$lYosF%Txt9p)JN|&CVTt6i@Hz{mpLunO&&82aINY!f#CbtEZN&|elyYv*z3jyQ(EE@->u3tD#>sg>P!<-L7VO0;gn&O z?Vf>;oqt4}+GtKFUaKNUICD zl9yJw4fwm287iMA8*1*zbD3WpO7vU8i}ZmAduH`OQTS(a8^%5`CR)q}5op|aQe~_{KZ8V@dULUNovPh!% z{yUZ$;X78pQ6+yUCbHjMj%1)I4t_3?`||yg84Pm#p0k_VGebQ?_H(shZ9Uynwkj72 zc>#YFZn+C;9cpSBdn}}V__id@>ACpMO}NnY;Qm(^IpJrPe}8cM;l%Y@ZTj%uOVM8D z^5QBHMrid!wuN~H-BUm?Dg=I4g?E--sAK3u!>Pc`d@H$lLkU=x>cGH-Wq-$)Z8N!wWpj3nZpx?W(YS+2pJUdTGsqqZ zwL3+5*}Zj)^U#phu|4_lclm8*SIM5|+G@3xRzym(Drk@2>~DEQYF!{j6?2{+3x(fz51Pwt6FDaHq7<%PqVVdYjC4fG z%mXEGsp?hx%ywK3G`>&?YE9d|G%EN#@ZX^4B4nW%hV29Y%MXV>P%&Xe%C&;4OKkWU@uHFZ=weFEdwHVt&DE*Cn-m*mYKfIW}Eyuwf%i z6+~2h{0f7HkoIdZ&?(ACdG zl0`i@n5v}>&sCQRoe#t?~O`pHZPPXTy($p8THrsVCCaBWlMab}E9myxg8`HLS`p#}1?v+dMPDH_qbE6D8$qoGxL3B+M(s?8nCog_|KPT)a7)q!5EK-);-E%L}{iJ%%V30;~9%x zAR}1h%#x7RjTvZB{i=M+Ohm|4rle`RRGcf1HFLXN<(ne}2*%>wkTJ37^pfeln?-+< znNLPiNzKO>Yb05+?@a8M6y3T{S(JD$xSZ&J)EAe9qWirF>S9fT2j3(Qr=f+IhZQ=& znu8i*dNGK_>fD!lFkt)XTT+E`-#fe%k_`|XrO$53Zsup~M^x1gVopTe@7Pn0S z|80z}f0HwQM}o-|qQjjertTAyjl#Ws6vp%^}0#&!v$u3I;MLg)WTz zG~u$hwfpny%ethYluS&|3FuA!m{B~xIRGaU_Lg~0I^V5|MSq}B*O7Vs$M4FwfblCc z4>lY|)l4@!c#6eu9f|BpyvR8Y?BIc7v|C&4T;5ED8f{hW7*lJQ>?2D(X8F?LTA=8| zivtgy@94aTFyfJ{LR>G+{QYVP^bJ{~)iYHu!#EQEVWB!5TR=!Y zA)x}euYjZQz~u~g)Iv_S_l@+apA%VVY^V|%hBKHRtlbnk43*;RCqBd_$KE>&qn{m> zt%1CWrn{PU+7Tj2thhchMn~e;*%RUrh@D-HxUwVSfQ5@1%v_Ty_Fb$D(LiJ@0AMLs zj{?D9l(s`a8DW`cFg$%=x`1Fh$mGI-eO;Cg1$H#uqf;B<{&2S}Mkm8tKXsfT4~RW~ zyhqGw1|6p;6u=n=fnSXvkq!eixU7c^>=VW>LSK|Q-7k)Dk0AZ6S&+zGi{Z?5NNA%F zq5@F9!+j6V`_7PBkGeQ>Xl7WL4zaMtOL>7}Pm_41qG(b3EVo9L%85eLM)eRSm;pZQ zSs62u?g;nqil{~xZvwKW$Od>Rm>p!ogh`AsxidTyC_*qDu@y0z&#OC|ywo9@mXNcb zOjm~utI&{9={DIlR78x4*Dik^TvsfKCW>C6&XRz+*(X=TknP3!X3L;$>&j5B18|9$ zK2s+l5u8^O*KW5l!>^bMS(UJ%lqL?Oe z2!lI6PUi*Ko%2P+qza!PQmB`)Nkf{ zO*BYD=zqY+Ihjys3z5O5i>>UD^?3gLQB?I(r+n#u04s~XkD2$v^z5Ph;GEOv%S+Pom zj~`SYKgrg8Ediog6&UC1oruH4Yt&Oy&%MvQz=7n9z^)Xh1qJs~iPjq9z7A(H@j`5$ zHwq#p%9uu8ut%9{(muVvm`mm=@d!9fFQW0^3J_6B zY~{cDlFZY|k&HzP zV91-zC27;EeCsOhayP-6=#>$X{T~v4oHqU#YD?lG8SG!?5vrSBRa;!#VwS#(09eq0 zaQSv(cUME;!eSy4o)z`~Z!&FgNr@5ze#ZJ8owz)2f00ysy70lAv^m5VQd=z8A0ay= z+X`B$FPm2V*}g%HYaxU+o6k9BJZlvZ=B!wCRMqrG zZ;$V0sO|;&YgG$a@jNAfG_kZzaM;~QueBf8tRWT7F_+rk&X-6> zqJ%bz!uM$(M3Q4?<%JgOsuv~@-v;|_FTm|?ryYJ%5lioNPM7=}u!~x;hbxIqI=;sq z8daEO2zXv2d5e>H|8#a}RGzUNNn0#o-x?j}EkEjY zPQCpnM}=+Et-o6K2{9*ac?yY^S`YP{A;(k}{Ejy{{xQp{HF;K@RIr47+as#5VJj<<+%wwwc$*qyH5+h$dEQ%P4ITDpP8=pwscs zzh>TP8wl#GSOVXp49FEPMcuQeez#FQuW)`hnX94)bus5|4GM)bE}P+k?KN&kR-O@8 z7@`-J?`L!`v^B4KX7jNfhDNv4kZA6WAD^1z#gH+j=5tRRJKGQ<-~(L6}7E`OCK4GJ~8_n zw1Fi}ue#2jc5$VGQB48D&g&6zy~hg@4E^-O?aIo@Xb9;f&%Ojslz%2crz}&k_oMM; zFL}xC9o#TdZDuj*f-}#xc|SPkER!wFn6Bk5@;}O?6yovE4E3vqb=2qn4OC93V8=<+kIm;X=PIi75{8zsS&a)YajDH=$Ly=~Z^3$FOT0f& zuoGpgs*J92-#D9E-lp+c+1?#xhW}GZ&2C?nw99U1(%nWuk87!|wfbTq>3>RphYl`eMQ5q1hC@ zq585s<3bHa{L5zNqRDRcX<>UB;t=~KRjj_#5SE$*z z-q+W|J_8-2LNZOAewrGy6UTH!Tm{qm+g=59?r)^*?O4CH#I*94V|PvAz>?8l|K1=~ z&d|a)+qiAEWgWuQKY?KVtGMQFB8L#>%3USH-BhMp)a~1dHt&>+(f1;vv(h4c3>1y2 z9C`8VFNYs`s*_flxl@*`9kb)=B)2 zm6F7Yl?xwyb9`_u=k#5X z-kF0`W^Qw+XUCO=?2Tl2`dBj7JQIqPBj3e>M-I=O5gu$fhT8r2SBDr3ejoQZ-^HTv zi46o%EnGoAoyx=j$VkCcd z8=6;no(;QHFNpx?N@P!8UV7@WvN24GLP$r}mPFUg&EtQE(0+VYxc1g(zr*ZC6j@CN zvXP0?LK=Mq0@lz`T)cC-qJR2t@CRN1E-t|P*z~8l=R&Hu1qTHz!A}rYuB3&j z8AC~&Q&|rqYvzjHg>Po`K#Mim@}%2|m#2^H?|g6aFzAL7EN!#Njd(qur?nKHc632< zyZIra-}JEl+i(;5A;@@hLlKc9{gdHZlwjaokXFo^cktgaA2RK&&f7;JF8LUU4U7l< z$D4jBH^U9YB^NZI!JJEE=GX9O)4mAH%fS99)UR;V_c0nE8x;Fen@Ix#E)-#iM1SCq~jOYA!(!TSbo+1O-xu*wQ|0FCal&? z&qEbHopzZx7nBO@*+y?}y+18QyK`JNyiXC&{u^`XT%h-QH3mw|b>>9;@crPk88tlR zT|uH<-gxc$0#eQ?#2lPkWalC}i(JLAi(BJiG`Xr_fnTStY|9`izi~zp`Be!W?GQ~~ z=5h>c4qro8foQjQUxDyVnJ`KQI!%}5D}d5KDg%|8(W_AMOW^6kSpYp-xPRZGZ7xZB zL$Or*@>+Y;+K1bdH^&U0o)R=VjPncy(fF*M)FHfkC)he79$C@6RFn?Ogp_33T5GCH z-;hzz9If(x0f@{{8l=jPhyFnsq)ZeAoUB?CKnD(;0Uf>M)RF0*`C2({zb}58N3C=u zYc+ZKDI7yX$sGT%fPRilC56yi%5;ohYJmQxAx0T&Za9z%{xsr8sT+ci&B9uvc1?Zx zsF(Jdh|Qcm_&LpCh-OzV_uoE(&!m*%FEq2tdY%{|k`82=>7AsVeH&$izhkS`{$224m41UUQ*X&%rL zIGl)>eO%$X_!^8_@zM5TLTI5kK*$?syxepD<+wURYqImMq9i!mp|q-N;;S+* z|Nqm^8*v7GfgFdlNGUWiw0_LWR+Ja{jdZv!m|F*fT|BLJisX(nY`sAVao=yu^CKOJ zHbXXe_zP92vyHm-*Szqqj;A=dV%)NG8h|VDgwZ!SG6HY}CyoqjocrZ6HR4d%_wJw- z_*<~hu?D2!Jqbex z9L_3?jIZ~Eb{&kqeJ^aeDMqZs4afp(cCAR(1hb6UbKgWO6mC#qk?)$(Hfgb3YXzc= z;z^gUFBZwXA;Ofs$q~|m23~BMb|oZ+a%28Crzl8txd@`k*0$8}P=?+YQL__Q!A&mc zi#n)o80}swR%V(HWNX$vzZ{&oamaNsGQ4<&V;?@Fl-tWQ-FN0UTZ+c}W8Cy}pdJ-+ z@zu2qVeS=woF3#CSrd00JFPmn7TxBxV(~Ta;8UP^kAebtBoUHs=9b$Q#z~=zeA-%) zE2upx0j^5T#@X4+f1y(uVip+B<8J8|4V>2|8XuSMC0A}p<9}%_-QtHzei4&fHm7Ih z+X?Vwm73L?CYG&1*A}C8*`>efC~M!-!F#+>OciT9ybcDsKWF88ADOl?w4vNjp*@}+ zg9{fQ;5gGGT5t48J=&!k@xh?H(1qsp*11300*|l@rqbB+it^evPEv!NSTCz9M1DjE z+PjbrQ5c+)If2AHci2XVhA)P};g65Xe#i~6K3>194 = ({ case connectorType.includes("pravega"): src = pravega; break; + case connectorType.includes("debezium"): + src = dbz; + break; } return size ? ( - {`mongo + {`mongo ) : ( - {`mongo + {`mongo ); }; diff --git a/src/components/dataFlow/CompositionFlow.tsx b/src/components/dataFlow/CompositionFlow.tsx index c624b5b17..7c0883f0a 100644 --- a/src/components/dataFlow/CompositionFlow.tsx +++ b/src/components/dataFlow/CompositionFlow.tsx @@ -51,8 +51,8 @@ const CompositionFlow: React.FC = ({ () => ({ id: "source", data: { - connectorType: "io.debezium.connector.mongodb.MongoDbConnector", - label: "user-mongodb", + connectorType: "io.debezium.connector.postgresql.PostgresConnector", + label: "postgres-source", type: "source", editAction: () => {}, compositionFlow: true, @@ -83,8 +83,8 @@ const CompositionFlow: React.FC = ({ () => ({ id: "destination", data: { - connectorType: "infinispan", - label: "sink-infinispan", + connectorType: "kafka", + label: "test-destination", type: "destination", editAction: () => {}, compositionFlow: true, diff --git a/src/components/dataFlow/CreationFlowTransform copy.tsx b/src/components/dataFlow/CreationFlowTransform copy.tsx new file mode 100644 index 000000000..b3030ba14 --- /dev/null +++ b/src/components/dataFlow/CreationFlowTransform copy.tsx @@ -0,0 +1,729 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useCallback, useMemo, useState } from "react"; +import ReactFlow, { + applyNodeChanges, + applyEdgeChanges, + addEdge, + NodeChange, + Node, + EdgeChange, + Edge, + Connection, + Background, + MiniMap, + PanOnScrollMode, +} from "reactflow"; +import TransformAdditionNode from "./TransformAdditionNode"; + +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 { 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"; +import TransformLinkNode from "./TransformLinkNode"; +import TransformPipelineModel from "./TransformPipelineModel"; +import TransformGroupNode from "./TransformGroupNode"; + +import TransformSelectorNode from "./TransformSelectorNode"; +import TransformSelectedNode from "./TransformSelectedNode"; + +const nodeTypes = { + dataSelectorNode: DataSelectorNode, + transformLinkNode: TransformLinkNode, + addTransformNode: TransformAdditionNode, + transformGroupNode: TransformGroupNode, + transformSelectorNode: TransformSelectorNode, + transformSelectedNode: TransformSelectedNode, + dataNode: DataNode, +}; + +const edgeTypes = { + customEdgeSource: CustomEdgeSource, + customEdgeDestination: CustomEdgeDestination, +}; + +const proOptions = { hideAttribution: true }; +interface CreationFlowTransformProps { + updateIfSourceConfigured: (isConfigured: boolean) => void; + updateIfDestinationConfigured: (isConfigured: boolean) => void; + updateSelectedSource: (source: Source) => void; + updateSelectedDestination: (destination: Destination) => void; + updateSelectedTransform: (transform: Transform) => void; + onToggleDrawer: () => void; + selectedTransform: Transform[]; +} + +const CreationFlowTransform: React.FC = ({ + updateIfSourceConfigured, + updateIfDestinationConfigured, + updateSelectedSource, + updateSelectedDestination, + updateSelectedTransform, + onToggleDrawer, + selectedTransform, +}) => { + const { darkMode } = useData(); + const [isSourceModalOpen, setIsSourceModalOpen] = useState(false); + const [isTransformModalOpen, setIsTransformModalOpen] = useState(false); + const [isDestinationModalOpen, setIsDestinationModalOpen] = useState(false); + + const handleSourceModalToggle = useCallback(() => { + setIsSourceModalOpen(!isSourceModalOpen); + }, [isSourceModalOpen]); + + const handleTransformModalToggle = useCallback(() => { + setIsTransformModalOpen(true); + }, []); + + const handleDestinationModalToggle = useCallback(() => { + setIsDestinationModalOpen(!isDestinationModalOpen); + }, [isDestinationModalOpen]); + + const cardButton = useCallback( + (buttonText: string): JSX.Element => { + return ( + + ); + }, + [ + handleSourceModalToggle, + handleDestinationModalToggle, + handleTransformModalToggle, + ] + ); + + const cardButtonTransform = useCallback((): JSX.Element => { + return ( + ); }, - [handleSourceModalToggle, handleDestinationModalToggle] + [ + handleSourceModalToggle, + handleDestinationModalToggle, + handleTransformModalToggle, + ] ); const cardButtonTransform = useCallback((): JSX.Element => { @@ -116,41 +131,6 @@ const CreationFlowTransform: React.FC = ({ ); }, [handleTransformModalToggle]); - const dataSelectorSourceNode = useMemo(() => { - return { - id: "source", - data: { - icon: MdLogout, - label: "Source", - type: "source", - action: cardButton("Source"), - }, - position: { x: 50, y: 80 }, - type: "dataSelectorNode", - draggable: false, - }; - }, [cardButton]); - - const transformGroupNode = useMemo(() => { - return { - id: "transform_group", - data: { - label: "Transform", - sourcePosition: "right", - targetPosition: "left", - onToggleDrawer: onToggleDrawer, - onToggleDrawer1: onToggleDrawer, - }, - position: { x: 260, y: 45 }, - style: { - width: 130, - height: 100, - zIndex: 1, - }, - type: "transformGroupNode", - }; - }, [onToggleDrawer]); - const addTransformNode = useMemo(() => { return { id: "add_transform", @@ -171,103 +151,68 @@ const CreationFlowTransform: React.FC = ({ }; }, [cardButtonTransform]); - const dataSelectorDestinationNode = useMemo(() => { + const transformSelectorNode = useMemo(() => { return { - id: "destination", + id: "transform_selector", data: { - icon: MdLogin, - label: "Destination", - type: "destination", - action: cardButton("Destination"), + label: "Transformation", + sourcePosition: "left", + targetPosition: "right", + action: cardButton("Transform"), }, - position: { x: 480, y: 80 }, - type: "dataSelectorNode", + position: { x: 270, y: 50 }, + targetPosition: "left", + type: "transformSelectorNode", draggable: false, }; }, [cardButton]); - const handleProcessor = useCallback(() => { - setNodes((prevNodes: any) => { - return [ - ...prevNodes.filter((node: any) => node.id !== "debezium"), - addTransformNode, - transformGroupNode, - ]; - }); - setEdges([ - { - id: "source-add_transform", - source: "source", - target: "add_transform", - type: "customEdgeSource", - sourceHandle: "a", - }, - { - id: "add_transform-destination", - source: "add_transform", - target: "destination", - type: "customEdgeDestination", + const dataSelectorSourceNode = useMemo(() => { + return { + id: "source", + data: { + icon: MdLogout, + label: "Source", + type: "source", + action: cardButton("Source"), }, - ]); - }, [addTransformNode, transformGroupNode]); + position: { x: 50, y: 80 }, + type: "dataSelectorNode", + draggable: false, + }; + }, [cardButton]); - const debeziumNode = useMemo(() => { + const dataSelectorDestinationNode = useMemo(() => { return { - id: "debezium", + id: "destination", data: { - label: "Transformation", - sourcePosition: "right", - targetPosition: "left", - handleProcessor: handleProcessor, + icon: MdLogin, + label: "Destination", + type: "destination", + action: cardButton("Destination"), }, - position: { x: 290, y: 50 }, - targetPosition: "left", - type: "debeziumNode", + position: { x: 480, y: 80 }, + type: "dataSelectorNode", draggable: false, }; - }, [handleProcessor]); - - // const handleCollapsed = useCallback(() => { - // setNodes((prevNodes: any) => { - // return [ - // ...prevNodes.filter((node: any) => node.id.includes("transform")), - - // debeziumNode, - // ]; - // }); - // setEdges([ - // { - // id: "source-add_transform", - // source: "source", - // target: "add_transform", - // type: "customEdgeSource", - // sourceHandle: "a", - // }, - // { - // id: "add_transform-destination", - // source: "add_transform", - // target: "destination", - // type: "customEdgeDestination", - // }, - // ]); - // }, [debeziumNode]); + }, [cardButton]); const initialNodes = [ dataSelectorSourceNode, - debeziumNode, + transformSelectorNode, dataSelectorDestinationNode, ]; const initialEdges = [ { - id: "source_debezium", + id: "source_transform_selector", source: "source", - target: "debezium", + target: "transform_selector", type: "customEdgeSource", sourceHandle: "a", }, { - id: "debezium-destination", - source: "debezium", + id: "transform_selector-destination", + source: "transform_selector", target: "destination", type: "customEdgeDestination", }, @@ -294,12 +239,12 @@ const CreationFlowTransform: React.FC = ({ const createNewTransformNode = ( id: string, xPosition: number, - transform: TransformData + transformName: string ) => { return { id, data: { - label: transform.name, + label: transformName, sourcePosition: "left", targetPosition: "right", }, @@ -313,17 +258,277 @@ const CreationFlowTransform: React.FC = ({ extent: "parent", }; }; + const selectedTransformRef = useRef(selectedTransform); + selectedTransformRef.current = selectedTransform; + + useEffect(() => { + const transformLinkNodes = nodes.filter( + (node: any) => node.type === "transformLinkNode" + ); + const updatedTransformLinkNodes = transformLinkNodes.map( + (node: any, index: number) => ({ + ...node, + data: { + ...node.data, + label: selectedTransformRef.current[index].name, + }, + }) + ); + + setNodes((prevNodes: any) => { + return [ + ...prevNodes.filter((node: any) => node.type !== "transformLinkNode"), + ...updatedTransformLinkNodes, + ]; + }); + }, [rearrangeTrigger]); + + const handleExpand = useCallback(() => { + const linkTransforms = selectedTransformRef.current.map((transform, id) => { + const newId = `transform_${id + 1}`; + const xPosition = 25 + id * 150; + const newTransformNode = createNewTransformNode( + newId, + xPosition, + transform.name + ); + return newTransformNode; + }); + const transformGroupNode = { + id: "transform_group", + data: { + label: "Transform", + sourcePosition: "right", + targetPosition: "left", + onToggleDrawer: onToggleDrawer, + handleCollapsed: handleCollapsed, + }, + position: { x: 260, y: 45 }, + style: { + width: 100 + +150 * selectedTransformRef.current.length, + height: 80, + zIndex: 1, + }, + type: "transformGroupNode", + }; + // console.log(linkTransforms); + + const addTransformNode = { + id: "add_transform", + data: { + label: "SMT2", + sourcePosition: "right", + targetPosition: "left", + action: cardButtonTransform(), + }, + position: { x: 45 + selectedTransformRef.current.length * 150, y: 37 }, + style: { + zIndex: 10, + }, + targetPosition: "left", + type: "addTransformNode", + parentId: "transform_group", + extent: "parent", + }; + + setNodes((prevNodes: any) => { + const dataSelectorDestinationNode = prevNodes.find( + (node: any) => node.id === "destination" + ); + + const updatedDataSelectorDestinationNode = { + ...dataSelectorDestinationNode, + position: { + ...dataSelectorDestinationNode.position, + x: + dataSelectorDestinationNode.position.x + + 150 * selectedTransformRef.current.length, + }, + }; + + return [ + ...prevNodes.filter( + (node: any) => + node.id !== "transform_selected" && node.id !== "destination" + ), + + transformGroupNode, + ...linkTransforms, + addTransformNode, + updatedDataSelectorDestinationNode, + ]; + }); + + const newEdge: { + id: string; + source: string; + target: string; + type: string; + }[] = []; + selectedTransformRef.current.map((_transform, id) => { + const newId = `transform_${id + 1}`; + if (id === 0) { + newEdge.push({ + id: `source-transform_${id + 1}`, + source: "source", + target: newId, + type: "customEdgeSource", + }); + } else { + newEdge.push({ + id: `transform_${id}-transform_${id + 1}`, + source: `transform_${id}`, + target: newId, + type: "customEdgeSource", + }); + } + }); + + newEdge.push({ + id: `transform_${selectedTransformRef.current.length}-add_transform`, + source: `transform_${selectedTransformRef.current.length}`, + target: "add_transform", + type: "customEdgeSource", + }); + + newEdge.push({ + id: "add_transform-destination", + source: "add_transform", + target: "destination", + type: "customEdgeDestination", + }); + + setEdges([...newEdge]); + }, [cardButtonTransform, onToggleDrawer]); + + const TransformSelectedNode = useMemo(() => { + return { + id: "transform_selected", + data: { + label: "Transformation", + sourcePosition: "right", + targetPosition: "left", + handleExpand: handleExpand, + selectedTransform: selectedTransformRef, + }, + position: { x: 270, y: 50 }, + targetPosition: "left", + type: "transformSelectedNode", + draggable: false, + }; + }, [handleExpand]); + + const isDestinationConfiguredRef = useRef(isDestinationConfigured); + isDestinationConfiguredRef.current = isDestinationConfigured; + + const handleCollapsed = useCallback(() => { + setNodes((prevNodes: any) => { + const destinationNode = prevNodes.find( + (node: any) => node.id === "destination" + ); + + const updatedDestinationNode = { + ...destinationNode, + position: { + ...dataSelectorDestinationNode.position, + x: 480, + }, + }; + + const updatedDataSelectorDestinationNode = isDestinationConfiguredRef + ? updatedDestinationNode + : dataSelectorDestinationNode; + + return [ + ...prevNodes.filter( + (node: any) => + !node.id.includes("transform") && node.id !== "destination" + ), + TransformSelectedNode, + updatedDataSelectorDestinationNode, + ]; + }); + setEdges([ + { + id: "source_transform_selected", + source: "source", + target: "transform_selected", + type: "customEdgeSource", + sourceHandle: "a", + }, + { + id: "transform_selected-destination", + source: "transform_selected", + target: "destination", + type: "customEdgeDestination", + }, + ]); + }, [ + TransformSelectedNode, + dataSelectorDestinationNode, + isDestinationConfiguredRef, + ]); + + const transformGroupNode = useMemo(() => { + return { + id: "transform_group", + data: { + label: "Transform", + sourcePosition: "right", + targetPosition: "left", + onToggleDrawer: onToggleDrawer, + handleCollapsed: handleCollapsed, + }, + position: { x: 260, y: 45 }, + style: { + width: 100, + height: 80, + zIndex: 1, + }, + type: "transformGroupNode", + }; + }, [handleCollapsed, onToggleDrawer]); + + const handleProcessor = useCallback(() => { + setNodes((prevNodes: any) => { + return [ + ...prevNodes.filter((node: any) => node.id !== "transform_selector"), + addTransformNode, + transformGroupNode, + ]; + }); + setEdges([ + { + id: "source-add_transform", + source: "source", + target: "add_transform", + type: "customEdgeSource", + sourceHandle: "a", + }, + { + id: "add_transform-destination", + source: "add_transform", + target: "destination", + type: "customEdgeDestination", + }, + ]); + }, [addTransformNode, transformGroupNode]); + const handleAddTransform = useCallback( (transform: TransformData) => { - const noOfTransformNodes = nodes.filter((node: any) => { + let noOfTransformNodes = nodes.filter((node: any) => { return node.parentId === "transform_group"; }).length; + if (noOfTransformNodes === 0) { + handleProcessor(); + noOfTransformNodes = 1; + } const newId = `transform_${noOfTransformNodes}`; const xPosition = 25 + (noOfTransformNodes - 1) * 150; const newTransformNode = createNewTransformNode( newId, xPosition, - transform + transform.name ); setNodes((prevNodes: any) => { @@ -423,13 +628,7 @@ const CreationFlowTransform: React.FC = ({ updateSelectedTransform({ name: transform.name, id: transform.id }); setIsTransformModalOpen(false); }, - [ - nodes, - setNodes, - setEdges, - setIsTransformModalOpen, - updateSelectedTransform, - ] + [nodes, updateSelectedTransform, handleProcessor] ); const onSourceSelection = useCallback( @@ -510,7 +709,7 @@ const CreationFlowTransform: React.FC = ({ fitView panOnScroll={true} panOnScrollMode={PanOnScrollMode.Horizontal} - maxZoom={1.3} + maxZoom={1.4} minZoom={1.1} panOnDrag={true} > diff --git a/src/components/dataFlow/DebeziumNode.css b/src/components/dataFlow/DebeziumNode.css index c82aa0e0c..9775f42ab 100644 --- a/src/components/dataFlow/DebeziumNode.css +++ b/src/components/dataFlow/DebeziumNode.css @@ -5,7 +5,7 @@ position: relative; border-radius: 0 8px 0 8px; flex-grow: 1; - box-shadow: var(--brand-shadow--large); + box-shadow: var(--brand-shadow); } .debeziumNodeGradient:before { diff --git a/src/components/dataFlow/DebeziumNode.tsx b/src/components/dataFlow/DebeziumNode.tsx index 8ff833bd5..ddac88586 100644 --- a/src/components/dataFlow/DebeziumNode.tsx +++ b/src/components/dataFlow/DebeziumNode.tsx @@ -7,7 +7,12 @@ import { AppColors } from "@utils/constants"; // import { Button } from "@patternfly/react-core"; interface DebeziumNodeProps { - data: { label: string; sourcePosition: Position; targetPosition: Position, handleProcessor: () => void }; + data: { + label: string; + sourcePosition: Position; + targetPosition: Position; + handleCollapsed: () => void; + }; } const DebeziumNode: React.FC = ({ data }) => { @@ -17,8 +22,6 @@ const DebeziumNode: React.FC = ({ data }) => {
= ({ data }) => { id="smt-output" position={data.sourcePosition} /> - -
{/* diff --git a/src/components/dataFlow/TransformGroupNode.css b/src/components/dataFlow/TransformGroupNode.css index b59241279..f9b6a11fb 100644 --- a/src/components/dataFlow/TransformGroupNode.css +++ b/src/components/dataFlow/TransformGroupNode.css @@ -1,5 +1,5 @@ -.transformationWrapper { - border-radius: 10px; +.transformationGroupWrapper { + border-radius: 0 10px 0 10px; box-shadow: var(--brand-shadow); } @@ -21,7 +21,7 @@ } .collapsedDataNodeDestination { border-radius: 100%; - background-color: #fff; + /* background-color: #fff; */ width: 25px; height: 25px; left: 5; diff --git a/src/components/dataFlow/TransformGroupNode.tsx b/src/components/dataFlow/TransformGroupNode.tsx index 0a3054c0e..9495ae7c3 100644 --- a/src/components/dataFlow/TransformGroupNode.tsx +++ b/src/components/dataFlow/TransformGroupNode.tsx @@ -1,7 +1,7 @@ import { Content, Tooltip } from "@patternfly/react-core"; import { Position } from "reactflow"; import "./TransformGroupNode.css"; -import { AngleRightIcon, PencilAltIcon } from "@patternfly/react-icons"; +import { AngleDownIcon, PencilAltIcon } from "@patternfly/react-icons"; import { AppColors } from "@utils/constants"; import { useData } from "@appContext/AppContext"; @@ -11,6 +11,7 @@ interface TransformGroupNodeProps { sourcePosition: Position; targetPosition: Position; onToggleDrawer: () => void; + handleCollapsed: () => void; }; } @@ -20,12 +21,11 @@ const TransformGroupNode: React.FC = ({ data }) => { return ( <>
{ - console.log("expand/collapsed clicked"); - }} + onClick={data.handleCollapsed} + style={{ cursor: "pointer" }} className={"collapsedDataNodeDestination"} > - Collapsed
}> + Collapse}>
= ({ data }) => { } } > - +
@@ -59,7 +59,7 @@ const TransformGroupNode: React.FC = ({ data }) => {
-
+
{/* */} = ({ data }) => { - + {data.label} - + void; + selectedTransform: React.MutableRefObject; + }; +} + +const TransformSelectedNode: React.FC = ({ + data, +}) => { + const { darkMode } = useData(); + return ( + <> +
{ + console.log("log count"); + }} + className={" gradientSource editDataNodeSource"} + > + + {" "} + {data.selectedTransform.current + ? data.selectedTransform.current + .map((transform) => transform.name) + .join(" > ") + : "None"} +
+ } + > +
+ + {data.selectedTransform.current.length} + +
+ +
+
+ Expand
}> +
+ +
+ +
+
+
+ + + + + + + + + + + + + Transforms + + + + + + + +
+
+ + ); +}; + +export default TransformSelectedNode; diff --git a/src/components/dataFlow/TransformSelectorNode.tsx b/src/components/dataFlow/TransformSelectorNode.tsx new file mode 100644 index 000000000..8a596fceb --- /dev/null +++ b/src/components/dataFlow/TransformSelectorNode.tsx @@ -0,0 +1,86 @@ +import { Handle, Position } from "reactflow"; +import "./DebeziumNode.css"; + +import { useData } from "../../appLayout/AppContext"; +import { AppColors } from "@utils/constants"; +import { + Bullseye, + Card, + CardBody, + Stack, + StackItem, +} from "@patternfly/react-core"; +import { DataProcessorIcon } from "@patternfly/react-icons"; + +interface TransformSelectorNodeProps { + data: { + label: string; + sourcePosition: Position; + targetPosition: Position; + action: React.ReactNode; + }; +} + +const TransformSelectorNode: React.FC = ({ + data, +}) => { + const { darkMode } = useData(); + return ( + <> +
+
+ + + + + + +
+ +
+
+ {data.action} +
+
+
+
+ +
+
+ + ); +}; + +export default TransformSelectorNode; diff --git a/src/pages/Pipeline/ConfigurePipeline.tsx b/src/pages/Pipeline/ConfigurePipeline.tsx index 2d514a3a2..c53639749 100644 --- a/src/pages/Pipeline/ConfigurePipeline.tsx +++ b/src/pages/Pipeline/ConfigurePipeline.tsx @@ -46,6 +46,8 @@ import { import { API_URL } from "../../utils/constants"; import { useData } from "../../appLayout/AppContext"; import PageHeader from "@components/PageHeader"; +import { useAtom } from 'jotai'; +import { selectedTransformAtom } from "./PipelineDesigner"; const ConfigurePipeline: React.FunctionComponent = () => { const navigate = useNavigate(); @@ -54,6 +56,10 @@ const ConfigurePipeline: React.FunctionComponent = () => { const sourceId = params.get("sourceId"); const destinationId = params.get("destinationId"); + const [selectedTransform] = useAtom(selectedTransformAtom); + + console.log("selectedTransform", selectedTransform) + const navigateTo = (url: string) => { navigate(url); }; @@ -62,6 +68,8 @@ const ConfigurePipeline: React.FunctionComponent = () => { navigate(-1); }; + + const { navigationCollapsed } = useData(); const [editorSelected, setEditorSelected] = useState("form-editor"); @@ -126,7 +134,7 @@ const ConfigurePipeline: React.FunctionComponent = () => { name: destination?.name, id: destination?.id, }, - transforms: [], + transforms: [...selectedTransform], name: values["pipeline-name"], }; diff --git a/src/pages/Pipeline/PipelineDesigner.tsx b/src/pages/Pipeline/PipelineDesigner.tsx index ace9a9199..b2ab2d191 100644 --- a/src/pages/Pipeline/PipelineDesigner.tsx +++ b/src/pages/Pipeline/PipelineDesigner.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import * as React from "react"; import { ActionGroup, @@ -22,18 +23,27 @@ import { PageSection, Tooltip, } from "@patternfly/react-core"; - +import { atom, useAtom } from "jotai"; import { useNavigate } from "react-router-dom"; import "./PipelineDesigner.css"; // import CreationFlow from "../../components/dataFlow/CreationFlow"; import { Destination, Source, Transform } from "../../apis/apis"; import CreationFlowTransform from "@components/dataFlow/CreationFlowTransform"; -import { DragDropSort, DraggableObject } from "@patternfly/react-drag-drop"; +import { + DragDropSort, + DragDropSortDragEndEvent, + DraggableObject, +} from "@patternfly/react-drag-drop"; import { TrashIcon } from "@patternfly/react-icons"; +// Define Jotai atoms +export const selectedSourceAtom = atom(undefined); +export const selectedDestinationAtom = atom(undefined); +export const selectedTransformAtom = atom([]); + const getItems = (selectedTransform: Transform[]): DraggableObject[] => selectedTransform.map((transform, idx) => ({ - id: `data-list-${transform.id}-item-${idx}`, + id: `${transform.id}=${transform.name}`, content: ( <> aria-label="Actions" > - } - + {selectedTransform.length === 0 ? ( + <>No transform configured + ) : ( + <> + + + + + + )} ); @@ -199,6 +253,9 @@ const PipelineDesigner: React.FunctionComponent = () => { updateSelectedDestination={updateSelectedDestination} onToggleDrawer={onToggleDrawer} updateSelectedTransform={updateSelectedTransform} + selectedTransform={selectedTransform} + isDestinationConfigured={isDestinationConfigured} + rearrangeTrigger={rearrangeTrigger} /> diff --git a/yarn.lock b/yarn.lock index 810e2f5bf..b2ce21f4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5607,6 +5607,11 @@ jackspeak@2.1.1, jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jotai@^2.10.1: + version "2.10.1" + resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.10.1.tgz#8d5598d06fa295110de0914f10bd1d10ea229723" + integrity sha512-4FycO+BOTl2auLyF2Chvi6KTDqdsdDDtpaL/WHQMs8f3KS1E3loiUShQzAzFA/sMU5cJ0hz/RT1xum9YbG/zaA== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" From e5a4e0a12528a296cb27f56547123089780ec460 Mon Sep 17 00:00:00 2001 From: indraraj Date: Mon, 4 Nov 2024 14:15:03 +0530 Subject: [PATCH 3/3] DBZ-8328: Fix test cases --- src/__mocks__/data/TransformsData.json | 86 +++++++++++ src/appLayout/AppSideNavigation.test.tsx | 2 +- src/pages/Transforms/Teansformations.test.tsx | 43 ------ src/pages/Transforms/Transforms.test.tsx | 138 ++++++++++++++++++ src/pages/Transforms/Transforms.tsx | 8 +- 5 files changed, 227 insertions(+), 50 deletions(-) create mode 100644 src/__mocks__/data/TransformsData.json delete mode 100644 src/pages/Transforms/Teansformations.test.tsx create mode 100644 src/pages/Transforms/Transforms.test.tsx diff --git a/src/__mocks__/data/TransformsData.json b/src/__mocks__/data/TransformsData.json new file mode 100644 index 000000000..9bb8579c5 --- /dev/null +++ b/src/__mocks__/data/TransformsData.json @@ -0,0 +1,86 @@ +[ + { + "id": 5, + "config": { + "debezium.transforms.extract-new-record.route.by.field": "user-id", + "debezium.transforms.extract-new-record.drop.fields.header.name": "__id" + }, + "description": "configured from ui", + "name": "extract-new-record", + "schema": "schema321", + "type": "io.debezium.transforms.ExtractNewRecordState", + "vaults": [] + }, + { + "id": 8, + "config": { + "debezium.transforms.mongo-eventRouter-long-name-verry-long.route.by.field": "test" + }, + "description": null, + "name": "mongo-eventRouter-long-name-verry-long", + "schema": "schema321", + "type": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState", + "vaults": [] + }, + { + "id": 6, + "config": { + "debezium.transforms.filter-transform.language": "groovy", + "debezium.transforms.filter-transform.condition": "value.before.id==2" + }, + "description": "configured from ui", + "name": "filter-transform", + "schema": "schema321", + "type": "io.debezium.transforms.Filter", + "vaults": [] + }, + { + "id": 4, + "config": { + "debezium.transforms.logical-router.topic.regex": "/[0-9]/[a-z]", + "debezium.transforms.logical-router.key.field.regex": "/[0-9]/[a-z]", + "debezium.transforms.logical-router.topic.replacement": "new_test", + "debezium.transforms.logical-router.key.field.replacement": "new_value" + }, + "description": "", + "name": "logical-router", + "schema": "schema321", + "type": "io.debezium.transforms.ByLogicalTableRouter", + "vaults": [] + }, + { + "id": 3, + "config": { + "debezium.transforms.mongo-eventRouter.collection.field.event.key": "new_id", + "debezium.transforms.mongo-eventRouter.collection.field.event.schema.version": "user_set" + }, + "description": "", + "name": "mongo-eventRouter", + "schema": "schema321", + "type": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter", + "vaults": [] + }, + { + "id": 9, + "config": { + "debezium.transforms.filter-new-transfrom.condition": "ramdon" + }, + "description": "Config", + "name": "filter-new-transfrom", + "schema": "schema321", + "type": "io.debezium.transforms.Filter", + "vaults": [] + }, + { + "id": 7, + "config": { + "debezium.transforms.filter-id-transform.language": "groovy", + "debezium.transforms.filter-id-transform.condition": "value.before.id==2" + }, + "description": "configured from ui", + "name": "filter-id-transform", + "schema": "schema321", + "type": "io.debezium.transforms.Filter", + "vaults": [] + } + ] \ No newline at end of file diff --git a/src/appLayout/AppSideNavigation.test.tsx b/src/appLayout/AppSideNavigation.test.tsx index 75262934d..1c00d508e 100644 --- a/src/appLayout/AppSideNavigation.test.tsx +++ b/src/appLayout/AppSideNavigation.test.tsx @@ -31,7 +31,7 @@ test("renders the side navigation Expanded", () => { const expectedTexts = [ "Pipeline", "Source", - "Transforms", + "Transform", "Destination", "Vaults", ]; diff --git a/src/pages/Transforms/Teansformations.test.tsx b/src/pages/Transforms/Teansformations.test.tsx deleted file mode 100644 index a88f83f92..000000000 --- a/src/pages/Transforms/Teansformations.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { Transforms } from "./Transforms"; -import { MemoryRouter } from "react-router-dom"; -import { useData } from "../../appLayout/AppContext"; - -vi.mock("react-router-dom", async () => { - const actual = await vi.importActual("react-router-dom"); - return { - ...actual, - useNavigate: () => vi.fn(), - }; -}); - -vi.mock("../../appLayout/AppContext", () => ({ - useData: vi.fn(), -})); - -describe("Transforms Component", () => { - beforeEach(() => { - vi.mocked(useData).mockReturnValue({ - darkMode: false, - navigationCollapsed: false, - setDarkMode: vi.fn(), - updateNavigationCollapsed: vi.fn(), - }); - }); - - it("renders the Transforms component with correct content", () => { - render( - - - - ); - - expect(screen.getByAltText("Coming Soon")).toBeInTheDocument(); - expect(screen.getByText("No transforms available")).toBeInTheDocument(); - expect(screen.getByText("Add Transforms")).toBeInTheDocument(); - expect(screen.getByText("Go to source")).toBeInTheDocument(); - expect(screen.getByText("Go to destination")).toBeInTheDocument(); - expect(screen.getByText("Go to pipeline")).toBeInTheDocument(); - }); -}); diff --git a/src/pages/Transforms/Transforms.test.tsx b/src/pages/Transforms/Transforms.test.tsx new file mode 100644 index 000000000..e9157a064 --- /dev/null +++ b/src/pages/Transforms/Transforms.test.tsx @@ -0,0 +1,138 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { Transforms } from "./Transforms"; +import transformsMock from "../../__mocks__/data/TransformsData.json"; +import { useQuery } from "react-query"; +import { useDeleteData } from "src/apis"; +import { useNotification } from "@appContext/AppNotificationContext"; + +vi.mock("react-router-dom", () => ({ + useNavigate: () => vi.fn(), +})); + +vi.mock("react-query", async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + useQuery: vi.fn(), + QueryClient: vi.fn().mockImplementation(() => ({ + invalidateQueries: vi.fn(), + })), + }; +}); + +vi.mock("src/apis", () => ({ + useDeleteData: vi.fn(), +})); + +vi.mock("../../appLayout/AppNotificationContext", () => ({ + useNotification: vi.fn(), +})); + +describe("Transforms", () => { + const mockTransforms = transformsMock; + // const mockPipelines = pipelinesMock; + + beforeEach(() => { + vi.clearAllMocks(); + + vi.mocked(useQuery).mockImplementation((key) => { + if (key === "transforms") { + return { + data: mockTransforms, + error: null, + isLoading: false, + } as any; + } + return { data: undefined, error: null, isLoading: false } as any; + }); + + vi.mocked(useDeleteData).mockReturnValue({ + mutate: vi.fn(), + } as any); + + vi.mocked(useNotification).mockReturnValue({ + addNotification: vi.fn(), + } as any); + }); + + it("displays loading state when data is being fetched", () => { + vi.mocked(useQuery).mockReturnValue({ + data: undefined, + error: null, + isLoading: true, + } as any); + + render(); + + expect(screen.getByText("Loading...")).toBeInTheDocument(); + }); + + it("displays error message when API fails", async () => { + // Mock the useQuery hook to simulate an API failure for transforms + vi.mocked(useQuery).mockImplementation((key) => { + if (key === "transforms") { + return { + data: undefined, + error: new Error("Failed to fetch transforms"), + isLoading: false, + } as any; + } + // Keep the original implementation for other queries + return { data: undefined, error: null, isLoading: false } as any; + }); + + render(); + + await waitFor(() => { + expect( + screen.getByText("Error: Failed to fetch transforms") + ).toBeInTheDocument(); + }); + }); + + it("renders transform when data is loaded", async () => { + render(); + + await waitFor(() => { + expect(screen.getByText("extract-new-record")).toBeInTheDocument(); + expect(screen.getByText("7 Items")).toBeInTheDocument(); + }); + }); + + it("filters transform based on search input", async () => { + render(); + + const searchInput = screen.getByPlaceholderText("Find by name"); + fireEvent.change(searchInput, { target: { value: "filter" } }); + + await waitFor(() => { + expect(screen.getByText("filter-transform")).toBeInTheDocument(); + expect(screen.getByText("3 Items")).toBeInTheDocument(); + }); + }); + + it("filters transform for unknown search input and clears search", async () => { + render(); + + const searchInput = screen.getByPlaceholderText("Find by name"); + fireEvent.change(searchInput, { target: { value: "xxx" } }); + + await waitFor(() => { + expect(screen.getByText("0 Items")).toBeInTheDocument(); + expect( + screen.getByText("No matching transform is present.") + ).toBeInTheDocument(); + expect(screen.getByText("Clear search")).toBeInTheDocument(); + }); + + const clearButton = screen.getByText("Clear search"); + fireEvent.click(clearButton); + + await waitFor(() => { + expect(searchInput).toHaveValue(""); + expect(screen.getByText("7 Items")).toBeInTheDocument(); + }); + }); +}); diff --git a/src/pages/Transforms/Transforms.tsx b/src/pages/Transforms/Transforms.tsx index 38680038e..860771668 100644 --- a/src/pages/Transforms/Transforms.tsx +++ b/src/pages/Transforms/Transforms.tsx @@ -86,7 +86,7 @@ const Transforms: React.FunctionComponent = () => { error, isLoading: isTransformsLoading, } = useQuery( - "destinations", + "transforms", () => fetchData(`${API_URL}/api/transforms`), { refetchInterval: 7000, @@ -175,10 +175,6 @@ const Transforms: React.FunctionComponent = () => { return ( <> - {/* - - */} - <> {error ? ( @@ -307,7 +303,7 @@ const Transforms: React.FunctionComponent = () => {