diff --git a/debezium-platform-stage/src/__fixtures__/catalog.json b/debezium-platform-stage/src/__fixtures__/catalog.json index b9a610d9..b2188b42 100644 --- a/debezium-platform-stage/src/__fixtures__/catalog.json +++ b/debezium-platform-stage/src/__fixtures__/catalog.json @@ -1,250 +1,1382 @@ { "schemaVersion": "1.0", "build": { - "version": "3.5.0-SNAPSHOT", - "timestamp": "2026-03-31T04:21:50Z", - "sourceRepository": "debezium/debezium", - "sourceCommit": "3b7009d972", - "sourceBranch": "main" + "version": "3.6.0-SNAPSHOT", + "timestamp": "2026-05-05T02:23:13Z", + "sourceRepository": "debezium/debezium", + "sourceCommit": "a2619598e4", + "sourceBranch": "main" }, "components": { - "converter": [ - { - "class": "io.debezium.converters.BinaryDataConverter", - "name": "io.debezium.converters.BinaryDataConverter", - "description": "io.debezium.converters.BinaryDataConverter", - "descriptor": "converter/io.debezium.converters.BinaryDataConverter.json" - }, - { - "class": "io.debezium.converters.ByteArrayConverter", - "name": "io.debezium.converters.ByteArrayConverter", - "description": "io.debezium.converters.ByteArrayConverter", - "descriptor": "converter/io.debezium.converters.ByteArrayConverter.json" - }, - { - "class": "io.debezium.converters.CloudEventsConverter", - "name": "io.debezium.converters.CloudEventsConverter", - "description": "io.debezium.converters.CloudEventsConverter", - "descriptor": "converter/io.debezium.converters.CloudEventsConverter.json" - } - ], - "custom-converter": [ - { - "class": "io.debezium.connector.binlog.converters.JdbcSinkDataTypesConverter", - "name": "io.debezium.connector.binlog.converters.JdbcSinkDataTypesConverter", - "description": "io.debezium.connector.binlog.converters.JdbcSinkDataTypesConverter", - "descriptor": "custom-converter/io.debezium.connector.binlog.converters.JdbcSinkDataTypesConverter.json" - }, - { - "class": "io.debezium.connector.binlog.converters.TinyIntOneToBooleanConverter", - "name": "io.debezium.connector.binlog.converters.TinyIntOneToBooleanConverter", - "description": "io.debezium.connector.binlog.converters.TinyIntOneToBooleanConverter", - "descriptor": "custom-converter/io.debezium.connector.binlog.converters.TinyIntOneToBooleanConverter.json" - }, - { - "class": "io.debezium.connector.oracle.converters.NumberOneToBooleanConverter", - "name": "io.debezium.connector.oracle.converters.NumberOneToBooleanConverter", - "description": "io.debezium.connector.oracle.converters.NumberOneToBooleanConverter", - "descriptor": "custom-converter/io.debezium.connector.oracle.converters.NumberOneToBooleanConverter.json" - }, - { - "class": "io.debezium.connector.oracle.converters.NumberToZeroScaleConverter", - "name": "io.debezium.connector.oracle.converters.NumberToZeroScaleConverter", - "description": "io.debezium.connector.oracle.converters.NumberToZeroScaleConverter", - "descriptor": "custom-converter/io.debezium.connector.oracle.converters.NumberToZeroScaleConverter.json" - }, - { - "class": "io.debezium.connector.oracle.converters.RawToStringConverter", - "name": "io.debezium.connector.oracle.converters.RawToStringConverter", - "description": "io.debezium.connector.oracle.converters.RawToStringConverter", - "descriptor": "custom-converter/io.debezium.connector.oracle.converters.RawToStringConverter.json" - } - ], - "sink-connector": [ - { - "class": "io.debezium.connector.jdbc.JdbcSinkConnector", - "name": "io.debezium.connector.jdbc.JdbcSinkConnector", - "description": "io.debezium.connector.jdbc.JdbcSinkConnector", - "descriptor": "sink-connector/io.debezium.connector.jdbc.JdbcSinkConnector.json" - }, - { - "class": "io.debezium.connector.mongodb.MongoDbSinkConnector", - "name": "io.debezium.connector.mongodb.MongoDbSinkConnector", - "description": "io.debezium.connector.mongodb.MongoDbSinkConnector", - "descriptor": "sink-connector/io.debezium.connector.mongodb.MongoDbSinkConnector.json" - } - ], - "source-connector": [ - { - "class": "io.debezium.connector.db2.Db2Connector", - "name": "io.debezium.connector.db2.Db2Connector", - "description": "io.debezium.connector.db2.Db2Connector", - "descriptor": "source-connector/io.debezium.connector.db2.Db2Connector.json" - }, - { - "class": "io.debezium.connector.mariadb.MariaDbConnector", - "name": "Debezium MariaDB Connector", - "description": "Debezium MariaDB Connector", - "descriptor": "source-connector/io.debezium.connector.mariadb.MariaDbConnector.json" - }, - { - "class": "io.debezium.connector.mongodb.MongoDbConnector", - "name": "Debezium MongoDB Connector", - "description": "Debezium MongoDB Connector", - "descriptor": "source-connector/io.debezium.connector.mongodb.MongoDbConnector.json" - }, - { - "class": "io.debezium.connector.mysql.MySqlConnector", - "name": "Debezium MySQL Connector", - "description": "Debezium MySQL Connector", - "descriptor": "source-connector/io.debezium.connector.mysql.MySqlConnector.json" - }, - { - "class": "io.debezium.connector.oracle.OracleConnector", - "name": "Debezium Oracle Connector", - "description": "Debezium Oracle Connector", - "descriptor": "source-connector/io.debezium.connector.oracle.OracleConnector.json" - }, - { - "class": "io.debezium.connector.postgresql.PostgresConnector", - "name": "Debezium PostgreSQL Connector", - "description": "Debezium PostgreSQL Connector", - "descriptor": "source-connector/io.debezium.connector.postgresql.PostgresConnector.json" - }, - { - "class": "io.debezium.connector.sqlserver.SqlServerConnector", - "name": "Debezium SQLServer Connector", - "description": "Debezium SQLServer Connector", - "descriptor": "source-connector/io.debezium.connector.sqlserver.SqlServerConnector.json" - } - ], - "transformation": [ - { - "class": "io.debezium.connector.jdbc.transforms.CollectionNameTransformation", - "name": "io.debezium.connector.jdbc.transforms.CollectionNameTransformation", - "description": "io.debezium.connector.jdbc.transforms.CollectionNameTransformation", - "descriptor": "transformation/io.debezium.connector.jdbc.transforms.CollectionNameTransformation.json" - }, - { - "class": "io.debezium.connector.jdbc.transforms.FieldNameTransformation", - "name": "io.debezium.connector.jdbc.transforms.FieldNameTransformation", - "description": "io.debezium.connector.jdbc.transforms.FieldNameTransformation", - "descriptor": "transformation/io.debezium.connector.jdbc.transforms.FieldNameTransformation.json" - }, - { - "class": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState", - "name": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState", - "description": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState", - "descriptor": "transformation/io.debezium.connector.mongodb.transforms.ExtractNewDocumentState.json" - }, - { - "class": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter", - "name": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter", - "description": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter", - "descriptor": "transformation/io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter.json" - }, - { - "class": "io.debezium.connector.mysql.transforms.ReadToInsertEvent", - "name": "io.debezium.connector.mysql.transforms.ReadToInsertEvent", - "description": "io.debezium.connector.mysql.transforms.ReadToInsertEvent", - "descriptor": "transformation/io.debezium.connector.mysql.transforms.ReadToInsertEvent.json" - }, - { - "class": "io.debezium.connector.postgresql.transforms.DecodeLogicalDecodingMessageContent", - "name": "io.debezium.connector.postgresql.transforms.DecodeLogicalDecodingMessageContent", - "description": "io.debezium.connector.postgresql.transforms.DecodeLogicalDecodingMessageContent", - "descriptor": "transformation/io.debezium.connector.postgresql.transforms.DecodeLogicalDecodingMessageContent.json" - }, - { - "class": "io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb", - "name": "io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb", - "description": "io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb", - "descriptor": "transformation/io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb.json" - }, - { - "class": "io.debezium.transforms.ByLogicalTableRouter", - "name": "io.debezium.transforms.ByLogicalTableRouter", - "description": "io.debezium.transforms.ByLogicalTableRouter", - "descriptor": "transformation/io.debezium.transforms.ByLogicalTableRouter.json" - }, - { - "class": "io.debezium.transforms.ExtractChangedRecordState", - "name": "io.debezium.transforms.ExtractChangedRecordState", - "description": "io.debezium.transforms.ExtractChangedRecordState", - "descriptor": "transformation/io.debezium.transforms.ExtractChangedRecordState.json" - }, - { - "class": "io.debezium.transforms.ExtractNewRecordState", - "name": "io.debezium.transforms.ExtractNewRecordState", - "description": "io.debezium.transforms.ExtractNewRecordState", - "descriptor": "transformation/io.debezium.transforms.ExtractNewRecordState.json" - }, - { - "class": "io.debezium.transforms.ExtractSchemaToNewRecord", - "name": "io.debezium.transforms.ExtractSchemaToNewRecord", - "description": "io.debezium.transforms.ExtractSchemaToNewRecord", - "descriptor": "transformation/io.debezium.transforms.ExtractSchemaToNewRecord.json" - }, - { - "class": "io.debezium.transforms.GeometryFormatTransformer", - "name": "io.debezium.transforms.GeometryFormatTransformer", - "description": "io.debezium.transforms.GeometryFormatTransformer", - "descriptor": "transformation/io.debezium.transforms.GeometryFormatTransformer.json" - }, - { - "class": "io.debezium.transforms.HeaderToValue", - "name": "io.debezium.transforms.HeaderToValue", - "description": "io.debezium.transforms.HeaderToValue", - "descriptor": "transformation/io.debezium.transforms.HeaderToValue.json" - }, - { - "class": "io.debezium.transforms.SchemaChangeEventFilter", - "name": "io.debezium.transforms.SchemaChangeEventFilter", - "description": "io.debezium.transforms.SchemaChangeEventFilter", - "descriptor": "transformation/io.debezium.transforms.SchemaChangeEventFilter.json" - }, - { - "class": "io.debezium.transforms.SwapGeometryCoordinates", - "name": "io.debezium.transforms.SwapGeometryCoordinates", - "description": "io.debezium.transforms.SwapGeometryCoordinates", - "descriptor": "transformation/io.debezium.transforms.SwapGeometryCoordinates.json" - }, - { - "class": "io.debezium.transforms.TimezoneConverter", - "name": "io.debezium.transforms.TimezoneConverter", - "description": "io.debezium.transforms.TimezoneConverter", - "descriptor": "transformation/io.debezium.transforms.TimezoneConverter.json" - }, - { - "class": "io.debezium.transforms.VectorToJsonConverter", - "name": "io.debezium.transforms.VectorToJsonConverter", - "description": "io.debezium.transforms.VectorToJsonConverter", - "descriptor": "transformation/io.debezium.transforms.VectorToJsonConverter.json" - }, - { - "class": "io.debezium.transforms.openlineage.OpenLineage", - "name": "io.debezium.transforms.openlineage.OpenLineage", - "description": "io.debezium.transforms.openlineage.OpenLineage", - "descriptor": "transformation/io.debezium.transforms.openlineage.OpenLineage.json" - }, - { - "class": "io.debezium.transforms.outbox.EventRouter", - "name": "io.debezium.transforms.outbox.EventRouter", - "description": "io.debezium.transforms.outbox.EventRouter", - "descriptor": "transformation/io.debezium.transforms.outbox.EventRouter.json" - }, - { - "class": "io.debezium.transforms.partitions.PartitionRouting", - "name": "io.debezium.transforms.partitions.PartitionRouting", - "description": "io.debezium.transforms.partitions.PartitionRouting", - "descriptor": "transformation/io.debezium.transforms.partitions.PartitionRouting.json" - }, - { - "class": "io.debezium.transforms.tracing.ActivateTracingSpan", - "name": "io.debezium.transforms.tracing.ActivateTracingSpan", - "description": "io.debezium.transforms.tracing.ActivateTracingSpan", - "descriptor": "transformation/io.debezium.transforms.tracing.ActivateTracingSpan.json" - } - ] + "converter": [ + { + "class": "io.debezium.converters.BinaryDataConverter", + "name": "io.debezium.converters.BinaryDataConverter", + "description": "io.debezium.converters.BinaryDataConverter", + "descriptor": "converter/io.debezium.converters.BinaryDataConverter.json" + }, + { + "class": "io.debezium.converters.ByteArrayConverter", + "name": "io.debezium.converters.ByteArrayConverter", + "description": "io.debezium.converters.ByteArrayConverter", + "descriptor": "converter/io.debezium.converters.ByteArrayConverter.json" + }, + { + "class": "io.debezium.converters.CloudEventsConverter", + "name": "io.debezium.converters.CloudEventsConverter", + "description": "io.debezium.converters.CloudEventsConverter", + "descriptor": "converter/io.debezium.converters.CloudEventsConverter.json" + }, + { + "class": "org.apache.kafka.connect.converters.BooleanConverter", + "name": "org.apache.kafka.connect.converters.BooleanConverter", + "description": "org.apache.kafka.connect.converters.BooleanConverter", + "descriptor": "converter/org.apache.kafka.connect.converters.BooleanConverter.json" + }, + { + "class": "org.apache.kafka.connect.converters.ByteArrayConverter", + "name": "org.apache.kafka.connect.converters.ByteArrayConverter", + "description": "org.apache.kafka.connect.converters.ByteArrayConverter", + "descriptor": "converter/org.apache.kafka.connect.converters.ByteArrayConverter.json" + }, + { + "class": "org.apache.kafka.connect.converters.DoubleConverter", + "name": "org.apache.kafka.connect.converters.DoubleConverter", + "description": "org.apache.kafka.connect.converters.DoubleConverter", + "descriptor": "converter/org.apache.kafka.connect.converters.DoubleConverter.json" + }, + { + "class": "org.apache.kafka.connect.converters.FloatConverter", + "name": "org.apache.kafka.connect.converters.FloatConverter", + "description": "org.apache.kafka.connect.converters.FloatConverter", + "descriptor": "converter/org.apache.kafka.connect.converters.FloatConverter.json" + }, + { + "class": "org.apache.kafka.connect.converters.IntegerConverter", + "name": "org.apache.kafka.connect.converters.IntegerConverter", + "description": "org.apache.kafka.connect.converters.IntegerConverter", + "descriptor": "converter/org.apache.kafka.connect.converters.IntegerConverter.json" + }, + { + "class": "org.apache.kafka.connect.converters.LongConverter", + "name": "org.apache.kafka.connect.converters.LongConverter", + "description": "org.apache.kafka.connect.converters.LongConverter", + "descriptor": "converter/org.apache.kafka.connect.converters.LongConverter.json" + }, + { + "class": "org.apache.kafka.connect.converters.ShortConverter", + "name": "org.apache.kafka.connect.converters.ShortConverter", + "description": "org.apache.kafka.connect.converters.ShortConverter", + "descriptor": "converter/org.apache.kafka.connect.converters.ShortConverter.json" + }, + { + "class": "org.apache.kafka.connect.json.JsonConverter", + "name": "org.apache.kafka.connect.json.JsonConverter", + "description": "org.apache.kafka.connect.json.JsonConverter", + "descriptor": "converter/org.apache.kafka.connect.json.JsonConverter.json" + }, + { + "class": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "name": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "description": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.SimpleHeaderConverter.json" + }, + { + "class": "org.apache.kafka.connect.storage.SimpleHeaderConverterHuggingFace", + "name": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "description": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.SimpleHeaderConverterHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.storage.SimpleHeaderConverterMinilm", + "name": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "description": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.SimpleHeaderConverterMinilm.json" + }, + { + "class": "org.apache.kafka.connect.storage.SimpleHeaderConverterOllama", + "name": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "description": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.SimpleHeaderConverterOllama.json" + }, + { + "class": "org.apache.kafka.connect.storage.SimpleHeaderConverterVoyageAi", + "name": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "description": "org.apache.kafka.connect.storage.SimpleHeaderConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.SimpleHeaderConverterVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.storage.StringConverter", + "name": "org.apache.kafka.connect.storage.StringConverter", + "description": "org.apache.kafka.connect.storage.StringConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.StringConverter.json" + }, + { + "class": "org.apache.kafka.connect.storage.StringConverterHuggingFace", + "name": "org.apache.kafka.connect.storage.StringConverter", + "description": "org.apache.kafka.connect.storage.StringConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.StringConverterHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.storage.StringConverterMinilm", + "name": "org.apache.kafka.connect.storage.StringConverter", + "description": "org.apache.kafka.connect.storage.StringConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.StringConverterMinilm.json" + }, + { + "class": "org.apache.kafka.connect.storage.StringConverterOllama", + "name": "org.apache.kafka.connect.storage.StringConverter", + "description": "org.apache.kafka.connect.storage.StringConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.StringConverterOllama.json" + }, + { + "class": "org.apache.kafka.connect.storage.StringConverterVoyageAi", + "name": "org.apache.kafka.connect.storage.StringConverter", + "description": "org.apache.kafka.connect.storage.StringConverter", + "descriptor": "converter/org.apache.kafka.connect.storage.StringConverterVoyageAi.json" + } + ], + "custom-converter": [ + { + "class": "io.debezium.connector.binlog.converters.JdbcSinkDataTypesConverter", + "name": "io.debezium.connector.binlog.converters.JdbcSinkDataTypesConverter", + "description": "io.debezium.connector.binlog.converters.JdbcSinkDataTypesConverter", + "descriptor": "custom-converter/io.debezium.connector.binlog.converters.JdbcSinkDataTypesConverter.json" + }, + { + "class": "io.debezium.connector.binlog.converters.TinyIntOneToBooleanConverter", + "name": "io.debezium.connector.binlog.converters.TinyIntOneToBooleanConverter", + "description": "io.debezium.connector.binlog.converters.TinyIntOneToBooleanConverter", + "descriptor": "custom-converter/io.debezium.connector.binlog.converters.TinyIntOneToBooleanConverter.json" + }, + { + "class": "io.debezium.connector.oracle.converters.NumberOneToBooleanConverter", + "name": "io.debezium.connector.oracle.converters.NumberOneToBooleanConverter", + "description": "io.debezium.connector.oracle.converters.NumberOneToBooleanConverter", + "descriptor": "custom-converter/io.debezium.connector.oracle.converters.NumberOneToBooleanConverter.json" + }, + { + "class": "io.debezium.connector.oracle.converters.NumberToZeroScaleConverter", + "name": "io.debezium.connector.oracle.converters.NumberToZeroScaleConverter", + "description": "io.debezium.connector.oracle.converters.NumberToZeroScaleConverter", + "descriptor": "custom-converter/io.debezium.connector.oracle.converters.NumberToZeroScaleConverter.json" + }, + { + "class": "io.debezium.connector.oracle.converters.RawToStringConverter", + "name": "io.debezium.connector.oracle.converters.RawToStringConverter", + "description": "io.debezium.connector.oracle.converters.RawToStringConverter", + "descriptor": "custom-converter/io.debezium.connector.oracle.converters.RawToStringConverter.json" + } + ], + "predicate": [ + { + "class": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "name": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "description": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.HasHeaderKey.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.HasHeaderKeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "description": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.HasHeaderKeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.HasHeaderKeyMinilm", + "name": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "description": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.HasHeaderKeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.HasHeaderKeyOllama", + "name": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "description": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.HasHeaderKeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.HasHeaderKeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "description": "org.apache.kafka.connect.transforms.predicates.HasHeaderKey", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.HasHeaderKeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "name": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "description": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.RecordIsTombstone.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstoneHuggingFace", + "name": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "description": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.RecordIsTombstoneHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstoneMinilm", + "name": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "description": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.RecordIsTombstoneMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstoneOllama", + "name": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "description": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.RecordIsTombstoneOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstoneVoyageAi", + "name": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "description": "org.apache.kafka.connect.transforms.predicates.RecordIsTombstone", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.RecordIsTombstoneVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "name": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "description": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.TopicNameMatches.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.TopicNameMatchesHuggingFace", + "name": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "description": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.TopicNameMatchesHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.TopicNameMatchesMinilm", + "name": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "description": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.TopicNameMatchesMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.TopicNameMatchesOllama", + "name": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "description": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.TopicNameMatchesOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.predicates.TopicNameMatchesVoyageAi", + "name": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "description": "org.apache.kafka.connect.transforms.predicates.TopicNameMatches", + "descriptor": "predicate/org.apache.kafka.connect.transforms.predicates.TopicNameMatchesVoyageAi.json" + } + ], + "server-sink": [ + { + "class": "io.debezium.server.eventhubs.EventHubsChangeConsumer", + "name": "io.debezium.server.eventhubs.EventHubsChangeConsumer", + "description": "io.debezium.server.eventhubs.EventHubsChangeConsumer", + "descriptor": "server-sink/io.debezium.server.eventhubs.EventHubsChangeConsumer.json" + }, + { + "class": "io.debezium.server.http.HttpChangeConsumer", + "name": "io.debezium.server.http.HttpChangeConsumer", + "description": "io.debezium.server.http.HttpChangeConsumer", + "descriptor": "server-sink/io.debezium.server.http.HttpChangeConsumer.json" + }, + { + "class": "io.debezium.server.infinispan.InfinispanSinkConsumer", + "name": "io.debezium.server.infinispan.InfinispanSinkConsumer", + "description": "io.debezium.server.infinispan.InfinispanSinkConsumer", + "descriptor": "server-sink/io.debezium.server.infinispan.InfinispanSinkConsumer.json" + }, + { + "class": "io.debezium.server.instructlab.InstructLabSinkConsumer", + "name": "io.debezium.server.instructlab.InstructLabSinkConsumer", + "description": "io.debezium.server.instructlab.InstructLabSinkConsumer", + "descriptor": "server-sink/io.debezium.server.instructlab.InstructLabSinkConsumer.json" + }, + { + "class": "io.debezium.server.kafka.KafkaChangeConsumer", + "name": "io.debezium.server.kafka.KafkaChangeConsumer", + "description": "io.debezium.server.kafka.KafkaChangeConsumer", + "descriptor": "server-sink/io.debezium.server.kafka.KafkaChangeConsumer.json" + }, + { + "class": "io.debezium.server.kinesis.KinesisChangeConsumer", + "name": "io.debezium.server.kinesis.KinesisChangeConsumer", + "description": "io.debezium.server.kinesis.KinesisChangeConsumer", + "descriptor": "server-sink/io.debezium.server.kinesis.KinesisChangeConsumer.json" + }, + { + "class": "io.debezium.server.milvus.MilvusChangeConsumer", + "name": "io.debezium.server.milvus.MilvusChangeConsumer", + "description": "io.debezium.server.milvus.MilvusChangeConsumer", + "descriptor": "server-sink/io.debezium.server.milvus.MilvusChangeConsumer.json" + }, + { + "class": "io.debezium.server.nats.jetstream.NatsJetStreamChangeConsumer", + "name": "io.debezium.server.nats.jetstream.NatsJetStreamChangeConsumer", + "description": "io.debezium.server.nats.jetstream.NatsJetStreamChangeConsumer", + "descriptor": "server-sink/io.debezium.server.nats.jetstream.NatsJetStreamChangeConsumer.json" + }, + { + "class": "io.debezium.server.nats.streaming.NatsStreamingChangeConsumer", + "name": "io.debezium.server.nats.streaming.NatsStreamingChangeConsumer", + "description": "io.debezium.server.nats.streaming.NatsStreamingChangeConsumer", + "descriptor": "server-sink/io.debezium.server.nats.streaming.NatsStreamingChangeConsumer.json" + }, + { + "class": "io.debezium.server.pravega.PravegaChangeConsumer", + "name": "io.debezium.server.pravega.PravegaChangeConsumer", + "description": "io.debezium.server.pravega.PravegaChangeConsumer", + "descriptor": "server-sink/io.debezium.server.pravega.PravegaChangeConsumer.json" + }, + { + "class": "io.debezium.server.pubsub.PubSubChangeConsumer", + "name": "io.debezium.server.pubsub.PubSubChangeConsumer", + "description": "io.debezium.server.pubsub.PubSubChangeConsumer", + "descriptor": "server-sink/io.debezium.server.pubsub.PubSubChangeConsumer.json" + }, + { + "class": "io.debezium.server.pubsub.PubSubLiteChangeConsumer", + "name": "io.debezium.server.pubsub.PubSubLiteChangeConsumer", + "description": "io.debezium.server.pubsub.PubSubLiteChangeConsumer", + "descriptor": "server-sink/io.debezium.server.pubsub.PubSubLiteChangeConsumer.json" + }, + { + "class": "io.debezium.server.pulsar.PulsarChangeConsumer", + "name": "io.debezium.server.pulsar.PulsarChangeConsumer", + "description": "io.debezium.server.pulsar.PulsarChangeConsumer", + "descriptor": "server-sink/io.debezium.server.pulsar.PulsarChangeConsumer.json" + }, + { + "class": "io.debezium.server.qdrant.QdrantChangeConsumer", + "name": "io.debezium.server.qdrant.QdrantChangeConsumer", + "description": "io.debezium.server.qdrant.QdrantChangeConsumer", + "descriptor": "server-sink/io.debezium.server.qdrant.QdrantChangeConsumer.json" + }, + { + "class": "io.debezium.server.rabbitmq.RabbitMqStreamChangeConsumer", + "name": "io.debezium.server.rabbitmq.RabbitMqStreamChangeConsumer", + "description": "io.debezium.server.rabbitmq.RabbitMqStreamChangeConsumer", + "descriptor": "server-sink/io.debezium.server.rabbitmq.RabbitMqStreamChangeConsumer.json" + }, + { + "class": "io.debezium.server.rabbitmq.RabbitMqStreamNativeChangeConsumer", + "name": "io.debezium.server.rabbitmq.RabbitMqStreamNativeChangeConsumer", + "description": "io.debezium.server.rabbitmq.RabbitMqStreamNativeChangeConsumer", + "descriptor": "server-sink/io.debezium.server.rabbitmq.RabbitMqStreamNativeChangeConsumer.json" + }, + { + "class": "io.debezium.server.redis.RedisStreamChangeConsumer", + "name": "io.debezium.server.redis.RedisStreamChangeConsumer", + "description": "io.debezium.server.redis.RedisStreamChangeConsumer", + "descriptor": "server-sink/io.debezium.server.redis.RedisStreamChangeConsumer.json" + }, + { + "class": "io.debezium.server.rocketmq.RocketMqChangeConsumer", + "name": "io.debezium.server.rocketmq.RocketMqChangeConsumer", + "description": "io.debezium.server.rocketmq.RocketMqChangeConsumer", + "descriptor": "server-sink/io.debezium.server.rocketmq.RocketMqChangeConsumer.json" + }, + { + "class": "io.debezium.server.sns.SnsChangeConsumer", + "name": "io.debezium.server.sns.SnsChangeConsumer", + "description": "io.debezium.server.sns.SnsChangeConsumer", + "descriptor": "server-sink/io.debezium.server.sns.SnsChangeConsumer.json" + }, + { + "class": "io.debezium.server.sqs.SqsChangeConsumer", + "name": "io.debezium.server.sqs.SqsChangeConsumer", + "description": "io.debezium.server.sqs.SqsChangeConsumer", + "descriptor": "server-sink/io.debezium.server.sqs.SqsChangeConsumer.json" + } + ], + "sink-connector": [ + { + "class": "io.debezium.connector.jdbc.JdbcSinkConnector", + "name": "io.debezium.connector.jdbc.JdbcSinkConnector", + "description": "io.debezium.connector.jdbc.JdbcSinkConnector", + "descriptor": "sink-connector/io.debezium.connector.jdbc.JdbcSinkConnector.json" + }, + { + "class": "io.debezium.connector.mongodb.MongoDbSinkConnector", + "name": "io.debezium.connector.mongodb.MongoDbSinkConnector", + "description": "io.debezium.connector.mongodb.MongoDbSinkConnector", + "descriptor": "sink-connector/io.debezium.connector.mongodb.MongoDbSinkConnector.json" + } + ], + "source-connector": [ + { + "class": "io.debezium.connector.db2.Db2Connector", + "name": "io.debezium.connector.db2.Db2Connector", + "description": "io.debezium.connector.db2.Db2Connector", + "descriptor": "source-connector/io.debezium.connector.db2.Db2Connector.json" + }, + { + "class": "io.debezium.connector.mariadb.MariaDbConnector", + "name": "Debezium MariaDB Connector", + "description": "Debezium MariaDB Connector", + "descriptor": "source-connector/io.debezium.connector.mariadb.MariaDbConnector.json" + }, + { + "class": "io.debezium.connector.mongodb.MongoDbConnector", + "name": "Debezium MongoDB Connector", + "description": "Debezium MongoDB Connector", + "descriptor": "source-connector/io.debezium.connector.mongodb.MongoDbConnector.json" + }, + { + "class": "io.debezium.connector.mysql.MySqlConnector", + "name": "Debezium MySQL Connector", + "description": "Debezium MySQL Connector", + "descriptor": "source-connector/io.debezium.connector.mysql.MySqlConnector.json" + }, + { + "class": "io.debezium.connector.oracle.OracleConnector", + "name": "Debezium Oracle Connector", + "description": "Debezium Oracle Connector", + "descriptor": "source-connector/io.debezium.connector.oracle.OracleConnector.json" + }, + { + "class": "io.debezium.connector.postgresql.PostgresConnector", + "name": "Debezium PostgreSQL Connector", + "description": "Debezium PostgreSQL Connector", + "descriptor": "source-connector/io.debezium.connector.postgresql.PostgresConnector.json" + }, + { + "class": "io.debezium.connector.sqlserver.SqlServerConnector", + "name": "Debezium SQLServer Connector", + "description": "Debezium SQLServer Connector", + "descriptor": "source-connector/io.debezium.connector.sqlserver.SqlServerConnector.json" + } + ], + "transformation": [ + { + "class": "io.debezium.ai.docling.FieldToDocling", + "name": "io.debezium.ai.docling.FieldToDocling", + "description": "io.debezium.ai.docling.FieldToDocling", + "descriptor": "transformation/io.debezium.ai.docling.FieldToDocling.json" + }, + { + "class": "io.debezium.ai.embeddings.FieldToEmbeddingHuggingFace", + "name": "io.debezium.ai.embeddings.FieldToEmbedding", + "description": "io.debezium.ai.embeddings.FieldToEmbedding", + "descriptor": "transformation/io.debezium.ai.embeddings.FieldToEmbeddingHuggingFace.json" + }, + { + "class": "io.debezium.ai.embeddings.FieldToEmbeddingMinilm", + "name": "io.debezium.ai.embeddings.FieldToEmbedding", + "description": "io.debezium.ai.embeddings.FieldToEmbedding", + "descriptor": "transformation/io.debezium.ai.embeddings.FieldToEmbeddingMinilm.json" + }, + { + "class": "io.debezium.ai.embeddings.FieldToEmbeddingOllama", + "name": "io.debezium.ai.embeddings.FieldToEmbedding", + "description": "io.debezium.ai.embeddings.FieldToEmbedding", + "descriptor": "transformation/io.debezium.ai.embeddings.FieldToEmbeddingOllama.json" + }, + { + "class": "io.debezium.ai.embeddings.FieldToEmbeddingVoyageAi", + "name": "io.debezium.ai.embeddings.FieldToEmbedding", + "description": "io.debezium.ai.embeddings.FieldToEmbedding", + "descriptor": "transformation/io.debezium.ai.embeddings.FieldToEmbeddingVoyageAi.json" + }, + { + "class": "io.debezium.connector.jdbc.transforms.CollectionNameTransformation", + "name": "io.debezium.connector.jdbc.transforms.CollectionNameTransformation", + "description": "io.debezium.connector.jdbc.transforms.CollectionNameTransformation", + "descriptor": "transformation/io.debezium.connector.jdbc.transforms.CollectionNameTransformation.json" + }, + { + "class": "io.debezium.connector.jdbc.transforms.FieldNameTransformation", + "name": "io.debezium.connector.jdbc.transforms.FieldNameTransformation", + "description": "io.debezium.connector.jdbc.transforms.FieldNameTransformation", + "descriptor": "transformation/io.debezium.connector.jdbc.transforms.FieldNameTransformation.json" + }, + { + "class": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState", + "name": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState", + "description": "io.debezium.connector.mongodb.transforms.ExtractNewDocumentState", + "descriptor": "transformation/io.debezium.connector.mongodb.transforms.ExtractNewDocumentState.json" + }, + { + "class": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter", + "name": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter", + "description": "io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter", + "descriptor": "transformation/io.debezium.connector.mongodb.transforms.outbox.MongoEventRouter.json" + }, + { + "class": "io.debezium.connector.mysql.transforms.ReadToInsertEvent", + "name": "io.debezium.connector.mysql.transforms.ReadToInsertEvent", + "description": "io.debezium.connector.mysql.transforms.ReadToInsertEvent", + "descriptor": "transformation/io.debezium.connector.mysql.transforms.ReadToInsertEvent.json" + }, + { + "class": "io.debezium.connector.postgresql.transforms.DecodeLogicalDecodingMessageContent", + "name": "io.debezium.connector.postgresql.transforms.DecodeLogicalDecodingMessageContent", + "description": "io.debezium.connector.postgresql.transforms.DecodeLogicalDecodingMessageContent", + "descriptor": "transformation/io.debezium.connector.postgresql.transforms.DecodeLogicalDecodingMessageContent.json" + }, + { + "class": "io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb", + "name": "io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb", + "description": "io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb", + "descriptor": "transformation/io.debezium.connector.postgresql.transforms.timescaledb.TimescaleDb.json" + }, + { + "class": "io.debezium.transforms.ByLogicalTableRouter", + "name": "io.debezium.transforms.ByLogicalTableRouter", + "description": "io.debezium.transforms.ByLogicalTableRouter", + "descriptor": "transformation/io.debezium.transforms.ByLogicalTableRouter.json" + }, + { + "class": "io.debezium.transforms.ExtractChangedRecordState", + "name": "io.debezium.transforms.ExtractChangedRecordState", + "description": "io.debezium.transforms.ExtractChangedRecordState", + "descriptor": "transformation/io.debezium.transforms.ExtractChangedRecordState.json" + }, + { + "class": "io.debezium.transforms.ExtractNewRecordState", + "name": "io.debezium.transforms.ExtractNewRecordState", + "description": "io.debezium.transforms.ExtractNewRecordState", + "descriptor": "transformation/io.debezium.transforms.ExtractNewRecordState.json" + }, + { + "class": "io.debezium.transforms.ExtractSchemaToNewRecord", + "name": "io.debezium.transforms.ExtractSchemaToNewRecord", + "description": "io.debezium.transforms.ExtractSchemaToNewRecord", + "descriptor": "transformation/io.debezium.transforms.ExtractSchemaToNewRecord.json" + }, + { + "class": "io.debezium.transforms.GeometryFormatTransformer", + "name": "io.debezium.transforms.GeometryFormatTransformer", + "description": "io.debezium.transforms.GeometryFormatTransformer", + "descriptor": "transformation/io.debezium.transforms.GeometryFormatTransformer.json" + }, + { + "class": "io.debezium.transforms.HeaderToValue", + "name": "io.debezium.transforms.HeaderToValue", + "description": "io.debezium.transforms.HeaderToValue", + "descriptor": "transformation/io.debezium.transforms.HeaderToValue.json" + }, + { + "class": "io.debezium.transforms.SchemaChangeEventFilter", + "name": "io.debezium.transforms.SchemaChangeEventFilter", + "description": "io.debezium.transforms.SchemaChangeEventFilter", + "descriptor": "transformation/io.debezium.transforms.SchemaChangeEventFilter.json" + }, + { + "class": "io.debezium.transforms.SwapGeometryCoordinates", + "name": "io.debezium.transforms.SwapGeometryCoordinates", + "description": "io.debezium.transforms.SwapGeometryCoordinates", + "descriptor": "transformation/io.debezium.transforms.SwapGeometryCoordinates.json" + }, + { + "class": "io.debezium.transforms.TimezoneConverter", + "name": "io.debezium.transforms.TimezoneConverter", + "description": "io.debezium.transforms.TimezoneConverter", + "descriptor": "transformation/io.debezium.transforms.TimezoneConverter.json" + }, + { + "class": "io.debezium.transforms.VectorToJsonConverter", + "name": "io.debezium.transforms.VectorToJsonConverter", + "description": "io.debezium.transforms.VectorToJsonConverter", + "descriptor": "transformation/io.debezium.transforms.VectorToJsonConverter.json" + }, + { + "class": "io.debezium.transforms.openlineage.OpenLineage", + "name": "io.debezium.transforms.openlineage.OpenLineage", + "description": "io.debezium.transforms.openlineage.OpenLineage", + "descriptor": "transformation/io.debezium.transforms.openlineage.OpenLineage.json" + }, + { + "class": "io.debezium.transforms.outbox.EventRouter", + "name": "io.debezium.transforms.outbox.EventRouter", + "description": "io.debezium.transforms.outbox.EventRouter", + "descriptor": "transformation/io.debezium.transforms.outbox.EventRouter.json" + }, + { + "class": "io.debezium.transforms.partitions.PartitionRouting", + "name": "io.debezium.transforms.partitions.PartitionRouting", + "description": "io.debezium.transforms.partitions.PartitionRouting", + "descriptor": "transformation/io.debezium.transforms.partitions.PartitionRouting.json" + }, + { + "class": "io.debezium.transforms.tracing.ActivateTracingSpan", + "name": "io.debezium.transforms.tracing.ActivateTracingSpan", + "description": "io.debezium.transforms.tracing.ActivateTracingSpan", + "descriptor": "transformation/io.debezium.transforms.tracing.ActivateTracingSpan.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$Key", + "name": "org.apache.kafka.connect.transforms.Cast$Key", + "description": "org.apache.kafka.connect.transforms.Cast$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.Cast$Key", + "description": "org.apache.kafka.connect.transforms.Cast$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.Cast$Key", + "description": "org.apache.kafka.connect.transforms.Cast$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$KeyOllama", + "name": "org.apache.kafka.connect.transforms.Cast$Key", + "description": "org.apache.kafka.connect.transforms.Cast$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.Cast$Key", + "description": "org.apache.kafka.connect.transforms.Cast$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$Value", + "name": "org.apache.kafka.connect.transforms.Cast$Value", + "description": "org.apache.kafka.connect.transforms.Cast$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.Cast$Value", + "description": "org.apache.kafka.connect.transforms.Cast$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.Cast$Value", + "description": "org.apache.kafka.connect.transforms.Cast$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$ValueOllama", + "name": "org.apache.kafka.connect.transforms.Cast$Value", + "description": "org.apache.kafka.connect.transforms.Cast$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Cast$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.Cast$Value", + "description": "org.apache.kafka.connect.transforms.Cast$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Cast$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.DropHeaders", + "name": "org.apache.kafka.connect.transforms.DropHeaders", + "description": "org.apache.kafka.connect.transforms.DropHeaders", + "descriptor": "transformation/org.apache.kafka.connect.transforms.DropHeaders.json" + }, + { + "class": "org.apache.kafka.connect.transforms.DropHeadersHuggingFace", + "name": "org.apache.kafka.connect.transforms.DropHeaders", + "description": "org.apache.kafka.connect.transforms.DropHeaders", + "descriptor": "transformation/org.apache.kafka.connect.transforms.DropHeadersHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.DropHeadersMinilm", + "name": "org.apache.kafka.connect.transforms.DropHeaders", + "description": "org.apache.kafka.connect.transforms.DropHeaders", + "descriptor": "transformation/org.apache.kafka.connect.transforms.DropHeadersMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.DropHeadersOllama", + "name": "org.apache.kafka.connect.transforms.DropHeaders", + "description": "org.apache.kafka.connect.transforms.DropHeaders", + "descriptor": "transformation/org.apache.kafka.connect.transforms.DropHeadersOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.DropHeadersVoyageAi", + "name": "org.apache.kafka.connect.transforms.DropHeaders", + "description": "org.apache.kafka.connect.transforms.DropHeaders", + "descriptor": "transformation/org.apache.kafka.connect.transforms.DropHeadersVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$Key", + "name": "org.apache.kafka.connect.transforms.ExtractField$Key", + "description": "org.apache.kafka.connect.transforms.ExtractField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.ExtractField$Key", + "description": "org.apache.kafka.connect.transforms.ExtractField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.ExtractField$Key", + "description": "org.apache.kafka.connect.transforms.ExtractField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$KeyOllama", + "name": "org.apache.kafka.connect.transforms.ExtractField$Key", + "description": "org.apache.kafka.connect.transforms.ExtractField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.ExtractField$Key", + "description": "org.apache.kafka.connect.transforms.ExtractField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$Value", + "name": "org.apache.kafka.connect.transforms.ExtractField$Value", + "description": "org.apache.kafka.connect.transforms.ExtractField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.ExtractField$Value", + "description": "org.apache.kafka.connect.transforms.ExtractField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.ExtractField$Value", + "description": "org.apache.kafka.connect.transforms.ExtractField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$ValueOllama", + "name": "org.apache.kafka.connect.transforms.ExtractField$Value", + "description": "org.apache.kafka.connect.transforms.ExtractField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ExtractField$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.ExtractField$Value", + "description": "org.apache.kafka.connect.transforms.ExtractField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ExtractField$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Filter", + "name": "org.apache.kafka.connect.transforms.Filter", + "description": "org.apache.kafka.connect.transforms.Filter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Filter.json" + }, + { + "class": "org.apache.kafka.connect.transforms.FilterHuggingFace", + "name": "org.apache.kafka.connect.transforms.Filter", + "description": "org.apache.kafka.connect.transforms.Filter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.FilterHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.FilterMinilm", + "name": "org.apache.kafka.connect.transforms.Filter", + "description": "org.apache.kafka.connect.transforms.Filter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.FilterMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.FilterOllama", + "name": "org.apache.kafka.connect.transforms.Filter", + "description": "org.apache.kafka.connect.transforms.Filter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.FilterOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.FilterVoyageAi", + "name": "org.apache.kafka.connect.transforms.Filter", + "description": "org.apache.kafka.connect.transforms.Filter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.FilterVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$Key", + "name": "org.apache.kafka.connect.transforms.Flatten$Key", + "description": "org.apache.kafka.connect.transforms.Flatten$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.Flatten$Key", + "description": "org.apache.kafka.connect.transforms.Flatten$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.Flatten$Key", + "description": "org.apache.kafka.connect.transforms.Flatten$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$KeyOllama", + "name": "org.apache.kafka.connect.transforms.Flatten$Key", + "description": "org.apache.kafka.connect.transforms.Flatten$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.Flatten$Key", + "description": "org.apache.kafka.connect.transforms.Flatten$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$Value", + "name": "org.apache.kafka.connect.transforms.Flatten$Value", + "description": "org.apache.kafka.connect.transforms.Flatten$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.Flatten$Value", + "description": "org.apache.kafka.connect.transforms.Flatten$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.Flatten$Value", + "description": "org.apache.kafka.connect.transforms.Flatten$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$ValueOllama", + "name": "org.apache.kafka.connect.transforms.Flatten$Value", + "description": "org.apache.kafka.connect.transforms.Flatten$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.Flatten$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.Flatten$Value", + "description": "org.apache.kafka.connect.transforms.Flatten$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.Flatten$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$KeyOllama", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$ValueOllama", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HeaderFrom$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "description": "org.apache.kafka.connect.transforms.HeaderFrom$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HeaderFrom$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$Key", + "name": "org.apache.kafka.connect.transforms.HoistField$Key", + "description": "org.apache.kafka.connect.transforms.HoistField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.HoistField$Key", + "description": "org.apache.kafka.connect.transforms.HoistField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.HoistField$Key", + "description": "org.apache.kafka.connect.transforms.HoistField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$KeyOllama", + "name": "org.apache.kafka.connect.transforms.HoistField$Key", + "description": "org.apache.kafka.connect.transforms.HoistField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.HoistField$Key", + "description": "org.apache.kafka.connect.transforms.HoistField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$Value", + "name": "org.apache.kafka.connect.transforms.HoistField$Value", + "description": "org.apache.kafka.connect.transforms.HoistField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.HoistField$Value", + "description": "org.apache.kafka.connect.transforms.HoistField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.HoistField$Value", + "description": "org.apache.kafka.connect.transforms.HoistField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$ValueOllama", + "name": "org.apache.kafka.connect.transforms.HoistField$Value", + "description": "org.apache.kafka.connect.transforms.HoistField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.HoistField$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.HoistField$Value", + "description": "org.apache.kafka.connect.transforms.HoistField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.HoistField$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$Key", + "name": "org.apache.kafka.connect.transforms.InsertField$Key", + "description": "org.apache.kafka.connect.transforms.InsertField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.InsertField$Key", + "description": "org.apache.kafka.connect.transforms.InsertField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.InsertField$Key", + "description": "org.apache.kafka.connect.transforms.InsertField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$KeyOllama", + "name": "org.apache.kafka.connect.transforms.InsertField$Key", + "description": "org.apache.kafka.connect.transforms.InsertField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.InsertField$Key", + "description": "org.apache.kafka.connect.transforms.InsertField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$Value", + "name": "org.apache.kafka.connect.transforms.InsertField$Value", + "description": "org.apache.kafka.connect.transforms.InsertField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.InsertField$Value", + "description": "org.apache.kafka.connect.transforms.InsertField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.InsertField$Value", + "description": "org.apache.kafka.connect.transforms.InsertField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$ValueOllama", + "name": "org.apache.kafka.connect.transforms.InsertField$Value", + "description": "org.apache.kafka.connect.transforms.InsertField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertField$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.InsertField$Value", + "description": "org.apache.kafka.connect.transforms.InsertField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertField$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertHeader", + "name": "org.apache.kafka.connect.transforms.InsertHeader", + "description": "org.apache.kafka.connect.transforms.InsertHeader", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertHeader.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertHeaderHuggingFace", + "name": "org.apache.kafka.connect.transforms.InsertHeader", + "description": "org.apache.kafka.connect.transforms.InsertHeader", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertHeaderHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertHeaderMinilm", + "name": "org.apache.kafka.connect.transforms.InsertHeader", + "description": "org.apache.kafka.connect.transforms.InsertHeader", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertHeaderMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertHeaderOllama", + "name": "org.apache.kafka.connect.transforms.InsertHeader", + "description": "org.apache.kafka.connect.transforms.InsertHeader", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertHeaderOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.InsertHeaderVoyageAi", + "name": "org.apache.kafka.connect.transforms.InsertHeader", + "description": "org.apache.kafka.connect.transforms.InsertHeader", + "descriptor": "transformation/org.apache.kafka.connect.transforms.InsertHeaderVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$Key", + "name": "org.apache.kafka.connect.transforms.MaskField$Key", + "description": "org.apache.kafka.connect.transforms.MaskField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.MaskField$Key", + "description": "org.apache.kafka.connect.transforms.MaskField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.MaskField$Key", + "description": "org.apache.kafka.connect.transforms.MaskField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$KeyOllama", + "name": "org.apache.kafka.connect.transforms.MaskField$Key", + "description": "org.apache.kafka.connect.transforms.MaskField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.MaskField$Key", + "description": "org.apache.kafka.connect.transforms.MaskField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$Value", + "name": "org.apache.kafka.connect.transforms.MaskField$Value", + "description": "org.apache.kafka.connect.transforms.MaskField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.MaskField$Value", + "description": "org.apache.kafka.connect.transforms.MaskField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.MaskField$Value", + "description": "org.apache.kafka.connect.transforms.MaskField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$ValueOllama", + "name": "org.apache.kafka.connect.transforms.MaskField$Value", + "description": "org.apache.kafka.connect.transforms.MaskField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.MaskField$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.MaskField$Value", + "description": "org.apache.kafka.connect.transforms.MaskField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.MaskField$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.RegexRouter", + "name": "org.apache.kafka.connect.transforms.RegexRouter", + "description": "org.apache.kafka.connect.transforms.RegexRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.RegexRouter.json" + }, + { + "class": "org.apache.kafka.connect.transforms.RegexRouterHuggingFace", + "name": "org.apache.kafka.connect.transforms.RegexRouter", + "description": "org.apache.kafka.connect.transforms.RegexRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.RegexRouterHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.RegexRouterMinilm", + "name": "org.apache.kafka.connect.transforms.RegexRouter", + "description": "org.apache.kafka.connect.transforms.RegexRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.RegexRouterMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.RegexRouterOllama", + "name": "org.apache.kafka.connect.transforms.RegexRouter", + "description": "org.apache.kafka.connect.transforms.RegexRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.RegexRouterOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.RegexRouterVoyageAi", + "name": "org.apache.kafka.connect.transforms.RegexRouter", + "description": "org.apache.kafka.connect.transforms.RegexRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.RegexRouterVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$KeyOllama", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$ValueOllama", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ReplaceField$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "description": "org.apache.kafka.connect.transforms.ReplaceField$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ReplaceField$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$KeyOllama", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$ValueOllama", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.SetSchemaMetadata$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "description": "org.apache.kafka.connect.transforms.SetSchemaMetadata$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.SetSchemaMetadata$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$Key.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$KeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$KeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$KeyMinilm", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$KeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$KeyOllama", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$KeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$KeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Key", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$KeyVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$Value.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$ValueHuggingFace", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$ValueHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$ValueMinilm", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$ValueMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$ValueOllama", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$ValueOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampConverter$ValueVoyageAi", + "name": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "description": "org.apache.kafka.connect.transforms.TimestampConverter$Value", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampConverter$ValueVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampRouter", + "name": "org.apache.kafka.connect.transforms.TimestampRouter", + "description": "org.apache.kafka.connect.transforms.TimestampRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampRouter.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampRouterHuggingFace", + "name": "org.apache.kafka.connect.transforms.TimestampRouter", + "description": "org.apache.kafka.connect.transforms.TimestampRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampRouterHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampRouterMinilm", + "name": "org.apache.kafka.connect.transforms.TimestampRouter", + "description": "org.apache.kafka.connect.transforms.TimestampRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampRouterMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampRouterOllama", + "name": "org.apache.kafka.connect.transforms.TimestampRouter", + "description": "org.apache.kafka.connect.transforms.TimestampRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampRouterOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.TimestampRouterVoyageAi", + "name": "org.apache.kafka.connect.transforms.TimestampRouter", + "description": "org.apache.kafka.connect.transforms.TimestampRouter", + "descriptor": "transformation/org.apache.kafka.connect.transforms.TimestampRouterVoyageAi.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ValueToKey", + "name": "org.apache.kafka.connect.transforms.ValueToKey", + "description": "org.apache.kafka.connect.transforms.ValueToKey", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ValueToKey.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ValueToKeyHuggingFace", + "name": "org.apache.kafka.connect.transforms.ValueToKey", + "description": "org.apache.kafka.connect.transforms.ValueToKey", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ValueToKeyHuggingFace.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ValueToKeyMinilm", + "name": "org.apache.kafka.connect.transforms.ValueToKey", + "description": "org.apache.kafka.connect.transforms.ValueToKey", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ValueToKeyMinilm.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ValueToKeyOllama", + "name": "org.apache.kafka.connect.transforms.ValueToKey", + "description": "org.apache.kafka.connect.transforms.ValueToKey", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ValueToKeyOllama.json" + }, + { + "class": "org.apache.kafka.connect.transforms.ValueToKeyVoyageAi", + "name": "org.apache.kafka.connect.transforms.ValueToKey", + "description": "org.apache.kafka.connect.transforms.ValueToKey", + "descriptor": "transformation/org.apache.kafka.connect.transforms.ValueToKeyVoyageAi.json" + } + ] } - } \ No newline at end of file +} \ No newline at end of file diff --git a/debezium-platform-stage/src/__mocks__/data/DestinationCatalog.json b/debezium-platform-stage/src/__mocks__/data/DestinationCatalog.json deleted file mode 100644 index 5b4f2248..00000000 --- a/debezium-platform-stage/src/__mocks__/data/DestinationCatalog.json +++ /dev/null @@ -1,114 +0,0 @@ -[ - { - "class": "kinesis", - "name": "Amazon Kinesis", - "description": "Streams change events to Amazon Kinesis, a fully managed, distributed streaming platform.", - "descriptor": "", - "role": "destination" - }, - { - "class": "pulsar", - "name": "Apache Pulsar", - "description": "Streams CDC events into Apache Pulsar, a distributed messaging and event-streaming platform offering low-latency and high-throughput.", - "descriptor": "", - "role": "destination" - }, - { - "class": "eventhubs", - "name": "Azure Event Hub", - "description": "Sends change events to Azure Event Hub, a real-time data ingestion service optimized for streaming millions of events per second.", - "descriptor": "", - "role": "destination" - }, - { - "class": "http", - "name": "HTTPClint", - "description": "Streams change events to any HTTP Server for additional processing with the original design goal to have Debezium act as a Knative Event Source.", - "descriptor": "", - "role": "destination" - }, - { - "class": "infinispan", - "name": "Infinispan", - "description": "Propagates events to Infinispan, a distributed in-memory data grid providing high availability and scalability for data replication.", - "descriptor": "", - "role": "destination" - }, - { - "class": "kafka", - "name": "Kafka", - "description": "Streams real-time change events into Apache Kafka, a distributed platform for building streaming data pipelines and real-time applications.", - "descriptor": "", - "role": "destination" - }, - { - "class": "milvus", - "name": "Milvus", - "description": "Streams change events to Milvus, a vector database for scalable similarity search and vector embedding.", - "descriptor": "", - "role": "destination" - }, - { - "class": "nats-streaming", - "name": "NATS Streaming", - "description": "Sends change events to NATS Streaming, a messaging system designed for distributed systems and microservices architectures.", - "descriptor": "", - "role": "destination" - }, - { - "class": "nats-jetstream", - "name": "NATS JetStream", - "description": "Sends change events to NATS JetStream, a messaging system designed for distributed systems and microservices architectures.", - "descriptor": "", - "role": "destination" - }, - { - "class": "pravega", - "name": "Pravega", - "description": "Streams change events to Pravega, a scalable storage system for distributed data streams, offering a tiered storage model.", - "descriptor": "", - "role": "destination" - }, - { - "class": "pubsub", - "name": "Pub/Sub", - "description": "Sends real-time change events to Google Pub/Sub, a fully managed messaging service for building event-driven systems and real-time analytics.", - "descriptor": "", - "role": "destination" - }, - { - "class": "pubsublite", - "name": "Pub/Sub Lite", - "description": "Streams events to Google Pub/Sub Lite, a cost-effective, low-latency message delivery service for simpler use cases.", - "descriptor": "", - "role": "destination" - }, - { - "class": "qdrant", - "name": "Qdrant", - "description": "Streams change events to Qdrant, a vector database for scalable similarity search and vector embedding.", - "descriptor": "", - "role": "destination" - }, - { - "class": "rabbitmq", - "name": "RabbitMQ", - "description": "Publishes change events to RabbitMQ, an open-source message broker supporting various messaging protocols for inter-service communication.", - "descriptor": "", - "role": "destination" - }, - { - "class": "redis", - "name": "Redis(Stream)", - "description": "Streams change events to Redis Streams, a high-performance, open-source, in-memory data structure store.", - "descriptor": "", - "role": "destination" - }, - { - "class": "rocketmq", - "name": "RocketMQ", - "description": "Streams change events to Apache RocketMQ, a high-throughput, low-latency messaging platform.", - "descriptor": "", - "role": "destination" - } -] diff --git a/debezium-platform-stage/src/__mocks__/data/SourceCatalog.json b/debezium-platform-stage/src/__mocks__/data/SourceCatalog.json deleted file mode 100644 index b1dd6a83..00000000 --- a/debezium-platform-stage/src/__mocks__/data/SourceCatalog.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "name": "MariaDB", - "id": "mariadb", - "type": "io.debezium.connector.mariadb.MariaDbConnector", - "role": "source", - "description": "Captures row-level changes from a MariaDB database in near real-time." - }, - { - "name": "MongoDB", - "id": "mongodb", - "type": "io.debezium.connector.mongodb.MongoDbConnector", - "role": "source", - "description": "Captures document changes from a MongoDB database in near real-time." - }, - { - "name": "MySQL", - "id": "mysql", - "type": "io.debezium.connector.mysql.MySqlConnector", - "role": "source", - "description": "Captures row-level changes from a MySQL database in near real-time." - }, - { - "name": "Oracle", - "id": "oracle", - "type": "io.debezium.connector.oracle.OracleConnector", - "role": "source", - "description": "Captures row-level changes from an Oracle database in near real-time." - }, - { - "name": "PostgreSQL", - "id": "postgresql", - "type": "io.debezium.connector.postgresql.PostgresConnector", - "role": "source", - "description": "Captures row-level changes from a PostgreSQL database in near real-time." - }, - { - "name": "SQL Server", - "id": "sqlserver", - "type": "io.debezium.connector.sqlserver.SqlServerConnector", - "role": "source", - "description": "Captures row-level changes from a SQL Server database in near real-time." - } -] diff --git a/debezium-platform-stage/src/apis/types.tsx b/debezium-platform-stage/src/apis/types.tsx index 61ff8867..b1e3d207 100644 --- a/debezium-platform-stage/src/apis/types.tsx +++ b/debezium-platform-stage/src/apis/types.tsx @@ -69,6 +69,7 @@ export interface CatalogApiResponse { components: { converter: CatalogComponentEntry[]; "custom-converter": CatalogComponentEntry[]; + "server-sink": CatalogComponentEntry[]; "sink-connector": CatalogComponentEntry[]; "source-connector": CatalogComponentEntry[]; transformation: CatalogComponentEntry[]; diff --git a/debezium-platform-stage/src/components/CreateSourceSchemaForm.css b/debezium-platform-stage/src/components/CreateSchemaForm.css similarity index 100% rename from debezium-platform-stage/src/components/CreateSourceSchemaForm.css rename to debezium-platform-stage/src/components/CreateSchemaForm.css diff --git a/debezium-platform-stage/src/components/CreateSourceSchemaForm.tsx b/debezium-platform-stage/src/components/CreateSchemaForm.tsx similarity index 97% rename from debezium-platform-stage/src/components/CreateSourceSchemaForm.tsx rename to debezium-platform-stage/src/components/CreateSchemaForm.tsx index 1f6c66f8..63298c38 100644 --- a/debezium-platform-stage/src/components/CreateSourceSchemaForm.tsx +++ b/debezium-platform-stage/src/components/CreateSchemaForm.tsx @@ -71,7 +71,6 @@ import { } from "@utils/helpers"; import { useNotification } from "@appContext/index"; import { Catalog, CatalogApiResponse, ConnectorSchema, SchemaGroup, SchemaProperty } from "../apis/types"; -import destinationCatalog from "../__mocks__/data/DestinationCatalog.json"; import ConnectorImage from "./ComponentImage"; import TableViewComponent from "./TableViewComponent"; import SchemaGroupSection from "./SchemaGroupSection"; @@ -92,7 +91,7 @@ import { import { buildSourceConnectorDisplayGroupedProperties } from "@utils/sourceConnectorDisplayGroups"; import { splitSourceConfigForHydration } from "@utils/sourceConfigSplit"; import _ from "lodash"; -import "./CreateSourceSchemaForm.css"; +import "./CreateSchemaForm.css"; interface connectionsList extends Connection { role: string; @@ -100,7 +99,7 @@ interface connectionsList extends Connection { type AdditionalProp = { key: string; value: string }; -interface CreateSourceSchemaFormProps { +interface CreateSchemaFormProps { connectorSchema: ConnectorSchema; sourceId: string; dataType?: string; @@ -109,9 +108,10 @@ interface CreateSourceSchemaFormProps { readOnly?: boolean; /** Initial layout; user can still switch via the toggle. Pipeline designer modal uses "tabs". */ defaultLayoutMode?: "jumplinks" | "tabs"; + hideSignalCollections?: boolean; } -export interface CreateSourceSchemaFormHandle { +export interface CreateSchemaFormHandle { validate: () => boolean; submit: () => void; /** Populated after the most recent failed `validate()` */ @@ -219,10 +219,10 @@ function formatValidationFailureNotificationBody(sections: string[], t: TFunctio return t("source:form.validationFailedInMultipleSections", { list: sections.join(", ") }); } -const CreateSourceSchemaForm = React.forwardRef< - CreateSourceSchemaFormHandle, - CreateSourceSchemaFormProps ->(({ connectorSchema, sourceId, dataType, onSubmit, initialSource, readOnly = false, defaultLayoutMode = "jumplinks" }, ref) => { +const CreateSchemaForm = React.forwardRef< + CreateSchemaFormHandle, + CreateSchemaFormProps +>(({ connectorSchema, sourceId, dataType, onSubmit, initialSource, readOnly = false, defaultLayoutMode = "jumplinks", hideSignalCollections = false }, ref) => { const { t } = useTranslation(); const { addNotification } = useNotification(); const hydratedSourceIdRef = useRef(null); @@ -357,9 +357,11 @@ const CreateSourceSchemaForm = React.forwardRef< } } sections.push({ id: "additional-properties", label: "Additional Properties", type: "custom" }); - sections.push({ id: "signal-collections", label: "Signal Collections", type: "custom" }); + if (!hideSignalCollections) { + sections.push({ id: "signal-collections", label: "Signal Collections", type: "custom" }); + } return sections; - }, [orderedGroups, groupedProperties]); + }, [orderedGroups, groupedProperties, hideSignalCollections]); useEffect(() => { if (layoutMode !== "jumplinks") return; @@ -405,6 +407,17 @@ const CreateSourceSchemaForm = React.forwardRef< } ); + const { data: destinationCatalog = [] } = useQuery( + "destinationConnectorCatalog", + async () => { + const response = await fetchData(`${API_URL}/api/catalog`); + return (response.components["server-sink"] ?? []).map((entry) => ({ + ...entry, + role: "destination", + })); + } + ); + const catalog: Catalog[] = [...sourceCatalog, ...destinationCatalog]; const { isLoading: isConnectionsLoading } = useQuery( @@ -1120,9 +1133,11 @@ const CreateSourceSchemaForm = React.forwardRef< {/* Signal Collections */} -
- {renderSignalCollections()} -
+ {!hideSignalCollections && ( +
+ {renderSignalCollections()} +
+ )} ); @@ -1183,9 +1198,11 @@ const CreateSourceSchemaForm = React.forwardRef< -
- {renderSignalCollections()} -
+ {!hideSignalCollections && ( +
+ {renderSignalCollections()} +
+ )} @@ -1281,5 +1298,5 @@ const CreateSourceSchemaForm = React.forwardRef< ); }); -CreateSourceSchemaForm.displayName = "CreateSourceSchemaForm"; -export default CreateSourceSchemaForm; +CreateSchemaForm.displayName = "CreateSchemaForm"; +export default CreateSchemaForm; diff --git a/debezium-platform-stage/src/components/SourceSchemaReviewView.css b/debezium-platform-stage/src/components/SchemaReviewView.css similarity index 100% rename from debezium-platform-stage/src/components/SourceSchemaReviewView.css rename to debezium-platform-stage/src/components/SchemaReviewView.css diff --git a/debezium-platform-stage/src/components/SourceSchemaReviewView.test.tsx b/debezium-platform-stage/src/components/SchemaReviewView.test.tsx similarity index 91% rename from debezium-platform-stage/src/components/SourceSchemaReviewView.test.tsx rename to debezium-platform-stage/src/components/SchemaReviewView.test.tsx index 35e07a28..7361f8d3 100644 --- a/debezium-platform-stage/src/components/SourceSchemaReviewView.test.tsx +++ b/debezium-platform-stage/src/components/SchemaReviewView.test.tsx @@ -2,7 +2,7 @@ import { screen } from "@testing-library/react"; import { describe, it, expect, vi, beforeAll, beforeEach } from "vitest"; import { useQuery } from "react-query"; -import SourceSchemaReviewView from "./SourceSchemaReviewView"; +import SchemaReviewView from "./SchemaReviewView"; import type { Source } from "../apis/apis"; import type { ConnectorSchema } from "../apis/types"; import { render } from "../__test__/unit/test-utils"; @@ -55,7 +55,7 @@ beforeAll(() => { } as unknown as typeof IntersectionObserver; }); -describe("SourceSchemaReviewView", () => { +describe("SchemaReviewView", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -69,7 +69,7 @@ describe("SourceSchemaReviewView", () => { } as any); render( - , + , ); expect(await screen.findByText("Review Source")).toBeInTheDocument(); @@ -87,7 +87,7 @@ describe("SourceSchemaReviewView", () => { } as any); render( - , + , ); const unsetValues = await screen.findAllByText("—"); @@ -143,7 +143,7 @@ describe("SourceSchemaReviewView", () => { }; render( - , + , ); expect( diff --git a/debezium-platform-stage/src/components/SourceSchemaReviewView.tsx b/debezium-platform-stage/src/components/SchemaReviewView.tsx similarity index 91% rename from debezium-platform-stage/src/components/SourceSchemaReviewView.tsx rename to debezium-platform-stage/src/components/SchemaReviewView.tsx index 8f11375f..c1663047 100644 --- a/debezium-platform-stage/src/components/SourceSchemaReviewView.tsx +++ b/debezium-platform-stage/src/components/SchemaReviewView.tsx @@ -33,15 +33,16 @@ import { import ApiComponentError from "./ApiComponentError"; import TableViewComponent from "./TableViewComponent"; import _ from "lodash"; -import "./CreateSourceSchemaForm.css"; -import "./SourceSchemaReviewView.css"; +import "./CreateSchemaForm.css"; +import "./SchemaReviewView.css"; const EMPTY_DISPLAY = "—"; -export interface SourceSchemaReviewViewProps { +export interface SchemaReviewViewProps { source: Source; connectorSchema: ConnectorSchema; dataType?: string; + hideSignalCollections?: boolean; } const ReviewDescriptionList: React.FC<{ children: React.ReactNode; ariaLabel: string }> = ({ @@ -80,10 +81,11 @@ const ReviewValueSpan: React.FC<{ raw: string | undefined }> = ({ raw }) => { ); }; -const SourceSchemaReviewView: React.FC = ({ +const SchemaReviewView: React.FC = ({ source, connectorSchema, dataType, + hideSignalCollections = false, }) => { const { t } = useTranslation(); const typeKey = dataType || source.type; @@ -185,12 +187,14 @@ const SourceSchemaReviewView: React.FC = ({ id: "additional-properties", label: t("source:jumplinks.additionalProperties", { defaultValue: "Additional Properties" }), }); - sections.push({ - id: "signal-collections", - label: t("source:jumplinks.signalCollections", { defaultValue: "Signal Collections" }), - }); + if (!hideSignalCollections) { + sections.push({ + id: "signal-collections", + label: t("source:jumplinks.signalCollections", { defaultValue: "Signal Collections" }), + }); + } return sections; - }, [orderedGroups, groupedProperties, t]); + }, [orderedGroups, groupedProperties, t, hideSignalCollections]); useEffect(() => { const sectionIds = allSections.map((s) => s.id); @@ -464,29 +468,31 @@ const SourceSchemaReviewView: React.FC = ({ )} -
- - {t("source:signal.title")} - - - {t("source:signal.description")} - - - - - {t("source:signal.signalingCollectionField.label", { - defaultValue: "Signaling collection", - })} - - - - - - -
+ {!hideSignalCollections && ( +
+ + {t("source:signal.title")} + + + {t("source:signal.description")} + + + + + {t("source:signal.signalingCollectionField.label", { + defaultValue: "Signaling collection", + })} + + + + + + +
+ )} ); }; -export default SourceSchemaReviewView; +export default SchemaReviewView; diff --git a/debezium-platform-stage/src/components/SourceSinkForm.css b/debezium-platform-stage/src/components/SourceSinkForm.css deleted file mode 100644 index cb1e1fd7..00000000 --- a/debezium-platform-stage/src/components/SourceSinkForm.css +++ /dev/null @@ -1,8 +0,0 @@ -.table-explorer-section { - max-width: var(--pf-v6-c-form--m-limit-width--MaxWidth); - padding-bottom: 10px; -} - -.table-explorer-section .pf-v6-c-form__field-group-body{ - padding-block-start: 0 !important; -} diff --git a/debezium-platform-stage/src/components/SourceSinkForm.tsx b/debezium-platform-stage/src/components/SourceSinkForm.tsx deleted file mode 100644 index 93e3c499..00000000 --- a/debezium-platform-stage/src/components/SourceSinkForm.tsx +++ /dev/null @@ -1,538 +0,0 @@ -import { - Card, - CardBody, - FormGroup, - Content, - TextInput, - FormHelperText, - HelperText, - HelperTextItem, - FormFieldGroup, - FormFieldGroupHeader, - Button, - Split, - SplitItem, - Grid, - Form, - InputGroup, - InputGroupItem, - Select, - SelectList, - SelectOption, - SelectOptionProps, - MenuToggle, - MenuToggleElement, - TextInputGroup, - TextInputGroupMain, - TextInputGroupUtilities, - Skeleton, -} from "@patternfly/react-core"; -import { ExclamationCircleIcon, PlusIcon, TimesIcon, TrashIcon } from "@patternfly/react-icons"; -import { getConnectionRole, getConnectorTypeName } from "@utils/helpers"; -import { Catalog } from "../apis/types"; -import destinationCatalog from "../__mocks__/data/DestinationCatalog.json"; -import ConnectorImage from "./ComponentImage"; -import { useTranslation } from "react-i18next"; -import * as React from "react"; -import { useEffect, useRef, useState } from "react"; -import { Connection, ConnectionConfig, fetchData } from "src/apis"; -import { API_URL } from "@utils/constants"; -import { useQuery } from "react-query"; -import "./SourceSinkForm.css"; - - -const getInitialSelectOptions = (connections: connectionsList[], connectorId: string): SelectOptionProps[] => { - const connectorLower = connectorId.toLowerCase(); - return connections.filter((connection) => { - const typeLower = connection.type.toLowerCase(); - return typeLower === connectorLower || connectorLower.includes(typeLower) || typeLower.includes(connectorLower); - }).map((connection) => ({ - value: connection.id, - children: connection.name, - icon: , - })); -} - -export interface connectionsList extends Connection { - role: string; -} - -interface SourceSinkFormProps { - ConnectorId: string; - dataType?: string; - errorWarning: string[]; - properties: Map; - setValue: (key: string, value: string) => void; - getValue: (key: string) => string; - setError: (key: string, error: string | undefined) => void; - errors: Record; - handleAddProperty: () => void; - handleDeleteProperty: (key: string) => void; - handlePropertyChange: ( - key: string, - type: "key" | "value", - value: string - ) => void; - viewMode?: boolean; - setSelectedConnection: (connection: ConnectionConfig | undefined) => void; - selectedConnection: ConnectionConfig | undefined; - handleConnectionModalToggle: () => void; -} -const SourceSinkForm = ({ - ConnectorId, - dataType, - properties, - setValue, - getValue, - setError, - errorWarning, - errors, - handleAddProperty, - handleDeleteProperty, - handlePropertyChange, - viewMode, - setSelectedConnection, - selectedConnection, - handleConnectionModalToggle, -}: SourceSinkFormProps) => { - const { t } = useTranslation(); - - const connectorType = "destination" as const; - const connectorLabel = "Destination"; - - const [isOpen, setIsOpen] = useState(false); - const [inputValue, setInputValue] = useState(() => selectedConnection?.name || ""); - const [filterValue, setFilterValue] = useState(''); - const [focusedItemIndex, setFocusedItemIndex] = useState(null); - const [activeItemId, setActiveItemId] = useState(null); - const textInputRef = useRef(null); - - const [connections, setConnections] = useState([]); - - const NO_RESULTS = 'no results'; - - useEffect(() => { - /* eslint-disable react-hooks/set-state-in-effect -- keep typeahead text in sync when persisted connection id/name change */ - setInputValue(selectedConnection?.name ?? ""); - /* eslint-enable react-hooks/set-state-in-effect */ - }, [selectedConnection?.id, selectedConnection?.name]); - - const catalog: Catalog[] = destinationCatalog as Catalog[]; - - const { - isLoading: isConnectionsLoading, - } = useQuery( - "connections", - () => fetchData(`${API_URL}/api/connections`), - { - refetchInterval: 70000, - onSuccess: (data) => { - const withRole = data.map((conn) => ({ - ...conn, - role: getConnectionRole(conn.type.toLowerCase(), catalog) || "", - })); - setConnections(withRole); - }, - } - ); - - const baseSelectOptions = React.useMemo(() => { - return getInitialSelectOptions(connections, dataType || ConnectorId); - }, [connections, dataType, ConnectorId]); - - const selectOptions = React.useMemo(() => { - if (!baseSelectOptions) return undefined; - if (filterValue) { - const filtered = baseSelectOptions.filter((menuItem) => - String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase()) - ); - if (!filtered.length) { - return [ - { isAriaDisabled: true, children: `No results found for "${filterValue}"`, value: NO_RESULTS } - ]; - } - return filtered; - } - return baseSelectOptions; - }, [baseSelectOptions, filterValue, NO_RESULTS]); - - const createItemId = (value: unknown) => `select-typeahead-${String(value ?? '').replace(/\s+/g, '-')}`; - - 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) => { - setInputValue(String(content)); - setFilterValue(''); - setSelectedConnection({ id: value as number, name: content as string }); - closeMenu(); - }; - - const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { - setError("connection", 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 !== selectedConnection?.name) { - setSelectedConnection(undefined); - } - if (value && !isOpen) { - setIsOpen(true); - } - }; - - 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 ? selectOptions.length - 1 : 0; - } else { - indexToFocus = focusedItemIndex - 1; - } - - while (selectOptions && selectOptions[indexToFocus]?.isDisabled) { - indexToFocus--; - if (indexToFocus === -1) { - indexToFocus = selectOptions ? selectOptions.length - 1 : 0; - } - } - } - - 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 && 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 = () => { - setSelectedConnection(undefined); - setInputValue(''); - setFilterValue(''); - resetActiveAndFocusedItem(); - textInputRef?.current?.focus(); - }; - - const toggle = (toggleRef: React.Ref) => ( - - - - - - - - )} - - - {!viewMode && !errors[`connection`] && ( - - - {t("connection:link.helperText")} - - - )} - - {errors[`connection`] ? ( - - - } variant="error"> - {errors[`connection`]} - - - ) : null} - - - {t("form.subHeading.title")}, - id: `field-group-${connectorType}-id`, - }} - titleDescription={!viewMode ? t("form.subHeading.description") : undefined} - actions={ - viewMode ? null : - <> - - - } - /> - } - > - {Array.from(properties.keys()).map((key) => ( - - - - - - handlePropertyChange(key, "key", value) - } - /> - - - - handlePropertyChange(key, "value", value) - } - /> - - - - - - - - ))} - - - - - - - - ); -}; - -export default SourceSinkForm; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx b/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx index 963ecc3b..a4892e38 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/PipelineDestinationModel.tsx @@ -7,6 +7,9 @@ import { CardBody, Divider, Content, + Alert, + Button, + PageSection, } from "@patternfly/react-core"; import React, { useCallback, useState } from "react"; import { Destination, fetchData } from "../../apis/apis"; @@ -15,7 +18,8 @@ import { API_URL } from "../../utils/constants"; import SourceDestinationSelectionList from "../SourceDestinationSelectionList"; import { CatalogGrid } from "@components/CatalogGrid"; import { CreateDestination } from "@destinationPage/CreateDestination"; -import destinationCatalog from "../../__mocks__/data/DestinationCatalog.json"; +import { Catalog, CatalogApiResponse } from "../../apis/types"; +import CatalogSkeleton from "@components/CatalogSkeleton"; type PipelineDestinationModelProps = { onDestinationSelection: (destination: Destination) => void; @@ -37,9 +41,24 @@ const PipelineDestinationModel: React.FC = ({ fetchData(`${API_URL}/api/destinations`) ); + const { + data: destinationCatalog = [], + error: catalogError, + isLoading: isCatalogLoading, + refetch: refetchCatalog, + } = useQuery("destinationConnectorCatalog", async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["server-sink"] ?? []).map((entry) => ({ + ...entry, + role: "destination", + })); + }); + const [userSelection, setUserSelection] = useState(null); - const isCreateChecked = userSelection !== null - ? userSelection + const isCreateChecked = userSelection !== null + ? userSelection : (!isDestinationLoading && destinationList.length === 0 ? id2 : id1); const selectDestination = useCallback( @@ -130,13 +149,31 @@ const PipelineDestinationModel: React.FC = ({ onSelection={onDestinationSelection} /> ) : selectedDestination === "" ? ( - + catalogError ? ( + + refetchCatalog()}> + Retry + + } + > + {catalogError.message} + + + ) : isCatalogLoading ? ( + + ) : ( + + ) ) : ( = () => { } ); + const { data: destinationCatalog = [] } = useQuery( + "destinationConnectorCatalog", + async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["server-sink"] ?? []).map((entry) => ({ + ...entry, + role: "destination", + })); + } + ); + const catalog: Catalog[] = React.useMemo( () => [...sourceCatalog, ...destinationCatalog], - [sourceCatalog] + [sourceCatalog, destinationCatalog] ); const searchResult = React.useMemo(() => { diff --git a/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.test.tsx b/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.test.tsx index 7b7fac9e..c19fed51 100644 --- a/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.test.tsx +++ b/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.test.tsx @@ -1,11 +1,21 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { screen, fireEvent, waitFor } from "@testing-library/react"; import { describe, it, expect, vi, beforeEach } from "vitest"; -import { useQuery } from "react-query"; import { ConnectionsCatalog } from "./ConnectionsCatalog"; import type { Catalog } from "../../apis/types"; -import destinationCatalogFixture from "../../__mocks__/data/DestinationCatalog.json"; import { render } from "../../__test__/unit/test-utils"; +import catalogFixture from "../../__fixtures__/catalog.json"; + +// Mock data +const sourceCatalogRows: Catalog[] = [ + { + class: "mariadb", + name: "MariaDB", + description: "d", + descriptor: "desc", + role: "source", + }, +]; vi.mock("react-router-dom", () => ({ useNavigate: () => vi.fn(), @@ -15,7 +25,26 @@ vi.mock("react-query", async (importOriginal) => { const mod = await importOriginal(); return { ...mod, - useQuery: vi.fn(), + useQuery: vi.fn((queryKey) => { + // Return different data based on query key + if (queryKey === "sourceConnectorCatalog") { + return { + data: sourceCatalogRows, + error: null, + isLoading: false, + }; + } else if (queryKey === "destinationConnectorCatalog") { + return { + data: catalogFixture.components["server-sink"].map((entry: any) => ({ + ...entry, + role: "destination", + })), + error: null, + isLoading: false, + }; + } + return { data: [], error: null, isLoading: false }; + }), }; }); @@ -33,46 +62,28 @@ vi.mock("@components/ConnectionCatalogGrid", () => ({ ), })); -const catalogRows: Catalog[] = [ - { - class: "mariadb", - name: "MariaDB", - description: "d", - descriptor: "desc", - role: "source", - }, - { - class: "kinesis", - name: "Amazon Kinesis", - description: "d", - descriptor: "desc2", - role: "destination", - }, -]; - describe("ConnectionsCatalog", () => { + const destinationCatalogFixture = catalogFixture.components["server-sink"].map((entry: any) => ({ + ...entry, + role: "destination", + })); const defaultMergedCount = - catalogRows.length + destinationCatalogFixture.length; + sourceCatalogRows.length + destinationCatalogFixture.length; beforeEach(() => { vi.clearAllMocks(); }); it("renders title and merged catalog entries", () => { - vi.mocked(useQuery).mockReturnValue({ - data: catalogRows, - error: null, - isLoading: false, - } as any); - render(); expect(screen.getByText("Connection catalog")).toBeInTheDocument(); expect(screen.getByTestId("connection-catalog-grid")).toHaveTextContent( "MariaDB", ); + // Check for one of the destinations from the fixture expect(screen.getByTestId("connection-catalog-grid")).toHaveTextContent( - "Amazon Kinesis", + "io.debezium.server.kafka.KafkaChangeConsumer", ); expect( screen.getByText(new RegExp(`${defaultMergedCount}\\s+Items`)), @@ -80,11 +91,6 @@ describe("ConnectionsCatalog", () => { }); it("shows empty search state when no connectors match", async () => { - vi.mocked(useQuery).mockReturnValue({ - data: catalogRows, - error: null, - isLoading: false, - } as any); render(); diff --git a/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.tsx b/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.tsx index bdaed2aa..a4562120 100644 --- a/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.tsx +++ b/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.tsx @@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next"; import { ThIcon, ListIcon, SearchIcon, FilterIcon } from "@patternfly/react-icons"; import { useState } from "react"; import { Catalog, CatalogApiResponse } from "src/apis/types"; -import destinationCatalog from "../../__mocks__/data/DestinationCatalog.json"; import _, { debounce } from "lodash"; import { useNavigate } from "react-router-dom"; import { ConnectionCatalogGrid } from "@components/ConnectionCatalogGrid"; @@ -40,6 +39,18 @@ const ConnectionsCatalog: React.FunctionComponent = () })); }); + const { + data: destinationCatalog = [], + } = useQuery("destinationConnectorCatalog", async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["server-sink"] ?? []).map((entry) => ({ + ...entry, + role: "destination", + })); + }); + const onConnectionsTypeToggle = () => { setConnectionsTypeIsExpanded(!connectionsTypeIsExpanded); }; @@ -71,7 +82,7 @@ const ConnectionsCatalog: React.FunctionComponent = () } return _.sortBy(filtered, (o) => o.name.toLowerCase()); - }, [connectionsTypeSelected, searchQuery, sourceCatalog]); + }, [connectionsTypeSelected, searchQuery, sourceCatalog, destinationCatalog]); const onClear = () => { onSearch?.(""); diff --git a/debezium-platform-stage/src/pages/Destination/CreateDestination.tsx b/debezium-platform-stage/src/pages/Destination/CreateDestination.tsx index acd0862e..b0063dda 100644 --- a/debezium-platform-stage/src/pages/Destination/CreateDestination.tsx +++ b/debezium-platform-stage/src/pages/Destination/CreateDestination.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; import { ActionList, @@ -7,59 +6,22 @@ import { Alert, Button, ButtonType, - FormContextProvider, PageSection, - Spinner, - ToggleGroup, - ToggleGroupItem, - Toolbar, - ToolbarContent, - ToolbarItem, + Skeleton, } from "@patternfly/react-core"; -import { PencilAltIcon, CodeIcon, PlayIcon } from "@patternfly/react-icons"; -import { useNavigate, useParams } from "react-router-dom"; -import { CodeEditor, CodeEditorControl, Language } from "@patternfly/react-code-editor"; - -import { ConnectionConfig, createPost, Destination, Payload } from "../../apis/apis"; +import { useNavigate, useParams, useLocation } from "react-router-dom"; +import { useRef, useState } from "react"; +import { createPost, Payload, Destination } from "../../apis/apis"; import { API_URL } from "../../utils/constants"; -import { convertMapToObject } from "../../utils/helpers"; import { useNotification } from "../../appLayout/AppNotificationContext"; import PageHeader from "@components/PageHeader"; -import SourceSinkForm from "@components/SourceSinkForm"; -import { useCallback, useEffect, useRef, useState } from "react"; -import Ajv from "ajv"; import { useTranslation } from "react-i18next"; -import PageTour from "../../components/PageTour"; -import { Step } from "react-joyride"; -import { connectorSchema } from "@utils/schemas"; -import { isValidJson, useFormatDetector } from "src/hooks/useFormatDetector"; -import { formatCode } from "@utils/formatCodeUtils"; -import style from "../../styles/createConnector.module.css" -import CreateConnectionModal from "../components/CreateConnectionModal"; -import { useData } from "@appContext/AppContext"; -import { Properties } from "src/apis/types"; - -const ajv = new Ajv(); - -const useCreateDestinationTourSteps = (): Step[] => { - const { t } = useTranslation("tour"); - return [ - { - target: "#form-editor", - placement: "bottom", - title: t("createDestination.formEditor.title"), - content: t("createDestination.formEditor.content"), - disableBeacon: true, - }, - { - target: "#smart-editor", - placement: "bottom", - title: t("createDestination.smartEditor.title"), - content: t("createDestination.smartEditor.content"), - disableBeacon: true, - }, - ]; -}; +import { useQuery } from "react-query"; +import { fetchData } from "../../apis/apis"; +import { ConnectorSchema } from "../../apis/types"; +import CreateSchemaForm, { + CreateSchemaFormHandle, +} from "@components/CreateSchemaForm"; interface CreateDestinationProps { modelLoaded?: boolean; @@ -68,134 +30,6 @@ interface CreateDestinationProps { onSelection?: (selection: Destination) => void; } -const initialCodeValue = { - name: "", - description: "", - type: "", - schema: "schema123", - connection: {}, - vaults: [], - config: {}, -}; - -const FormSyncManager: React.FC<{ - getFormValue: (key: string) => string; - setFormValue: (key: string, value: string) => void; - code: any; - setCode: (code: any) => void; - destinationId: string | undefined; - properties: Map; - setProperties: (properties: Map) => void; - setCodeAlert: (alert: string | React.ReactElement) => void; - setFormatType: (type: string) => void; -}> = ({ - getFormValue, - setFormValue, - code, - setCode, - destinationId, - properties, - setProperties, - setCodeAlert, - setFormatType, -}) => { - const { t } = useTranslation(); - // Ref to track the source of the update - const updateSource = useRef<"form" | "code" | null>(null); - - // Update code state when form values change - useEffect(() => { - if (updateSource.current === "code") { - updateSource.current = null; - return; - } - - updateSource.current = "form"; - const type = destinationId || ""; - const configuration = convertMapToObject(properties); - - setCode((prevCode: any) => { - if ( - prevCode.name === getFormValue("destination-name") && - prevCode.description === getFormValue("description") && - JSON.stringify(prevCode.config) === JSON.stringify(configuration) - ) { - return prevCode; - } - - return { - ...prevCode, - type, - config: configuration, - name: getFormValue("destination-name") || "", - description: getFormValue("description") || "", - }; - }); - }, [ - getFormValue("destination-name"), - getFormValue("description"), - properties, - destinationId, - ]); - - // Use the useFormatDetector hook - const { formatType, isValidFormat, errorMsg } = useFormatDetector(code, "destination"); - - // Update form values when code changes - useEffect(() => { - if (formatType === "properties-file") { - setFormatType("properties-file"); - setCodeAlert(t('statusMessage:smartEditor.debeziumServerFormatMsg')); - return; - } - else { - setFormatType("dbz-platform"); - } - if (isValidFormat) { - if (updateSource.current === "form") { - updateSource.current = null; - return; - } - updateSource.current = "code"; - if (code.name !== getFormValue("destination-name")) { - setFormValue( - "destination-name", - typeof code.name === "string" ? code.name : "" - ); - } - if (code.description !== getFormValue("description")) { - setFormValue( - "description", - typeof code.description === "string" ? code.description : "" - ); - } - const currentConfig = convertMapToObject(properties); - if (JSON.stringify(currentConfig) !== JSON.stringify(code.config)) { - const configMap = new Map(); - Object.entries(code.config || {}).forEach(([key, value], index) => { - configMap.set(`key${index}`, { key, value: value as string }); - }); - setProperties(configMap); - } - if (updateSource.current === "code") { - if (!code.name || code.name.trim() === "") { - setCodeAlert(t('statusMessage:smartEditor.connectorNameRequired')); - return; - } - if (!code.type || code.type.trim() === "") { - setCodeAlert(t('statusMessage:smartEditor.connectorTypeRequired')); - return; - } - } - setCodeAlert(""); - } else { - setCodeAlert(errorMsg); - } - }, [code, formatType, isValidFormat, errorMsg]); - - return null; - }; - const CreateDestination: React.FunctionComponent = ({ modelLoaded, selectedId, @@ -203,217 +37,101 @@ const CreateDestination: React.FunctionComponent = ({ onSelection, }) => { const navigate = useNavigate(); + const location = useLocation(); const { t } = useTranslation(); - const createDestinationTourSteps = useCreateDestinationTourSteps(); - const destinationIdParam = useParams<{ destinationId: string }>(); - const destinationIdModel = selectedId; - const destinationId = modelLoaded - ? destinationIdModel - : destinationIdParam.destinationId; - const { darkMode } = useData(); - const rawConfiguration = location.pathname.includes("create_destination") ? !destinationIdParam.destinationId : false; - - const navigateTo = (url: string) => { - navigate(url); - }; - const { addNotification } = useNotification(); - const [code, setCode] = useState(initialCodeValue); - - const [codeAlert, setCodeAlert] = useState(""); - const [formatType, setFormatType] = useState("dbz-platform"); - const [errorWarning, setErrorWarning] = useState([]); - const [editorSelected, setEditorSelected] = React.useState("form-editor"); - const [isLoading, setIsLoading] = React.useState(false); - const [selectedConnection, setSelectedConnection] = useState(); - - const [properties, setProperties] = useState>( - new Map([["key0", { key: "", value: "" }]]) - ); - const [keyCount, setKeyCount] = React.useState(1); - - const [isConnectionModalOpen, setIsConnectionModalOpen] = useState(false); + const destinationIdParam = useParams<{ destinationId: string }>(); + const destinationId = modelLoaded ? selectedId : destinationIdParam.destinationId; + const descriptor = (location.state as { descriptor?: string } | null)?.descriptor; - const handleConnectionModalToggle = useCallback(() => { - setIsConnectionModalOpen(!isConnectionModalOpen); - }, [isConnectionModalOpen]); + const [isLoading, setIsLoading] = useState(false); + const formRef = useRef(null); - const validate = ajv.compile(connectorSchema); + const descriptorPath = React.useMemo(() => { + if (descriptor) return descriptor.replace(/\.json$/, ""); + if (destinationId) return `server-sink/${destinationId}`; + return null; + }, [descriptor, destinationId]); + + const { + data: connectorSchema, + isLoading: isSchemaLoading, + error: schemaError, + } = useQuery( + ["connectorSchema", descriptorPath], + () => fetchData(`${API_URL}/api/catalog/${descriptorPath}`), + { enabled: !!descriptorPath } + ); - const handleAddProperty = () => { - const newKey = `key${keyCount}`; - setProperties( - (prevProperties) => - new Map(prevProperties.set(newKey, { key: "", value: "" })) + const createNewDestination = async (payload: Record) => { + setIsLoading(true); + const response = await createPost( + `${API_URL}/api/destinations`, + payload as unknown as Payload ); - setKeyCount((prevCount) => prevCount + 1); - }; - - const handleDeleteProperty = (key: string) => { - setProperties((prevProperties) => { - const newProperties = new Map(prevProperties); - newProperties.delete(key); - return newProperties; - }); - }; - - const handlePropertyChange = ( - key: string, - type: "key" | "value", - newValue: string - ) => { - setProperties((prevProperties) => { - const newProperties = new Map(prevProperties); - const property = newProperties.get(key); - if (property) { - if (type === "key") property.key = newValue; - else if (type === "value") property.value = newValue; - newProperties.set(key, property); - } - return newProperties; - }); - }; - - const createNewDestination = async (payload: Payload) => { - const response = await createPost(`${API_URL}/api/destinations`, payload); - if (response.error) { addNotification( "danger", - `Destination creation failed`, - `Failed to create ${(response.data as Destination)?.name}: ${response.error - }` + "Destination creation failed", + `Failed to create ${(response.data as Destination)?.name}: ${response.error}` ); } else { - modelLoaded && onSelection && onSelection(response.data as Destination); + if (modelLoaded) onSelection?.(response.data as Destination); addNotification( "success", - `Create successful`, - `Destination "${(response.data as Destination).name - }" created successfully.` + "Create successful", + `Destination "${(response.data as Destination).name}" created successfully.` ); - !modelLoaded && navigateTo("/destination"); + if (!modelLoaded) navigate("/destination"); } + setIsLoading(false); }; - const handleCreate = async ( - values: Record, - setError: (fieldId: string, error: string | undefined) => void - ) => { - if (editorSelected === "form-editor") { - if (!values["destination-name"]) { - setError("destination-name", "Destination name is required."); - } else if(selectedConnection === undefined){ - setError("connection", t("statusMessage:smartEditor.connectionRequired")); - } - else { - setIsLoading(true); - const errorWarning = [] as string[]; - properties.forEach((value: Properties, key: string) => { - if (value.key === "" || value.value === "") { - errorWarning.push(key); - } - }); - setErrorWarning(errorWarning); - if (errorWarning.length > 0) { - addNotification( - "danger", - `Destination creation failed`, - `Please fill both Key and Value fields for all the properties.` - ); - setIsLoading(false); - return; - } - const payload = { - description: values["description"], - type: destinationId || (code as Payload).type || "", - schema: "schema321", - vaults: [], - ...(selectedConnection ? { connection: selectedConnection } : {}), - config: convertMapToObject(properties), - name: values["destination-name"], - } as unknown as Payload; - await createNewDestination(payload); - setIsLoading(false); - } - } else { - const payload = code; - const isValid = validate(payload); - if (!isValid) { - setCodeAlert(ajv.errorsText(validate.errors)); - return; - } else { - setIsLoading(true); - await createNewDestination(payload); - setIsLoading(false); - } + const renderContent = () => { + if (!destinationId) { + return ( + + Please select a connector from the catalog first. + + ); } - }; - const handleItemClick = ( - event: - | MouseEvent - | React.MouseEvent - | React.KeyboardEvent - ) => { - const id = event.currentTarget.id; - setEditorSelected(id); - }; - - const [isFormatting, setIsFormatting] = useState(false); - - const customControl = ( - : } - aria-label="Execute code" - tooltipProps={{ content: t('statusMessage:smartEditor.autoConvertTooltip') }} - onClick={async () => { - setIsFormatting(true); - await new Promise((resolve) => setTimeout(resolve, 500)); - // formatCode(formatType); - setCode(formatCode("destination", formatType, code)); - setIsFormatting(false); - }} - isVisible={formatType === "properties-file"} - > - {t('statusMessage:smartEditor.autoConvertButton')} - - ); - - const onEditorDidMount = ( - editor: { layout: () => void; focus: () => void }, - monaco: { - editor: { - getModels: () => { - updateOptions: (arg0: { tabSize: number }) => void; - }[]; - }; + if (isSchemaLoading) { + return ( +
+ +
+ +
+ +
+ +
+ ); } - ) => { - editor.layout(); - editor.focus(); - monaco.editor.getModels()[0].updateOptions({ tabSize: 5 }); - }; - const getDisplayCode = () => { - if (!isValidJson(code)) return code as string; - let displayCode = code; - if (typeof code === 'object') { - displayCode = { ...(code as Payload) }; - if (selectedConnection) { - displayCode = { - ...displayCode, - connection: { - id: selectedConnection.id, - name: selectedConnection.name - } - }; - } + if (schemaError) { + return ( + + {schemaError.message} + + ); } - return JSON.stringify(displayCode, null, 2); + + if (!connectorSchema) return null; + + return ( + + ); }; return ( @@ -421,175 +139,58 @@ const CreateDestination: React.FunctionComponent = ({ {!modelLoaded && ( )} - {!rawConfiguration && ( - - - - - - } - text={t("formEditor")} - aria-label={t("formEditor")} - buttonId="form-editor" - isSelected={editorSelected === "form-editor"} - onChange={handleItemClick} - /> - - } - text={t("smartEditor")} - aria-label={t("smartEditor")} - buttonId="smart-editor" - isSelected={editorSelected === "smart-editor"} - onChange={handleItemClick} - /> - - - - - - )} - - - {({ setValue, getValue, setError, values, errors }) => ( - <> - - - {editorSelected === "form-editor" && !rawConfiguration ? ( - + + {renderContent()} + + + + + + + + + + {modelLoaded ? ( + ) : ( - <> - - {codeAlert && ( - - {formatType !== "dbz-platform" && codeAlert} - - - )} -
- - { - try { - if (isValidJson(value)) { - const parsedCode = JSON.parse(value); - setCode(parsedCode); - } else { - setCode(value) - } - } catch (error) { - console.error("Invalid JSON:", error); - } - }} - onEditorDidMount={onEditorDidMount} - /> -
- + )} -
- - - - - - - - {modelLoaded ? ( - - ) : ( - - )} - - - - - - - )} -
- - {!modelLoaded && !rawConfiguration && ( - - )} + + + + ); }; diff --git a/debezium-platform-stage/src/pages/Destination/DestinationCatalog.test.tsx b/debezium-platform-stage/src/pages/Destination/DestinationCatalog.test.tsx index f08b7b73..0276dcdd 100644 --- a/debezium-platform-stage/src/pages/Destination/DestinationCatalog.test.tsx +++ b/debezium-platform-stage/src/pages/Destination/DestinationCatalog.test.tsx @@ -1,8 +1,10 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { screen, fireEvent, waitFor } from "@testing-library/react"; -import { describe, it, expect, vi } from "vitest"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { useQuery } from "react-query"; import { DestinationCatalog } from "./DestinationCatalog"; -import destinationCatalogFixture from "../../__mocks__/data/DestinationCatalog.json"; import { render } from "../../__test__/unit/test-utils"; +import catalogFixture from "../../__fixtures__/catalog.json"; const mockNavigate = vi.fn(); @@ -10,6 +12,14 @@ vi.mock("react-router-dom", () => ({ useNavigate: () => mockNavigate, })); +vi.mock("react-query", async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + useQuery: vi.fn(), + }; +}); + vi.mock("@components/CatalogGrid", () => ({ CatalogGrid: ({ searchResult }: { searchResult: { name: string }[] }) => (
@@ -20,18 +30,65 @@ vi.mock("@components/CatalogGrid", () => ({ ), })); +vi.mock("@components/CatalogSkeleton", () => ({ + __esModule: true, + default: () =>
, +})); + vi.mock("@components/PageTour", () => ({ __esModule: true, default: () => null, })); +// Extract destination catalog from fixture +const destinationCatalogFixture = (catalogFixture.components["server-sink"] ?? []).map((entry) => ({ + ...entry, + role: "destination", +})); + describe("DestinationCatalog", () => { - it("renders heading and static catalog entries", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("shows skeleton while catalog is loading", () => { + vi.mocked(useQuery).mockReturnValue({ + data: undefined, + error: null, + isLoading: true, + refetch: vi.fn(), + } as any); + + render(); + expect(screen.getByTestId("catalog-skeleton")).toBeInTheDocument(); + }); + + it("shows error alert when catalog query fails", () => { + vi.mocked(useQuery).mockReturnValue({ + data: undefined, + error: new Error("network failed"), + isLoading: false, + refetch: vi.fn(), + } as any); + + render(); + expect(screen.getByText("Failed to load destination catalog")).toBeInTheDocument(); + expect(screen.getByText("network failed")).toBeInTheDocument(); + }); + + it("renders catalog grid with connector names when loaded", () => { + vi.mocked(useQuery).mockReturnValue({ + data: destinationCatalogFixture as any, + error: null, + isLoading: false, + refetch: vi.fn(), + } as any); + render(); expect(screen.getByText("Destination catalog")).toBeInTheDocument(); expect(screen.getByTestId("destination-catalog-grid")).toHaveTextContent( - "Amazon Kinesis", + "io.debezium.server.kafka.KafkaChangeConsumer", ); expect( screen.getByText(`${destinationCatalogFixture.length} Items`), @@ -39,6 +96,13 @@ describe("DestinationCatalog", () => { }); it("debounces search to filter visible connectors", async () => { + vi.mocked(useQuery).mockReturnValue({ + data: destinationCatalogFixture as any, + error: null, + isLoading: false, + refetch: vi.fn(), + } as any); + render(); const searchInput = screen.getByPlaceholderText("Search by name"); @@ -47,9 +111,9 @@ describe("DestinationCatalog", () => { await waitFor( () => { expect(screen.getByTestId("destination-catalog-grid")).toHaveTextContent( - "Apache Pulsar", + "io.debezium.server.pulsar.PulsarChangeConsumer", ); - expect(screen.queryByText("Amazon Kinesis")).not.toBeInTheDocument(); + expect(screen.queryByText("io.debezium.server.kafka.KafkaChangeConsumer")).not.toBeInTheDocument(); }, { timeout: 3000 }, ); diff --git a/debezium-platform-stage/src/pages/Destination/DestinationCatalog.tsx b/debezium-platform-stage/src/pages/Destination/DestinationCatalog.tsx index 1635bd9b..ecbfc7da 100644 --- a/debezium-platform-stage/src/pages/Destination/DestinationCatalog.tsx +++ b/debezium-platform-stage/src/pages/Destination/DestinationCatalog.tsx @@ -1,5 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import * as React from "react"; import { + Alert, Button, Content, ContentVariants, @@ -16,14 +18,17 @@ import { import { CogIcon, ListIcon, ThIcon } from "@patternfly/react-icons"; import { useNavigate } from "react-router-dom"; import { CatalogGrid } from "@components/CatalogGrid"; -import * as React from "react"; -import { FunctionComponent, useState } from "react"; -import destinationCatalog from "../../__mocks__/data/DestinationCatalog.json"; +import { useState } from "react"; import { debounce } from "lodash"; import _ from "lodash"; import { useTranslation } from "react-i18next"; import PageTour from "../../components/PageTour"; import { Step } from "react-joyride"; +import { useQuery } from "react-query"; +import { fetchData } from "../../apis/apis"; +import { API_URL } from "../../utils/constants"; +import { Catalog, CatalogApiResponse } from "../../apis/types"; +import CatalogSkeleton from "@components/CatalogSkeleton"; const useDestinationCatalogTourSteps = (): Step[] => { const { t } = useTranslation("tour"); @@ -49,12 +54,26 @@ export interface ISinkProps { sampleProp?: string; } -const DestinationCatalog: FunctionComponent = () => { +const DestinationCatalog: React.FunctionComponent = () => { const navigate = useNavigate(); - const [isSelected, setIsSelected] = useState<"list" | "grid">("grid"); const { t } = useTranslation(); + const [isSelected, setIsSelected] = React.useState<"grid" | "list">("grid"); const [searchQuery, setSearchQuery] = useState(""); - const catalogTourSteps = useDestinationCatalogTourSteps(); + + const { + data: destinationCatalog = [], + error: catalogError, + isLoading: isCatalogLoading, + refetch, + } = useQuery("destinationConnectorCatalog", async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["server-sink"] ?? []).map((entry) => ({ + ...entry, + role: "destination", + })); + }); const searchResult = React.useMemo(() => { if (searchQuery.length === 0) { @@ -63,7 +82,7 @@ const DestinationCatalog: FunctionComponent = () => { return _.filter(destinationCatalog, (o) => o.name.toLowerCase().includes(searchQuery.toLowerCase()) ); - }, [searchQuery]); + }, [searchQuery, destinationCatalog]); const onClear = () => { onSearch?.(""); @@ -102,9 +121,14 @@ const DestinationCatalog: FunctionComponent = () => { ); const onDestinationSelection = (destinationId: string) => { - navigate(`/destination/create_destination/${destinationId}`); + const entry = destinationCatalog.find((c) => c.class === destinationId); + navigate(`/destination/create_destination/${destinationId}`, { + state: { descriptor: entry?.descriptor }, + }); }; + const catalogTourSteps = useDestinationCatalogTourSteps(); + return ( <> @@ -169,13 +193,34 @@ const DestinationCatalog: FunctionComponent = () => { - + + {catalogError ? ( + + refetch()}> + {t("retry", "Retry")} + + } + > + {catalogError.message} + + + ) : isCatalogLoading ? ( + + + + ) : ( + + )} ); diff --git a/debezium-platform-stage/src/pages/Destination/EditDestination.tsx b/debezium-platform-stage/src/pages/Destination/EditDestination.tsx index d0ed9988..edbb1c1c 100644 --- a/debezium-platform-stage/src/pages/Destination/EditDestination.tsx +++ b/debezium-platform-stage/src/pages/Destination/EditDestination.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; import { ActionList, @@ -7,574 +6,300 @@ import { Alert, Button, ButtonType, - FormContextProvider, PageSection, - ToggleGroup, - ToggleGroupItem, - Toolbar, - ToolbarContent, - ToolbarItem, + Skeleton, } from "@patternfly/react-core"; -import { PencilAltIcon, CodeIcon } from "@patternfly/react-icons"; -import { useParams, useSearchParams } from "react-router-dom"; -import { CodeEditor, Language } from "@patternfly/react-code-editor"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { PencilAltIcon } from "@patternfly/react-icons"; +import { useLocation, useParams, useSearchParams } from "react-router-dom"; +import { useEffect, useRef, useState } from "react"; import { - ConnectionConfig, - Destination, - DestinationConfig, editPut, + fetchData, fetchDataTypeTwo, Payload, + Destination, } from "../../apis/apis"; import { API_URL } from "../../utils/constants"; -import { convertMapToObject, getConnectorTypeName } from "../../utils/helpers"; import { useNotification } from "../../appLayout/AppNotificationContext"; -import SourceSinkForm from "@components/SourceSinkForm"; -import Ajv from "ajv"; -import { useTranslation } from "react-i18next"; -import { connectorSchema, initialConnectorSchema } from "@utils/schemas"; -import style from "../../styles/createConnector.module.css" import { PageHeader } from "@patternfly/react-component-groups"; +import { useTranslation } from "react-i18next"; +import { useQuery, useQueryClient } from "react-query"; +import { ConnectorSchema } from "../../apis/types"; +import { getConnectorTypeName } from "../../utils/helpers"; +import CreateSchemaForm, { + CreateSchemaFormHandle, +} from "@components/CreateSchemaForm"; +import SchemaReviewView from "@components/SchemaReviewView"; import EditConfirmationModel from "../components/EditConfirmationModel"; -import CreateConnectionModal from "../components/CreateConnectionModal"; -import { useData } from "@appContext/AppContext"; -import { isValidJson } from "src/hooks/useFormatDetector"; - -const ajv = new Ajv(); - -type Properties = { key: string; value: string }; - -const FormSyncManager: React.FC<{ - getFormValue: (key: string) => string; - setFormValue: (key: string, value: string) => void; - code: any; - setCode: (code: any) => void; - destinationId: string | undefined; - properties: Map; - setProperties: (properties: Map) => void; - setCodeAlert: (alert: string) => void; -}> = ({ - getFormValue, - setFormValue, - code, - setCode, - destinationId, - properties, - setProperties, - setCodeAlert, -}) => { - const validate = ajv.compile(initialConnectorSchema); - // Ref to track the source of the update - const updateSource = useRef<"form" | "code" | null>(null); - - // Update code state when form values change - useEffect(() => { - if (updateSource.current === "code") { - updateSource.current = null; - return; - } - - updateSource.current = "form"; - const configuration = convertMapToObject(properties); - - setCode((prevCode: any) => { - if ( - prevCode.name === getFormValue("destination-name") && - prevCode.description === getFormValue("description") && - JSON.stringify(prevCode.config) === JSON.stringify(configuration) - ) { - return prevCode; - } - - return { - ...prevCode, - config: configuration, - name: getFormValue("destination-name") || "", - description: getFormValue("description") || "", - }; - }); - }, [ - getFormValue("destination-name"), - getFormValue("description"), - properties, - destinationId, - ]); - - // Update form values when code changes - useEffect(() => { - const isValid = validate(code); - if (isValid) { - if (updateSource.current === "form") { - updateSource.current = null; - return; - } - updateSource.current = "code"; - if (code.name !== getFormValue("destination-name")) { - setFormValue( - "destination-name", - typeof code.name === "string" ? code.name : "" - ); - } - if (code.description !== getFormValue("description")) { - setFormValue( - "description", - typeof code.description === "string" ? code.description : "" - ); - } - const currentConfig = convertMapToObject(properties); - if (JSON.stringify(currentConfig) !== JSON.stringify(code.config)) { - const configMap = new Map(); - Object.entries(code.config || {}).forEach(([key, value], index) => { - configMap.set(`key${index}`, { key, value: value as string }); - }); - setProperties(configMap); - } - setCodeAlert(""); - } else { - setCodeAlert(ajv.errorsText(validate.errors)); - } - }, [code]); - - return null; - }; +import { resolveDestinationPageViewMode } from "./destinationPageNavigation"; const EditDestination: React.FunctionComponent = () => { const { t } = useTranslation(); - const { destinationId } = useParams<{ destinationId: string }>(); + const location = useLocation(); + const { destinationId: routeDestinationId } = useParams<{ destinationId: string }>(); + const [searchParams] = useSearchParams(); + const queryStateParam = searchParams.get("state"); + + const [viewMode, setViewMode] = useState(() => + resolveDestinationPageViewMode(location.state, queryStateParam) + ); const [isWarningOpen, setIsWarningOpen] = useState(false); const [pendingSave, setPendingSave] = useState<{ values: Record; setError: (fieldId: string, error: string | undefined) => void; } | null>(null); - // const { navigationCollapsed } = useData(); - const { addNotification } = useNotification(); - const { darkMode } = useData(); - const [editorSelected, setEditorSelected] = React.useState("form-editor"); - const [errorWarning, setErrorWarning] = useState([]); - const [destination, setDestination] = useState(); - const [isFetchLoading, setIsFetchLoading] = useState(true); - const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [properties, setProperties] = useState>( - new Map([["key0", { key: "", value: "" }]]) - ); - const [keyCount, setKeyCount] = useState(1); - - const [selectedConnection, setSelectedConnection] = useState(); - const [searchParams] = useSearchParams(); - const initialState = searchParams.get("state") as "view" | "edit" | null; - const [viewMode, setViewMode] = useState(initialState === "view"); - - - const [code, setCode] = useState({ - name: "", - description: "", - type: "", - schema: "schema123", - connection: {}, - vaults: [], - config: {}, - }); - const [codeAlert, setCodeAlert] = useState(""); - - const [isConnectionModalOpen, setIsConnectionModalOpen] = useState(false); - - - const handleConnectionModalToggle = useCallback(() => { - setIsConnectionModalOpen(!isConnectionModalOpen); - }, [isConnectionModalOpen]); - - const validate = ajv.compile(connectorSchema); - - const setConfigProperties = (configProp: DestinationConfig) => { - let i = 0; - const configMap = new Map(); - for (const config in configProp) { - configMap.set(`key${i}`, { key: config, value: configProp[config] }); - i++; - } - setProperties(configMap); - setKeyCount(configMap.size); - }; + const formRef = useRef(null); + const { addNotification } = useNotification(); + const queryClient = useQueryClient(); - React.useEffect(() => { - const fetchDestinations = async () => { - setIsFetchLoading(true); + useEffect(() => { + setViewMode( + resolveDestinationPageViewMode(location.state, searchParams.get("state")) + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [routeDestinationId, location.key]); + + const { + data: destination, + isLoading: isDestinationLoading, + error: destinationQueryError, + } = useQuery( + ["destination", routeDestinationId], + async () => { const response = await fetchDataTypeTwo( - `${API_URL}/api/destinations/${destinationId}` + `${API_URL}/api/destinations/${routeDestinationId}` ); - if (response.error) { - setError(response.error); - } else { - setDestination(response.data as Destination); - setConfigProperties(response.data?.config ?? { "": "" }); - setSelectedConnection(response.data?.connection as ConnectionConfig); - setCode((prevCode: any) => { - return { - ...prevCode - }; - }); + throw new Error(response.error); } + return response.data as Destination; + }, + { enabled: !!routeDestinationId } + ); - setIsFetchLoading(false); - }; + const connectorType = destination?.type; + const descriptorPath = connectorType + ? `server-sink/${connectorType}` + : null; + + const { + data: connectorSchema, + isLoading: isSchemaLoading, + error: schemaError, + } = useQuery( + ["connectorSchema", descriptorPath], + () => fetchData(`${API_URL}/api/catalog/${descriptorPath}`), + { enabled: !!descriptorPath } + ); - fetchDestinations(); - }, [destinationId, setSelectedConnection]); + const destinationErrorMessage = + destinationQueryError instanceof Error ? destinationQueryError.message : destinationQueryError + ? String(destinationQueryError) + : null; - const handleAddProperty = () => { - const newKey = `key${keyCount}`; - setProperties( - (prevProperties) => - new Map(prevProperties.set(newKey, { key: "", value: "" })) + const handleSchemaSubmit = async (payload: Record) => { + setIsLoading(true); + const response = await editPut( + `${API_URL}/api/destinations/${routeDestinationId}`, + payload as unknown as Payload ); - setKeyCount((prevCount) => prevCount + 1); - }; - - const handleDeleteProperty = (key: string) => { - setProperties((prevProperties) => { - const newProperties = new Map(prevProperties); - newProperties.delete(key); - return newProperties; - }); - }; - - const handlePropertyChange = ( - key: string, - type: "key" | "value", - newValue: string - ) => { - setProperties((prevProperties) => { - const newProperties = new Map(prevProperties); - const property = newProperties.get(key); - if (property) { - if (type === "key") property.key = newValue; - else if (type === "value") property.value = newValue; - newProperties.set(key, property); - } - return newProperties; - }); - }; - - const editDestination = async (payload: Payload) => { - const response = await editPut( - `${API_URL}/api/destinations/${destinationId}`, - payload - ); - if (response.error) { addNotification( "danger", - `Edit failed`, - `Failed to edit ${(response.data as Destination)?.name}: ${response.error - }` + t("statusMessage:edit.failedTitle"), + t("statusMessage:edit.failedDescription", { + val: `${(response.data as Destination)?.name ?? destination?.name}: ${response.error}`, + }) ); } else { addNotification( "success", - `Edit successful`, - `Destination "${(response.data as Destination)?.name - }" edited successfully.` + t("statusMessage:edit.successTitle"), + t("statusMessage:edit.successDescription", { + val: `${(response.data as Destination)?.name ?? destination?.name}`, + }) ); + await queryClient.invalidateQueries(["destination", routeDestinationId]); setViewMode(true); } + setIsLoading(false); }; - const handleEditDestination = async ( + const handleEditConfirm = ( values: Record, setError: (fieldId: string, error: string | undefined) => void ) => { - if (editorSelected === "form-editor") { - if (!values["destination-name"]) { - setError("destination-name", "Destination name is required."); - } else if(selectedConnection === undefined){ - setError("connection", t("statusMessage:smartEditor.connectionRequired")); - }else { - setIsLoading(true); - const errorWarning = [] as string[]; - properties.forEach((value: Properties, key: string) => { - if (value.key === "" || value.value === "") { - errorWarning.push(key); - } - }); - setErrorWarning(errorWarning); - if (errorWarning.length > 0) { - addNotification( - "danger", - `Destination edit failed`, - `Please fill both Key and Value fields for all the properties.` - ); - setIsLoading(false); - return; - } - const payload: Payload = { - name: values["destination-name"], - description: values["description"], - type: destination?.type ?? "", - schema: destination?.schema ?? "", - vaults: destination?.vaults ?? [], - config: convertMapToObject(properties) as DestinationConfig, - ...(selectedConnection ? { connection: selectedConnection } : {}), - }; - await editDestination(payload); - setIsLoading(false); - } - } else { - if (codeAlert) return; - const payload = code; - const isValid = validate(payload); - if (!isValid) { - setCodeAlert(ajv.errorsText(validate.errors)); - return; - } else { - setIsLoading(true); - await editDestination(payload as Payload); - setIsLoading(false); - } - } + void values; + void setError; + formRef.current?.submit(); }; - const handleItemClick = ( - event: - | MouseEvent - | React.MouseEvent - | React.KeyboardEvent - ) => { - const id = event.currentTarget.id; - setEditorSelected(id); + const onSaveClick = () => { + const form = formRef.current; + if (!form?.validate()) { + addNotification( + "danger", + t("statusMessage:edit.failedTitle", { defaultValue: "Update failed" }), + form?.getLastValidationFailureBody() ?? + t("destination:form.validationFailedGeneric", { defaultValue: "Please fill all required fields." }) + ); + return; + } + setPendingSave({ values: {}, setError: () => {} }); + setIsWarningOpen(true); }; - const onEditorDidMount = ( - editor: { layout: () => void; focus: () => void }, - monaco: { - editor: { - getModels: () => { - updateOptions: (arg0: { tabSize: number }) => void; - }[]; - }; + const renderLoading = () => ( + + +
+ +
+ +
+ ); + + const renderContent = () => { + if (!routeDestinationId) { + return ( + + + Missing destination id in the URL. + + + ); } - ) => { - editor.layout(); - editor.focus(); - monaco.editor.getModels()[0].updateOptions({ tabSize: 5 }); - }; - const getDisplayCode = () => { - if (!isValidJson(code)) return code as unknown as string; - let displayCode: any = code; - if (typeof code === 'object') { - displayCode = { ...(code as Payload) }; - if (selectedConnection) { - displayCode = { - ...displayCode, - connection: { - id: selectedConnection.id, - name: selectedConnection.name - } - }; - } + if (isDestinationLoading) { + return renderLoading(); + } + + if (destinationErrorMessage) { + return ( + + + {destinationErrorMessage} + + + ); } - return JSON.stringify(displayCode, null, 2); - }; - if (isFetchLoading) { - return
{t("loading")}
; - } + if (!destination) { + return null; + } + + if (isSchemaLoading) { + return renderLoading(); + } + + if (schemaError) { + return ( + + + {schemaError.message} + + + ); + } - if (error) { - return
Error: {error}
; - } + if (!connectorSchema) { + return null; + } + + return ( + + {viewMode ? ( + + ) : ( + + )} + + ); + }; return ( <> - {viewMode ? ( - } - onClick={() => { setViewMode(false); }}> + } - // icon={ } /> ) : ( {t("edit")} {destination?.name}} + title={ + <> + {t("edit")} {destination?.name} + + } subtitle={t("destination:edit.description")} /> )} + {renderContent()} + + {!viewMode && destination && connectorSchema && ( + + + + + + + + + + + + + )} - {!viewMode && ( - - - - - - } - text={t("formEditor")} - aria-label={t("formEditor")} - buttonId="form-editor" - isSelected={editorSelected === "form-editor"} - onChange={handleItemClick} - /> - - } - text={t("smartEditor")} - aria-label={t("smartEditor")} - buttonId="smart-editor" - isSelected={editorSelected === "smart-editor"} - onChange={handleItemClick} - /> - - - - - )} - - - {({ setValue, getValue, setError, values, errors }) => ( - <> - - - {editorSelected === "form-editor" ? ( - - ) : ( - <> - {codeAlert && ( - - )} -
- { - try { - const parsedCode = JSON.parse(value); - if (parsedCode.type !== destination?.type) { - setCodeAlert( - "Connector type cannot be changed in the edit flow." - ); - } else { - setCode(parsedCode); - } - } catch (error) { - console.error("Invalid JSON:", error); - } - }} - onEditorDidMount={onEditorDidMount} - /> -
- - - )} -
- {!viewMode && ( - - - - - - - - - - - - )} - - )} -
- - ); }; diff --git a/debezium-platform-stage/src/pages/Destination/destinationPageNavigation.ts b/debezium-platform-stage/src/pages/Destination/destinationPageNavigation.ts new file mode 100644 index 00000000..03745450 --- /dev/null +++ b/debezium-platform-stage/src/pages/Destination/destinationPageNavigation.ts @@ -0,0 +1,24 @@ + +export type DestinationPageLocationState = { + mode: "view" | "edit"; +}; + +/** Use with navigate(url, { state: destinationPageNavState.view }) */ +export const destinationPageNavState = { + view: { mode: "view" } satisfies DestinationPageLocationState, + edit: { mode: "edit" } satisfies DestinationPageLocationState, +}; + +export function resolveDestinationPageViewMode( + locationState: unknown, + queryState: string | null +): boolean { + const s = locationState as DestinationPageLocationState | null | undefined; + if (s?.mode === "view") return true; + if (s?.mode === "edit") return false; + + if (queryState === "view") return true; + if (queryState === "edit") return false; + + return false; +} diff --git a/debezium-platform-stage/src/pages/Destination/index.ts b/debezium-platform-stage/src/pages/Destination/index.ts index 4957401d..6a131f49 100644 --- a/debezium-platform-stage/src/pages/Destination/index.ts +++ b/debezium-platform-stage/src/pages/Destination/index.ts @@ -2,3 +2,8 @@ export * from './Destinations' export * from './DestinationCatalog' export * from './CreateDestination' export * from './EditDestination' +export { + resolveDestinationPageViewMode, + destinationPageNavState, + type DestinationPageLocationState, +} from './destinationPageNavigation' diff --git a/debezium-platform-stage/src/pages/Source/CreateSource.test.tsx b/debezium-platform-stage/src/pages/Source/CreateSource.test.tsx index ed627f3b..ae1b2863 100644 --- a/debezium-platform-stage/src/pages/Source/CreateSource.test.tsx +++ b/debezium-platform-stage/src/pages/Source/CreateSource.test.tsx @@ -33,7 +33,7 @@ vi.mock("../../appLayout/AppNotificationContext", () => ({ useNotification: vi.fn(), })); -vi.mock("@components/CreateSourceSchemaForm", () => ({ +vi.mock("@components/CreateSchemaForm", () => ({ __esModule: true, default: () =>
, })); diff --git a/debezium-platform-stage/src/pages/Source/CreateSource.tsx b/debezium-platform-stage/src/pages/Source/CreateSource.tsx index 4e6e568a..b98864c2 100644 --- a/debezium-platform-stage/src/pages/Source/CreateSource.tsx +++ b/debezium-platform-stage/src/pages/Source/CreateSource.tsx @@ -19,9 +19,9 @@ import { useTranslation } from "react-i18next"; import { useQuery } from "react-query"; import { fetchData } from "../../apis/apis"; import { ConnectorSchema } from "../../apis/types"; -import CreateSourceSchemaForm, { - CreateSourceSchemaFormHandle, -} from "@components/CreateSourceSchemaForm"; +import CreateSchemaForm, { + CreateSchemaFormHandle, +} from "@components/CreateSchemaForm"; interface CreateSourceProps { modelLoaded?: boolean; @@ -47,7 +47,7 @@ const CreateSource: React.FunctionComponent = ({ const descriptor = (location.state as { descriptor?: string } | null)?.descriptor; const [isLoading, setIsLoading] = useState(false); - const formRef = useRef(null); + const formRef = useRef(null); const descriptorPath = React.useMemo(() => { if (descriptor) return descriptor.replace(/\.json$/, ""); @@ -123,7 +123,7 @@ const CreateSource: React.FunctionComponent = ({ if (!connectorSchema) return null; return ( - { } | null>(null); const [isLoading, setIsLoading] = useState(false); - const formRef = useRef(null); + const formRef = useRef(null); const { addNotification } = useNotification(); const queryClient = useQueryClient(); @@ -211,13 +211,13 @@ const EditSource: React.FunctionComponent = () => { return ( {viewMode ? ( - ) : ( - ({ + ...entry, + role: "source", +})); const mockNavigate = vi.fn();