瀏覽代碼

Merge branch 'master' into vlad/develop

VladSenyuta 2 年之前
父節點
當前提交
733460c471
共有 100 個文件被更改,包括 1047 次插入635 次删除
  1. 2 0
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 1 1
      .github/workflows/branch-deploy.yml
  3. 1 1
      .github/workflows/build-public-image.yml
  4. 2 2
      .github/workflows/cve.yaml
  5. 6 2
      .github/workflows/e2e-automation.yml
  6. 6 2
      .github/workflows/e2e-weekly.yml
  7. 1 1
      .github/workflows/master.yaml
  8. 1 1
      .github/workflows/release.yaml
  9. 1 1
      .github/workflows/separate_env_public_create.yml
  10. 1 1
      .github/workflows/terraform-deploy.yml
  11. 2 0
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java
  12. 19 7
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalTopic.java
  13. 7 2
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/TopicsService.java
  14. 11 13
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/Oddrn.java
  15. 59 59
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/schema/AvroExtractor.java
  16. 3 6
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/schema/DataSetFieldsExtractors.java
  17. 54 54
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/schema/JsonSchemaExtractor.java
  18. 47 47
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/schema/ProtoExtractor.java
  19. 18 18
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/KafkaServicesValidation.java
  20. 4 3
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/KafkaVersion.java
  21. 6 16
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/SslPropertiesUtil.java
  22. 8 8
      kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/TopicsServicePaginationTest.java
  23. 1 1
      kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml
  24. 3 3
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/models/Schema.java
  25. 25 1
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/BasePage.java
  26. 2 3
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/brokers/BrokersList.java
  27. 14 3
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/connectors/ConnectorCreateForm.java
  28. 2 1
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/connectors/KafkaConnectList.java
  29. 2 5
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/consumers/ConsumersList.java
  30. 99 97
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqlDb/KsqlDbList.java
  31. 16 23
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/panels/NaviSideBar.java
  32. 2 1
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/panels/TopPanel.java
  33. 28 0
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/panels/enums/MenuItem.java
  34. 23 5
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/schemas/SchemaCreateForm.java
  35. 2 1
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/schemas/SchemaRegistryList.java
  36. 1 1
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/topics/ProduceMessagePanel.java
  37. 9 5
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/topics/TopicsList.java
  38. 15 3
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/services/ApiService.java
  39. 1 0
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/settings/BaseSource.java
  40. 44 43
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/settings/drivers/LocalWebDriver.java
  41. 2 2
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/settings/listeners/QaseResultListener.java
  42. 9 0
      kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/utilities/WebUtils.java
  43. 0 0
      kafka-ui-e2e-checks/src/main/resources/testData/connectors/config_for_create_connector.json
  44. 0 0
      kafka-ui-e2e-checks/src/main/resources/testData/connectors/config_for_create_connector_via_api.json
  45. 0 0
      kafka-ui-e2e-checks/src/main/resources/testData/connectors/config_for_update_connector.json
  46. 0 0
      kafka-ui-e2e-checks/src/main/resources/testData/connectors/delete_connector_config.json
  47. 2 9
      kafka-ui-e2e-checks/src/main/resources/testData/schemas/schema_avro_for_update.json
  48. 0 0
      kafka-ui-e2e-checks/src/main/resources/testData/schemas/schema_avro_value.json
  49. 0 0
      kafka-ui-e2e-checks/src/main/resources/testData/schemas/schema_json_Value.json
  50. 0 0
      kafka-ui-e2e-checks/src/main/resources/testData/schemas/schema_protobuf_value.txt
  51. 0 0
      kafka-ui-e2e-checks/src/main/resources/testData/topics/message_content_create_topic.json
  52. 20 9
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/BaseTest.java
  53. 2 2
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/Facade.java
  54. 3 1
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/BaseManualTest.java
  55. 19 0
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/backlog/SanityBacklog.java
  56. 32 6
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/backlog/SmokeBacklog.java
  57. 10 16
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/suite/TopicsTest.java
  58. 4 4
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/suite/WizardTest.java
  59. 7 0
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/qaseSuite/BaseQaseTest.java
  60. 1 1
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/qaseSuite/Template.java
  61. 53 0
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/SmokeTest.java
  62. 13 15
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/connectors/ConnectorsTest.java
  63. 6 5
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/schemas/SchemasTest.java
  64. 5 6
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/topics/MessagesTest.java
  65. 6 5
      kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/topics/TopicsTest.java
  66. 10 8
      kafka-ui-react-app/src/components/App.tsx
  67. 2 2
      kafka-ui-react-app/src/components/Connect/Details/Actions/Action.styled.ts
  68. 1 1
      kafka-ui-react-app/src/components/Connect/List/TopicsCell.tsx
  69. 1 2
      kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx
  70. 4 3
      kafka-ui-react-app/src/components/ConsumerGroups/Details/ListItem.styled.ts
  71. 11 9
      kafka-ui-react-app/src/components/ConsumerGroups/Details/ListItem.tsx
  72. 4 3
      kafka-ui-react-app/src/components/ConsumerGroups/Details/TopicContents/TopicContent.styled.ts
  73. 1 1
      kafka-ui-react-app/src/components/ConsumerGroups/Details/__tests__/Details.spec.tsx
  74. 3 1
      kafka-ui-react-app/src/components/ConsumerGroups/Details/__tests__/ListItem.spec.tsx
  75. 1 0
      kafka-ui-react-app/src/components/Dashboard/Dashboard.styled.ts
  76. 1 1
      kafka-ui-react-app/src/components/ErrorPage/ErrorPage.styled.ts
  77. 32 1
      kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/QueryForm.styled.ts
  78. 2 0
      kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/QueryForm.tsx
  79. 1 0
      kafka-ui-react-app/src/components/Nav/ClusterTab/ClusterTab.styled.ts
  80. 1 1
      kafka-ui-react-app/src/components/Nav/ClusterTab/__tests__/ClusterTab.styled.spec.tsx
  81. 7 4
      kafka-ui-react-app/src/components/NavBar/NavBar.styled.ts
  82. 83 2
      kafka-ui-react-app/src/components/NavBar/NavBar.tsx
  83. 1 7
      kafka-ui-react-app/src/components/NavBar/UserInfo/UserInfo.tsx
  84. 9 1
      kafka-ui-react-app/src/components/NavBar/__tests__/NavBar.spec.tsx
  85. 4 2
      kafka-ui-react-app/src/components/PageContainer/PageContainer.tsx
  86. 8 1
      kafka-ui-react-app/src/components/PageContainer/__tests__/PageContainer.spec.tsx
  87. 7 4
      kafka-ui-react-app/src/components/Schemas/Details/LatestVersion/LatestVersionItem.styled.tsx
  88. 31 6
      kafka-ui-react-app/src/components/Schemas/Diff/Diff.styled.ts
  89. 2 1
      kafka-ui-react-app/src/components/Schemas/Edit/Edit.styled.ts
  90. 3 0
      kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.styled.ts
  91. 3 10
      kafka-ui-react-app/src/components/Topics/List/ActionsCell.tsx
  92. 10 5
      kafka-ui-react-app/src/components/Topics/List/BatchActionsBar.tsx
  93. 9 8
      kafka-ui-react-app/src/components/Topics/List/__tests__/TopicTable.spec.tsx
  94. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Edit/DangerZone/DangerZone.styled.tsx
  95. 36 15
      kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.styled.ts
  96. 7 7
      kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx
  97. 3 1
      kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/SavedFilters.tsx
  98. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/__tests__/Filters.styled.spec.tsx
  99. 3 3
      kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/MessageContent.styled.ts
  100. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/__tests__/MessageContent.spec.tsx

+ 2 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -9,6 +9,8 @@ assignees: ''
 
 <!--
 
+We will close the issue without further explanation if you don't follow this template and don't provide the information requested within this template.
+
 Don't forget to check for existing issues/discussions regarding your proposal. We might already have it.
 https://github.com/provectus/kafka-ui/issues
 https://github.com/provectus/kafka-ui/discussions

+ 1 - 1
.github/workflows/branch-deploy.yml

@@ -55,7 +55,7 @@ jobs:
         uses: aws-actions/amazon-ecr-login@v1
       - name: Build and push
         id: docker_build_and_push
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v4
         with:
           builder: ${{ steps.buildx.outputs.name }}
           context: kafka-ui-api

+ 1 - 1
.github/workflows/build-public-image.yml

@@ -54,7 +54,7 @@ jobs:
           registry-type: 'public'
       - name: Build and push
         id: docker_build_and_push
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v4
         with:
           builder: ${{ steps.buildx.outputs.name }}
           context: kafka-ui-api

+ 2 - 2
.github/workflows/cve.yaml

@@ -40,7 +40,7 @@ jobs:
             ${{ runner.os }}-buildx-
 
       - name: Build docker image
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v4
         with:
           builder: ${{ steps.buildx.outputs.name }}
           context: kafka-ui-api
@@ -55,7 +55,7 @@ jobs:
           cache-to: type=local,dest=/tmp/.buildx-cache
 
       - name: Run CVE checks
-        uses: aquasecurity/trivy-action@0.9.1
+        uses: aquasecurity/trivy-action@0.9.2
         with:
           image-ref: "provectuslabs/kafka-ui:${{ steps.build.outputs.version }}"
           format: "table"

+ 6 - 2
.github/workflows/e2e-automation.yml

@@ -23,6 +23,12 @@ jobs:
       - uses: actions/checkout@v3
         with:
           ref: ${{ github.sha }}
+      - name: Configure AWS credentials
+        uses: aws-actions/configure-aws-credentials@v1-node16
+        with:
+          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+          aws-region: eu-central-1
       - name: Set up environment
         id: set_env_values
         run: |
@@ -65,8 +71,6 @@ jobs:
         if: always()
         env:
           AWS_S3_BUCKET: 'kafkaui-allure-reports'
-          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
-          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
           AWS_REGION: 'eu-central-1'
           SOURCE_DIR: 'allure-history/allure-results'
       - name: Deploy report to Amazon S3

+ 6 - 2
.github/workflows/e2e-weekly.yml

@@ -10,6 +10,12 @@ jobs:
       - uses: actions/checkout@v3
         with:
           ref: ${{ github.sha }}
+      - name: Configure AWS credentials
+        uses: aws-actions/configure-aws-credentials@v1-node16
+        with:
+          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+          aws-region: eu-central-1
       - name: Set up environment
         id: set_env_values
         run: |
@@ -52,8 +58,6 @@ jobs:
         if: always()
         env:
           AWS_S3_BUCKET: 'kafkaui-allure-reports'
-          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
-          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
           AWS_REGION: 'eu-central-1'
           SOURCE_DIR: 'allure-history/allure-results'
       - name: Deploy report to Amazon S3

+ 1 - 1
.github/workflows/master.yaml

@@ -53,7 +53,7 @@ jobs:
 
       - name: Build and push
         id: docker_build_and_push
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v4
         with:
           builder: ${{ steps.buildx.outputs.name }}
           context: kafka-ui-api

+ 1 - 1
.github/workflows/release.yaml

@@ -72,7 +72,7 @@ jobs:
 
       - name: Build and push
         id: docker_build_and_push
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v4
         with:
           builder: ${{ steps.buildx.outputs.name }}
           context: kafka-ui-api

+ 1 - 1
.github/workflows/separate_env_public_create.yml

@@ -57,7 +57,7 @@ jobs:
         uses: aws-actions/amazon-ecr-login@v1
       - name: Build and push
         id: docker_build_and_push
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v4
         with:
           builder: ${{ steps.buildx.outputs.name }}
           context: kafka-ui-api

+ 1 - 1
.github/workflows/terraform-deploy.yml

@@ -26,7 +26,7 @@ jobs:
           echo "Terraform will be triggered in this dir $TF_DIR"
 
       - name: Configure AWS credentials for Kafka-UI account
-        uses: aws-actions/configure-aws-credentials@v1
+        uses: aws-actions/configure-aws-credentials@v1-node16
         with:
           aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
           aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

+ 2 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java

@@ -25,6 +25,8 @@ public class ClustersProperties {
 
   List<Cluster> clusters = new ArrayList<>();
 
+  String internalTopicPrefix;
+
   @Data
   public static class Cluster {
     String name;

+ 19 - 7
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalTopic.java

@@ -1,9 +1,11 @@
 package com.provectus.kafka.ui.model;
 
+import com.provectus.kafka.ui.config.ClustersProperties;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 import lombok.Builder;
 import lombok.Data;
 import org.apache.kafka.clients.admin.ConfigEntry;
@@ -14,6 +16,8 @@ import org.apache.kafka.common.TopicPartition;
 @Builder(toBuilder = true)
 public class InternalTopic {
 
+  ClustersProperties clustersProperties;
+
   // from TopicDescription
   private final String name;
   private final boolean internal;
@@ -40,9 +44,17 @@ public class InternalTopic {
                                    List<ConfigEntry> configs,
                                    InternalPartitionsOffsets partitionsOffsets,
                                    Metrics metrics,
-                                   InternalLogDirStats logDirInfo) {
+                                   InternalLogDirStats logDirInfo,
+                                   @Nullable String internalTopicPrefix) {
     var topic = InternalTopic.builder();
-    topic.internal(topicDescription.isInternal());
+
+    internalTopicPrefix = internalTopicPrefix == null || internalTopicPrefix.isEmpty()
+        ? "_"
+        : internalTopicPrefix;
+
+    topic.internal(
+        topicDescription.isInternal() || topicDescription.name().startsWith(internalTopicPrefix)
+    );
     topic.name(topicDescription.name());
 
     List<InternalPartition> partitions = topicDescription.partitions().stream()
@@ -56,10 +68,10 @@ public class InternalTopic {
           List<InternalReplica> replicas = partition.replicas().stream()
               .map(r ->
                   InternalReplica.builder()
-                    .broker(r.id())
-                    .inSync(partition.isr().contains(r))
-                    .leader(partition.leader() != null && partition.leader().id() == r.id())
-                    .build())
+                      .broker(r.id())
+                      .inSync(partition.isr().contains(r))
+                      .leader(partition.leader() != null && partition.leader().id() == r.id())
+                      .build())
               .collect(Collectors.toList());
           partitionDto.replicas(replicas);
 
@@ -79,7 +91,7 @@ public class InternalTopic {
 
           return partitionDto.build();
         })
-        .collect(Collectors.toList());
+        .toList();
 
     topic.partitions(partitions.stream().collect(
         Collectors.toMap(InternalPartition::getPartition, t -> t)));

+ 7 - 2
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/TopicsService.java

@@ -3,6 +3,7 @@ package com.provectus.kafka.ui.service;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toMap;
 
+import com.provectus.kafka.ui.config.ClustersProperties;
 import com.provectus.kafka.ui.exception.TopicMetadataException;
 import com.provectus.kafka.ui.exception.TopicNotFoundException;
 import com.provectus.kafka.ui.exception.TopicRecreationException;
@@ -52,6 +53,7 @@ public class TopicsService {
 
   private final AdminClientService adminClientService;
   private final StatisticsCache statisticsCache;
+  private final ClustersProperties clustersProperties;
   @Value("${topic.recreate.maxRetries:15}")
   private int recreateMaxRetries;
   @Value("${topic.recreate.delay.seconds:1}")
@@ -127,7 +129,8 @@ public class TopicsService {
             configs.getOrDefault(t, List.of()),
             partitionsOffsets,
             metrics,
-            logDirInfo
+            logDirInfo,
+            clustersProperties.getInternalTopicPrefix()
         ))
         .collect(toList());
   }
@@ -459,7 +462,9 @@ public class TopicsService {
                     stats.getTopicConfigs().getOrDefault(topicName, List.of()),
                     InternalPartitionsOffsets.empty(),
                     stats.getMetrics(),
-                    stats.getLogDirInfo()))
+                    stats.getLogDirInfo(),
+                    clustersProperties.getInternalTopicPrefix()
+                    ))
             .collect(toList())
         );
   }

+ 11 - 13
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/Oddrn.java

@@ -4,36 +4,34 @@ import com.provectus.kafka.ui.model.KafkaCluster;
 import java.net.URI;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import lombok.experimental.UtilityClass;
-import org.opendatadiscovery.oddrn.Generator;
 import org.opendatadiscovery.oddrn.model.AwsS3Path;
 import org.opendatadiscovery.oddrn.model.KafkaConnectorPath;
 import org.opendatadiscovery.oddrn.model.KafkaPath;
 
-@UtilityClass
-public class Oddrn {
+public final class Oddrn {
 
-  private static final Generator GENERATOR = new Generator();
+  private Oddrn() {
+  }
 
-  String clusterOddrn(KafkaCluster cluster) {
+  static String clusterOddrn(KafkaCluster cluster) {
     return KafkaPath.builder()
         .cluster(bootstrapServersForOddrn(cluster.getBootstrapServers()))
         .build()
         .oddrn();
   }
 
-  KafkaPath topicOddrnPath(KafkaCluster cluster, String topic) {
+  static KafkaPath topicOddrnPath(KafkaCluster cluster, String topic) {
     return KafkaPath.builder()
         .cluster(bootstrapServersForOddrn(cluster.getBootstrapServers()))
         .topic(topic)
         .build();
   }
 
-  String topicOddrn(KafkaCluster cluster, String topic) {
+  static String topicOddrn(KafkaCluster cluster, String topic) {
     return topicOddrnPath(cluster, topic).oddrn();
   }
 
-  String awsS3Oddrn(String bucket, String key) {
+  static String awsS3Oddrn(String bucket, String key) {
     return AwsS3Path.builder()
         .bucket(bucket)
         .key(key)
@@ -41,14 +39,14 @@ public class Oddrn {
         .oddrn();
   }
 
-  String connectDataSourceOddrn(String connectUrl) {
+  static String connectDataSourceOddrn(String connectUrl) {
     return KafkaConnectorPath.builder()
         .host(normalizedConnectHosts(connectUrl))
         .build()
         .oddrn();
   }
 
-  private String normalizedConnectHosts(String connectUrlStr) {
+  private static String normalizedConnectHosts(String connectUrlStr) {
     return Stream.of(connectUrlStr.split(","))
         .map(String::trim)
         .sorted()
@@ -61,7 +59,7 @@ public class Oddrn {
         .collect(Collectors.joining(","));
   }
 
-  String connectorOddrn(String connectUrl, String connectorName) {
+  static String connectorOddrn(String connectUrl, String connectorName) {
     return KafkaConnectorPath.builder()
         .host(normalizedConnectHosts(connectUrl))
         .connector(connectorName)
@@ -69,7 +67,7 @@ public class Oddrn {
         .oddrn();
   }
 
-  private String bootstrapServersForOddrn(String bootstrapServers) {
+  private static String bootstrapServersForOddrn(String bootstrapServers) {
     return Stream.of(bootstrapServers.split(","))
         .map(String::trim)
         .sorted()

+ 59 - 59
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/schema/AvroExtractor.java

@@ -1,18 +1,18 @@
 package com.provectus.kafka.ui.service.integration.odd.schema;
 
 import com.google.common.collect.ImmutableSet;
-import com.provectus.kafka.ui.service.integration.odd.Oddrn;
 import com.provectus.kafka.ui.sr.model.SchemaSubject;
 import java.util.ArrayList;
 import java.util.List;
-import lombok.experimental.UtilityClass;
 import org.apache.avro.Schema;
 import org.opendatadiscovery.client.model.DataSetField;
 import org.opendatadiscovery.client.model.DataSetFieldType;
 import org.opendatadiscovery.oddrn.model.KafkaPath;
 
-@UtilityClass
-class AvroExtractor {
+final class AvroExtractor {
+
+  private AvroExtractor() {
+  }
 
   static List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
     var schema = new Schema.Parser().parse(subject.getSchema());
@@ -31,14 +31,14 @@ class AvroExtractor {
     return result;
   }
 
-  private void extract(Schema schema,
-                       String parentOddr,
-                       String oddrn, //null for root
-                       String name,
-                       String doc,
-                       Boolean nullable,
-                       ImmutableSet<String> registeredRecords,
-                       List<DataSetField> sink
+  private static void extract(Schema schema,
+                              String parentOddr,
+                              String oddrn, //null for root
+                              String name,
+                              String doc,
+                              Boolean nullable,
+                              ImmutableSet<String> registeredRecords,
+                              List<DataSetField> sink
   ) {
     switch (schema.getType()) {
       case RECORD -> extractRecord(schema, parentOddr, oddrn, name, doc, nullable, registeredRecords, sink);
@@ -49,12 +49,12 @@ class AvroExtractor {
     }
   }
 
-  private DataSetField createDataSetField(String name,
-                                          String doc,
-                                          String parentOddrn,
-                                          String oddrn,
-                                          Schema schema,
-                                          Boolean nullable) {
+  private static DataSetField createDataSetField(String name,
+                                                 String doc,
+                                                 String parentOddrn,
+                                                 String oddrn,
+                                                 Schema schema,
+                                                 Boolean nullable) {
     return new DataSetField()
         .name(name)
         .description(doc)
@@ -63,14 +63,14 @@ class AvroExtractor {
         .type(mapSchema(schema, nullable));
   }
 
-  private void extractRecord(Schema schema,
-                             String parentOddr,
-                             String oddrn, //null for root
-                             String name,
-                             String doc,
-                             Boolean nullable,
-                             ImmutableSet<String> registeredRecords,
-                             List<DataSetField> sink) {
+  private static void extractRecord(Schema schema,
+                                    String parentOddr,
+                                    String oddrn, //null for root
+                                    String name,
+                                    String doc,
+                                    Boolean nullable,
+                                    ImmutableSet<String> registeredRecords,
+                                    List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     if (!isRoot) {
       sink.add(createDataSetField(name, doc, parentOddr, oddrn, schema, nullable));
@@ -99,13 +99,13 @@ class AvroExtractor {
         ));
   }
 
-  private void extractUnion(Schema schema,
-                            String parentOddr,
-                            String oddrn, //null for root
-                            String name,
-                            String doc,
-                            ImmutableSet<String> registeredRecords,
-                            List<DataSetField> sink) {
+  private static void extractUnion(Schema schema,
+                                   String parentOddr,
+                                   String oddrn, //null for root
+                                   String name,
+                                   String doc,
+                                   ImmutableSet<String> registeredRecords,
+                                   List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     boolean containsNull = schema.getTypes().stream().map(Schema::getType).anyMatch(t -> t == Schema.Type.NULL);
     // if it is not root and there is only 2 values for union (null and smth else)
@@ -149,14 +149,14 @@ class AvroExtractor {
     }
   }
 
-  private void extractArray(Schema schema,
-                            String parentOddr,
-                            String oddrn, //null for root
-                            String name,
-                            String doc,
-                            Boolean nullable,
-                            ImmutableSet<String> registeredRecords,
-                            List<DataSetField> sink) {
+  private static void extractArray(Schema schema,
+                                   String parentOddr,
+                                   String oddrn, //null for root
+                                   String name,
+                                   String doc,
+                                   Boolean nullable,
+                                   ImmutableSet<String> registeredRecords,
+                                   List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     oddrn = isRoot ? parentOddr + "/array" : oddrn;
     if (isRoot) {
@@ -176,14 +176,14 @@ class AvroExtractor {
     );
   }
 
-  private void extractMap(Schema schema,
-                          String parentOddr,
-                          String oddrn, //null for root
-                          String name,
-                          String doc,
-                          Boolean nullable,
-                          ImmutableSet<String> registeredRecords,
-                          List<DataSetField> sink) {
+  private static void extractMap(Schema schema,
+                                 String parentOddr,
+                                 String oddrn, //null for root
+                                 String name,
+                                 String doc,
+                                 Boolean nullable,
+                                 ImmutableSet<String> registeredRecords,
+                                 List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     oddrn = isRoot ? parentOddr + "/map" : oddrn;
     if (isRoot) {
@@ -214,13 +214,13 @@ class AvroExtractor {
   }
 
 
-  private void extractPrimitive(Schema schema,
-                                String parentOddr,
-                                String oddrn, //null for root
-                                String name,
-                                String doc,
-                                Boolean nullable,
-                                List<DataSetField> sink) {
+  private static void extractPrimitive(Schema schema,
+                                       String parentOddr,
+                                       String oddrn, //null for root
+                                       String name,
+                                       String doc,
+                                       Boolean nullable,
+                                       List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     String primOddrn = isRoot ? (parentOddr + "/" + schema.getType()) : oddrn;
     if (isRoot) {
@@ -231,7 +231,7 @@ class AvroExtractor {
     }
   }
 
-  private DataSetFieldType.TypeEnum mapType(Schema.Type type) {
+  private static DataSetFieldType.TypeEnum mapType(Schema.Type type) {
     return switch (type) {
       case INT, LONG -> DataSetFieldType.TypeEnum.INTEGER;
       case FLOAT, DOUBLE, FIXED -> DataSetFieldType.TypeEnum.NUMBER;
@@ -246,14 +246,14 @@ class AvroExtractor {
     };
   }
 
-  private DataSetFieldType mapSchema(Schema schema, Boolean nullable) {
+  private static DataSetFieldType mapSchema(Schema schema, Boolean nullable) {
     return new DataSetFieldType()
         .logicalType(logicalType(schema))
         .isNullable(nullable)
         .type(mapType(schema.getType()));
   }
 
-  private String logicalType(Schema schema) {
+  private static String logicalType(Schema schema) {
     return schema.getType() == Schema.Type.RECORD
         ? schema.getFullName()
         : schema.getType().toString().toLowerCase();

+ 3 - 6
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/schema/DataSetFieldsExtractors.java

@@ -1,19 +1,16 @@
 package com.provectus.kafka.ui.service.integration.odd.schema;
 
-import com.provectus.kafka.ui.service.integration.odd.Oddrn;
 import com.provectus.kafka.ui.sr.model.SchemaSubject;
 import com.provectus.kafka.ui.sr.model.SchemaType;
 import java.util.List;
 import java.util.Optional;
-import lombok.experimental.UtilityClass;
 import org.opendatadiscovery.client.model.DataSetField;
 import org.opendatadiscovery.client.model.DataSetFieldType;
 import org.opendatadiscovery.oddrn.model.KafkaPath;
 
-@UtilityClass
-public class DataSetFieldsExtractors {
+public final class DataSetFieldsExtractors {
 
-  public List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
+  public static List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
     SchemaType schemaType = Optional.ofNullable(subject.getSchemaType()).orElse(SchemaType.AVRO);
     return switch (schemaType) {
       case AVRO -> AvroExtractor.extract(subject, topicOddrn, isKey);
@@ -23,7 +20,7 @@ public class DataSetFieldsExtractors {
   }
 
 
-  DataSetField rootField(KafkaPath topicOddrn, boolean isKey) {
+  static DataSetField rootField(KafkaPath topicOddrn, boolean isKey) {
     var rootOddrn = topicOddrn.oddrn() + "/columns/" + (isKey ? "key" : "value");
     return new DataSetField()
         .name(isKey ? "key" : "value")

+ 54 - 54
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/schema/JsonSchemaExtractor.java

@@ -1,7 +1,6 @@
 package com.provectus.kafka.ui.service.integration.odd.schema;
 
 import com.google.common.collect.ImmutableSet;
-import com.provectus.kafka.ui.service.integration.odd.Oddrn;
 import com.provectus.kafka.ui.sr.model.SchemaSubject;
 import io.confluent.kafka.schemaregistry.json.JsonSchema;
 import java.net.URI;
@@ -10,7 +9,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import javax.annotation.Nullable;
-import lombok.experimental.UtilityClass;
 import org.everit.json.schema.ArraySchema;
 import org.everit.json.schema.BooleanSchema;
 import org.everit.json.schema.CombinedSchema;
@@ -27,8 +25,10 @@ import org.opendatadiscovery.client.model.DataSetFieldType;
 import org.opendatadiscovery.client.model.MetadataExtension;
 import org.opendatadiscovery.oddrn.model.KafkaPath;
 
-@UtilityClass
-class JsonSchemaExtractor {
+final class JsonSchemaExtractor {
+
+  private JsonSchemaExtractor() {
+  }
 
   static List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
     Schema schema = new JsonSchema(subject.getSchema()).rawSchema();
@@ -46,13 +46,13 @@ class JsonSchemaExtractor {
     return result;
   }
 
-  private void extract(Schema schema,
-                       String parentOddr,
-                       String oddrn, //null for root
-                       String name,
-                       Boolean nullable,
-                       ImmutableSet<String> registeredRecords,
-                       List<DataSetField> sink) {
+  private static void extract(Schema schema,
+                              String parentOddr,
+                              String oddrn, //null for root
+                              String name,
+                              Boolean nullable,
+                              ImmutableSet<String> registeredRecords,
+                              List<DataSetField> sink) {
     if (schema instanceof ReferenceSchema s) {
       Optional.ofNullable(s.getReferredSchema())
           .ifPresent(refSchema -> extract(refSchema, parentOddr, oddrn, name, nullable, registeredRecords, sink));
@@ -73,12 +73,12 @@ class JsonSchemaExtractor {
     }
   }
 
-  private void extractPrimitive(Schema schema,
-                                String parentOddr,
-                                String oddrn, //null for root
-                                String name,
-                                Boolean nullable,
-                                List<DataSetField> sink) {
+  private static void extractPrimitive(Schema schema,
+                                       String parentOddr,
+                                       String oddrn, //null for root
+                                       String name,
+                                       Boolean nullable,
+                                       List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     sink.add(
         createDataSetField(
@@ -93,12 +93,12 @@ class JsonSchemaExtractor {
     );
   }
 
-  private void extractUnknown(Schema schema,
-                              String parentOddr,
-                              String oddrn, //null for root
-                              String name,
-                              Boolean nullable,
-                              List<DataSetField> sink) {
+  private static void extractUnknown(Schema schema,
+                                     String parentOddr,
+                                     String oddrn, //null for root
+                                     String name,
+                                     Boolean nullable,
+                                     List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     sink.add(
         createDataSetField(
@@ -113,13 +113,13 @@ class JsonSchemaExtractor {
     );
   }
 
-  private void extractObject(ObjectSchema schema,
-                             String parentOddr,
-                             String oddrn, //null for root
-                             String name,
-                             Boolean nullable,
-                             ImmutableSet<String> registeredRecords,
-                             List<DataSetField> sink) {
+  private static void extractObject(ObjectSchema schema,
+                                    String parentOddr,
+                                    String oddrn, //null for root
+                                    String name,
+                                    Boolean nullable,
+                                    ImmutableSet<String> registeredRecords,
+                                    List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     // schemaLocation can be null for empty object schemas (like if it used in anyOf)
     @Nullable var schemaLocation = schema.getSchemaLocation();
@@ -162,13 +162,13 @@ class JsonSchemaExtractor {
     });
   }
 
-  private void extractArray(ArraySchema schema,
-                            String parentOddr,
-                            String oddrn, //null for root
-                            String name,
-                            Boolean nullable,
-                            ImmutableSet<String> registeredRecords,
-                            List<DataSetField> sink) {
+  private static void extractArray(ArraySchema schema,
+                                   String parentOddr,
+                                   String oddrn, //null for root
+                                   String name,
+                                   Boolean nullable,
+                                   ImmutableSet<String> registeredRecords,
+                                   List<DataSetField> sink) {
     boolean isRoot = oddrn == null;
     oddrn = isRoot ? parentOddr + "/array" : oddrn;
     if (isRoot) {
@@ -208,13 +208,13 @@ class JsonSchemaExtractor {
     }
   }
 
-  private void extractCombined(CombinedSchema schema,
-                               String parentOddr,
-                               String oddrn, //null for root
-                               String name,
-                               Boolean nullable,
-                               ImmutableSet<String> registeredRecords,
-                               List<DataSetField> sink) {
+  private static void extractCombined(CombinedSchema schema,
+                                      String parentOddr,
+                                      String oddrn, //null for root
+                                      String name,
+                                      Boolean nullable,
+                                      ImmutableSet<String> registeredRecords,
+                                      List<DataSetField> sink) {
     String combineType = "unknown";
     if (schema.getCriterion() == CombinedSchema.ALL_CRITERION) {
       combineType = "allOf";
@@ -255,24 +255,24 @@ class JsonSchemaExtractor {
     }
   }
 
-  private String getDescription(Schema schema) {
+  private static String getDescription(Schema schema) {
     return Optional.ofNullable(schema.getTitle())
         .orElse(schema.getDescription());
   }
 
-  private String logicalTypeName(Schema schema) {
+  private static String logicalTypeName(Schema schema) {
     return schema.getClass()
         .getSimpleName()
         .replace("Schema", "");
   }
 
-  private DataSetField createDataSetField(Schema schema,
-                                          String name,
-                                          String parentOddrn,
-                                          String oddrn,
-                                          DataSetFieldType.TypeEnum type,
-                                          String logicalType,
-                                          Boolean nullable) {
+  private static DataSetField createDataSetField(Schema schema,
+                                                 String name,
+                                                 String parentOddrn,
+                                                 String oddrn,
+                                                 DataSetFieldType.TypeEnum type,
+                                                 String logicalType,
+                                                 Boolean nullable) {
     return new DataSetField()
         .name(name)
         .parentFieldOddrn(parentOddrn)
@@ -286,7 +286,7 @@ class JsonSchemaExtractor {
         );
   }
 
-  private DataSetFieldType.TypeEnum mapType(Schema type) {
+  private static DataSetFieldType.TypeEnum mapType(Schema type) {
     if (type instanceof NumberSchema) {
       return DataSetFieldType.TypeEnum.NUMBER;
     }

+ 47 - 47
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/integration/odd/schema/ProtoExtractor.java

@@ -15,20 +15,17 @@ import com.google.protobuf.Timestamp;
 import com.google.protobuf.UInt32Value;
 import com.google.protobuf.UInt64Value;
 import com.google.protobuf.Value;
-import com.provectus.kafka.ui.service.integration.odd.Oddrn;
 import com.provectus.kafka.ui.sr.model.SchemaSubject;
 import io.confluent.kafka.schemaregistry.protobuf.ProtobufSchema;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
-import lombok.experimental.UtilityClass;
 import org.opendatadiscovery.client.model.DataSetField;
 import org.opendatadiscovery.client.model.DataSetFieldType;
 import org.opendatadiscovery.client.model.DataSetFieldType.TypeEnum;
 import org.opendatadiscovery.oddrn.model.KafkaPath;
 
-@UtilityClass
-class ProtoExtractor {
+final class ProtoExtractor {
 
   private static final Set<String> PRIMITIVES_WRAPPER_TYPE_NAMES = Set.of(
       BoolValue.getDescriptor().getFullName(),
@@ -42,7 +39,10 @@ class ProtoExtractor {
       DoubleValue.getDescriptor().getFullName()
   );
 
-  List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
+  private ProtoExtractor() {
+  }
+
+  static List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
     Descriptor schema = new ProtobufSchema(subject.getSchema()).toDescriptor();
     List<DataSetField> result = new ArrayList<>();
     result.add(DataSetFieldsExtractors.rootField(topicOddrn, isKey));
@@ -60,14 +60,14 @@ class ProtoExtractor {
     return result;
   }
 
-  private void extract(Descriptors.FieldDescriptor field,
-                       String parentOddr,
-                       String oddrn, //null for root
-                       String name,
-                       boolean nullable,
-                       boolean repeated,
-                       ImmutableSet<String> registeredRecords,
-                       List<DataSetField> sink) {
+  private static void extract(Descriptors.FieldDescriptor field,
+                              String parentOddr,
+                              String oddrn, //null for root
+                              String name,
+                              boolean nullable,
+                              boolean repeated,
+                              ImmutableSet<String> registeredRecords,
+                              List<DataSetField> sink) {
     if (repeated) {
       extractRepeated(field, parentOddr, oddrn, name, nullable, registeredRecords, sink);
     } else if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
@@ -79,12 +79,12 @@ class ProtoExtractor {
 
   // converts some(!) Protobuf Well-known type (from google.protobuf.* packages)
   // see JsonFormat::buildWellKnownTypePrinters for impl details
-  private boolean extractProtoWellKnownType(Descriptors.FieldDescriptor field,
-                                            String parentOddr,
-                                            String oddrn, //null for root
-                                            String name,
-                                            boolean nullable,
-                                            List<DataSetField> sink) {
+  private static boolean extractProtoWellKnownType(Descriptors.FieldDescriptor field,
+                                                   String parentOddr,
+                                                   String oddrn, //null for root
+                                                   String name,
+                                                   boolean nullable,
+                                                   List<DataSetField> sink) {
     // all well-known types are messages
     if (field.getType() != Descriptors.FieldDescriptor.Type.MESSAGE) {
       return false;
@@ -111,13 +111,13 @@ class ProtoExtractor {
     return false;
   }
 
-  private void extractRepeated(Descriptors.FieldDescriptor field,
-                               String parentOddr,
-                               String oddrn, //null for root
-                               String name,
-                               boolean nullable,
-                               ImmutableSet<String> registeredRecords,
-                               List<DataSetField> sink) {
+  private static void extractRepeated(Descriptors.FieldDescriptor field,
+                                      String parentOddr,
+                                      String oddrn, //null for root
+                                      String name,
+                                      boolean nullable,
+                                      ImmutableSet<String> registeredRecords,
+                                      List<DataSetField> sink) {
     sink.add(createDataSetField(name, parentOddr, oddrn, TypeEnum.LIST, "repeated", nullable));
 
     String itemName = field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE
@@ -136,13 +136,13 @@ class ProtoExtractor {
     );
   }
 
-  private void extractMessage(Descriptors.FieldDescriptor field,
-                              String parentOddr,
-                              String oddrn, //null for root
-                              String name,
-                              boolean nullable,
-                              ImmutableSet<String> registeredRecords,
-                              List<DataSetField> sink) {
+  private static void extractMessage(Descriptors.FieldDescriptor field,
+                                     String parentOddr,
+                                     String oddrn, //null for root
+                                     String name,
+                                     boolean nullable,
+                                     ImmutableSet<String> registeredRecords,
+                                     List<DataSetField> sink) {
     if (extractProtoWellKnownType(field, parentOddr, oddrn, name, nullable, sink)) {
       return;
     }
@@ -173,12 +173,12 @@ class ProtoExtractor {
         });
   }
 
-  private void extractPrimitive(Descriptors.FieldDescriptor field,
-                                String parentOddr,
-                                String oddrn,
-                                String name,
-                                boolean nullable,
-                                List<DataSetField> sink) {
+  private static void extractPrimitive(Descriptors.FieldDescriptor field,
+                                       String parentOddr,
+                                       String oddrn,
+                                       String name,
+                                       boolean nullable,
+                                       List<DataSetField> sink) {
     sink.add(
         createDataSetField(
             name,
@@ -191,18 +191,18 @@ class ProtoExtractor {
     );
   }
 
-  private String getLogicalTypeName(Descriptors.FieldDescriptor f) {
+  private static String getLogicalTypeName(Descriptors.FieldDescriptor f) {
     return f.getType() == Descriptors.FieldDescriptor.Type.MESSAGE
         ? f.getMessageType().getFullName()
         : f.getType().name().toLowerCase();
   }
 
-  private DataSetField createDataSetField(String name,
-                                          String parentOddrn,
-                                          String oddrn,
-                                          TypeEnum type,
-                                          String logicalType,
-                                          Boolean nullable) {
+  private static DataSetField createDataSetField(String name,
+                                                 String parentOddrn,
+                                                 String oddrn,
+                                                 TypeEnum type,
+                                                 String logicalType,
+                                                 Boolean nullable) {
     return new DataSetField()
         .name(name)
         .parentFieldOddrn(parentOddrn)
@@ -216,7 +216,7 @@ class ProtoExtractor {
   }
 
 
-  private TypeEnum mapType(Descriptors.FieldDescriptor.Type type) {
+  private static TypeEnum mapType(Descriptors.FieldDescriptor.Type type) {
     return switch (type) {
       case INT32, INT64, SINT32, SFIXED32, SINT64, UINT32, UINT64, FIXED32, FIXED64, SFIXED64 -> TypeEnum.INTEGER;
       case FLOAT, DOUBLE -> TypeEnum.NUMBER;

+ 18 - 18
kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/KafkaServicesValidation.java

@@ -1,6 +1,7 @@
 package com.provectus.kafka.ui.util;
 
-import com.provectus.kafka.ui.config.ClustersProperties;
+import static com.provectus.kafka.ui.config.ClustersProperties.TruststoreConfig;
+
 import com.provectus.kafka.ui.connect.api.KafkaConnectClientApi;
 import com.provectus.kafka.ui.model.ApplicationPropertyValidationDTO;
 import com.provectus.kafka.ui.service.ReactiveAdminClient;
@@ -13,38 +14,36 @@ import java.util.Optional;
 import java.util.Properties;
 import java.util.function.Supplier;
 import javax.annotation.Nullable;
-import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.TrustManagerFactory;
-import lombok.experimental.UtilityClass;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.kafka.clients.admin.AdminClient;
 import org.apache.kafka.clients.admin.AdminClientConfig;
 import org.springframework.util.ResourceUtils;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
-import reactor.util.function.Tuple2;
-import reactor.util.function.Tuples;
 
 @Slf4j
-@UtilityClass
-public class KafkaServicesValidation {
+public final class KafkaServicesValidation {
+
+  private KafkaServicesValidation() {
+  }
 
-  private Mono<ApplicationPropertyValidationDTO> valid() {
+  private static Mono<ApplicationPropertyValidationDTO> valid() {
     return Mono.just(new ApplicationPropertyValidationDTO().error(false));
   }
 
-  private Mono<ApplicationPropertyValidationDTO> invalid(String errorMsg) {
+  private static Mono<ApplicationPropertyValidationDTO> invalid(String errorMsg) {
     return Mono.just(new ApplicationPropertyValidationDTO().error(true).errorMessage(errorMsg));
   }
 
-  private Mono<ApplicationPropertyValidationDTO> invalid(Throwable th) {
+  private static Mono<ApplicationPropertyValidationDTO> invalid(Throwable th) {
     return Mono.just(new ApplicationPropertyValidationDTO().error(true).errorMessage(th.getMessage()));
   }
 
   /**
    * Returns error msg, if any.
    */
-  public Optional<String> validateTruststore(ClustersProperties.TruststoreConfig truststoreConfig) {
+  public static Optional<String> validateTruststore(TruststoreConfig truststoreConfig) {
     if (truststoreConfig.getTruststoreLocation() != null && truststoreConfig.getTruststorePassword() != null) {
       try {
         KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
@@ -63,10 +62,10 @@ public class KafkaServicesValidation {
     return Optional.empty();
   }
 
-  public Mono<ApplicationPropertyValidationDTO> validateClusterConnection(String bootstrapServers,
-                                                                          Properties clusterProps,
-                                                                          @Nullable
-                                                                          ClustersProperties.TruststoreConfig ssl) {
+  public static Mono<ApplicationPropertyValidationDTO> validateClusterConnection(String bootstrapServers,
+                                                                                 Properties clusterProps,
+                                                                                 @Nullable
+                                                                                 TruststoreConfig ssl) {
     Properties properties = new Properties();
     SslPropertiesUtil.addKafkaSslProperties(ssl, properties);
     properties.putAll(clusterProps);
@@ -93,7 +92,7 @@ public class KafkaServicesValidation {
         });
   }
 
-  public Mono<ApplicationPropertyValidationDTO> validateSchemaRegistry(
+  public static Mono<ApplicationPropertyValidationDTO> validateSchemaRegistry(
       Supplier<ReactiveFailover<KafkaSrClientApi>> clientSupplier) {
     ReactiveFailover<KafkaSrClientApi> client;
     try {
@@ -108,7 +107,7 @@ public class KafkaServicesValidation {
         .onErrorResume(KafkaServicesValidation::invalid);
   }
 
-  public Mono<ApplicationPropertyValidationDTO> validateConnect(
+  public static Mono<ApplicationPropertyValidationDTO> validateConnect(
       Supplier<ReactiveFailover<KafkaConnectClientApi>> clientSupplier) {
     ReactiveFailover<KafkaConnectClientApi> client;
     try {
@@ -123,7 +122,8 @@ public class KafkaServicesValidation {
         .onErrorResume(KafkaServicesValidation::invalid);
   }
 
-  public Mono<ApplicationPropertyValidationDTO> validateKsql(Supplier<ReactiveFailover<KsqlApiClient>> clientSupplier) {
+  public static Mono<ApplicationPropertyValidationDTO> validateKsql(
+      Supplier<ReactiveFailover<KsqlApiClient>> clientSupplier) {
     ReactiveFailover<KsqlApiClient> client;
     try {
       client = clientSupplier.get();

+ 4 - 3
kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/KafkaVersion.java

@@ -1,11 +1,12 @@
 package com.provectus.kafka.ui.util;
 
-import lombok.experimental.UtilityClass;
 import lombok.extern.slf4j.Slf4j;
 
-@UtilityClass
 @Slf4j
-public class KafkaVersion {
+public final class KafkaVersion {
+
+  private KafkaVersion() {
+  }
 
   public static float parse(String version) throws NumberFormatException {
     log.trace("Parsing cluster version [{}]", version);

+ 6 - 16
kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/SslPropertiesUtil.java

@@ -1,27 +1,17 @@
 package com.provectus.kafka.ui.util;
 
 import com.provectus.kafka.ui.config.ClustersProperties;
-import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
-import java.io.FileInputStream;
-import java.security.KeyStore;
 import java.util.Properties;
 import javax.annotation.Nullable;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManagerFactory;
-import lombok.SneakyThrows;
-import lombok.experimental.UtilityClass;
 import org.apache.kafka.common.config.SslConfigs;
-import org.springframework.http.client.reactive.ReactorClientHttpConnector;
-import org.springframework.util.ResourceUtils;
-import reactor.netty.http.client.HttpClient;
 
-@UtilityClass
-public class SslPropertiesUtil {
+public final class SslPropertiesUtil {
 
-  public void addKafkaSslProperties(@Nullable ClustersProperties.TruststoreConfig truststoreConfig,
-                                    Properties sink) {
+  private SslPropertiesUtil() {
+  }
+
+  public static void addKafkaSslProperties(@Nullable ClustersProperties.TruststoreConfig truststoreConfig,
+                                           Properties sink) {
     if (truststoreConfig != null && truststoreConfig.getTruststoreLocation() != null) {
       sink.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, truststoreConfig.getTruststoreLocation());
       if (truststoreConfig.getTruststorePassword() != null) {

+ 8 - 8
kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/TopicsServicePaginationTest.java

@@ -69,7 +69,7 @@ class TopicsServicePaginationTest {
             .map(Objects::toString)
             .map(name -> new TopicDescription(name, false, List.of()))
             .map(topicDescription -> InternalTopic.from(topicDescription, List.of(), null,
-                Metrics.empty(), InternalLogDirStats.empty()))
+                Metrics.empty(), InternalLogDirStats.empty(), "_"))
             .collect(Collectors.toMap(InternalTopic::getName, Function.identity()))
     );
 
@@ -95,7 +95,7 @@ class TopicsServicePaginationTest {
         .map(Objects::toString)
         .map(name -> new TopicDescription(name, false, List.of()))
         .map(topicDescription -> InternalTopic.from(topicDescription, List.of(), null,
-            Metrics.empty(), InternalLogDirStats.empty()))
+            Metrics.empty(), InternalLogDirStats.empty(), "_"))
         .collect(Collectors.toMap(InternalTopic::getName, Function.identity()));
     init(internalTopics);
 
@@ -122,7 +122,7 @@ class TopicsServicePaginationTest {
             .map(Objects::toString)
             .map(name -> new TopicDescription(name, false, List.of()))
             .map(topicDescription -> InternalTopic.from(topicDescription, List.of(), null,
-                Metrics.empty(), InternalLogDirStats.empty()))
+                Metrics.empty(), InternalLogDirStats.empty(), "_"))
             .collect(Collectors.toMap(InternalTopic::getName, Function.identity()))
     );
 
@@ -141,7 +141,7 @@ class TopicsServicePaginationTest {
             .map(Objects::toString)
             .map(name -> new TopicDescription(name, false, List.of()))
             .map(topicDescription -> InternalTopic.from(topicDescription, List.of(), null,
-                Metrics.empty(), InternalLogDirStats.empty()))
+                Metrics.empty(), InternalLogDirStats.empty(), "_"))
             .collect(Collectors.toMap(InternalTopic::getName, Function.identity()))
     );
 
@@ -160,7 +160,7 @@ class TopicsServicePaginationTest {
             .map(Objects::toString)
             .map(name -> new TopicDescription(name, Integer.parseInt(name) % 10 == 0, List.of()))
             .map(topicDescription -> InternalTopic.from(topicDescription, List.of(), null,
-                Metrics.empty(), InternalLogDirStats.empty()))
+                Metrics.empty(), InternalLogDirStats.empty(), "_"))
             .collect(Collectors.toMap(InternalTopic::getName, Function.identity()))
     );
 
@@ -181,7 +181,7 @@ class TopicsServicePaginationTest {
             .map(Objects::toString)
             .map(name -> new TopicDescription(name, Integer.parseInt(name) % 5 == 0, List.of()))
             .map(topicDescription -> InternalTopic.from(topicDescription, List.of(), null,
-                Metrics.empty(), InternalLogDirStats.empty()))
+                Metrics.empty(), InternalLogDirStats.empty(), "_"))
             .collect(Collectors.toMap(InternalTopic::getName, Function.identity()))
     );
 
@@ -202,7 +202,7 @@ class TopicsServicePaginationTest {
             .map(Objects::toString)
             .map(name -> new TopicDescription(name, false, List.of()))
             .map(topicDescription -> InternalTopic.from(topicDescription, List.of(), null,
-                Metrics.empty(), InternalLogDirStats.empty()))
+                Metrics.empty(), InternalLogDirStats.empty(), "_"))
             .collect(Collectors.toMap(InternalTopic::getName, Function.identity()))
     );
 
@@ -224,7 +224,7 @@ class TopicsServicePaginationTest {
                     new TopicPartitionInfo(p, null, List.of(), List.of()))
                 .collect(Collectors.toList())))
         .map(topicDescription -> InternalTopic.from(topicDescription, List.of(), InternalPartitionsOffsets.empty(),
-            Metrics.empty(), InternalLogDirStats.empty()))
+            Metrics.empty(), InternalLogDirStats.empty(), "_"))
         .collect(Collectors.toMap(InternalTopic::getName, Function.identity()));
 
     init(internalTopics);

+ 1 - 1
kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml

@@ -775,7 +775,7 @@ paths:
     get:
       tags:
         - Consumer Groups
-      summary: Get consumer croups with paging support
+      summary: Get consumer groups with paging support
       operationId: getConsumerGroupsPage
       parameters:
         - name: clusterName

+ 3 - 3
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/models/Schema.java

@@ -16,18 +16,18 @@ public class Schema {
     public static Schema createSchemaAvro() {
         return new Schema().setName("schema_avro-" + randomAlphabetic(5))
                 .setType(SchemaType.AVRO)
-                .setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schema_avro_value.json");
+                .setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_avro_value.json");
     }
 
     public static Schema createSchemaJson() {
         return new Schema().setName("schema_json-" + randomAlphabetic(5))
                 .setType(SchemaType.JSON)
-                .setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schema_Json_Value.json");
+                .setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_json_Value.json");
     }
 
     public static Schema createSchemaProtobuf() {
         return new Schema().setName("schema_protobuf-" + randomAlphabetic(5))
                 .setType(SchemaType.PROTOBUF)
-                .setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schema_protobuf_value.txt");
+                .setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_protobuf_value.txt");
     }
 }

+ 25 - 1
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/BasePage.java

@@ -3,8 +3,12 @@ package com.provectus.kafka.ui.pages;
 import com.codeborne.selenide.Condition;
 import com.codeborne.selenide.ElementsCollection;
 import com.codeborne.selenide.SelenideElement;
+import com.codeborne.selenide.WebDriverRunner;
+import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
 import com.provectus.kafka.ui.utilities.WebUtils;
 import lombok.extern.slf4j.Slf4j;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.interactions.Actions;
 
 import java.time.Duration;
 
@@ -30,18 +34,38 @@ public abstract class BasePage extends WebUtils {
     protected String summaryCellLocator = "//div[contains(text(),'%s')]";
     protected String tableElementNameLocator = "//tbody//a[contains(text(),'%s')]";
     protected String columnHeaderLocator = "//table//tr/th//div[text()='%s']";
+    protected String pageTitleFromHeader = "//h1[text()='%s']";
+    protected String pagePathFromHeader = "//a[text()='%s']/../h1";
 
     protected void waitUntilSpinnerDisappear() {
         log.debug("\nwaitUntilSpinnerDisappear");
         if (isVisible(loadingSpinner)) {
-            loadingSpinner.shouldBe(Condition.disappear, Duration.ofSeconds(30));
+            loadingSpinner.shouldBe(Condition.disappear, Duration.ofSeconds(60));
         }
     }
 
+    protected SelenideElement getPageTitleFromHeader(MenuItem menuItem) {
+        return $x(String.format(pageTitleFromHeader, menuItem.getPageTitle()));
+    }
+
+    protected SelenideElement getPagePathFromHeader(MenuItem menuItem) {
+        return $x(String.format(pagePathFromHeader, menuItem.getPageTitle()));
+    }
+
     protected void clickSubmitBtn() {
         clickByJavaScript(submitBtn);
     }
 
+    protected void setJsonInputValue(SelenideElement jsonInput, String jsonConfig) {
+        sendKeysByActions(jsonInput, jsonConfig.replace("  ", ""));
+        new Actions(WebDriverRunner.getWebDriver())
+                .keyDown(Keys.SHIFT)
+                .sendKeys(Keys.PAGE_DOWN)
+                .keyUp(Keys.SHIFT)
+                .sendKeys(Keys.DELETE)
+                .perform();
+    }
+
     protected SelenideElement getTableElement(String elementName) {
         log.debug("\ngetTableElement: {}", elementName);
         return $x(String.format(tableElementNameLocator, elementName));

+ 2 - 3
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/brokers/BrokersList.java

@@ -12,15 +12,14 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static com.codeborne.selenide.Selenide.$x;
+import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.BROKERS;
 
 public class BrokersList extends BasePage {
 
-    protected SelenideElement brokersListHeader = $x("//h1[text()='Brokers']");
-
     @Step
     public BrokersList waitUntilScreenReady() {
         waitUntilSpinnerDisappear();
-        brokersListHeader.shouldBe(Condition.visible);
+        getPageTitleFromHeader(BROKERS).shouldBe(Condition.visible);
         return this;
     }
 

+ 14 - 3
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/connectors/ConnectorCreateForm.java

@@ -21,11 +21,22 @@ public class ConnectorCreateForm extends BasePage {
     }
 
     @Step
-    public ConnectorCreateForm setConnectorDetails(String connectName, String configJson) {
+    public ConnectorCreateForm setName(String connectName) {
         nameField.shouldBe(Condition.enabled).setValue(connectName);
+        return this;
+    }
+
+    @Step
+    public ConnectorCreateForm setConfig(String configJson) {
         configField.shouldBe(Condition.enabled).click();
-        contentTextArea.setValue(configJson);
-        nameField.shouldBe(Condition.enabled).click();
+        setJsonInputValue(contentTextArea, configJson);
+        return this;
+    }
+
+    @Step
+    public ConnectorCreateForm setConnectorDetails(String connectName, String configJson) {
+        setName(connectName);
+        setConfig(configJson);
         return this;
     }
 

+ 2 - 1
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/connectors/KafkaConnectList.java

@@ -6,6 +6,7 @@ import com.provectus.kafka.ui.pages.BasePage;
 import io.qameta.allure.Step;
 
 import static com.codeborne.selenide.Selenide.$x;
+import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.KAFKA_CONNECT;
 
 
 public class KafkaConnectList extends BasePage {
@@ -19,7 +20,7 @@ public class KafkaConnectList extends BasePage {
     @Step
     public KafkaConnectList waitUntilScreenReady() {
         waitUntilSpinnerDisappear();
-        createConnectorBtn.shouldBe(Condition.visible);
+        getPageTitleFromHeader(KAFKA_CONNECT).shouldBe(Condition.visible);
         return this;
     }
 

+ 2 - 5
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/consumers/ConsumersList.java

@@ -1,20 +1,17 @@
 package com.provectus.kafka.ui.pages.consumers;
 
 import com.codeborne.selenide.Condition;
-import com.codeborne.selenide.SelenideElement;
 import com.provectus.kafka.ui.pages.BasePage;
 import io.qameta.allure.Step;
 
-import static com.codeborne.selenide.Selenide.$x;
+import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.CONSUMERS;
 
 public class ConsumersList extends BasePage {
 
-    protected SelenideElement consumerListHeader = $x("//h1[text()='Consumers']");
-    
     @Step
     public ConsumersList waitUntilScreenReady() {
         waitUntilSpinnerDisappear();
-        consumerListHeader.shouldHave(Condition.visible);
+        getPageTitleFromHeader(CONSUMERS).shouldBe(Condition.visible);
         return this;
     }
 }

+ 99 - 97
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqlDb/KsqlDbList.java

@@ -1,137 +1,139 @@
 package com.provectus.kafka.ui.pages.ksqlDb;
 
-import static com.codeborne.selenide.Selenide.$;
-import static com.codeborne.selenide.Selenide.$x;
-
 import com.codeborne.selenide.CollectionCondition;
 import com.codeborne.selenide.Condition;
 import com.codeborne.selenide.SelenideElement;
 import com.provectus.kafka.ui.pages.BasePage;
 import com.provectus.kafka.ui.pages.ksqlDb.enums.KsqlMenuTabs;
 import io.qameta.allure.Step;
+import org.openqa.selenium.By;
+
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
-import org.openqa.selenium.By;
+
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$x;
+import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.KSQL_DB;
 
 public class KsqlDbList extends BasePage {
-  protected SelenideElement executeKsqlBtn = $x("//button[text()='Execute KSQL Request']");
-  protected SelenideElement tablesTab = $x("//nav[@role='navigation']/a[text()='Tables']");
-  protected SelenideElement streamsTab = $x("//nav[@role='navigation']/a[text()='Streams']");
-
-  @Step
-  public KsqlDbList waitUntilScreenReady() {
-    waitUntilSpinnerDisappear();
-    Arrays.asList(tablesTab, streamsTab).forEach(tab -> tab.shouldBe(Condition.visible));
-    return this;
-  }
-
-  @Step
-  public KsqlDbList clickExecuteKsqlRequestBtn() {
-    clickByJavaScript(executeKsqlBtn);
-    return this;
-  }
-
-  @Step
-  public KsqlDbList openDetailsTab(KsqlMenuTabs menu) {
-    $(By.linkText(menu.toString())).shouldBe(Condition.visible).click();
-    waitUntilSpinnerDisappear();
-    return this;
-  }
-
-  private List<KsqlDbList.KsqlTablesGridItem> initTablesItems() {
-    List<KsqlDbList.KsqlTablesGridItem> gridItemList = new ArrayList<>();
-    gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
-        .forEach(item -> gridItemList.add(new KsqlDbList.KsqlTablesGridItem(item)));
-    return gridItemList;
-  }
-
-  @Step
-  public KsqlDbList.KsqlTablesGridItem getTableByName(String tableName) {
-    return initTablesItems().stream()
-        .filter(e -> e.getTableName().equals(tableName))
-        .findFirst().orElseThrow();
-  }
-
-  public static class KsqlTablesGridItem extends BasePage {
-
-    private final SelenideElement element;
-
-    public KsqlTablesGridItem(SelenideElement element) {
-      this.element = element;
-    }
+
+    protected SelenideElement executeKsqlBtn = $x("//button[text()='Execute KSQL Request']");
+    protected SelenideElement tablesTab = $x("//nav[@role='navigation']/a[text()='Tables']");
+    protected SelenideElement streamsTab = $x("//nav[@role='navigation']/a[text()='Streams']");
 
     @Step
-    public String getTableName() {
-      return element.$x("./td[1]").getText().trim();
+    public KsqlDbList waitUntilScreenReady() {
+        waitUntilSpinnerDisappear();
+        getPageTitleFromHeader(KSQL_DB).shouldBe(Condition.visible);
+        return this;
     }
 
     @Step
-    public String getTopicName() {
-      return element.$x("./td[2]").getText().trim();
+    public KsqlDbList clickExecuteKsqlRequestBtn() {
+        clickByJavaScript(executeKsqlBtn);
+        return this;
     }
 
     @Step
-    public String getKeyFormat() {
-      return element.$x("./td[3]").getText().trim();
+    public KsqlDbList openDetailsTab(KsqlMenuTabs menu) {
+        $(By.linkText(menu.toString())).shouldBe(Condition.visible).click();
+        waitUntilSpinnerDisappear();
+        return this;
+    }
+
+    private List<KsqlDbList.KsqlTablesGridItem> initTablesItems() {
+        List<KsqlDbList.KsqlTablesGridItem> gridItemList = new ArrayList<>();
+        gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
+                .forEach(item -> gridItemList.add(new KsqlDbList.KsqlTablesGridItem(item)));
+        return gridItemList;
     }
 
     @Step
-    public String getValueFormat() {
-      return element.$x("./td[4]").getText().trim();
+    public KsqlDbList.KsqlTablesGridItem getTableByName(String tableName) {
+        return initTablesItems().stream()
+                .filter(e -> e.getTableName().equals(tableName))
+                .findFirst().orElseThrow();
+    }
+
+    private List<KsqlDbList.KsqlStreamsGridItem> initStreamsItems() {
+        List<KsqlDbList.KsqlStreamsGridItem> gridItemList = new ArrayList<>();
+        gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
+                .forEach(item -> gridItemList.add(new KsqlDbList.KsqlStreamsGridItem(item)));
+        return gridItemList;
     }
 
     @Step
-    public String getIsWindowed() {
-      return element.$x("./td[5]").getText().trim();
+    public KsqlDbList.KsqlStreamsGridItem getStreamByName(String streamName) {
+        return initStreamsItems().stream()
+                .filter(e -> e.getStreamName().equals(streamName))
+                .findFirst().orElseThrow();
     }
-  }
 
-  private List<KsqlDbList.KsqlStreamsGridItem> initStreamsItems() {
-    List<KsqlDbList.KsqlStreamsGridItem> gridItemList = new ArrayList<>();
-    gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
-        .forEach(item -> gridItemList.add(new KsqlDbList.KsqlStreamsGridItem(item)));
-    return gridItemList;
-  }
+    public static class KsqlTablesGridItem extends BasePage {
 
-  @Step
-  public KsqlDbList.KsqlStreamsGridItem getStreamByName(String streamName) {
-    return initStreamsItems().stream()
-        .filter(e -> e.getStreamName().equals(streamName))
-        .findFirst().orElseThrow();
-  }
+        private final SelenideElement element;
 
-  public static class KsqlStreamsGridItem extends BasePage {
+        public KsqlTablesGridItem(SelenideElement element) {
+            this.element = element;
+        }
 
-    private final SelenideElement element;
+        @Step
+        public String getTableName() {
+            return element.$x("./td[1]").getText().trim();
+        }
 
-    public KsqlStreamsGridItem(SelenideElement element) {
-      this.element = element;
-    }
+        @Step
+        public String getTopicName() {
+            return element.$x("./td[2]").getText().trim();
+        }
 
-    @Step
-    public String getStreamName() {
-      return element.$x("./td[1]").getText().trim();
-    }
+        @Step
+        public String getKeyFormat() {
+            return element.$x("./td[3]").getText().trim();
+        }
 
-    @Step
-    public String getTopicName() {
-      return element.$x("./td[2]").getText().trim();
-    }
+        @Step
+        public String getValueFormat() {
+            return element.$x("./td[4]").getText().trim();
+        }
 
-    @Step
-    public String getKeyFormat() {
-      return element.$x("./td[3]").getText().trim();
+        @Step
+        public String getIsWindowed() {
+            return element.$x("./td[5]").getText().trim();
+        }
     }
 
-    @Step
-    public String getValueFormat() {
-      return element.$x("./td[4]").getText().trim();
-    }
+    public static class KsqlStreamsGridItem extends BasePage {
 
-    @Step
-    public String getIsWindowed() {
-      return element.$x("./td[5]").getText().trim();
+        private final SelenideElement element;
+
+        public KsqlStreamsGridItem(SelenideElement element) {
+            this.element = element;
+        }
+
+        @Step
+        public String getStreamName() {
+            return element.$x("./td[1]").getText().trim();
+        }
+
+        @Step
+        public String getTopicName() {
+            return element.$x("./td[2]").getText().trim();
+        }
+
+        @Step
+        public String getKeyFormat() {
+            return element.$x("./td[3]").getText().trim();
+        }
+
+        @Step
+        public String getValueFormat() {
+            return element.$x("./td[4]").getText().trim();
+        }
+
+        @Step
+        public String getIsWindowed() {
+            return element.$x("./td[5]").getText().trim();
+        }
     }
-  }
 }

+ 16 - 23
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/NaviSideBar.java → kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/panels/NaviSideBar.java

@@ -1,7 +1,9 @@
-package com.provectus.kafka.ui.pages;
+package com.provectus.kafka.ui.pages.panels;
 
 import com.codeborne.selenide.Condition;
 import com.codeborne.selenide.SelenideElement;
+import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
+import com.provectus.kafka.ui.pages.BasePage;
 import io.qameta.allure.Step;
 
 import java.time.Duration;
@@ -34,38 +36,29 @@ public class NaviSideBar extends BasePage {
     }
 
     @Step
-    public NaviSideBar openSideMenu(String clusterName, SideMenuOption option) {
+    public String getPagePath(MenuItem menuItem) {
+        return getPagePathFromHeader(menuItem)
+                .shouldBe(Condition.visible)
+                .getText().trim();
+    }
+
+    @Step
+    public NaviSideBar openSideMenu(String clusterName, MenuItem menuItem) {
         clickByActions(expandCluster(clusterName).parent()
-                .$x(String.format(sideMenuOptionElementLocator, option.value)));
+                .$x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())));
         return this;
     }
 
     @Step
-    public NaviSideBar openSideMenu(SideMenuOption option) {
-        openSideMenu(CLUSTER_NAME, option);
+    public NaviSideBar openSideMenu(MenuItem menuItem) {
+        openSideMenu(CLUSTER_NAME, menuItem);
         return this;
     }
 
     public List<SelenideElement> getAllMenuButtons() {
         expandCluster(CLUSTER_NAME);
-        return Stream.of(SideMenuOption.values())
-                .map(option -> $x(String.format(sideMenuOptionElementLocator, option.value)))
+        return Stream.of(MenuItem.values())
+                .map(menuItem -> $x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())))
                 .collect(Collectors.toList());
     }
-
-    public enum SideMenuOption {
-        DASHBOARD("Dashboard"),
-        BROKERS("Brokers"),
-        TOPICS("Topics"),
-        CONSUMERS("Consumers"),
-        SCHEMA_REGISTRY("Schema Registry"),
-        KAFKA_CONNECT("Kafka Connect"),
-        KSQL_DB("KSQL DB");
-
-        final String value;
-
-        SideMenuOption(String value) {
-            this.value = value;
-        }
-    }
 }

+ 2 - 1
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/TopPanel.java → kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/panels/TopPanel.java

@@ -1,6 +1,7 @@
-package com.provectus.kafka.ui.pages;
+package com.provectus.kafka.ui.pages.panels;
 
 import com.codeborne.selenide.SelenideElement;
+import com.provectus.kafka.ui.pages.BasePage;
 
 import java.util.Arrays;
 import java.util.List;

+ 28 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/panels/enums/MenuItem.java

@@ -0,0 +1,28 @@
+package com.provectus.kafka.ui.pages.panels.enums;
+
+public enum MenuItem {
+    
+    DASHBOARD("Dashboard", "Dashboard"),
+    BROKERS("Brokers", "Brokers"),
+    TOPICS("Topics", "Topics"),
+    CONSUMERS("Consumers", "Consumers"),
+    SCHEMA_REGISTRY("Schema Registry", "Schema Registry"),
+    KAFKA_CONNECT("Kafka Connect", "Connectors"),
+    KSQL_DB("KSQL DB", "KSQL DB");
+    
+    private final String naviTitle;
+    private final String pageTitle;
+    
+    MenuItem(String naviTitle, String pageTitle) {
+        this.naviTitle = naviTitle;
+        this.pageTitle = pageTitle;
+    }
+    
+    public String getNaviTitle() {
+        return naviTitle;
+    }
+    
+    public String getPageTitle() {
+        return pageTitle;
+    }
+}

+ 23 - 5
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/schemas/SchemaCreateForm.java

@@ -2,16 +2,20 @@ package com.provectus.kafka.ui.pages.schemas;
 
 import com.codeborne.selenide.Condition;
 import com.codeborne.selenide.SelenideElement;
+import com.codeborne.selenide.WebDriverRunner;
 import com.provectus.kafka.ui.api.model.CompatibilityLevel;
 import com.provectus.kafka.ui.api.model.SchemaType;
 import com.provectus.kafka.ui.pages.BasePage;
 import io.qameta.allure.Step;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.interactions.Actions;
 
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static com.codeborne.selenide.Selenide.*;
+import static org.openqa.selenium.By.id;
 
 public class SchemaCreateForm extends BasePage {
 
@@ -23,7 +27,8 @@ public class SchemaCreateForm extends BasePage {
     protected SelenideElement compatibilityLevelList = $x("//ul[@name='compatibilityLevel']");
     protected SelenideElement newSchemaTextArea = $x("//div[@id='newSchema']");
     protected SelenideElement latestSchemaTextArea = $x("//div[@id='latestSchema']");
-    protected SelenideElement schemaVersionDdl = $$x("//ul[@role='listbox']/li[text()='Version 2']").first();
+    protected SelenideElement leftVersionDdl = $(id("left-select"));
+    protected SelenideElement rightVersionDdl = $(id("right-select"));
     protected List<SelenideElement> visibleMarkers = $$x("//div[@class='ace_scroller']//div[contains(@class,'codeMarker')]");
     protected List<SelenideElement> elementsCompareVersionDdl = $$x("//ul[@role='listbox']/ul/li");
     protected String ddlElementLocator = "//li[@value='%s']";
@@ -68,8 +73,14 @@ public class SchemaCreateForm extends BasePage {
     }
 
     @Step
-    public SchemaCreateForm openSchemaVersionDdl() {
-        schemaVersionDdl.shouldBe(Condition.enabled).click();
+    public SchemaCreateForm openLeftVersionDdl() {
+        leftVersionDdl.shouldBe(Condition.enabled).click();
+        return this;
+    }
+
+    @Step
+    public SchemaCreateForm openRightVersionDdl() {
+        rightVersionDdl.shouldBe(Condition.enabled).click();
         return this;
     }
 
@@ -92,8 +103,15 @@ public class SchemaCreateForm extends BasePage {
     @Step
     public SchemaCreateForm setNewSchemaValue(String configJson) {
         newSchemaTextArea.shouldBe(Condition.visible).click();
-        clearByKeyboard(newSchemaInput);
-        newSchemaInput.setValue(configJson);
+        newSchemaInput.shouldBe(Condition.enabled);
+        new Actions(WebDriverRunner.getWebDriver())
+                .sendKeys(Keys.PAGE_UP)
+                .keyDown(Keys.SHIFT)
+                .sendKeys(Keys.PAGE_DOWN)
+                .keyUp(Keys.SHIFT)
+                .sendKeys(Keys.DELETE)
+                .perform();
+        setJsonInputValue(newSchemaInput, configJson);
         return this;
     }
 

+ 2 - 1
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/schemas/SchemaRegistryList.java

@@ -6,6 +6,7 @@ import com.provectus.kafka.ui.pages.BasePage;
 import io.qameta.allure.Step;
 
 import static com.codeborne.selenide.Selenide.$x;
+import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.SCHEMA_REGISTRY;
 
 public class SchemaRegistryList extends BasePage {
 
@@ -14,7 +15,7 @@ public class SchemaRegistryList extends BasePage {
     @Step
     public SchemaRegistryList waitUntilScreenReady() {
         waitUntilSpinnerDisappear();
-        createSchemaBtn.shouldBe(Condition.visible);
+        getPageTitleFromHeader(SCHEMA_REGISTRY).shouldBe(Condition.visible);
         return this;
     }
 

+ 1 - 1
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/topics/ProduceMessagePanel.java

@@ -49,7 +49,7 @@ public class ProduceMessagePanel extends BasePage {
 
     @Step
     public ProduceMessagePanel submitProduceMessage() {
-        submitBtn.shouldBe(Condition.enabled).click();
+        clickByActions(submitBtn);
         submitBtn.shouldBe(Condition.disappear);
         refresh();
         return this;

+ 9 - 5
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/topics/TopicsList.java

@@ -6,7 +6,6 @@ import com.codeborne.selenide.SelenideElement;
 import com.provectus.kafka.ui.pages.BasePage;
 import io.qameta.allure.Step;
 
-import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -15,10 +14,10 @@ import java.util.stream.Stream;
 
 import static com.codeborne.selenide.Condition.visible;
 import static com.codeborne.selenide.Selenide.$x;
+import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.TOPICS;
 
 public class TopicsList extends BasePage {
 
-    protected SelenideElement topicListHeader = $x("//h1[text()='Topics']");
     protected SelenideElement addTopicBtn = $x("//button[normalize-space(text()) ='Add a Topic']");
     protected SelenideElement searchField = $x("//input[@placeholder='Search by Topic Name']");
     protected SelenideElement showInternalRadioBtn = $x("//input[@name='ShowInternalTopics']");
@@ -32,7 +31,7 @@ public class TopicsList extends BasePage {
     @Step
     public TopicsList waitUntilScreenReady() {
         waitUntilSpinnerDisappear();
-        topicListHeader.shouldBe(visible);
+        getPageTitleFromHeader(TOPICS).shouldBe(visible);
         return this;
     }
 
@@ -175,6 +174,12 @@ public class TopicsList extends BasePage {
                 .findFirst().orElseThrow();
     }
 
+    @Step
+    public TopicGridItem getAnyNonInternalTopic() {
+        return getNonInternalTopics().stream()
+                .findAny().orElseThrow();
+    }
+
     @Step
     public List<TopicGridItem> getNonInternalTopics() {
         return initGridItems().stream()
@@ -207,8 +212,7 @@ public class TopicsList extends BasePage {
         public boolean isInternal() {
             boolean internal = false;
             try {
-                element.$x("./td[2]/a/span").shouldBe(visible, Duration.ofMillis(500));
-                internal = true;
+                internal = element.$x("./td[2]/a/span").isDisplayed();
             } catch (Throwable ignored) {
             }
             return internal;

+ 15 - 3
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/services/ApiService.java

@@ -67,8 +67,8 @@ public class ApiService extends BaseSource {
     }
 
     @Step
-    public ApiService createTopic(String topicName) {
-        createTopic(CLUSTER_NAME, topicName);
+    public ApiService createTopic(Topic topic) {
+        createTopic(CLUSTER_NAME, topic.getName());
         return this;
     }
 
@@ -133,6 +133,12 @@ public class ApiService extends BaseSource {
         return this;
     }
 
+    @Step
+    public ApiService deleteConnector(String connectorName) {
+        deleteConnector(CLUSTER_NAME, CONNECT_NAME, connectorName);
+        return this;
+    }
+
     @SneakyThrows
     private void createConnector(String clusterName, String connectName, Connector connector) {
         NewConnector connectorProperties = new NewConnector();
@@ -152,9 +158,15 @@ public class ApiService extends BaseSource {
         return this;
     }
 
+    @Step
+    public ApiService createConnector(Connector connector) {
+        createConnector(CLUSTER_NAME, CONNECT_NAME, connector);
+        return this;
+    }
+
     @Step
     public String getFirstConnectName(String clusterName) {
-        return connectorApi().getConnects(clusterName).blockFirst().getName();
+        return Objects.requireNonNull(connectorApi().getConnects(clusterName).blockFirst()).getName();
     }
 
     @SneakyThrows

+ 1 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/settings/BaseSource.java

@@ -8,6 +8,7 @@ public abstract class BaseSource {
     public static final String BASE_CONTAINER_URL = "http://host.testcontainers.internal:8080";
     public static final String BASE_LOCAL_URL = "http://localhost:8080";
     public static final String CLUSTER_NAME = "local";
+    public static final String CONNECT_NAME = "first";
     private static Config config;
     public static final String BROWSER = config().browser();
     public static final String SUITE_NAME = config().suite();

+ 44 - 43
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/settings/drivers/LocalWebDriver.java

@@ -14,54 +14,55 @@ import org.openqa.selenium.chrome.ChromeOptions;
 
 public abstract class LocalWebDriver {
 
-  private static org.openqa.selenium.WebDriver getWebDriver() {
-    try {
-      return WebDriverRunner.getWebDriver();
-    } catch (IllegalStateException ex) {
-      Configuration.headless = false;
-      Configuration.browser = "chrome";
-      Configuration.browserSize = "1920x1080";
-      /**screenshots and savePageSource config is needed for local debug
-       * optionally can be set as 'false' to not duplicate Allure report
-       */
-      Configuration.screenshots = true;
-      Configuration.savePageSource = true;
-      Configuration.pageLoadTimeout = 120000;
-      Configuration.browserCapabilities = new ChromeOptions()
-          .addArguments("--lang=en_US");
-      open();
-      return WebDriverRunner.getWebDriver();
+    private static org.openqa.selenium.WebDriver getWebDriver() {
+        try {
+            return WebDriverRunner.getWebDriver();
+        } catch (IllegalStateException ex) {
+            Configuration.headless = false;
+            Configuration.browser = "chrome";
+            Configuration.browserSize = "1920x1080";
+            /**screenshots and savePageSource config is needed for local debug
+             * optionally can be set as 'false' to not duplicate Allure report
+             */
+            Configuration.screenshots = true;
+            Configuration.savePageSource = true;
+            Configuration.pageLoadTimeout = 120000;
+            Configuration.browserCapabilities = new ChromeOptions()
+                    .addArguments("--remote-allow-origins=*")
+                    .addArguments("--lang=en_US");
+            open();
+            return WebDriverRunner.getWebDriver();
+        }
     }
-  }
 
-  @Step
-  public static void openUrl(String url) {
-    if (!getWebDriver().getCurrentUrl().equals(url)) {
-      getWebDriver().get(url);
+    @Step
+    public static void openUrl(String url) {
+        if (!getWebDriver().getCurrentUrl().equals(url)) {
+            getWebDriver().get(url);
+        }
     }
-  }
 
-  @Step
-  public static void browserInit() {
-    getWebDriver();
-  }
+    @Step
+    public static void browserInit() {
+        getWebDriver();
+    }
 
-  @Step
-  public static void browserClear() {
-    clearBrowserLocalStorage();
-    clearBrowserCookies();
-    refresh();
-  }
+    @Step
+    public static void browserClear() {
+        clearBrowserLocalStorage();
+        clearBrowserCookies();
+        refresh();
+    }
 
-  @Step
-  public static void browserQuit() {
-    getWebDriver().quit();
-  }
+    @Step
+    public static void browserQuit() {
+        getWebDriver().quit();
+    }
 
-  @Step
-  public static void loggerSetup() {
-    SelenideLogger.addListener("AllureSelenide", new AllureSelenide()
-        .screenshots(true)
-        .savePageSource(false));
-  }
+    @Step
+    public static void loggerSetup() {
+        SelenideLogger.addListener("AllureSelenide", new AllureSelenide()
+                .screenshots(true)
+                .savePageSource(false));
+    }
 }

+ 2 - 2
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/settings/listeners/QaseResultListener.java

@@ -39,9 +39,9 @@ public class QaseResultListener extends TestListenerAdapter implements ITestList
     }
 
     @Override
-    public void onTestStart(ITestResult result) {
+    public void onTestStart(ITestResult tr) {
         getQaseTestCaseListener().onTestCaseStarted();
-        super.onTestStart(result);
+        super.onTestStart(tr);
     }
 
     @Override

+ 9 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/utilities/WebUtils.java

@@ -21,6 +21,15 @@ public class WebUtils {
                 .perform();
     }
 
+    public static void sendKeysByActions(SelenideElement element, String keys) {
+        log.debug("\nsendKeysByActions: {} \nsend keys '{}'", element.getSearchCriteria(), keys);
+        element.shouldBe(Condition.enabled);
+        new Actions(WebDriverRunner.getWebDriver())
+                .moveToElement(element)
+                .sendKeys(element, keys)
+                .perform();
+    }
+
     public static void clickByJavaScript(SelenideElement element) {
         log.debug("\nclickByJavaScript: {}", element.getSearchCriteria());
         element.shouldBe(Condition.enabled);

+ 0 - 0
kafka-ui-e2e-checks/src/main/resources/config_for_create_connector.json → kafka-ui-e2e-checks/src/main/resources/testData/connectors/config_for_create_connector.json


+ 0 - 0
kafka-ui-e2e-checks/src/main/resources/config_for_create_connector_via_api.json → kafka-ui-e2e-checks/src/main/resources/testData/connectors/config_for_create_connector_via_api.json


+ 0 - 0
kafka-ui-e2e-checks/src/main/resources/config_for_update_connector.json → kafka-ui-e2e-checks/src/main/resources/testData/connectors/config_for_update_connector.json


+ 0 - 0
kafka-ui-e2e-checks/src/main/resources/delete_connector_config.json → kafka-ui-e2e-checks/src/main/resources/testData/connectors/delete_connector_config.json


+ 2 - 9
kafka-ui-e2e-checks/src/main/resources/testData/schema_avro_for_update.json → kafka-ui-e2e-checks/src/main/resources/testData/schemas/schema_avro_for_update.json

@@ -5,19 +5,12 @@
   "fields": [
     {
       "name": "text",
-      "type": [
-        "null",
-        "string"
-      ],
+      "type": "string",
       "default": null
     },
     {
       "name": "value",
-      "type": [
-        "null",
-        "string",
-        "long"
-      ],
+      "type": "string",
       "default": null
     }
   ]

+ 0 - 0
kafka-ui-e2e-checks/src/main/resources/testData/schema_avro_value.json → kafka-ui-e2e-checks/src/main/resources/testData/schemas/schema_avro_value.json


+ 0 - 0
kafka-ui-e2e-checks/src/main/resources/testData/schema_Json_Value.json → kafka-ui-e2e-checks/src/main/resources/testData/schemas/schema_json_Value.json


+ 0 - 0
kafka-ui-e2e-checks/src/main/resources/testData/schema_protobuf_value.txt → kafka-ui-e2e-checks/src/main/resources/testData/schemas/schema_protobuf_value.txt


+ 0 - 0
kafka-ui-e2e-checks/src/main/resources/message_content_create_topic.json → kafka-ui-e2e-checks/src/main/resources/testData/topics/message_content_create_topic.json


+ 20 - 9
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/BaseTest.java

@@ -22,7 +22,7 @@ import org.testng.asserts.SoftAssert;
 import java.time.Duration;
 import java.util.List;
 
-import static com.provectus.kafka.ui.pages.NaviSideBar.SideMenuOption.*;
+import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.*;
 import static com.provectus.kafka.ui.settings.BaseSource.*;
 import static com.provectus.kafka.ui.settings.drivers.LocalWebDriver.*;
 import static com.provectus.kafka.ui.utilities.qaseUtils.QaseSetup.qaseIntegrationSetup;
@@ -108,7 +108,7 @@ public abstract class BaseTest extends Facade {
     public void afterMethod() {
         browserClear();
     }
-    
+
     @Step
     protected void navigateToBrokers() {
         naviSideBar
@@ -117,6 +117,17 @@ public abstract class BaseTest extends Facade {
                 .waitUntilScreenReady();
     }
 
+    @Step
+    protected void navigateToBrokersAndOpenDetails(int brokerId) {
+        naviSideBar
+                .openSideMenu(BROKERS);
+        brokersList
+                .waitUntilScreenReady()
+                .openBroker(brokerId);
+        brokersDetails
+                .waitUntilScreenReady();
+    }
+
     @Step
     protected void navigateToTopics() {
         naviSideBar
@@ -135,7 +146,7 @@ public abstract class BaseTest extends Facade {
         topicDetails
                 .waitUntilScreenReady();
     }
-    
+
     @Step
     protected void navigateToConsumers() {
         naviSideBar
@@ -143,7 +154,7 @@ public abstract class BaseTest extends Facade {
         consumersList
                 .waitUntilScreenReady();
     }
-    
+
     @Step
     protected void navigateToSchemaRegistry() {
         naviSideBar
@@ -151,7 +162,7 @@ public abstract class BaseTest extends Facade {
         schemaRegistryList
                 .waitUntilScreenReady();
     }
-    
+
     @Step
     protected void navigateToSchemaRegistryAndOpenDetails(String schemaName) {
         navigateToSchemaRegistry();
@@ -160,7 +171,7 @@ public abstract class BaseTest extends Facade {
         schemaDetails
                 .waitUntilScreenReady();
     }
-    
+
     @Step
     protected void navigateToConnectors() {
         naviSideBar
@@ -168,7 +179,7 @@ public abstract class BaseTest extends Facade {
         kafkaConnectList
                 .waitUntilScreenReady();
     }
-    
+
     @Step
     protected void navigateToConnectorsAndOpenDetails(String connectorName) {
         navigateToConnectors();
@@ -177,7 +188,7 @@ public abstract class BaseTest extends Facade {
         connectorDetails
                 .waitUntilScreenReady();
     }
-    
+
     @Step
     protected void navigateToKsqlDb() {
         naviSideBar
@@ -185,7 +196,7 @@ public abstract class BaseTest extends Facade {
         ksqlDbList
                 .waitUntilScreenReady();
     }
-    
+
     @Step
     protected void verifyElementsCondition(List<SelenideElement> elementList, Condition expectedCondition) {
         SoftAssert softly = new SoftAssert();

+ 2 - 2
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/Facade.java

@@ -1,7 +1,7 @@
 package com.provectus.kafka.ui;
 
-import com.provectus.kafka.ui.pages.NaviSideBar;
-import com.provectus.kafka.ui.pages.TopPanel;
+import com.provectus.kafka.ui.pages.panels.NaviSideBar;
+import com.provectus.kafka.ui.pages.panels.TopPanel;
 import com.provectus.kafka.ui.pages.brokers.BrokersConfigTab;
 import com.provectus.kafka.ui.pages.brokers.BrokersDetails;
 import com.provectus.kafka.ui.pages.brokers.BrokersList;

+ 3 - 1
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/BaseManualTest.java

@@ -11,6 +11,7 @@ import java.lang.reflect.Method;
 
 import static com.provectus.kafka.ui.utilities.qaseUtils.QaseSetup.qaseIntegrationSetup;
 import static com.provectus.kafka.ui.utilities.qaseUtils.enums.State.NOT_AUTOMATED;
+import static com.provectus.kafka.ui.utilities.qaseUtils.enums.State.TO_BE_AUTOMATED;
 
 @Listeners(QaseResultListener.class)
 public abstract class BaseManualTest {
@@ -22,7 +23,8 @@ public abstract class BaseManualTest {
 
     @BeforeMethod
     public void beforeMethod(Method method) {
-        if (method.getAnnotation(Automation.class).state().equals(NOT_AUTOMATED))
+        if (method.getAnnotation(Automation.class).state().equals(NOT_AUTOMATED)
+                || method.getAnnotation(Automation.class).state().equals(TO_BE_AUTOMATED))
             throw new SkipException("Skip test exception");
     }
 }

+ 19 - 0
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/backlog/SanityBacklog.java

@@ -0,0 +1,19 @@
+package com.provectus.kafka.ui.manualSuite.backlog;
+
+import com.provectus.kafka.ui.manualSuite.BaseManualTest;
+import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Automation;
+import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Suite;
+import io.qase.api.annotation.QaseId;
+import org.testng.annotations.Test;
+
+import static com.provectus.kafka.ui.utilities.qaseUtils.enums.State.TO_BE_AUTOMATED;
+
+public class SanityBacklog extends BaseManualTest {
+
+    @Automation(state = TO_BE_AUTOMATED)
+    @Suite(id = 19)
+    @QaseId(285)
+    @Test
+    public void testCaseA() {
+    }
+}

+ 32 - 6
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/suite/KsqlDbTest.java → kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/backlog/SmokeBacklog.java

@@ -1,35 +1,61 @@
-package com.provectus.kafka.ui.manualSuite.suite;
+package com.provectus.kafka.ui.manualSuite.backlog;
 
 import com.provectus.kafka.ui.manualSuite.BaseManualTest;
 import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Automation;
+import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Suite;
 import io.qase.api.annotation.QaseId;
 import org.testng.annotations.Test;
 
 import static com.provectus.kafka.ui.utilities.qaseUtils.enums.State.TO_BE_AUTOMATED;
 
-public class KsqlDbTest extends BaseManualTest {
+public class SmokeBacklog extends BaseManualTest {
 
     @Automation(state = TO_BE_AUTOMATED)
-    @QaseId(276)
+    @Suite(id = 1)
+    @QaseId(330)
     @Test
     public void testCaseA() {
     }
 
     @Automation(state = TO_BE_AUTOMATED)
-    @QaseId(277)
+    @Suite(id = 8)
+    @QaseId(276)
     @Test
     public void testCaseB() {
     }
 
     @Automation(state = TO_BE_AUTOMATED)
-    @QaseId(278)
+    @Suite(id = 8)
+    @QaseId(277)
     @Test
     public void testCaseC() {
     }
 
     @Automation(state = TO_BE_AUTOMATED)
-    @QaseId(284)
+    @Suite(id = 8)
+    @QaseId(278)
     @Test
     public void testCaseD() {
     }
+
+    @Automation(state = TO_BE_AUTOMATED)
+    @Suite(id = 8)
+    @QaseId(284)
+    @Test
+    public void testCaseE() {
+    }
+
+    @Automation(state = TO_BE_AUTOMATED)
+    @Suite(id = 1)
+    @QaseId(331)
+    @Test
+    public void testCaseF() {
+    }
+
+    @Automation(state = TO_BE_AUTOMATED)
+    @Suite(id = 1)
+    @QaseId(332)
+    @Test
+    public void testCaseG() {
+    }
 }

+ 10 - 16
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/suite/TopicsTest.java

@@ -33,69 +33,63 @@ public class TopicsTest extends BaseManualTest {
     public void testCaseD() {
     }
 
-    @Automation(state = NOT_AUTOMATED)
-    @QaseId(46)
-    @Test
-    public void testCaseE() {
-    }
-
     @Automation(state = NOT_AUTOMATED)
     @QaseId(47)
     @Test
-    public void testCaseF() {
+    public void testCaseE() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(48)
     @Test
-    public void testCaseG() {
+    public void testCaseF() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(49)
     @Test
-    public void testCaseH() {
+    public void testCaseG() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(50)
     @Test
-    public void testCaseI() {
+    public void testCaseH() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(57)
     @Test
-    public void testCaseJ() {
+    public void testCaseI() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(58)
     @Test
-    public void testCaseK() {
+    public void testCaseJ() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(269)
     @Test
-    public void testCaseL() {
+    public void testCaseK() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(270)
     @Test
-    public void testCaseM() {
+    public void testCaseL() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(271)
     @Test
-    public void testCaseN() {
+    public void testCaseM() {
     }
 
     @Automation(state = NOT_AUTOMATED)
     @QaseId(272)
     @Test
-    public void testCaseO() {
+    public void testCaseN() {
     }
 }

+ 4 - 4
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/suite/BrokersTest.java → kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualSuite/suite/WizardTest.java

@@ -5,12 +5,12 @@ import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Automation;
 import io.qase.api.annotation.QaseId;
 import org.testng.annotations.Test;
 
-import static com.provectus.kafka.ui.utilities.qaseUtils.enums.State.TO_BE_AUTOMATED;
+import static com.provectus.kafka.ui.utilities.qaseUtils.enums.State.NOT_AUTOMATED;
 
-public class BrokersTest extends BaseManualTest {
+public class WizardTest extends BaseManualTest {
 
-    @Automation(state = TO_BE_AUTOMATED)
-    @QaseId(330)
+    @Automation(state = NOT_AUTOMATED)
+    @QaseId(333)
     @Test
     public void testCaseA() {
     }

+ 7 - 0
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/qaseSuite/BaseQaseTest.java

@@ -9,6 +9,13 @@ import static com.provectus.kafka.ui.utilities.qaseUtils.QaseSetup.qaseIntegrati
 @Listeners(QaseCreateListener.class)
 public abstract class BaseQaseTest {
 
+    protected static final long BROKERS_SUITE_ID = 1;
+    protected static final long CONNECTORS_SUITE_ID = 10;
+    protected static final long KSQL_DB_SUITE_ID = 8;
+    protected static final long SANITY_SUITE_ID = 19;
+    protected static final long SCHEMAS_SUITE_ID = 11;
+    protected static final long TOPICS_SUITE_ID = 2;
+
     @BeforeSuite
     public void beforeSuite() {
         qaseIntegrationSetup();

+ 1 - 1
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/qaseSuite/Template.java

@@ -13,7 +13,7 @@ public class Template extends BaseQaseTest {
 
     /**
      * this class is a kind of placeholder or example, use is as template to create new one
-     * copy class into kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/qaseSuite/suite
+     * copy Template into kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/qaseSuite/
      * place it into regarding folder and rename according to test case summary from Qase.io
      * uncomment @Test and set all annotations according to kafka-ui-e2e-checks/QASE.md
      */

+ 53 - 0
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/SmokeTest.java

@@ -3,20 +3,46 @@ package com.provectus.kafka.ui.smokeSuite;
 import com.codeborne.selenide.Condition;
 import com.codeborne.selenide.WebDriverRunner;
 import com.provectus.kafka.ui.BaseTest;
+import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
+import com.provectus.kafka.ui.models.Connector;
+import com.provectus.kafka.ui.models.Schema;
+import com.provectus.kafka.ui.models.Topic;
 import io.qameta.allure.Step;
 import io.qase.api.annotation.QaseId;
 import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.*;
 import static com.provectus.kafka.ui.settings.BaseSource.BROWSER;
+import static com.provectus.kafka.ui.utilities.FileUtils.getResourceAsString;
 import static com.provectus.kafka.ui.variables.Browser.LOCAL;
 import static com.provectus.kafka.ui.variables.Url.*;
+import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
 
 public class SmokeTest extends BaseTest {
 
+    private static final int BROKER_ID = 1;
+    private static final Schema TEST_SCHEMA = Schema.createSchemaAvro();
+    private static final Topic TEST_TOPIC = new Topic()
+            .setName("new-topic-" + randomAlphabetic(5))
+            .setNumberOfPartitions(1);
+    private static final Connector TEST_CONNECTOR = new Connector()
+            .setName("new-connector-" + randomAlphabetic(5))
+            .setConfig(getResourceAsString("testData/connectors/config_for_create_connector_via_api.json"));
+
+    @BeforeClass(alwaysRun = true)
+    public void beforeClass() {
+        apiService
+                .createTopic(TEST_TOPIC)
+                .createSchema(TEST_SCHEMA)
+                .createConnector(TEST_CONNECTOR);
+    }
+
     @QaseId(198)
     @Test
     public void checkBasePageElements() {
@@ -45,10 +71,37 @@ public class SmokeTest extends BaseTest {
         verifyCurrentUrl(KSQL_DB_LIST_URL);
     }
 
+    @QaseId(46)
+    @Test
+    public void checkComponentsPathWhileNavigating() {
+        navigateToBrokersAndOpenDetails(BROKER_ID);
+        verifyComponentsPath(BROKERS, String.format("Broker %d", BROKER_ID));
+        navigateToTopicsAndOpenDetails(TEST_TOPIC.getName());
+        verifyComponentsPath(TOPICS, TEST_TOPIC.getName());
+        navigateToSchemaRegistryAndOpenDetails(TEST_SCHEMA.getName());
+        verifyComponentsPath(SCHEMA_REGISTRY, TEST_SCHEMA.getName());
+        navigateToConnectorsAndOpenDetails(TEST_CONNECTOR.getName());
+        verifyComponentsPath(KAFKA_CONNECT, TEST_CONNECTOR.getName());
+    }
+
     @Step
     private void verifyCurrentUrl(String expectedUrl) {
         String host = BROWSER.equals(LOCAL) ? "localhost" : "host.testcontainers.internal";
         Assert.assertEquals(WebDriverRunner.getWebDriver().getCurrentUrl(),
                 String.format(expectedUrl, host), "getCurrentUrl()");
     }
+
+    @Step
+    private void verifyComponentsPath(MenuItem menuItem, String expectedPath) {
+        Assert.assertEquals(naviSideBar.getPagePath(menuItem), expectedPath,
+                String.format("getPagePath() for %s", menuItem.getPageTitle().toUpperCase()));
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void afterClass() {
+        apiService
+                .deleteTopic(TEST_TOPIC.getName())
+                .deleteSchema(TEST_SCHEMA.getName())
+                .deleteConnector(TEST_CONNECTOR.getName());
+    }
 }

+ 13 - 15
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/connectors/ConnectorsTest.java

@@ -18,45 +18,43 @@ import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
 
 public class ConnectorsTest extends BaseTest {
 
-    private static final String CONNECT_NAME = "first";
     private static final List<Topic> TOPIC_LIST = new ArrayList<>();
     private static final List<Connector> CONNECTOR_LIST = new ArrayList<>();
-    private static final String MESSAGE_CONTENT = "message_content_create_topic.json";
+    private static final String MESSAGE_CONTENT = "testData/topics/message_content_create_topic.json";
     private static final String MESSAGE_KEY = " ";
     private static final Topic TOPIC_FOR_CREATE = new Topic()
-            .setName("topic_for_create_connector-" + randomAlphabetic(5))
+            .setName("topic-for-create-connector-" + randomAlphabetic(5))
             .setMessageContent(MESSAGE_CONTENT).setMessageKey(MESSAGE_KEY);
     private static final Topic TOPIC_FOR_DELETE = new Topic()
-            .setName("topic_for_delete_connector-" + randomAlphabetic(5))
+            .setName("topic-for-delete-connector-" + randomAlphabetic(5))
             .setMessageContent(MESSAGE_CONTENT).setMessageKey(MESSAGE_KEY);
     private static final Topic TOPIC_FOR_UPDATE = new Topic()
-            .setName("topic_for_update_connector-" + randomAlphabetic(5))
+            .setName("topic-for-update-connector-" + randomAlphabetic(5))
             .setMessageContent(MESSAGE_CONTENT).setMessageKey(MESSAGE_KEY);
     private static final Connector CONNECTOR_FOR_DELETE = new Connector()
-            .setName("sink_postgres_activities_e2e_checks_for_delete-" + randomAlphabetic(5))
-            .setConfig(getResourceAsString("delete_connector_config.json"));
+            .setName("connector-for-delete-" + randomAlphabetic(5))
+            .setConfig(getResourceAsString("testData/connectors/delete_connector_config.json"));
     private static final Connector CONNECTOR_FOR_UPDATE = new Connector()
-            .setName("sink_postgres_activities_e2e_checks_for_update-" + randomAlphabetic(5))
-            .setConfig(getResourceAsString("config_for_create_connector_via_api.json"));
+            .setName("connector-for-update-and-delete-" + randomAlphabetic(5))
+            .setConfig(getResourceAsString("testData/connectors/config_for_create_connector_via_api.json"));
 
     @BeforeClass(alwaysRun = true)
     public void beforeClass() {
         TOPIC_LIST.addAll(List.of(TOPIC_FOR_CREATE, TOPIC_FOR_DELETE, TOPIC_FOR_UPDATE));
         TOPIC_LIST.forEach(topic -> apiService
-                .createTopic(topic.getName())
+                .createTopic(topic)
                 .sendMessage(topic)
         );
         CONNECTOR_LIST.addAll(List.of(CONNECTOR_FOR_DELETE, CONNECTOR_FOR_UPDATE));
-        CONNECTOR_LIST.forEach(connector -> apiService
-                .createConnector(CONNECT_NAME, connector));
+        CONNECTOR_LIST.forEach(connector -> apiService.createConnector(connector));
     }
 
     @QaseId(42)
     @Test
     public void createConnector() {
         Connector connectorForCreate = new Connector()
-                .setName("sink_postgres_activities_e2e_checks-" + randomAlphabetic(5))
-                .setConfig(getResourceAsString("config_for_create_connector.json"));
+                .setName("connector-for-create-" + randomAlphabetic(5))
+                .setConfig(getResourceAsString("testData/connectors/config_for_create_connector.json"));
         navigateToConnectors();
         kafkaConnectList
                 .clickCreateConnectorBtn();
@@ -102,7 +100,7 @@ public class ConnectorsTest extends BaseTest {
     @AfterClass(alwaysRun = true)
     public void afterClass() {
         CONNECTOR_LIST.forEach(connector ->
-                apiService.deleteConnector(CONNECT_NAME, connector.getName()));
+                apiService.deleteConnector(connector.getName()));
         TOPIC_LIST.forEach(topic -> apiService.deleteTopic(topic.getName()));
     }
 }

+ 6 - 5
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/schemas/SchemasTest.java

@@ -57,7 +57,7 @@ public class SchemasTest extends BaseTest {
     @QaseId(186)
     @Test(priority = 2)
     public void updateSchemaAvro() {
-        AVRO_API.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schema_avro_for_update.json");
+        AVRO_API.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_avro_for_update.json");
         navigateToSchemaRegistryAndOpenDetails(AVRO_API.getName());
         schemaDetails
                 .openEditSchema();
@@ -74,7 +74,8 @@ public class SchemasTest extends BaseTest {
                 .clickSubmitButton();
         schemaDetails
                 .waitUntilScreenReady();
-        Assert.assertEquals(CompatibilityLevel.CompatibilityEnum.NONE.toString(), schemaDetails.getCompatibility(), "getCompatibility()");
+        Assert.assertEquals(schemaDetails.getCompatibility(), CompatibilityLevel.CompatibilityEnum.NONE.toString(),
+                "getCompatibility()");
     }
 
     @QaseId(44)
@@ -88,12 +89,12 @@ public class SchemasTest extends BaseTest {
                 .openCompareVersionMenu();
         int versionsNumberFromDdl = schemaCreateForm
                 .waitUntilScreenReady()
-                .openSchemaVersionDdl()
+                .openLeftVersionDdl()
                 .getVersionsNumberFromList();
-        Assert.assertEquals(latestVersion, versionsNumberFromDdl, "Versions number is not matched");
+        Assert.assertEquals(versionsNumberFromDdl, latestVersion, "Versions number is not matched");
         schemaCreateForm
                 .selectVersionFromDropDown(1);
-        Assert.assertEquals(53, schemaCreateForm.getMarkedLinesNumber(), "getAllMarkedLines()");
+        Assert.assertEquals(schemaCreateForm.getMarkedLinesNumber(), 42, "getAllMarkedLines()");
     }
 
     @QaseId(187)

+ 5 - 6
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/topics/MessagesTest.java

@@ -32,7 +32,7 @@ public class MessagesTest extends BaseTest {
             .setMessageKey(randomAlphabetic(5))
             .setMessageContent(randomAlphabetic(10));
     private static final Topic TOPIC_TO_CLEAR_AND_PURGE_MESSAGES = new Topic()
-            .setName("topic-to-clear-and-purge-messages-attribute-" + randomAlphabetic(5))
+            .setName("topic-to-clear-and-purge-messages-" + randomAlphabetic(5))
             .setMessageKey(randomAlphabetic(5))
             .setMessageContent(randomAlphabetic(10));
     private static final Topic TOPIC_FOR_CHECK_FILTERS = new Topic()
@@ -53,7 +53,7 @@ public class MessagesTest extends BaseTest {
     public void beforeClass() {
         TOPIC_LIST.addAll(List.of(TOPIC_FOR_MESSAGES, TOPIC_FOR_CHECK_FILTERS, TOPIC_TO_CLEAR_AND_PURGE_MESSAGES,
                 TOPIC_TO_RECREATE, TOPIC_FOR_CHECK_MESSAGES_COUNT));
-        TOPIC_LIST.forEach(topic -> apiService.createTopic(topic.getName()));
+        TOPIC_LIST.forEach(topic -> apiService.createTopic(topic));
         IntStream.range(1, 3).forEach(i -> apiService.sendMessage(TOPIC_FOR_CHECK_FILTERS));
         waitUntilNewMinuteStarted();
         IntStream.range(1, 3).forEach(i -> apiService.sendMessage(TOPIC_FOR_CHECK_FILTERS));
@@ -75,8 +75,6 @@ public class MessagesTest extends BaseTest {
         softly.assertAll();
     }
 
-    @Ignore
-    @Issue("https://github.com/provectus/kafka-ui/issues/2778")
     @QaseId(19)
     @Test(priority = 2)
     public void clearMessage() {
@@ -85,12 +83,13 @@ public class MessagesTest extends BaseTest {
                 .openDetailsTab(OVERVIEW);
         int messageAmount = topicDetails.getMessageCountAmount();
         produceMessage(TOPIC_FOR_MESSAGES);
-        Assert.assertEquals(messageAmount + 1, topicDetails.getMessageCountAmount(), "getMessageCountAmount()");
+        Assert.assertEquals(topicDetails.getMessageCountAmount(), messageAmount + 1, "getMessageCountAmount()");
         topicDetails
                 .openDotMenu()
                 .clickClearMessagesMenu()
+                .clickConfirmBtnMdl()
                 .waitUntilScreenReady();
-        Assert.assertEquals(0, topicDetails.getMessageCountAmount(), "getMessageCountAmount()");
+        Assert.assertEquals(topicDetails.getMessageCountAmount(), 0, "getMessageCountAmount()");
     }
 
     @QaseId(239)

+ 6 - 5
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokeSuite/topics/TopicsTest.java

@@ -52,13 +52,14 @@ public class TopicsTest extends BaseTest {
             .setMaxSizeOnDisk(NOT_SET);
     private static final Topic TOPIC_FOR_CHECK_FILTERS = new Topic()
             .setName("topic-for-check-filters-" + randomAlphabetic(5));
-    private static final Topic TOPIC_FOR_DELETE = new Topic().setName("topic-to-delete-" + randomAlphabetic(5));
+    private static final Topic TOPIC_FOR_DELETE = new Topic()
+            .setName("topic-to-delete-" + randomAlphabetic(5));
     private static final List<Topic> TOPIC_LIST = new ArrayList<>();
 
     @BeforeClass(alwaysRun = true)
     public void beforeClass() {
         TOPIC_LIST.addAll(List.of(TOPIC_TO_UPDATE_AND_DELETE, TOPIC_FOR_DELETE, TOPIC_FOR_CHECK_FILTERS));
-        TOPIC_LIST.forEach(topic -> apiService.createTopic(topic.getName()));
+        TOPIC_LIST.forEach(topic -> apiService.createTopic(topic));
     }
 
     @QaseId(199)
@@ -89,11 +90,11 @@ public class TopicsTest extends BaseTest {
     void checkAvailableOperations() {
         navigateToTopics();
         topicsList
-                .getTopicItem("my_ksql_1ksql_processing_log")
+                .getTopicItem(TOPIC_TO_UPDATE_AND_DELETE.getName())
                 .selectItem(true);
         verifyElementsCondition(topicsList.getActionButtons(), Condition.enabled);
         topicsList
-                .getTopicItem("_confluent-ksql-my_ksql_1_command_topic")
+                .getTopicItem(TOPIC_FOR_CHECK_FILTERS.getName())
                 .selectItem(true);
         Assert.assertFalse(topicsList.isCopySelectedTopicBtnEnabled(), "isCopySelectedTopicBtnEnabled()");
     }
@@ -456,7 +457,7 @@ public class TopicsTest extends BaseTest {
                 .setNumberOfPartitions(1);
         navigateToTopics();
         topicsList
-                .getTopicItem("_schemas")
+                .getAnyNonInternalTopic()
                 .selectItem(true)
                 .clickCopySelectedTopicBtn();
         topicCreateEditForm

+ 10 - 8
kafka-ui-react-app/src/components/App.tsx

@@ -11,7 +11,7 @@ import PageLoader from 'components/common/PageLoader/PageLoader';
 import Dashboard from 'components/Dashboard/Dashboard';
 import ClusterPage from 'components/ClusterPage/ClusterPage';
 import { ThemeProvider } from 'styled-components';
-import theme from 'theme/theme';
+import { theme, darkTheme } from 'theme/theme';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import { showServerError } from 'lib/errorHandling';
 import { Toaster } from 'react-hot-toast';
@@ -39,16 +39,18 @@ const queryClient = new QueryClient({
   },
 });
 const App: React.FC = () => {
+  const [isDarkMode, setDarkMode] = React.useState<boolean>(false);
+
   return (
     <QueryClientProvider client={queryClient}>
-      <ThemeProvider theme={theme}>
-        <Suspense fallback={<PageLoader />}>
-          <GlobalSettingsProvider>
+      <GlobalSettingsProvider>
+        <ThemeProvider theme={isDarkMode ? darkTheme : theme}>
+          <Suspense fallback={<PageLoader />}>
             <UserInfoRolesAccessProvider>
               <ConfirmContextProvider>
                 <GlobalCSS />
                 <S.Layout>
-                  <PageContainer>
+                  <PageContainer setDarkMode={setDarkMode}>
                     <Routes>
                       {['/', '/ui', '/ui/clusters'].map((path) => (
                         <Route
@@ -83,9 +85,9 @@ const App: React.FC = () => {
                 <ConfirmationModal />
               </ConfirmContextProvider>
             </UserInfoRolesAccessProvider>
-          </GlobalSettingsProvider>
-        </Suspense>
-      </ThemeProvider>
+          </Suspense>
+        </ThemeProvider>
+      </GlobalSettingsProvider>
     </QueryClientProvider>
   );
 };

+ 2 - 2
kafka-ui-react-app/src/components/Connect/Details/Actions/Action.styled.ts

@@ -15,8 +15,8 @@ export const RestartButton = styled.div`
   border-radius: 4px;
   display: flex;
   -webkit-align-items: center;
-  background: #e8e8fc;
-  color: #171a1c;
+  background: ${({ theme }) => theme.button.primary.backgroundColor.normal};
+  color: ${({ theme }) => theme.button.primary.color.normal};
   font-size: 14px;
   font-weight: 500;
   height: 32px;

+ 1 - 1
kafka-ui-react-app/src/components/Connect/List/TopicsCell.tsx

@@ -27,7 +27,7 @@ const TopicsCell: React.FC<CellContext<FullConnectorInfo, unknown>> = ({
   return (
     <S.TagsWrapper>
       {topics?.map((t) => (
-        <Tag key={t} color="gray">
+        <Tag key={t} color="green">
           <span
             role="link"
             onClick={(e) => navigateToTopic(e, t)}

+ 1 - 2
kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx

@@ -14,7 +14,6 @@ import * as Metrics from 'components/common/Metrics';
 import { Tag } from 'components/common/Tag/Tag.styled';
 import groupBy from 'lodash/groupBy';
 import { Table } from 'components/common/table/Table/Table.styled';
-import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
 import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
 import {
   deleteConsumerGroup,
@@ -27,6 +26,7 @@ import { Dropdown } from 'components/common/Dropdown';
 import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled';
 import { Action, ResourceType } from 'generated-sources';
 import { ActionDropdownItem } from 'components/common/ActionComponent';
+import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
 
 import ListItem from './ListItem';
 
@@ -137,7 +137,6 @@ const Details: React.FC = () => {
       <Table isFullwidth>
         <thead>
           <tr>
-            <TableHeaderCell> </TableHeaderCell>
             <TableHeaderCell title="Topic" />
             <TableHeaderCell title="Messages behind" />
           </tr>

+ 4 - 3
kafka-ui-react-app/src/components/ConsumerGroups/Details/ListItem.styled.ts

@@ -1,6 +1,7 @@
 import styled from 'styled-components';
 
-export const ToggleButton = styled.td`
-  padding: 8px 8px 8px 16px !important;
-  width: 30px;
+export const FlexWrapper = styled.div`
+  display: flex;
+  align-items: center;
+  gap: 8px;
 `;

+ 11 - 9
kafka-ui-react-app/src/components/ConsumerGroups/Details/ListItem.tsx

@@ -8,7 +8,7 @@ import IconButtonWrapper from 'components/common/Icons/IconButtonWrapper';
 import { TableKeyLink } from 'components/common/table/Table/TableKeyLink.styled';
 
 import TopicContents from './TopicContents/TopicContents';
-import { ToggleButton } from './ListItem.styled';
+import { FlexWrapper } from './ListItem.styled';
 
 interface Props {
   clusterName: ClusterName;
@@ -30,14 +30,16 @@ const ListItem: React.FC<Props> = ({ clusterName, name, consumers }) => {
   return (
     <>
       <tr>
-        <ToggleButton>
-          <IconButtonWrapper onClick={() => setIsOpen(!isOpen)} aria-hidden>
-            <MessageToggleIcon isOpen={isOpen} />
-          </IconButtonWrapper>
-        </ToggleButton>
-        <TableKeyLink>
-          <Link to={clusterTopicPath(clusterName, name)}>{name}</Link>
-        </TableKeyLink>
+        <td>
+          <FlexWrapper>
+            <IconButtonWrapper onClick={() => setIsOpen(!isOpen)} aria-hidden>
+              <MessageToggleIcon isOpen={isOpen} />
+            </IconButtonWrapper>
+            <TableKeyLink>
+              <Link to={clusterTopicPath(clusterName, name)}>{name}</Link>
+            </TableKeyLink>
+          </FlexWrapper>
+        </td>
         <td>{getTotalMessagesBehind()}</td>
       </tr>
       {isOpen && <TopicContents consumers={consumers} />}

+ 4 - 3
kafka-ui-react-app/src/components/ConsumerGroups/Details/TopicContents/TopicContent.styled.ts

@@ -1,16 +1,17 @@
 import styled, { css } from 'styled-components';
 
 export const TopicContentWrapper = styled.tr`
-  background-color: ${({ theme }) =>
-    theme.consumerTopicContent.backgroundColor};
+  background-color: ${({ theme }) => theme.default.backgroundColor};
   & > td {
     padding: 16px !important;
+    background-color: ${({ theme }) =>
+      theme.consumerTopicContent.td.backgroundColor};
   }
 `;
 
 export const ContentBox = styled.div(
   ({ theme }) => css`
-    background-color: ${theme.menu.backgroundColor.normal};
+    background-color: ${theme.default.backgroundColor};
     padding: 20px;
     border-radius: 8px;
   `

+ 1 - 1
kafka-ui-react-app/src/components/ConsumerGroups/Details/__tests__/Details.spec.tsx

@@ -64,7 +64,7 @@ describe('Details component', () => {
       expect(screen.getByText(groupId)).toBeInTheDocument();
 
       expect(screen.getByRole('table')).toBeInTheDocument();
-      expect(screen.getAllByRole('columnheader').length).toEqual(3);
+      expect(screen.getAllByRole('columnheader').length).toEqual(2);
 
       expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
     });

+ 3 - 1
kafka-ui-react-app/src/components/ConsumerGroups/Details/__tests__/ListItem.spec.tsx

@@ -40,7 +40,9 @@ describe('ListItem', () => {
   });
 
   it('should renders list item with topic content open', async () => {
-    await userEvent.click(screen.getAllByRole('cell')[0].children[0]);
+    await userEvent.click(
+      screen.getByRole('cell', { name: 'cluster1' }).children[0].children[0]
+    );
     expect(screen.getByText('Consumer ID')).toBeInTheDocument();
   });
 });

+ 1 - 0
kafka-ui-react-app/src/components/Dashboard/Dashboard.styled.ts

@@ -5,4 +5,5 @@ export const Toolbar = styled.div`
   display: flex;
   justify-content: space-between;
   align-items: center;
+  color: ${({ theme }) => theme.default.color.normal};
 `;

+ 1 - 1
kafka-ui-react-app/src/components/ErrorPage/ErrorPage.styled.ts

@@ -11,7 +11,7 @@ export const Wrapper = styled.div`
 
 export const Number = styled.div`
   font-size: 100px;
-  color: ${({ theme }) => theme.errorPage.text};
+  color: ${({ theme }) => theme.default.color.normal};
   line-height: initial;
 `;
 

+ 32 - 1
kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/QueryForm.styled.ts

@@ -19,6 +19,7 @@ export const KSQLInputsWrapper = styled.div`
 export const KSQLInputHeader = styled.div`
   display: flex;
   justify-content: space-between;
+  color: ${({ theme }) => theme.default.color.normal};
 `;
 
 export const KSQLButtons = styled.div`
@@ -31,6 +32,7 @@ export const StreamPropertiesContainer = styled.label`
   flex-direction: column;
   gap: 10px;
   width: 50%;
+  color: ${({ theme }) => theme.default.color.normal};
 `;
 
 export const InputsContainer = styled.div`
@@ -49,9 +51,18 @@ export const StreamPropertiesInputWrapper = styled.div`
     width: 100%;
     height: 40px;
     border: 1px solid grey;
+    &:focus {
+      outline: none;
+      border-color: ${({ theme }) => theme.input.borderColor.focus};
+      &::placeholder {
+        color: transparent;
+      }
+    }
     border-radius: 4px;
     font-size: 16px;
     padding-left: 15px;
+    background-color: ${({ theme }) => theme.input.backgroundColor.normal};
+    color: ${({ theme }) => theme.input.color.normal};
   }
 `;
 
@@ -73,8 +84,28 @@ export const SQLEditor = styled(BaseSQLEditor)(
     css`
       background: ${readOnly && theme.ksqlDb.query.editor.readonly.background};
       .ace-cursor {
-        ${readOnly && theme.ksqlDb.query.editor.readonly.cursor}
+        ${readOnly && `background: ${theme.default.transparentColor} `}
+      }
+
+      .ace_content {
+        background-color: ${theme.default.backgroundColor};
+        color: ${theme.default.color.normal};
+      }
+      .ace_line {
+        background-color: ${theme.ksqlDb.query.editor.activeLine
+          .backgroundColor};
       }
+      .ace_gutter-cell {
+        background-color: ${theme.ksqlDb.query.editor.cell.backgroundColor};
+      }
+      .ace_gutter-layer {
+        background-color: ${theme.ksqlDb.query.editor.layer.backgroundColor};
+        color: ${theme.default.color.normal};
+      }
+      .ace_cursor {
+        color: ${theme.ksqlDb.query.editor.cursor};
+      }
+
       .ace_print-margin {
         display: none;
       }

+ 2 - 0
kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/QueryForm.tsx

@@ -146,6 +146,7 @@ const QueryForm: React.FC<Props> = ({
                         placeholder="Key"
                         aria-label="key"
                         type="text"
+                        autoComplete="off"
                       />
                     )}
                   />
@@ -166,6 +167,7 @@ const QueryForm: React.FC<Props> = ({
                         placeholder="Value"
                         aria-label="value"
                         type="text"
+                        autoComplete="off"
                       />
                     )}
                   />

+ 1 - 0
kafka-ui-react-app/src/components/Nav/ClusterTab/ClusterTab.styled.ts

@@ -36,6 +36,7 @@ export const Title = styled.div`
   max-width: 110px;
   overflow: hidden;
   text-overflow: ellipsis;
+  color: ${({ theme }) => theme.menu.titleColor};
 `;
 
 export const StatusIconWrapper = styled.svg.attrs({

+ 1 - 1
kafka-ui-react-app/src/components/Nav/ClusterTab/__tests__/ClusterTab.styled.spec.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import { render } from 'lib/testHelpers';
-import theme from 'theme/theme';
+import { theme } from 'theme/theme';
 import { screen } from '@testing-library/react';
 import * as S from 'components/Nav/ClusterTab/ClusterTab.styled';
 import { ServerStatus } from 'generated-sources';

+ 7 - 4
kafka-ui-react-app/src/components/NavBar/NavBar.styled.ts

@@ -28,11 +28,11 @@ export const NavbarBrand = styled.div`
 `;
 
 export const SocialLink = styled.a(
-  ({ theme: { layout, icons } }) => css`
+  ({ theme: { icons } }) => css`
     display: block;
     margin-top: 5px;
     cursor: pointer;
-    fill: ${layout.socialLink.color};
+    fill: ${icons.discord.normal};
 
     &:hover {
       ${DiscordIcon} {
@@ -60,7 +60,7 @@ export const NavbarSocial = styled.div`
   display: flex;
   align-items: center;
   gap: 10px;
-  margin: 10px;
+  margin: 5px 10px 5px;
 `;
 
 export const NavbarItem = styled.div`
@@ -138,7 +138,10 @@ export const Hyperlink = styled(Link)(
     font-weight: bold;
     font-size: 12px;
     line-height: 16px;
-    color: ${theme.menu.color.active};
+    color: ${theme.default.color.normal};
+    &:hover {
+      color: ${theme.default.color.normal};
+    }
     text-decoration: none;
     word-break: break-word;
     cursor: pointer;

+ 83 - 2
kafka-ui-react-app/src/components/NavBar/NavBar.tsx

@@ -1,17 +1,92 @@
 import React from 'react';
+import Select from 'components/common/Select/Select';
 import Logo from 'components/common/Logo/Logo';
 import Version from 'components/Version/Version';
 import GitIcon from 'components/common/Icons/GitIcon';
 import DiscordIcon from 'components/common/Icons/DiscordIcon';
+import AutoIcon from 'components/common/Icons/AutoIcon';
+import SunIcon from 'components/common/Icons/SunIcon';
+import MoonIcon from 'components/common/Icons/MoonIcon';
 
-import * as S from './NavBar.styled';
 import UserInfo from './UserInfo/UserInfo';
+import * as S from './NavBar.styled';
 
 interface Props {
   onBurgerClick: () => void;
+  setDarkMode: (value: boolean) => void;
 }
 
-const NavBar: React.FC<Props> = ({ onBurgerClick }) => {
+type ThemeDropDownValue = 'auto_theme' | 'light_theme' | 'dark_theme';
+
+const options = [
+  {
+    label: (
+      <>
+        <AutoIcon />
+        <div>Auto theme</div>
+      </>
+    ),
+    value: 'auto_theme',
+  },
+  {
+    label: (
+      <>
+        <SunIcon />
+        <div>Light theme</div>
+      </>
+    ),
+    value: 'light_theme',
+  },
+  {
+    label: (
+      <>
+        <MoonIcon />
+        <div>Dark theme</div>
+      </>
+    ),
+    value: 'dark_theme',
+  },
+];
+
+const NavBar: React.FC<Props> = ({ onBurgerClick, setDarkMode }) => {
+  const matchDark = window.matchMedia('(prefers-color-scheme: dark)');
+  const [themeMode, setThemeMode] = React.useState<ThemeDropDownValue>();
+
+  React.useLayoutEffect(() => {
+    const mode = localStorage.getItem('mode');
+    if (mode) {
+      setThemeMode(mode as ThemeDropDownValue);
+      if (mode === 'auto_theme') {
+        setDarkMode(matchDark.matches);
+      } else if (mode === 'light_theme') {
+        setDarkMode(false);
+      } else if (mode === 'dark_theme') {
+        setDarkMode(true);
+      }
+    } else {
+      setThemeMode('auto_theme');
+    }
+  }, []);
+
+  React.useEffect(() => {
+    if (themeMode === 'auto_theme') {
+      setDarkMode(matchDark.matches);
+      matchDark.addListener((e) => {
+        setDarkMode(e.matches);
+      });
+    }
+  }, [matchDark, themeMode]);
+
+  const onChangeThemeMode = (value: string | number) => {
+    setThemeMode(value as ThemeDropDownValue);
+    localStorage.setItem('mode', value as string);
+    if (value === 'light_theme') {
+      setDarkMode(false);
+    } else if (value === 'dark_theme') {
+      setDarkMode(true);
+    }
+  };
+
   return (
     <S.Navbar role="navigation" aria-label="Page Header">
       <S.NavbarBrand>
@@ -39,6 +114,12 @@ const NavBar: React.FC<Props> = ({ onBurgerClick }) => {
         </S.NavbarBrand>
       </S.NavbarBrand>
       <S.NavbarSocial>
+        <Select
+          options={options}
+          value={themeMode}
+          onChange={onChangeThemeMode}
+          isThemeMode
+        />
         <S.SocialLink
           href="https://github.com/provectus/kafka-ui"
           target="_blank"

+ 1 - 7
kafka-ui-react-app/src/components/NavBar/UserInfo/UserInfo.tsx

@@ -2,14 +2,12 @@ import React from 'react';
 import { Dropdown, DropdownItem } from 'components/common/Dropdown';
 import UserIcon from 'components/common/Icons/UserIcon';
 import DropdownArrowIcon from 'components/common/Icons/DropdownArrowIcon';
-import { useTheme } from 'styled-components';
 import { useUserInfo } from 'lib/hooks/useUserInfo';
 
 import * as S from './UserInfo.styled';
 
 const UserInfo = () => {
   const { username } = useUserInfo();
-  const theme = useTheme();
 
   return username ? (
     <Dropdown
@@ -17,11 +15,7 @@ const UserInfo = () => {
         <S.Wrapper>
           <UserIcon />
           <S.Text>{username}</S.Text>
-          <DropdownArrowIcon
-            isOpen={false}
-            style={{}}
-            color={theme.button.primary.invertedColors.normal}
-          />
+          <DropdownArrowIcon isOpen={false} />
         </S.Wrapper>
       }
     >

+ 9 - 1
kafka-ui-react-app/src/components/NavBar/__tests__/NavBar.spec.tsx

@@ -12,7 +12,15 @@ jest.mock('components/NavBar/UserInfo/UserInfo', () => () => (
 
 describe('NavBar', () => {
   beforeEach(() => {
-    render(<NavBar onBurgerClick={jest.fn()} />);
+    Object.defineProperty(window, 'matchMedia', {
+      writable: true,
+      value: jest.fn().mockImplementation(() => ({
+        matches: false,
+        addListener: jest.fn(),
+      })),
+    });
+
+    render(<NavBar onBurgerClick={jest.fn()} setDarkMode={jest.fn()} />);
   });
 
   it('correctly renders header', () => {

+ 4 - 2
kafka-ui-react-app/src/components/PageContainer/PageContainer.tsx

@@ -5,7 +5,9 @@ import * as S from 'components/PageContainer/PageContainer.styled';
 import Nav from 'components/Nav/Nav';
 import useBoolean from 'lib/hooks/useBoolean';
 
-const PageContainer: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
+const PageContainer: React.FC<
+  PropsWithChildren<{ setDarkMode: (value: boolean) => void }>
+> = ({ children, setDarkMode }) => {
   const {
     value: isSidebarVisible,
     toggle,
@@ -19,7 +21,7 @@ const PageContainer: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
 
   return (
     <>
-      <NavBar onBurgerClick={toggle} />
+      <NavBar onBurgerClick={toggle} setDarkMode={setDarkMode} />
       <S.Container>
         <S.Sidebar aria-label="Sidebar" $visible={isSidebarVisible}>
           <Nav />

+ 8 - 1
kafka-ui-react-app/src/components/PageContainer/__tests__/PageContainer.spec.tsx

@@ -19,9 +19,16 @@ describe('Page Container', () => {
     (useClusters as jest.Mock).mockImplementation(() => ({
       isSuccess: false,
     }));
+    Object.defineProperty(window, 'matchMedia', {
+      writable: true,
+      value: jest.fn().mockImplementation(() => ({
+        matches: false,
+        addListener: jest.fn(),
+      })),
+    });
 
     render(
-      <PageContainer>
+      <PageContainer setDarkMode={jest.fn()}>
         <div>child</div>
       </PageContainer>
     );

+ 7 - 4
kafka-ui-react-app/src/components/Schemas/Details/LatestVersion/LatestVersionItem.styled.tsx

@@ -1,11 +1,10 @@
 import Heading from 'components/common/heading/Heading.styled';
 import React from 'react';
 import styled from 'styled-components';
-import theme from 'theme/theme';
 
 export const Wrapper = styled.div`
   width: 100%;
-  background-color: ${theme.layout.stuffColor};
+  background-color: ${({ theme }) => theme.layout.stuffColor};
   padding: 16px;
   display: flex;
   justify-content: center;
@@ -14,7 +13,7 @@ export const Wrapper = styled.div`
   max-height: 700px;
 
   & > * {
-    background-color: ${theme.panelColor};
+    background-color: ${({ theme }) => theme.default.backgroundColor};
     padding: 24px;
     overflow-y: scroll;
   }
@@ -33,12 +32,16 @@ export const Wrapper = styled.div`
       gap: 16px;
       padding-bottom: 16px;
     }
+
+    p {
+      color: ${({ theme }) => theme.schema.backgroundColor.p};
+    }
   }
 `;
 
 export const MetaDataLabel = styled((props) => (
   <Heading level={4} {...props} />
 ))`
-  color: ${theme.lastestVersionItem.metaDataLabel.color};
+  color: ${({ theme }) => theme.lastestVersionItem.metaDataLabel.color};
   width: 110px;
 `;

+ 31 - 6
kafka-ui-react-app/src/components/Schemas/Diff/Diff.styled.ts

@@ -8,12 +8,37 @@ export const DiffWrapper = styled.div`
   flex-shrink: 1;
   min-height: min-content;
   padding-top: 1.5rem !important;
-  &
-    .ace_editor
-    > .ace_scroller
-    > .ace_content
-    > .ace_marker-layer
-    > .codeMarker {
+
+  .ace_content {
+    background-color: ${({ theme }) => theme.default.backgroundColor};
+    color: ${({ theme }) => theme.default.color.normal};
+  }
+  .ace_line {
+    background-color: ${({ theme }) => theme.default.backgroundColor};
+  }
+  .ace_gutter-cell {
+    background-color: ${({ theme }) =>
+      theme.ksqlDb.query.editor.cell.backgroundColor};
+  }
+  .ace_gutter-layer {
+    background-color: ${({ theme }) =>
+      theme.ksqlDb.query.editor.layer.backgroundColor};
+    color: ${({ theme }) => theme.default.color.normal};
+  }
+  .ace_cursor {
+    color: ${({ theme }) => theme.ksqlDb.query.editor.cursor};
+  }
+
+  .ace_print-margin {
+    display: none;
+  }
+  .ace_variable {
+    color: ${({ theme }) => theme.ksqlDb.query.editor.variable};
+  }
+  .ace_string {
+    color: ${({ theme }) => theme.ksqlDb.query.editor.aceString};
+  }
+  > .codeMarker {
     background: ${({ theme }) => theme.icons.warningIcon};
     position: absolute;
     z-index: 20;

+ 2 - 1
kafka-ui-react-app/src/components/Schemas/Edit/Edit.styled.ts

@@ -2,7 +2,7 @@ import styled, { css } from 'styled-components';
 
 export const EditWrapper = styled.div`
   padding: 16px;
-  padding-top: 0px;
+  padding-top: 0;
   & > form {
     display: flex;
     flex-direction: column;
@@ -44,6 +44,7 @@ export const EditorContainer = styled.div(
       font-size: 16px;
       line-height: 24px;
       padding-bottom: 16px;
+      color: ${theme.heading.h4};
     }
   `
 );

+ 3 - 0
kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.styled.ts

@@ -4,4 +4,7 @@ export const Wrapper = styled.div`
   display: flex;
   gap: 5px;
   align-items: center;
+  & > div {
+    color: ${({ theme }) => theme.select.label};
+  }
 `;

+ 3 - 10
kafka-ui-react-app/src/components/Topics/List/ActionsCell.tsx

@@ -1,20 +1,17 @@
 import React from 'react';
 import { Action, CleanUpPolicy, Topic, ResourceType } from 'generated-sources';
 import { CellContext } from '@tanstack/react-table';
-import { useAppDispatch } from 'lib/hooks/redux';
 import ClusterContext from 'components/contexts/ClusterContext';
 import { ClusterNameRoute } from 'lib/paths';
 import useAppParams from 'lib/hooks/useAppParams';
-import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import {
   Dropdown,
   DropdownItem,
   DropdownItemHint,
 } from 'components/common/Dropdown';
-import { useQueryClient } from '@tanstack/react-query';
 import {
-  topicKeys,
   useDeleteTopic,
+  useClearTopicMessages,
   useRecreateTopic,
 } from 'lib/hooks/api/topics';
 import { ActionDropdownItem } from 'components/common/ActionComponent';
@@ -24,20 +21,16 @@ const ActionsCell: React.FC<CellContext<Topic, unknown>> = ({ row }) => {
 
   const { isReadOnly, isTopicDeletionAllowed } =
     React.useContext(ClusterContext);
-  const dispatch = useAppDispatch();
   const { clusterName } = useAppParams<ClusterNameRoute>();
-  const queryClient = useQueryClient();
 
+  const clearMessages = useClearTopicMessages(clusterName);
   const deleteTopic = useDeleteTopic(clusterName);
   const recreateTopic = useRecreateTopic({ clusterName, topicName: name });
 
   const disabled = internal || isReadOnly;
 
   const clearTopicMessagesHandler = async () => {
-    await dispatch(
-      clearTopicMessages({ clusterName, topicName: name })
-    ).unwrap();
-    return queryClient.invalidateQueries(topicKeys.all(clusterName));
+    await clearMessages.mutateAsync(name);
   };
 
   const isCleanupDisabled = cleanUpPolicy !== CleanUpPolicy.DELETE;

+ 10 - 5
kafka-ui-react-app/src/components/Topics/List/BatchActionsBar.tsx

@@ -3,11 +3,13 @@ import { Row } from '@tanstack/react-table';
 import { Action, Topic, ResourceType } from 'generated-sources';
 import useAppParams from 'lib/hooks/useAppParams';
 import { ClusterName } from 'redux/interfaces';
-import { topicKeys, useDeleteTopic } from 'lib/hooks/api/topics';
+import {
+  topicKeys,
+  useClearTopicMessages,
+  useDeleteTopic,
+} from 'lib/hooks/api/topics';
 import { useConfirm } from 'lib/hooks/useConfirm';
 import { Button } from 'components/common/Button/Button';
-import { useAppDispatch } from 'lib/hooks/redux';
-import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import { clusterTopicCopyRelativePath } from 'lib/paths';
 import { useQueryClient } from '@tanstack/react-query';
 import { ActionCanButton } from 'components/common/ActionComponent';
@@ -25,11 +27,14 @@ const BatchActionsbar: React.FC<BatchActionsbarProps> = ({
 }) => {
   const { clusterName } = useAppParams<{ clusterName: ClusterName }>();
   const confirm = useConfirm();
-  const dispatch = useAppDispatch();
   const deleteTopic = useDeleteTopic(clusterName);
   const selectedTopics = rows.map(({ original }) => original.name);
   const client = useQueryClient();
 
+  const clearMessages = useClearTopicMessages(clusterName);
+  const clearTopicMessagesHandler = async (topicName: Topic['name']) => {
+    await clearMessages.mutateAsync(topicName);
+  };
   const deleteTopicsHandler = () => {
     confirm('Are you sure you want to remove selected topics?', async () => {
       try {
@@ -50,7 +55,7 @@ const BatchActionsbar: React.FC<BatchActionsbarProps> = ({
         try {
           await Promise.all(
             selectedTopics.map((topicName) =>
-              dispatch(clearTopicMessages({ clusterName, topicName })).unwrap()
+              clearTopicMessagesHandler(topicName)
             )
           );
           resetRowSelection();

+ 9 - 8
kafka-ui-react-app/src/components/Topics/List/__tests__/TopicTable.spec.tsx

@@ -6,16 +6,15 @@ import { externalTopicPayload, topicsPayload } from 'lib/fixtures/topics';
 import ClusterContext from 'components/contexts/ClusterContext';
 import userEvent from '@testing-library/user-event';
 import {
+  useClearTopicMessages,
   useDeleteTopic,
   useRecreateTopic,
   useTopics,
 } from 'lib/hooks/api/topics';
 import TopicTable from 'components/Topics/List/TopicTable';
 import { clusterTopicsPath } from 'lib/paths';
-import { useAppDispatch } from 'lib/hooks/redux';
 
 const clusterName = 'test-cluster';
-const unwrapMock = jest.fn();
 
 jest.mock('lib/hooks/redux', () => ({
   ...jest.requireActual('lib/hooks/redux'),
@@ -29,22 +28,24 @@ jest.mock('lib/hooks/api/topics', () => ({
   useDeleteTopic: jest.fn(),
   useRecreateTopic: jest.fn(),
   useTopics: jest.fn(),
+  useClearTopicMessages: jest.fn(),
 }));
 
 const deleteTopicMock = jest.fn();
 const recreateTopicMock = jest.fn();
+const clearTopicMessages = jest.fn();
 
 describe('TopicTable Components', () => {
   beforeEach(() => {
     (useDeleteTopic as jest.Mock).mockImplementation(() => ({
       mutateAsync: deleteTopicMock,
     }));
+    (useClearTopicMessages as jest.Mock).mockImplementation(() => ({
+      mutateAsync: clearTopicMessages,
+    }));
     (useRecreateTopic as jest.Mock).mockImplementation(() => ({
       mutateAsync: recreateTopicMock,
     }));
-    (useAppDispatch as jest.Mock).mockImplementation(() => () => ({
-      unwrap: unwrapMock,
-    }));
   });
 
   const renderComponent = (
@@ -185,9 +186,9 @@ describe('TopicTable Components', () => {
             ).toBeInTheDocument();
             const confirmBtn = getButtonByName('Confirm');
             expect(confirmBtn).toBeInTheDocument();
-            expect(unwrapMock).not.toHaveBeenCalled();
+            expect(clearTopicMessages).not.toHaveBeenCalled();
             await userEvent.click(confirmBtn);
-            expect(unwrapMock).toHaveBeenCalledTimes(2);
+            expect(clearTopicMessages).toHaveBeenCalledTimes(2);
             expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked();
             expect(screen.getAllByRole('checkbox')[2]).not.toBeChecked();
           });
@@ -282,7 +283,7 @@ describe('TopicTable Components', () => {
           await userEvent.click(
             screen.getByRole('button', { name: 'Confirm' })
           );
-          expect(unwrapMock).toHaveBeenCalled();
+          expect(clearTopicMessages).toHaveBeenCalled();
         });
       });
 

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Edit/DangerZone/DangerZone.styled.tsx

@@ -3,7 +3,7 @@ import styled from 'styled-components';
 export const Wrapper = styled.div`
   margin-top: 16px;
   padding: 16px;
-  border-top: 1px solid ${({ theme }) => theme.dangerZone.borderColor};
+  border: 1px solid ${({ theme }) => theme.dangerZone.borderColor};
   box-sizing: border-box;
   width: 768px;
 

+ 36 - 15
kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.styled.ts

@@ -48,18 +48,24 @@ export const SeekTypeSelectorWrapper = styled.div`
 
 export const OffsetSelector = styled(Input)`
   border-radius: 0 4px 4px 0 !important;
-  border-left: none;
+  &::placeholder {
+    color: ${({ theme }) => theme.input.color.normal};
+  }
 `;
 
 export const DatePickerInput = styled(DatePicker)`
   height: 32px;
-  border: 1px ${(props) => props.theme.select.borderColor.normal} solid;
+  border: 1px ${({ theme }) => theme.select.borderColor.normal} solid;
   border-left: none;
   border-radius: 0 4px 4px 0;
   font-size: 14px;
   width: 100%;
   padding-left: 12px;
-  color: ${(props) => props.theme.select.color.normal};
+  background-color: ${({ theme }) => theme.input.backgroundColor.normal};
+  color: ${({ theme }) => theme.input.color.normal};
+  &::placeholder {
+    color: ${({ theme }) => theme.input.color.normal};
+  }
 
   background-image: url('data:image/svg+xml,%3Csvg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M1 1L5 5L9 1" stroke="%23454F54"/%3E%3C/svg%3E%0A') !important;
   background-repeat: no-repeat !important;
@@ -83,7 +89,10 @@ export const FiltersMetrics = styled.div`
   padding-top: 16px;
   padding-bottom: 16px;
 `;
-
+export const Message = styled.div`
+  font-size: 14px;
+  color: ${({ theme }) => theme.metrics.filters.color.normal};
+`;
 export const Metric = styled.div`
   color: ${({ theme }) => theme.metrics.filters.color.normal};
   font-size: 12px;
@@ -168,6 +177,7 @@ export const FilterTitle = styled.h3`
   display: flex;
   align-items: center;
   justify-content: space-between;
+  color: ${({ theme }) => theme.modal.color};
   &:after {
     content: '';
     width: calc(100% + 32px);
@@ -176,7 +186,7 @@ export const FilterTitle = styled.h3`
     top: 40px;
     left: -16px;
     display: inline-block;
-    background-color: #f1f2f3;
+    background-color: ${({ theme }) => theme.modal.border.top};
   }
 `;
 
@@ -184,8 +194,12 @@ export const CreatedFilter = styled.p`
   margin: 25px 0 10px;
   font-size: 14px;
   line-height: 20px;
+  color: ${({ theme }) => theme.savedFilter.color};
 `;
 
+export const NoSavedFilter = styled.p`
+  color: ${({ theme }) => theme.savedFilter.color};
+`;
 export const SavedFiltersContainer = styled.div`
   overflow-y: auto;
   height: 195px;
@@ -196,6 +210,7 @@ export const SavedFiltersContainer = styled.div`
 export const SavedFilterName = styled.div`
   font-size: 14px;
   line-height: 20px;
+  color: ${({ theme }) => theme.savedFilter.filterName};
 `;
 
 export const FilterButtonWrapper = styled.div`
@@ -213,7 +228,7 @@ export const FilterButtonWrapper = styled.div`
     top: 0;
     left: -16px;
     display: inline-block;
-    background-color: #f1f2f3;
+    background-color: ${({ theme }) => theme.modal.border.bottom};
   }
 `;
 
@@ -241,7 +256,7 @@ export const FilterOptions = styled.div`
   display: none;
   width: 50px;
   justify-content: space-between;
-  color: ${({ theme }) => theme.editFilterText.color};
+  color: ${({ theme }) => theme.editFilter.textColor};
 `;
 
 export const SavedFilter = styled.div.attrs({
@@ -253,28 +268,28 @@ export const SavedFilter = styled.div.attrs({
   height: 32px;
   align-items: center;
   cursor: pointer;
-  border-top: 1px solid #f1f2f3;
+  border-top: 1px solid ${({ theme }) => theme.panelColor.borderTop};
   &:hover ${FilterOptions} {
     display: flex;
   }
   &:hover {
     background: ${({ theme }) => theme.layout.stuffColor};
   }
-  background: ${(props) =>
-    props.selected ? props.theme.layout.stuffColor : props.theme.panelColor};
+  background: ${({ selected, theme }) =>
+    selected ? theme.layout.stuffColor : theme.modal.backgroundColor};
 `;
 
 export const ActiveSmartFilter = styled.div`
   border-radius: 4px;
   min-width: 115px;
   height: 24px;
-  background: ${({ theme }) => theme.layout.stuffColor};
+  background: ${({ theme }) => theme.savedFilter.backgroundColor};
   font-size: 14px;
   line-height: 20px;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  color: ${({ theme }) => theme.input.label.color};
+  color: ${({ theme }) => theme.savedFilter.color};
   padding: 16px 8px;
 `;
 
@@ -293,7 +308,7 @@ export const MessageLoading = styled.div.attrs({
 })<MessageLoadingProps>`
   color: ${({ theme }) => theme.heading.h3.color};
   font-size: ${({ theme }) => theme.heading.h3.fontSize};
-  display: ${(props) => (props.isLive ? 'flex' : 'none')};
+  display: ${({ isLive }) => (isLive ? 'flex' : 'none')};
   justify-content: space-around;
   width: 250px;
 `;
@@ -305,7 +320,7 @@ export const StopLoading = styled.div`
 `;
 
 export const MessageLoadingSpinner = styled.div<MessageLoadingSpinnerProps>`
-  display: ${(props) => (props.isFetching ? 'block' : 'none')};
+  display: ${({ isFetching }) => (isFetching ? 'block' : 'none')};
   border: 3px solid ${({ theme }) => theme.pageLoader.borderColor};
   border-bottom: 3px solid ${({ theme }) => theme.pageLoader.borderBottomColor};
   border-radius: 50%;
@@ -334,7 +349,7 @@ export const SavedFiltersTextContainer = styled.div.attrs({
 
 const textStyle = css`
   font-size: 14px;
-  color: ${({ theme }) => theme.editFilterText.color};
+  color: ${({ theme }) => theme.editFilter.textColor};
   font-weight: 500;
 `;
 
@@ -353,3 +368,9 @@ export const SeekTypeSelect = styled(Select)`
   border-bottom-right-radius: 0;
   user-select: none;
 `;
+
+export const Serdes = styled.div`
+  display: flex;
+  gap: 24px;
+  padding: 8px 0;
+`;

+ 7 - 7
kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx

@@ -125,10 +125,10 @@ const Filters: React.FC<FiltersProps> = ({
     getTimestampFromSeekToParam(searchParams)
   );
   const [keySerde, setKeySerde] = React.useState<string>(
-    searchParams.get('keySerde') as string
+    searchParams.get('keySerde') || ''
   );
   const [valueSerde, setValueSerde] = React.useState<string>(
-    searchParams.get('valueSerde') as string
+    searchParams.get('valueSerde') || ''
   );
 
   const [savedFilters, setSavedFilters] = React.useState<MessageFilters[]>(
@@ -206,8 +206,8 @@ const Filters: React.FC<FiltersProps> = ({
       limit: PER_PAGE,
       page: page || 0,
       seekDirection,
-      keySerde: keySerde || (searchParams.get('keySerde') as string),
-      valueSerde: valueSerde || (searchParams.get('valueSerde') as string),
+      keySerde: keySerde || searchParams.get('keySerde') || '',
+      valueSerde: valueSerde || searchParams.get('valueSerde') || '',
     };
 
     if (isSeekTypeControlVisible) {
@@ -517,7 +517,7 @@ const Filters: React.FC<FiltersProps> = ({
       <S.ActiveSmartFilterWrapper>
         <Search placeholder="Search" disabled={isTailing} />
 
-        <Button buttonType="primary" buttonSize="M" onClick={toggle}>
+        <Button buttonType="secondary" buttonSize="M" onClick={toggle}>
           <PlusIcon />
           Add Filters
         </Button>
@@ -542,11 +542,11 @@ const Filters: React.FC<FiltersProps> = ({
         />
       )}
       <S.FiltersMetrics>
-        <p style={{ fontSize: 14 }}>
+        <S.Message>
           {seekDirection !== SeekDirection.TAILING &&
             isFetching &&
             phaseMessage}
-        </p>
+        </S.Message>
         <S.MessageLoading isLive={isTailing}>
           <S.MessageLoadingSpinner isFetching={isFetching} />
           Loading messages.

+ 3 - 1
kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/SavedFilters.tsx

@@ -63,7 +63,9 @@ const SavedFilters: FC<Props> = ({
       </S.BackToCustomText>
       <S.SavedFiltersContainer>
         <S.CreatedFilter>Saved filters</S.CreatedFilter>
-        {filters.length === 0 && <p>No saved filter(s)</p>}
+        {filters.length === 0 && (
+          <S.NoSavedFilter>No saved filter(s)</S.NoSavedFilter>
+        )}
         {filters.map((filter, index) => (
           <S.SavedFilter
             key={Symbol(filter.name).toString()}

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/__tests__/Filters.styled.spec.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 import { render } from 'lib/testHelpers';
 import * as S from 'components/Topics/Topic/Messages/Filters/Filters.styled';
 import { screen } from '@testing-library/react';
-import theme from 'theme/theme';
+import { theme } from 'theme/theme';
 
 describe('Filters Styled components', () => {
   describe('MessageLoading component', () => {

+ 3 - 3
kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/MessageContent.styled.ts

@@ -22,7 +22,7 @@ export const Section = styled.div`
 `;
 
 export const ContentBox = styled.div`
-  background-color: white;
+  background-color: ${({ theme }) => theme.topicMetaData.backgroundColor};
   padding: 24px;
   border-radius: 8px 0 0 8px;
   flex-grow: 3;
@@ -37,7 +37,7 @@ export const ContentBox = styled.div`
 `;
 
 export const MetadataWrapper = styled.div`
-  background-color: white;
+  background-color: ${({ theme }) => theme.topicMetaData.backgroundColor};
   padding: 24px;
   border-radius: 0 8px 8px 0;
   flex-grow: 1;
@@ -89,7 +89,7 @@ export const Tab = styled.button<{ $active?: boolean }>(
       border-radius: 0 4px 4px 0;
     }
     &:not(:last-child) {
-      border-right: 0px;
+      border-right: 0;
     }
   `
 );

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/__tests__/MessageContent.spec.tsx

@@ -8,7 +8,7 @@ import MessageContent, {
 import { TopicMessageTimestampTypeEnum } from 'generated-sources';
 import userEvent from '@testing-library/user-event';
 import { render } from 'lib/testHelpers';
-import theme from 'theme/theme';
+import { theme } from 'theme/theme';
 
 const setupWrapper = (props?: Partial<MessageContentProps>) => {
   return (

Some files were not shown because too many files changed in this diff