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 00000000..957b9fe2 Binary files /dev/null and b/debezium-platform-stage/src/assets/ibm-db2.png differ 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/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/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..4c8bc2a2 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, @@ -7,6 +9,7 @@ import { Flex, FlexItem, Content, + PageSection, } from "@patternfly/react-core"; import React, { useCallback, useState } from "react"; import { fetchData, Source } from "../../apis/apis"; @@ -15,7 +18,8 @@ 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"; +import CatalogSkeleton from "@components/CatalogSkeleton"; type PipelineSourceModelProps = { onSourceSelection: (source: Source) => void; @@ -37,6 +41,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 +149,31 @@ const PipelineSourceModel: React.FC = ({ onSelection={onSourceSelection} /> ) : selectedSource === "" ? ( - + catalogError ? ( + + refetchCatalog()}> + Retry + + } + > + {catalogError.message} + + + ) : isCatalogLoading ? ( + + ) : ( + + ) ) : ( void; navigateTo: (path: string) => void; createEndpoint: string; - catalog: any[]; } export const useConnectorForm = ({ @@ -23,7 +21,6 @@ export const useConnectorForm = ({ onSelection, navigateTo, createEndpoint, - catalog }: UseConnectorFormProps) => { const { addNotification } = useNotification(); const [code, setCode] = useState({ @@ -125,7 +122,7 @@ export const useConnectorForm = ({ } const payload = { description: values["details"], - type: find(catalog, { id: connectorId })?.type || "", + type: connectorId || "", schema: "schema321", vaults: [], config: convertMapToObject(properties), diff --git a/debezium-platform-stage/src/pages/Connection/Connections.tsx b/debezium-platform-stage/src/pages/Connection/Connections.tsx index cbdc85a5..d7c0cffd 100644 --- a/debezium-platform-stage/src/pages/Connection/Connections.tsx +++ b/debezium-platform-stage/src/pages/Connection/Connections.tsx @@ -14,6 +14,8 @@ import PageHeader from "@components/PageHeader"; import { useState } from "react"; import { getConnectionRole } from "@utils/helpers"; import ConnectionTable from "@components/ConnectionTable"; +import { Catalog, CatalogApiResponse } from "src/apis/types"; +import destinationCatalog from "../../__mocks__/data/DestinationCatalog.json"; export interface IConnectionsProps { @@ -81,11 +83,28 @@ const Connections: React.FunctionComponent = () => { } ); - // 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[] = React.useMemo( + () => [...sourceCatalog, ...destinationCatalog], + [sourceCatalog] + ); + const searchResult = React.useMemo(() => { const withRole = connectionsList.map((conn) => ({ ...conn, - role: getConnectionRole(conn.type.toLowerCase()) || "", + role: getConnectionRole(conn.type.toLowerCase(), catalog) || "", })); let result = withRole; @@ -103,7 +122,7 @@ const Connections: React.FunctionComponent = () => { } return result; - }, [connectionsTypeSelected, connectionsList, searchQuery]); + }, [connectionsTypeSelected, connectionsList, searchQuery, catalog]); const onClear = () => { onSearch?.(""); @@ -252,6 +271,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..5b630d28 100644 --- a/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx +++ b/debezium-platform-stage/src/pages/Source/SourceCatalog.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; import { + Alert, Button, Content, ContentVariants, @@ -20,10 +21,14 @@ 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"; +import CatalogSkeleton from "@components/CatalogSkeleton"; const useSourceCatalogTourSteps = (): Step[] => { const { t } = useTranslation("tour"); @@ -55,7 +60,21 @@ const SourceCatalog: React.FunctionComponent = () => { 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 +82,7 @@ const SourceCatalog: React.FunctionComponent = () => { return _.filter(sourceCatalog, (o) => o.name.toLowerCase().includes(searchQuery.toLowerCase()) ); - }, [searchQuery]); + }, [searchQuery, sourceCatalog]); const onClear = () => { onSearch?.(""); @@ -170,13 +189,33 @@ 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) => {