From 1151310df1e406275ac01e888b9a5572a2e273fd Mon Sep 17 00:00:00 2001 From: indraraj Date: Thu, 2 Apr 2026 14:10:53 +0530 Subject: [PATCH 1/2] debezium/dbz#1207 Update the catalog to integrate the catalog API call for source types Signed-off-by: indraraj --- .../src/__fixtures__/catalog.json | 250 ++++++++++ .../src/__fixtures__/schema.json | 435 ++++++++++++++++++ .../__mocks__/data/DestinationCatalog.json | 130 +++--- debezium-platform-stage/src/apis/types.tsx | 31 +- .../src/assets/ibm-db2.png | Bin 0 -> 2828 bytes .../src/components/CatalogGrid.css | 2 +- .../src/components/CatalogGrid.tsx | 14 +- .../src/components/ComponentImage.tsx | 5 + .../src/components/ConnectionCatalogGrid.tsx | 14 +- .../src/components/ConnectionTable.tsx | 7 +- .../src/components/SourceSinkForm.tsx | 31 +- .../pipelineDesigner/PipelineSourceModel.tsx | 77 +++- .../src/hooks/useConnectorForm.tsx | 3 +- .../src/pages/Connection/Connections.tsx | 21 +- .../pages/Connection/ConnectionsCatalog.tsx | 26 +- .../src/pages/Connection/CreateConnection.tsx | 6 +- .../pages/Destination/CreateDestination.tsx | 7 +- .../src/pages/Source/CreateSource.tsx | 6 +- .../src/pages/Source/SourceCatalog.tsx | 86 +++- debezium-platform-stage/src/utils/helpers.ts | 10 +- 20 files changed, 1022 insertions(+), 139 deletions(-) create mode 100644 debezium-platform-stage/src/__fixtures__/catalog.json create mode 100644 debezium-platform-stage/src/__fixtures__/schema.json create mode 100644 debezium-platform-stage/src/assets/ibm-db2.png diff --git a/debezium-platform-stage/src/__fixtures__/catalog.json b/debezium-platform-stage/src/__fixtures__/catalog.json new file mode 100644 index 00000000..b9a610d9 --- /dev/null +++ b/debezium-platform-stage/src/__fixtures__/catalog.json @@ -0,0 +1,250 @@ +{ + "schemaVersion": "1.0", + "build": { + "version": "3.5.0-SNAPSHOT", + "timestamp": "2026-03-31T04:21:50Z", + "sourceRepository": "debezium/debezium", + "sourceCommit": "3b7009d972", + "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" + } + ] + } + } \ No newline at end of file diff --git a/debezium-platform-stage/src/__fixtures__/schema.json b/debezium-platform-stage/src/__fixtures__/schema.json new file mode 100644 index 00000000..bd6cc7af --- /dev/null +++ b/debezium-platform-stage/src/__fixtures__/schema.json @@ -0,0 +1,435 @@ +[ + { + "type": "POSTGRESQL", + "schema": { + "title": "PostgreSQL connection properties", + "description": "PostgreSQL connection properties", + "type": "object", + "required": [ + "hostname", + "port", + "username", + "password", + "database" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "hostname": { + "type": "string", + "title": "The hostname of the database" + }, + "port": { + "type": "integer", + "title": "The port of the database" + }, + "username": { + "type": "string", + "title": "Username to connect to the database" + }, + "password": { + "type": "string", + "title": "Password to connect to the database" + }, + "database": { + "type": "string", + "title": "The name of the database" + } + } + } + }, + { + "type": "ORACLE", + "schema": { + "title": "Oracle connection properties", + "description": "Oracle connection properties", + "type": "object", + "required": [ + "hostname", + "port", + "username", + "password", + "database" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "hostname": { + "type": "string", + "title": "The hostname of the database" + }, + "port": { + "type": "integer", + "title": "The port of the database" + }, + "username": { + "type": "string", + "title": "Username to connect to the database" + }, + "password": { + "type": "string", + "title": "Password to connect to the database" + }, + "database": { + "type": "string", + "title": "The name of the database" + } + } + } + }, + { + "type": "MYSQL", + "schema": { + "title": "MySQL connection properties", + "description": "MySQL connection properties", + "type": "object", + "required": [ + "hostname", + "port", + "username", + "password", + "server.id" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "hostname": { + "type": "string", + "title": "The hostname of the database" + }, + "port": { + "type": "integer", + "title": "The port of the database" + }, + "username": { + "type": "string", + "title": "Username to connect to the database" + }, + "password": { + "type": "string", + "title": "Password to connect to the database" + }, + "server.id": { + "type": "string", + "title": "The name of the database" + } + } + } + }, + { + "type": "MARIADB", + "schema": { + "title": "MariaDB connection properties", + "description": "MariaDB connection properties", + "type": "object", + "required": [ + "hostname", + "port", + "username", + "password", + "database" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "hostname": { + "type": "string", + "title": "The hostname of the database" + }, + "port": { + "type": "integer", + "title": "The port of the database" + }, + "username": { + "type": "string", + "title": "Username to connect to the database" + }, + "password": { + "type": "string", + "title": "Password to connect to the database" + }, + "database": { + "type": "string", + "title": "The name of the database" + } + } + } + }, + { + "type": "SQLSERVER", + "schema": { + "title": "Microsoft SQL Server connection properties", + "description": "Microsoft SQL Server connection properties", + "type": "object", + "required": [ + "hostname", + "port", + "username", + "password", + "database" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "hostname": { + "type": "string", + "title": "The hostname of the database" + }, + "port": { + "type": "integer", + "title": "The port of the database" + }, + "username": { + "type": "string", + "title": "Username to connect to the database" + }, + "password": { + "type": "string", + "title": "Password to connect to the database" + }, + "database": { + "type": "string", + "title": "The name of the database" + } + } + } + }, + { + "type": "KAFKA", + "schema": { + "title": "Kafka connection properties", + "description": "Kafka connection properties", + "type": "object", + "required": [ + "bootstrap.servers" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "bootstrap.servers": { + "type": "list", + "title": "List of \"hostname:port\" pairs that address one or more (even all) of the brokers." + } + } + } + }, + { + "type": "INFINISPAN", + "schema": { + "title": "Infinispan connection properties", + "description": "Infinispan connection properties", + "type": "object", + "required": [ + "server.host", + "cache" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "server.host": { + "type": "string", + "title": "The hostname of the Infinispan server" + }, + "server.port": { + "type": "integer", + "title": "The port of the Infinispan server" + }, + "cache": { + "type": "string", + "title": "The name of the Infinispan cache" + }, + "user": { + "type": "string", + "title": "Username to connect to the Infinispan server" + }, + "password": { + "type": "string", + "title": "Password to connect to the Infinispan server" + } + } + } + }, + { + "type": "PRAVEGA", + "schema": { + "title": "Pravega connection properties", + "description": "Pravega connection properties", + "type": "object", + "required": [ + "controller.uri", + "scope" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "controller.uri": { + "type": "string", + "title": "The URI of the Pravega controller" + }, + "scope": { + "type": "string", + "title": "The Pravega scope that contains the target streams" + } + } + } + }, + { + "type": "REDIS", + "schema": { + "title": "Redis connection properties", + "description": "Properties required to connect to a Redis server, including optional authentication and SSL", + "type": "object", + "required": [ + "host", + "port" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "host": { + "type": "string", + "title": "Redis server hostname or IP address" + }, + "port": { + "type": "integer", + "title": "Redis server port" + }, + "username": { + "type": "string", + "title": "Username for Redis ACL authentication (optional)" + }, + "password": { + "type": "string", + "title": "Password for authentication (optional)" + }, + "use.ssl": { + "type": "string", + "enum": [ + "true", + "false" + ], + "title": "Use SSL/TLS to connect to Redis (optional)" + } + } + } + }, + { + "type": "NATS_STREAMING", + "schema": { + "title": "NATS Streaming connection properties", + "description": "NATS Streaming connection properties", + "type": "object", + "required": [ + "url", + "cluster.id", + "client.id" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "url": { + "type": "string", + "title": "NATS Streaming server URL (e.g. nats://localhost:4222)" + }, + "cluster.id": { + "type": "string", + "title": "NATS Streaming cluster ID" + }, + "client.id": { + "type": "string", + "title": "NATS Streaming client ID" + }, + "username": { + "type": "string", + "title": "Username for NATS Streaming authentication" + }, + "password": { + "type": "string", + "title": "Password for NATS Streaming authentication" + } + } + } + }, + { + "type": "QDRANT", + "schema": { + "title": "Qdrant connection properties", + "description": "Qdrant connection properties", + "type": "object", + "required": [ + "host", + "port" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "host": { + "type": "string", + "title": "The hostname or IP address of the Qdrant instance" + }, + "port": { + "type": "integer", + "title": "The port of the Qdrant instance (default: 6333)" + }, + "api.key": { + "type": "string", + "title": "Optional API key for authentication" + } + } + } + }, + { + "type": "RABBITMQ", + "schema": { + "title": "RabbitMQ connection properties", + "description": "RabbitMQ connection properties", + "type": "object", + "required": [ + "host", + "port", + "username", + "password" + ], + "additionalProperties": { + "type": "string" + }, + "properties": { + "host": { + "type": "string", + "title": "RabbitMQ broker host" + }, + "port": { + "type": "integer", + "title": "RabbitMQ broker port" + }, + "username": { + "type": "string", + "title": "Username for RabbitMQ authentication" + }, + "password": { + "type": "string", + "title": "Password for RabbitMQ authentication" + }, + "virtualHost": { + "type": "string", + "title": "Optional virtual host to connect to" + }, + "ssl.enabled": { + "type": "boolean", + "title": "Enable TLS for the connection" + } + } + } + } + ] \ 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 index 97bef057..5b4f2248 100644 --- a/debezium-platform-stage/src/__mocks__/data/DestinationCatalog.json +++ b/debezium-platform-stage/src/__mocks__/data/DestinationCatalog.json @@ -1,114 +1,114 @@ [ { + "class": "kinesis", "name": "Amazon Kinesis", - "id": "kinesis", - "type": "kinesis", - "role": "destination", - "description": "Streams change events to Amazon Kinesis, a fully managed, distributed streaming platform." + "description": "Streams change events to Amazon Kinesis, a fully managed, distributed streaming platform.", + "descriptor": "", + "role": "destination" }, { + "class": "pulsar", "name": "Apache Pulsar", - "id": "pulsar", - "type": "pulsar", - "role": "destination", - "description": "Streams CDC events into Apache Pulsar, a distributed messaging and event-streaming platform offering low-latency and high-throughput." + "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", - "id": "eventhubs", - "type": "eventhubs", - "role": "destination", - "description": "Sends change events to Azure Event Hub, a real-time data ingestion service optimized for streaming millions of events per second." + "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", - "id": "http", - "type": "http", - "role": "destination", - "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." + "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", - "id": "infinispan", - "type": "infinispan", - "role": "destination", - "description": "Propagates events to Infinispan, a distributed in-memory data grid providing high availability and scalability for data replication." + "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", - "id": "kafka", - "type": "kafka", - "role": "destination", - "description": "Streams real-time change events into Apache Kafka, a distributed platform for building streaming data pipelines and real-time applications." + "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", - "id": "milvus", - "type": "milvus", - "role": "destination", - "description": "Streams change events to Milvus, a vector database for scalable similarity search and vector embedding." + "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", - "id": "nats-streaming", - "type": "nats-streaming", - "role": "destination", - "description": "Sends change events to NATS Streaming, a messaging system designed for distributed systems and microservices architectures." + "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", - "id": "nats-jetstream", - "type": "nats-jetstream", - "role": "destination", - "description": "Sends change events to NATS JetStream, a messaging system designed for distributed systems and microservices architectures." + "description": "Sends change events to NATS JetStream, a messaging system designed for distributed systems and microservices architectures.", + "descriptor": "", + "role": "destination" }, { + "class": "pravega", "name": "Pravega", - "id": "pravega", - "type": "pravega", - "role": "destination", - "description": "Streams change events to Pravega, a scalable storage system for distributed data streams, offering a tiered storage model." + "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", - "id": "pubsub", - "type": "pubsub", - "role": "destination", - "description": "Sends real-time change events to Google Pub/Sub, a fully managed messaging service for building event-driven systems and real-time analytics." + "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", - "id": "pubsublite", - "type": "pubsublite", - "role": "destination", - "description": "Streams events to Google Pub/Sub Lite, a cost-effective, low-latency message delivery service for simpler use cases." + "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", - "id": "qdrant", - "type": "qdrant", - "role": "destination", - "description": "Streams change events to Qdrant, a vector database for scalable similarity search and vector embedding." + "description": "Streams change events to Qdrant, a vector database for scalable similarity search and vector embedding.", + "descriptor": "", + "role": "destination" }, { + "class": "rabbitmq", "name": "RabbitMQ", - "id": "rabbitmq", - "type": "rabbitmq", - "role": "destination", - "description": "Publishes change events to RabbitMQ, an open-source message broker supporting various messaging protocols for inter-service communication." + "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)", - "id": "redis", - "type": "redis", - "role": "destination", - "description": "Streams change events to Redis Streams, a high-performance, open-source, in-memory data structure store." + "description": "Streams change events to Redis Streams, a high-performance, open-source, in-memory data structure store.", + "descriptor": "", + "role": "destination" }, { + "class": "rocketmq", "name": "RocketMQ", - "id": "rocketmq", - "type": "rocketmq", - "role": "destination", - "description": "Streams change events to Apache RocketMQ, a high-throughput, low-latency messaging platform." + "description": "Streams change events to Apache RocketMQ, a high-throughput, low-latency messaging platform.", + "descriptor": "", + "role": "destination" } -] \ No newline at end of file +] diff --git a/debezium-platform-stage/src/apis/types.tsx b/debezium-platform-stage/src/apis/types.tsx index bc2475f8..1862680f 100644 --- a/debezium-platform-stage/src/apis/types.tsx +++ b/debezium-platform-stage/src/apis/types.tsx @@ -1,7 +1,26 @@ -export type Catalog = { - type: string; - description: string; +export interface CatalogComponentEntry { + class: string; name: string; - id: string; - role: string; -}; \ No newline at end of file + description: string; + descriptor: string; +} + +export type Catalog = CatalogComponentEntry & { role: string }; + +export interface CatalogApiResponse { + schemaVersion: string; + build: { + version: string; + timestamp: string; + sourceRepository: string; + sourceCommit: string; + sourceBranch: string; + }; + components: { + converter: CatalogComponentEntry[]; + "custom-converter": CatalogComponentEntry[]; + "sink-connector": CatalogComponentEntry[]; + "source-connector": CatalogComponentEntry[]; + transformation: CatalogComponentEntry[]; + }; +} diff --git a/debezium-platform-stage/src/assets/ibm-db2.png b/debezium-platform-stage/src/assets/ibm-db2.png new file mode 100644 index 0000000000000000000000000000000000000000..957b9fe2bdede3e590549f2a30b448f7883efdc6 GIT binary patch literal 2828 zcma)8`#aN(1D%)R&F%f>PD_Z$i^wIHQLed8DBn_eE3cw8#Aa;ng~VuXStys>DVK=0 zA(~<&cgFCteO+cQ+iY&1Z-2u3Jm)#*{BoZ2Jm;sA>h9(senRmC2m}&$ylU$K0tvu> z`BV|XUp$)PDE>=cySsSV0RRB<13=yY=neq734pu+&~*Uh1AtrskULP+0-W#%J`Dn( z`@qXafW-lNCV=W50A2trZvfOKKqV9q3j%H=0`_r0<9DF32UuePeWSqq8o=fO`~zTU z4ls@cKsEr#2`DE4>Gc5NCor`DNdFD^qyrjoz&i~{ssfNDz{4Ei5(0=Q27XKf_;%pc zdtgyq!yg1Xq3vjU#VhhP^K~$?#7z3KO8l7?ogl5%aA_DU*rx@{2LPsZ!cvmZHQ%Oj zYLgY!$a|?p$!*XvA@}JSk%WJXs|_z=mJgWDe7}&*SDgg@Ox`xhe{u3R#XHS@pT!aa zsKa&&C~+g-E_$$(UPD@Ev6&GcQ$#K zPP=YekjqCtd+& z5lc(#IMsx(bte16dctf%6SbA{mA=zk?CVK;=uDMHwDs&RPPT`ChCx3$Wk!{o>`k@7 zt^Qm2f|h@-VA=`dvVpg!Ybz_`ky5nO&F(=tp09m*J1#A%W|{!rNPC-_Oso?h!Rzn3KDP2$7|8S^pM_8m zJ%e0V?39V~%K%O4t1j>$7u^5A`ewM&+_42ZSdmo`Kd}j>mf=U^RMCEQ9>t^M*pF%l z(B`lqjw_86D@wQ8H&VI$LNa=(3DbX+Xojlj5Fkf9H}yU@x(VBIu_2IZF+L+~?zj3j zhxxL=t=i!!;uzVSYVPqOjNXK4z*6uA?y4^oF4byDd~^lVEP~vHW5U^ot~{_z)0E!a zXSm)Lrs1)CeDx1uD3NY{z%eSU3-o)!$xWy$KKz#AQA@dB89j|mbI=~9%hMu&I9?9O-K%$ z_9rwINgOoD6XlEy!M&g9Y>hZJ(8qA#Kot7Oab~wd4c*KVtl>vGO}ULub=QM>r8f4# zG1>dFO5pOSc|zr`g5M%Oh**cet&UTbp-E+Kmn-vc$;od#d%N9LbISg1uN72obc@n) zy2U(?D0gst^2DR1*x%-yjULj*EgQToUMXFnDZxYr3m-UWg+C`P7x(EX2L)rN`ERBb zPlwS|+s!>cr8~>&w_e+i$xGKfXL#( zE}Fh1D+>?LEAb`D;03#qKX)PSc{YX5v&PXt*`rJPtWm|heR}ky3*|#RJed3_-)3OM z_2y-`G;|TE&`H87JNY5@CQ%Y9-_gJE%~1ROefgYlaxYPiWfA4Ddin?Qy(^MZ`d4-Q z-wJl6TrfjDjk4V<*=f=O-u^@)6iVa-vG2=VJihWRwLb{kj}~6Yko-IIDsx$erdk0p z{vq!D8X}%PZBpsFq!I_m6Y6>(%&xW+RGWdIA>3>|CWBnP&2*I+&%zrPp8WukCN=Of z_-gap;l^{@PL{#@e)$ATliQH~sD>vIXT4>QV@g>$fI|vizIRuO6-}@0@8G4-}UIo@BpqH{Sbb`Mms2A{TScdq4V3!b{@Vu0Zk5mei(vcTdk$5xTWT zdzDW=RA>LqC1}25aWD;?i!f!exwt6bmEetljeO{(&YmPnl;rH|C?daETz?r{(3=r+Z{SyMo#Opr$Q*1n-<73}vaE9oB2 zKSo$Ou)!e2{ruiRvQGXAJ4%r`UbT#G_5DEs8fKa0jPQGmgm<7%wX8maVI5g~t`sco8baJhG$e;H298Jo#nG}j3P(+%Exof1>Brm5%K7theI@W$8rPSUy} zHSSh5cy$x_T+x1|GB}IE-n(ge9rKL!a`*=#g}jrUzNvF0YzbXF?B7>CEBdb9%y|q4 zH`BxbKM^)xltvnHretKE=th&D{2vfe8Il_)wxN?36MFPqU)#%oz1O+ycFcBI*wo@n-i+MB|Bxcx&6O6c_N&*6gyV1qT!Ts8UD2 zQW`~{_>MjYRxzy=GuPbNS-A0VVd=PqMC|M?u2dklA+Bw}_N60cZgpzlLFW2~cg8HX zNJJ&klN4)h$TKbd^7Y_7gH+M;(_bFVBaeN^7Sq@?V?g)bidrC6_b&{`c{<;_IHCQI z-P4s}i5!wzBkxH>1czZeo%2|4(-Yw~s7W%|8X%zCbm6kYO$D%|XEt)Yku{d~;l(6U m%;BxKW`gGo6CE=FtlIy7UXE>W?OIVpYVUB2+zR) literal 0 HcmV?d00001 diff --git a/debezium-platform-stage/src/components/CatalogGrid.css b/debezium-platform-stage/src/components/CatalogGrid.css index 444412b3..d97992fe 100644 --- a/debezium-platform-stage/src/components/CatalogGrid.css +++ b/debezium-platform-stage/src/components/CatalogGrid.css @@ -4,7 +4,7 @@ } .catalog-grid-card-source .pf-v6-c-card__body{ - height: 80px; + height: 70px; overflow-y: auto; } diff --git a/debezium-platform-stage/src/components/CatalogGrid.tsx b/debezium-platform-stage/src/components/CatalogGrid.tsx index ad77834a..3cf88ab1 100644 --- a/debezium-platform-stage/src/components/CatalogGrid.tsx +++ b/debezium-platform-stage/src/components/CatalogGrid.tsx @@ -89,15 +89,15 @@ const CatalogGrid: React.FunctionComponent = ({ */} {searchResult.map((item) => ( - + onCardClick(item.id), + onClickAction: () => onCardClick(item.class), selectableActionAriaLabelledby: `catalog-card-id-${item.name}`, }} > - + {item.name} @@ -179,8 +179,8 @@ const CatalogGrid: React.FunctionComponent = ({ {searchResult.map((item) => ( = ({ isFilled={false} style={{ minWidth: "80px", + maxWidth: "80px", }} > - + , @@ -219,6 +220,7 @@ const CatalogGrid: React.FunctionComponent = ({ isFilled={false} style={{ minWidth: "80px", + maxWidth: "80px", // display: "flex", // justifyContent: "center", }} diff --git a/debezium-platform-stage/src/components/ComponentImage.tsx b/debezium-platform-stage/src/components/ComponentImage.tsx index 6d7e9c49..34482cef 100644 --- a/debezium-platform-stage/src/components/ComponentImage.tsx +++ b/debezium-platform-stage/src/components/ComponentImage.tsx @@ -26,6 +26,7 @@ import milvus from "../assets/milvus.png"; import qdrant from "../assets/qdrant.png"; import redis from "../assets/redis.png"; import http from "../assets/http2.png"; +import db2 from "../assets/ibm-db2.png"; interface ConnectorImageProps { connectorType: string; @@ -135,6 +136,10 @@ const ConnectorImage: React.FC = ({ altText = "HTTP"; src = http; break; + case connectorType.includes("db2"): + altText = "IBM Db2"; + src = db2; + break; default: altText = "Debezium"; src = dbz; diff --git a/debezium-platform-stage/src/components/ConnectionCatalogGrid.tsx b/debezium-platform-stage/src/components/ConnectionCatalogGrid.tsx index 408fa5c6..2105d334 100644 --- a/debezium-platform-stage/src/components/ConnectionCatalogGrid.tsx +++ b/debezium-platform-stage/src/components/ConnectionCatalogGrid.tsx @@ -44,11 +44,11 @@ const ConnectionCatalogGrid: React.FunctionComponent ( - + onCardClick(item.id, item.role)} + onClick={() => onCardClick(item.class, item.role)} > // }} > - + {item.name} @@ -94,9 +94,9 @@ const ConnectionCatalogGrid: React.FunctionComponent ( onCardClick(item.id, item.role)} + id={item.class} + key={item.class} + onClick={() => onCardClick(item.class, item.role)} style={{ cursor: "pointer" }} > @@ -109,7 +109,7 @@ const ConnectionCatalogGrid: React.FunctionComponent - + , diff --git a/debezium-platform-stage/src/components/ConnectionTable.tsx b/debezium-platform-stage/src/components/ConnectionTable.tsx index 498dab8d..bfbcc24f 100644 --- a/debezium-platform-stage/src/components/ConnectionTable.tsx +++ b/debezium-platform-stage/src/components/ConnectionTable.tsx @@ -37,6 +37,7 @@ import { import { getConnectionRole, getConnectorTypeName } from "../utils/helpers"; import ConnectorImage from "./ComponentImage"; import { API_URL } from "../utils/constants"; +import { Catalog } from "../apis/types"; import { useNavigate } from "react-router-dom"; import { useNotification } from "../appLayout/AppNotificationContext"; import { useDeleteData } from "src/apis"; @@ -48,6 +49,7 @@ interface IConnectionTableProps { data: ConnectionsApiResponse; sourceList: Source[]; destinationList: Destination[]; + catalog: Catalog[]; onClear: () => void; } @@ -65,6 +67,7 @@ const ConnectionTable: React.FunctionComponent = ({ data, sourceList, destinationList, + catalog, onClear, }) => { const { t } = useTranslation(); @@ -181,11 +184,11 @@ const ConnectionTable: React.FunctionComponent = ({ - {getConnectorTypeName(instance.type.toLowerCase())} {getConnectionRole(instance.type.toLowerCase())} + {getConnectorTypeName(instance.type.toLowerCase())} {getConnectionRole(instance.type.toLowerCase(), catalog)} - + { - return connections.filter((connection) => connection.type.toLowerCase() === connectorId.toLowerCase()).map((connection) => ({ + 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: , @@ -153,6 +159,21 @@ const SourceSinkForm = ({ } }, [signalCollectionName]); + const { data: sourceCatalog = [] } = useQuery( + "sourceConnectorCatalog", + async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["source-connector"] ?? []).map((e) => ({ + ...e, + role: "source", + })); + } + ); + + const catalog: Catalog[] = [...sourceCatalog, ...destinationCatalog]; + const { isLoading: isConnectionsLoading, } = useQuery( @@ -161,13 +182,11 @@ const SourceSinkForm = ({ { refetchInterval: 70000, onSuccess: (data) => { - // Persist filters across polling refreshes by deriving from latest data const withRole = data.map((conn) => ({ ...conn, - role: getConnectionRole(conn.type.toLowerCase()) || "", + role: getConnectionRole(conn.type.toLowerCase(), catalog) || "", })); - const result = withRole; - setConnections(result); + setConnections(withRole); }, } ); @@ -596,7 +615,7 @@ const SourceSinkForm = ({ text: {t("source:create.dataTableTitle", { val: getConnectorTypeName(dataType || ConnectorId || "") })}, id: `field-group-data-table-id`, }} - titleDescription={t("source:create.dataTableDescription", editFlow ? { val: DatabaseItemsList[dataType?.split(".")[3] as keyof typeof DatabaseItemsList].join(" and ") } : { val: DatabaseItemsList[ConnectorId as keyof typeof DatabaseItemsList].join(" and ") })} + titleDescription={t("source:create.dataTableDescription", editFlow ? { val: DatabaseItemsList[dataType?.split(".")[3] as keyof typeof DatabaseItemsList]?.join(" and ") } : { val: DatabaseItemsList[ConnectorId?.split(".")?.[3] as keyof typeof DatabaseItemsList]?.join(" and ") })} /> } > diff --git a/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx b/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx index 53410708..7cac8653 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx @@ -1,4 +1,6 @@ import { + Alert, + Button, Card, CardHeader, CardTitle, @@ -6,7 +8,11 @@ import { Divider, Flex, FlexItem, + Gallery, + GalleryItem, Content, + PageSection, + Skeleton, } from "@patternfly/react-core"; import React, { useCallback, useState } from "react"; import { fetchData, Source } from "../../apis/apis"; @@ -15,12 +21,34 @@ import { useQuery } from "react-query"; import SourceDestinationSelectionList from "../SourceDestinationSelectionList"; import { CatalogGrid } from "@components/CatalogGrid"; import { CreateSource } from "@sourcePage/CreateSource"; -import sourceCatalog from "../../__mocks__/data/SourceCatalog.json"; +import { Catalog, CatalogApiResponse } from "../../apis/types"; type PipelineSourceModelProps = { onSourceSelection: (source: Source) => void; }; +const CatalogSkeleton: React.FC = () => ( + + {Array.from({ length: 6 }).map((_, i) => ( + + + + + + + + + + +
+ +
+
+
+ ))} +
+); + const PipelineSourceModel: React.FC = ({ onSourceSelection, }) => { @@ -37,6 +65,21 @@ const PipelineSourceModel: React.FC = ({ fetchData(`${API_URL}/api/sources`) ); + const { + data: sourceCatalog = [], + error: catalogError, + isLoading: isCatalogLoading, + refetch: refetchCatalog, + } = useQuery("sourceConnectorCatalog", async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["source-connector"] ?? []).map((entry) => ({ + ...entry, + role: "source", + })); + }); + const [userSelection, setUserSelection] = useState(null); const isCreateChecked = userSelection !== null @@ -130,13 +173,31 @@ const PipelineSourceModel: React.FC = ({ onSelection={onSourceSelection} /> ) : selectedSource === "" ? ( - + catalogError ? ( + + refetchCatalog()}> + Retry + + } + > + {catalogError.message} + + + ) : isCatalogLoading ? ( + + ) : ( + + ) ) : ( = () => { } ); - // Compute filtered and sorted results + const { data: sourceCatalog = [] } = useQuery( + "sourceConnectorCatalog", + async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["source-connector"] ?? []).map((e) => ({ + ...e, + role: "source", + })); + } + ); + + const catalog: Catalog[] = [...sourceCatalog, ...destinationCatalog]; + const searchResult = React.useMemo(() => { const withRole = connectionsList.map((conn) => ({ ...conn, - role: getConnectionRole(conn.type.toLowerCase()) || "", + role: getConnectionRole(conn.type.toLowerCase(), catalog) || "", })); let result = withRole; @@ -252,6 +268,7 @@ const Connections: React.FunctionComponent = () => { data={searchResult} sourceList={sourceList} destinationList={destinationList} + catalog={catalog} onClear={onClear} />
diff --git a/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.tsx b/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.tsx index f2ae4382..bdaed2aa 100644 --- a/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.tsx +++ b/debezium-platform-stage/src/pages/Connection/ConnectionsCatalog.tsx @@ -4,12 +4,14 @@ import * as React from "react"; import { useTranslation } from "react-i18next"; import { ThIcon, ListIcon, SearchIcon, FilterIcon } from "@patternfly/react-icons"; import { useState } from "react"; -import { Catalog } from "src/apis"; -import sourceCatalog from "../../__mocks__/data/SourceCatalog.json"; +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"; +import { useQuery } from "react-query"; +import { fetchData } from "../../apis/apis"; +import { API_URL } from "../../utils/constants"; export interface IConnectionsCatalogProps { sampleProp?: string; @@ -26,6 +28,17 @@ const ConnectionsCatalog: React.FunctionComponent = () const [isSelected, setIsSelected] = React.useState<"grid" | "list">("grid"); const [searchQuery, setSearchQuery] = useState(""); + const { + data: sourceCatalog = [], + } = useQuery("sourceConnectorCatalog", async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["source-connector"] ?? []).map((entry) => ({ + ...entry, + role: "source", + })); + }); const onConnectionsTypeToggle = () => { setConnectionsTypeIsExpanded(!connectionsTypeIsExpanded); @@ -39,7 +52,6 @@ const ConnectionsCatalog: React.FunctionComponent = () setConnectionsTypeIsExpanded(false); }; - // Compute filtered and sorted results const searchResult = React.useMemo(() => { let catalogData: Catalog[] = []; @@ -51,7 +63,6 @@ const ConnectionsCatalog: React.FunctionComponent = () catalogData = [...sourceCatalog, ...destinationCatalog]; } - // Apply search filter let filtered = catalogData; if (searchQuery.length > 0) { filtered = _.filter(catalogData, (o) => @@ -59,9 +70,8 @@ const ConnectionsCatalog: React.FunctionComponent = () ); } - // Sort by name return _.sortBy(filtered, (o) => o.name.toLowerCase()); - }, [connectionsTypeSelected, searchQuery]); + }, [connectionsTypeSelected, searchQuery, sourceCatalog]); const onClear = () => { onSearch?.(""); @@ -76,7 +86,6 @@ const ConnectionsCatalog: React.FunctionComponent = () setIsSelected(id.split("-")[2] as "grid" | "list"); }; - // Debounce the search query state update const debouncedSetSearchQuery = React.useMemo( () => debounce((value: string) => { setSearchQuery(value); @@ -84,7 +93,6 @@ const ConnectionsCatalog: React.FunctionComponent = () [] ); - // Cleanup debounced function on unmount React.useEffect(() => { return () => { debouncedSetSearchQuery.cancel(); @@ -224,4 +232,4 @@ const ConnectionsCatalog: React.FunctionComponent = () ); }; -export { ConnectionsCatalog }; \ No newline at end of file +export { ConnectionsCatalog }; diff --git a/debezium-platform-stage/src/pages/Connection/CreateConnection.tsx b/debezium-platform-stage/src/pages/Connection/CreateConnection.tsx index 9d16b8ee..036746c8 100644 --- a/debezium-platform-stage/src/pages/Connection/CreateConnection.tsx +++ b/debezium-platform-stage/src/pages/Connection/CreateConnection.tsx @@ -54,7 +54,11 @@ const CreateConnection: React.FunctionComponent = ({ sel ); const selectedSchema = React.useMemo(() => { - return connectionsSchema.find((schema) => schema.type.toLowerCase() === (connectionId || "").toLowerCase()); + const normalizedId = (connectionId || "").toLowerCase().replace(/-/g, "_"); + return connectionsSchema.find((schema) => { + const normalizedType = schema.type.toLowerCase().replace(/-/g, "_"); + return normalizedId === normalizedType || normalizedId.includes(normalizedType); + }); }, [connectionsSchema, connectionId]); const selectedSchemaProperties = selectedSchema?.schema; diff --git a/debezium-platform-stage/src/pages/Destination/CreateDestination.tsx b/debezium-platform-stage/src/pages/Destination/CreateDestination.tsx index 592f4ff8..b4a7885d 100644 --- a/debezium-platform-stage/src/pages/Destination/CreateDestination.tsx +++ b/debezium-platform-stage/src/pages/Destination/CreateDestination.tsx @@ -17,10 +17,9 @@ import { ToolbarItem, } from "@patternfly/react-core"; import { PencilAltIcon, CodeIcon, PlayIcon } from "@patternfly/react-icons"; -import destinationCatalog from "../../__mocks__/data/DestinationCatalog.json"; import { useNavigate, useParams } from "react-router-dom"; import { CodeEditor, CodeEditorControl, Language } from "@patternfly/react-code-editor"; -import { find } from "lodash"; + import { ConnectionConfig, createPost, Destination, Payload } from "../../apis/apis"; import { API_URL } from "../../utils/constants"; import { convertMapToObject } from "../../utils/helpers"; @@ -113,7 +112,7 @@ const FormSyncManager: React.FC<{ } updateSource.current = "form"; - const type = find(destinationCatalog, { id: destinationId })?.type || ""; + const type = destinationId || ""; const configuration = convertMapToObject(properties); setCode((prevCode: any) => { @@ -330,7 +329,7 @@ const CreateDestination: React.FunctionComponent = ({ } const payload = { description: values["description"], - type: find(destinationCatalog, { id: destinationId })?.type || "", + type: destinationId || (code as Payload).type || "", schema: "schema321", vaults: [], ...(selectedConnection ? { connection: selectedConnection } : {}), diff --git a/debezium-platform-stage/src/pages/Source/CreateSource.tsx b/debezium-platform-stage/src/pages/Source/CreateSource.tsx index 9bc83ba8..d0ad034a 100644 --- a/debezium-platform-stage/src/pages/Source/CreateSource.tsx +++ b/debezium-platform-stage/src/pages/Source/CreateSource.tsx @@ -25,8 +25,6 @@ import { API_URL, } from "../../utils/constants"; import { convertMapToObject } from "../../utils/helpers"; -import sourceCatalog from "../../__mocks__/data/SourceCatalog.json"; -import { find } from "lodash"; import { useNotification } from "../../appLayout/AppNotificationContext"; import SourceSinkForm from "@components/SourceSinkForm"; import PageHeader from "@components/PageHeader"; @@ -122,7 +120,7 @@ const FormSyncManager: React.FC<{ return; } updateSource.current = "form"; - const type = find(sourceCatalog, { id: sourceId })?.type || ""; + const type = sourceId || ""; const configuration = convertMapToObject(properties); setCode((prevCode: any) => { @@ -356,7 +354,7 @@ const CreateSource: React.FunctionComponent = ({ const payload = { description: values["description"], - type: find(sourceCatalog, { id: sourceId })?.type || (code as Payload).type || "", + type: sourceId || (code as Payload).type || "", schema: "schema321", vaults: [], ...(selectedConnection ? { connection: selectedConnection } : {}), diff --git a/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx b/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx index 3dfaa6a0..4cabea67 100644 --- a/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx +++ b/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx @@ -1,11 +1,18 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; import { + Alert, Button, Content, ContentVariants, + Card, + CardBody, + CardHeader, + Gallery, + GalleryItem, PageSection, SearchInput, + Skeleton, ToggleGroup, ToggleGroupItem, Toolbar, @@ -20,10 +27,13 @@ import { CatalogGrid } from "@components/CatalogGrid"; import { useState } from "react"; import { debounce } from "lodash"; import _ from "lodash"; -import sourceCatalog from "../../__mocks__/data/SourceCatalog.json"; 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"; const useSourceCatalogTourSteps = (): Step[] => { const { t } = useTranslation("tour"); @@ -49,13 +59,51 @@ export interface ISinkProps { sampleProp?: string; } +const CatalogSkeleton: React.FC = () => ( + + + {Array.from({ length: 6 }).map((_, i) => ( + + + + + + + + + + +
+ +
+
+
+ ))} +
+
+); + const SourceCatalog: React.FunctionComponent = () => { const navigate = useNavigate(); const { t } = useTranslation(); const [isSelected, setIsSelected] = React.useState<"grid" | "list">("grid"); const [searchQuery, setSearchQuery] = useState(""); - // Compute filtered results based on search query + const { + data: sourceCatalog = [], + error: catalogError, + isLoading: isCatalogLoading, + refetch, + } = useQuery("sourceConnectorCatalog", async () => { + const response = await fetchData( + `${API_URL}/api/catalog` + ); + return (response.components["source-connector"] ?? []).map((entry) => ({ + ...entry, + role: "source", + })); + }); + const searchResult = React.useMemo(() => { if (searchQuery.length === 0) { return sourceCatalog; @@ -63,7 +111,7 @@ const SourceCatalog: React.FunctionComponent = () => { return _.filter(sourceCatalog, (o) => o.name.toLowerCase().includes(searchQuery.toLowerCase()) ); - }, [searchQuery]); + }, [searchQuery, sourceCatalog]); const onClear = () => { onSearch?.(""); @@ -170,13 +218,31 @@ const SourceCatalog: React.FunctionComponent = () => { - + {catalogError ? ( + + refetch()}> + {t("retry", "Retry")} + + } + > + {catalogError.message} + + + ) : isCatalogLoading ? ( + + ) : ( + + )} ); diff --git a/debezium-platform-stage/src/utils/helpers.ts b/debezium-platform-stage/src/utils/helpers.ts index ce498811..d302e7bf 100644 --- a/debezium-platform-stage/src/utils/helpers.ts +++ b/debezium-platform-stage/src/utils/helpers.ts @@ -1,6 +1,5 @@ import { DatabaseType } from "./constants"; -import DestinationCatalog from "../__mocks__/data/DestinationCatalog.json"; -import SourceCatalog from "../__mocks__/data/SourceCatalog.json"; +import { Catalog } from "../apis/types"; /* eslint-disable @typescript-eslint/no-explicit-any */ export const convertMapToObject = ( @@ -53,10 +52,9 @@ export const getDatabaseType = (connectorType: string) => { } return type; } -export const getConnectionRole = (connectorType: string) => { - const connectionsCatalog = [...SourceCatalog, ...DestinationCatalog]; - const connection = connectionsCatalog.find((connection) => connection.id === connectorType); - return connection?.role; +export const getConnectionRole = (connectorType: string, catalog: Catalog[]): string | undefined => { + const lower = connectorType.toLowerCase(); + return catalog.find((entry) => entry.class.toLowerCase() === lower || lower.includes(entry.class.toLowerCase()) || entry.class.toLowerCase().includes(lower))?.role; } export const getConnectorTypeName = (connectorType: string) => { From 30228e838ca97896f889f69c6954873d52352448 Mon Sep 17 00:00:00 2001 From: indraraj Date: Thu, 2 Apr 2026 15:35:14 +0530 Subject: [PATCH 2/2] debezium/dbz#1207 extract the catalog skelton loader Signed-off-by: indraraj --- .../src/components/CatalogSkeleton.tsx | 33 +++++++++++++++++ .../pipelineDesigner/PipelineSourceModel.tsx | 26 +------------- .../src/hooks/useConnectorForm.tsx | 2 -- .../src/pages/Connection/Connections.tsx | 7 ++-- .../src/pages/Source/SourceCatalog.tsx | 35 +++---------------- 5 files changed, 43 insertions(+), 60 deletions(-) create mode 100644 debezium-platform-stage/src/components/CatalogSkeleton.tsx diff --git a/debezium-platform-stage/src/components/CatalogSkeleton.tsx b/debezium-platform-stage/src/components/CatalogSkeleton.tsx new file mode 100644 index 00000000..027d96b6 --- /dev/null +++ b/debezium-platform-stage/src/components/CatalogSkeleton.tsx @@ -0,0 +1,33 @@ +import { + Card, + CardBody, + CardHeader, + Gallery, + GalleryItem, + Skeleton, +} from "@patternfly/react-core"; +import * as React from "react"; + +const CatalogSkeleton: React.FC = () => ( + + {Array.from({ length: 6 }).map((_, i) => ( + + + + + + + + + + +
+ +
+
+
+ ))} +
+); + +export default CatalogSkeleton; diff --git a/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx b/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx index 7cac8653..4c8bc2a2 100644 --- a/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx +++ b/debezium-platform-stage/src/components/pipelineDesigner/PipelineSourceModel.tsx @@ -8,11 +8,8 @@ import { Divider, Flex, FlexItem, - Gallery, - GalleryItem, Content, PageSection, - Skeleton, } from "@patternfly/react-core"; import React, { useCallback, useState } from "react"; import { fetchData, Source } from "../../apis/apis"; @@ -22,33 +19,12 @@ import SourceDestinationSelectionList from "../SourceDestinationSelectionList"; import { CatalogGrid } from "@components/CatalogGrid"; import { CreateSource } from "@sourcePage/CreateSource"; import { Catalog, CatalogApiResponse } from "../../apis/types"; +import CatalogSkeleton from "@components/CatalogSkeleton"; type PipelineSourceModelProps = { onSourceSelection: (source: Source) => void; }; -const CatalogSkeleton: React.FC = () => ( - - {Array.from({ length: 6 }).map((_, i) => ( - - - - - - - - - - -
- -
-
-
- ))} -
-); - const PipelineSourceModel: React.FC = ({ onSourceSelection, }) => { diff --git a/debezium-platform-stage/src/hooks/useConnectorForm.tsx b/debezium-platform-stage/src/hooks/useConnectorForm.tsx index 5addb857..a4235637 100644 --- a/debezium-platform-stage/src/hooks/useConnectorForm.tsx +++ b/debezium-platform-stage/src/hooks/useConnectorForm.tsx @@ -13,7 +13,6 @@ interface UseConnectorFormProps { onSelection?: (selection: Source) => void; navigateTo: (path: string) => void; createEndpoint: string; - catalog: any[]; } export const useConnectorForm = ({ @@ -22,7 +21,6 @@ export const useConnectorForm = ({ onSelection, navigateTo, createEndpoint, - catalog }: UseConnectorFormProps) => { const { addNotification } = useNotification(); const [code, setCode] = useState({ diff --git a/debezium-platform-stage/src/pages/Connection/Connections.tsx b/debezium-platform-stage/src/pages/Connection/Connections.tsx index 1b7422fb..d7c0cffd 100644 --- a/debezium-platform-stage/src/pages/Connection/Connections.tsx +++ b/debezium-platform-stage/src/pages/Connection/Connections.tsx @@ -96,7 +96,10 @@ const Connections: React.FunctionComponent = () => { } ); - const catalog: Catalog[] = [...sourceCatalog, ...destinationCatalog]; + const catalog: Catalog[] = React.useMemo( + () => [...sourceCatalog, ...destinationCatalog], + [sourceCatalog] + ); const searchResult = React.useMemo(() => { const withRole = connectionsList.map((conn) => ({ @@ -119,7 +122,7 @@ const Connections: React.FunctionComponent = () => { } return result; - }, [connectionsTypeSelected, connectionsList, searchQuery]); + }, [connectionsTypeSelected, connectionsList, searchQuery, catalog]); const onClear = () => { onSearch?.(""); diff --git a/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx b/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx index 4cabea67..5b630d28 100644 --- a/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx +++ b/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx @@ -5,14 +5,8 @@ import { Button, Content, ContentVariants, - Card, - CardBody, - CardHeader, - Gallery, - GalleryItem, PageSection, SearchInput, - Skeleton, ToggleGroup, ToggleGroupItem, Toolbar, @@ -34,6 +28,7 @@ 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 useSourceCatalogTourSteps = (): Step[] => { const { t } = useTranslation("tour"); @@ -59,30 +54,6 @@ export interface ISinkProps { sampleProp?: string; } -const CatalogSkeleton: React.FC = () => ( - - - {Array.from({ length: 6 }).map((_, i) => ( - - - - - - - - - - -
- -
-
-
- ))} -
-
-); - const SourceCatalog: React.FunctionComponent = () => { const navigate = useNavigate(); const { t } = useTranslation(); @@ -233,7 +204,9 @@ const SourceCatalog: React.FunctionComponent = () => { ) : isCatalogLoading ? ( - + + + ) : (