From 8ecb719e9b762a4bc132997dd660b8519c185ef6 Mon Sep 17 00:00:00 2001 From: Ilya Kuramshin Date: Fri, 21 Apr 2023 21:39:30 +0400 Subject: [PATCH 01/11] Broker partitions skew added to API (#3566) --- .../kafka/ui/model/InternalBroker.java | 19 +++- .../ui/model/PartitionDistributionStats.java | 93 +++++++++++++++++++ .../kafka/ui/service/BrokerService.java | 5 +- .../model/PartitionDistributionStatsTest.java | 83 +++++++++++++++++ .../main/resources/swagger/kafka-ui-api.yaml | 10 ++ 5 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/PartitionDistributionStats.java create mode 100644 kafka-ui-api/src/test/java/com/provectus/kafka/ui/model/PartitionDistributionStatsTest.java diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalBroker.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalBroker.java index edab9a8aeb..4a0d1ba0dd 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalBroker.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalBroker.java @@ -1,6 +1,7 @@ package com.provectus.kafka.ui.model; import java.math.BigDecimal; +import javax.annotation.Nullable; import lombok.Data; import org.apache.kafka.common.Node; @@ -10,15 +11,27 @@ public class InternalBroker { private final Integer id; private final String host; private final Integer port; - private final BigDecimal bytesInPerSec; - private final BigDecimal bytesOutPerSec; + private final @Nullable BigDecimal bytesInPerSec; + private final @Nullable BigDecimal bytesOutPerSec; + private final @Nullable Integer partitionsLeader; + private final @Nullable Integer partitions; + private final @Nullable Integer inSyncPartitions; + private final @Nullable BigDecimal leadersSkew; + private final @Nullable BigDecimal partitionsSkew; - public InternalBroker(Node node, Statistics statistics) { + public InternalBroker(Node node, + PartitionDistributionStats partitionDistribution, + Statistics statistics) { this.id = node.id(); this.host = node.host(); this.port = node.port(); this.bytesInPerSec = statistics.getMetrics().getBrokerBytesInPerSec().get(node.id()); this.bytesOutPerSec = statistics.getMetrics().getBrokerBytesOutPerSec().get(node.id()); + this.partitionsLeader = partitionDistribution.getPartitionLeaders().get(node); + this.partitions = partitionDistribution.getPartitionsCount().get(node); + this.inSyncPartitions = partitionDistribution.getInSyncPartitions().get(node); + this.leadersSkew = partitionDistribution.leadersSkew(node); + this.partitionsSkew = partitionDistribution.partitionsSkew(node); } } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/PartitionDistributionStats.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/PartitionDistributionStats.java new file mode 100644 index 0000000000..b625533d1d --- /dev/null +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/PartitionDistributionStats.java @@ -0,0 +1,93 @@ +package com.provectus.kafka.ui.model; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.admin.TopicDescription; +import org.apache.kafka.common.Node; +import org.apache.kafka.common.TopicPartitionInfo; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Slf4j +public class PartitionDistributionStats { + + // avg skew will show unuseful results on low number of partitions + private static final int MIN_PARTITIONS_FOR_SKEW_CALCULATION = 50; + + private static final MathContext ROUNDING_MATH_CTX = new MathContext(3); + + private final Map partitionLeaders; + private final Map partitionsCount; + private final Map inSyncPartitions; + private final double avgLeadersCntPerBroker; + private final double avgPartitionsPerBroker; + private final boolean skewCanBeCalculated; + + public static PartitionDistributionStats create(Statistics stats) { + return create(stats, MIN_PARTITIONS_FOR_SKEW_CALCULATION); + } + + static PartitionDistributionStats create(Statistics stats, int minPartitionsForSkewCalculation) { + var partitionLeaders = new HashMap(); + var partitionsReplicated = new HashMap(); + var isr = new HashMap(); + int partitionsCnt = 0; + for (TopicDescription td : stats.getTopicDescriptions().values()) { + for (TopicPartitionInfo tp : td.partitions()) { + partitionsCnt++; + tp.replicas().forEach(r -> incr(partitionsReplicated, r)); + tp.isr().forEach(r -> incr(isr, r)); + if (tp.leader() != null) { + incr(partitionLeaders, tp.leader()); + } + } + } + int nodesWithPartitions = partitionsReplicated.size(); + int partitionReplications = partitionsReplicated.values().stream().mapToInt(i -> i).sum(); + var avgPartitionsPerBroker = nodesWithPartitions == 0 ? 0 : ((double) partitionReplications) / nodesWithPartitions; + + int nodesWithLeaders = partitionLeaders.size(); + int leadersCnt = partitionLeaders.values().stream().mapToInt(i -> i).sum(); + var avgLeadersCntPerBroker = nodesWithLeaders == 0 ? 0 : ((double) leadersCnt) / nodesWithLeaders; + + return new PartitionDistributionStats( + partitionLeaders, + partitionsReplicated, + isr, + avgLeadersCntPerBroker, + avgPartitionsPerBroker, + partitionsCnt >= minPartitionsForSkewCalculation + ); + } + + private static void incr(Map map, Node n) { + map.compute(n, (k, c) -> c == null ? 1 : ++c); + } + + @Nullable + public BigDecimal partitionsSkew(Node node) { + return calculateAvgSkew(partitionsCount.get(node), avgPartitionsPerBroker); + } + + @Nullable + public BigDecimal leadersSkew(Node node) { + return calculateAvgSkew(partitionLeaders.get(node), avgLeadersCntPerBroker); + } + + // Returns difference (in percents) from average value, null if it can't be calculated + @Nullable + private BigDecimal calculateAvgSkew(@Nullable Integer value, double avgValue) { + if (avgValue == 0 || !skewCanBeCalculated) { + return null; + } + value = value == null ? 0 : value; + return new BigDecimal((value - avgValue) / avgValue * 100.0).round(ROUNDING_MATH_CTX); + } +} diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/BrokerService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/BrokerService.java index 720642157b..8a2ac1a63e 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/BrokerService.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/BrokerService.java @@ -10,6 +10,7 @@ import com.provectus.kafka.ui.model.BrokersLogdirsDTO; import com.provectus.kafka.ui.model.InternalBroker; import com.provectus.kafka.ui.model.InternalBrokerConfig; import com.provectus.kafka.ui.model.KafkaCluster; +import com.provectus.kafka.ui.model.PartitionDistributionStats; import com.provectus.kafka.ui.service.metrics.RawMetric; import java.util.Collections; import java.util.HashMap; @@ -64,11 +65,13 @@ public class BrokerService { } public Flux getBrokers(KafkaCluster cluster) { + var stats = statisticsCache.get(cluster); + var partitionsDistribution = PartitionDistributionStats.create(stats); return adminClientService .get(cluster) .flatMap(ReactiveAdminClient::describeCluster) .map(description -> description.getNodes().stream() - .map(node -> new InternalBroker(node, statisticsCache.get(cluster))) + .map(node -> new InternalBroker(node, partitionsDistribution, stats)) .collect(Collectors.toList())) .flatMapMany(Flux::fromIterable); } diff --git a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/model/PartitionDistributionStatsTest.java b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/model/PartitionDistributionStatsTest.java new file mode 100644 index 0000000000..c83c4f5cd8 --- /dev/null +++ b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/model/PartitionDistributionStatsTest.java @@ -0,0 +1,83 @@ +package com.provectus.kafka.ui.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.provectus.kafka.ui.service.ReactiveAdminClient; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.kafka.clients.admin.TopicDescription; +import org.apache.kafka.common.Node; +import org.apache.kafka.common.TopicPartitionInfo; +import org.assertj.core.data.Percentage; +import org.junit.jupiter.api.Test; + +class PartitionDistributionStatsTest { + + @Test + void skewCalculatedBasedOnPartitionsCounts() { + Node n1 = new Node(1, "n1", 9092); + Node n2 = new Node(2, "n2", 9092); + Node n3 = new Node(3, "n3", 9092); + Node n4 = new Node(4, "n4", 9092); + + var stats = PartitionDistributionStats.create( + Statistics.builder() + .clusterDescription( + new ReactiveAdminClient.ClusterDescription(null, "test", Set.of(n1, n2, n3), null)) + .topicDescriptions( + Map.of( + "t1", new TopicDescription( + "t1", false, + List.of( + new TopicPartitionInfo(0, n1, List.of(n1, n2), List.of(n1, n2)), + new TopicPartitionInfo(1, n2, List.of(n2, n3), List.of(n2, n3)) + ) + ), + "t2", new TopicDescription( + "t2", false, + List.of( + new TopicPartitionInfo(0, n1, List.of(n1, n2), List.of(n1, n2)), + new TopicPartitionInfo(1, null, List.of(n2, n1), List.of(n1)) + ) + ) + ) + ) + .build(), 4 + ); + + assertThat(stats.getPartitionLeaders()) + .containsExactlyInAnyOrderEntriesOf(Map.of(n1, 2, n2, 1)); + assertThat(stats.getPartitionsCount()) + .containsExactlyInAnyOrderEntriesOf(Map.of(n1, 3, n2, 4, n3, 1)); + assertThat(stats.getInSyncPartitions()) + .containsExactlyInAnyOrderEntriesOf(Map.of(n1, 3, n2, 3, n3, 1)); + + // Node(partitions): n1(3), n2(4), n3(1), n4(0) + // average partitions cnt = (3+4+1) / 3 = 2.666 (counting only nodes with partitions!) + assertThat(stats.getAvgPartitionsPerBroker()) + .isCloseTo(2.666, Percentage.withPercentage(1)); + + assertThat(stats.partitionsSkew(n1)) + .isCloseTo(BigDecimal.valueOf(12.5), Percentage.withPercentage(1)); + assertThat(stats.partitionsSkew(n2)) + .isCloseTo(BigDecimal.valueOf(50), Percentage.withPercentage(1)); + assertThat(stats.partitionsSkew(n3)) + .isCloseTo(BigDecimal.valueOf(-62.5), Percentage.withPercentage(1)); + assertThat(stats.partitionsSkew(n4)) + .isCloseTo(BigDecimal.valueOf(-100), Percentage.withPercentage(1)); + + // Node(leaders): n1(2), n2(1), n3(0), n4(0) + // average leaders cnt = (2+1) / 2 = 1.5 (counting only nodes with leaders!) + assertThat(stats.leadersSkew(n1)) + .isCloseTo(BigDecimal.valueOf(33.33), Percentage.withPercentage(1)); + assertThat(stats.leadersSkew(n2)) + .isCloseTo(BigDecimal.valueOf(-33.33), Percentage.withPercentage(1)); + assertThat(stats.leadersSkew(n3)) + .isCloseTo(BigDecimal.valueOf(-100), Percentage.withPercentage(1)); + assertThat(stats.leadersSkew(n4)) + .isCloseTo(BigDecimal.valueOf(-100), Percentage.withPercentage(1)); + } + +} diff --git a/kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml b/kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml index 4bd3d2207c..78c7cf3bf5 100644 --- a/kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml +++ b/kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml @@ -2375,6 +2375,16 @@ components: type: number bytesOutPerSec: type: number + partitionsLeader: + type: integer + partitions: + type: integer + inSyncPartitions: + type: integer + partitionsSkew: + type: number + leadersSkew: + type: number required: - id From fb515871cba27686037d06e5a8dbaa417c681732 Mon Sep 17 00:00:00 2001 From: Vlad Senyuta <66071557+VladSenyuta@users.noreply.github.com> Date: Mon, 24 Apr 2023 12:56:01 +0300 Subject: [PATCH 02/11] issues/streamsAndTablesVisibilityCheck1 (#3702) --- .../kafka/ui/services/ApiService.java | 12 +++-- .../ui/manualsuite/backlog/SmokeBacklog.java | 51 +++++++++++++++---- .../ui/manualsuite/suite/TopicsTest.java | 24 +++++++++ .../ui/manualsuite/suite/WizardTest.java | 12 +++++ .../ui/smokesuite/ksqldb/KsqlDbTest.java | 50 ++++++++++++------ 5 files changed, 118 insertions(+), 31 deletions(-) diff --git a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/services/ApiService.java b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/services/ApiService.java index a041defc93..b4cc54a38f 100644 --- a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/services/ApiService.java +++ b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/services/ApiService.java @@ -36,29 +36,31 @@ import org.springframework.web.reactive.function.client.WebClientResponseExcepti @Slf4j public class ApiService extends BaseSource { + private final ApiClient apiClient = new ApiClient().setBasePath(BASE_API_URL); + @SneakyThrows private TopicsApi topicApi() { - return new TopicsApi(new ApiClient().setBasePath(BASE_API_URL)); + return new TopicsApi(apiClient); } @SneakyThrows private SchemasApi schemaApi() { - return new SchemasApi(new ApiClient().setBasePath(BASE_API_URL)); + return new SchemasApi(apiClient); } @SneakyThrows private KafkaConnectApi connectorApi() { - return new KafkaConnectApi(new ApiClient().setBasePath(BASE_API_URL)); + return new KafkaConnectApi(apiClient); } @SneakyThrows private MessagesApi messageApi() { - return new MessagesApi(new ApiClient().setBasePath(BASE_API_URL)); + return new MessagesApi(apiClient); } @SneakyThrows private KsqlApi ksqlApi() { - return new KsqlApi(new ApiClient().setBasePath(BASE_API_URL)); + return new KsqlApi(apiClient); } @SneakyThrows diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/backlog/SmokeBacklog.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/backlog/SmokeBacklog.java index d96bbb7f3a..3ce086ee7b 100644 --- a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/backlog/SmokeBacklog.java +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/backlog/SmokeBacklog.java @@ -2,6 +2,7 @@ package com.provectus.kafka.ui.manualsuite.backlog; import static com.provectus.kafka.ui.qasesuite.BaseQaseTest.BROKERS_SUITE_ID; import static com.provectus.kafka.ui.qasesuite.BaseQaseTest.KSQL_DB_SUITE_ID; +import static com.provectus.kafka.ui.qasesuite.BaseQaseTest.SCHEMAS_SUITE_ID; import static com.provectus.kafka.ui.qasesuite.BaseQaseTest.TOPICS_PROFILE_SUITE_ID; import static com.provectus.kafka.ui.utilities.qase.enums.State.TO_BE_AUTOMATED; @@ -35,37 +36,65 @@ public class SmokeBacklog extends BaseManualTest { } @Automation(state = TO_BE_AUTOMATED) - @Suite(id = KSQL_DB_SUITE_ID) - @QaseId(284) + @Suite(id = BROKERS_SUITE_ID) + @QaseId(331) @Test public void testCaseD() { } - @Automation(state = TO_BE_AUTOMATED) - @Suite(id = BROKERS_SUITE_ID) - @QaseId(331) - @Test - public void testCaseE() { - } - @Automation(state = TO_BE_AUTOMATED) @Suite(id = BROKERS_SUITE_ID) @QaseId(332) @Test - public void testCaseF() { + public void testCaseE() { } @Automation(state = TO_BE_AUTOMATED) @Suite(id = TOPICS_PROFILE_SUITE_ID) @QaseId(335) @Test - public void testCaseG() { + public void testCaseF() { } @Automation(state = TO_BE_AUTOMATED) @Suite(id = TOPICS_PROFILE_SUITE_ID) @QaseId(336) @Test + public void testCaseG() { + } + + @Automation(state = TO_BE_AUTOMATED) + @Suite(id = TOPICS_PROFILE_SUITE_ID) + @QaseId(343) + @Test public void testCaseH() { } + + @Automation(state = TO_BE_AUTOMATED) + @Suite(id = KSQL_DB_SUITE_ID) + @QaseId(344) + @Test + public void testCaseI() { + } + + @Automation(state = TO_BE_AUTOMATED) + @Suite(id = SCHEMAS_SUITE_ID) + @QaseId(345) + @Test + public void testCaseJ() { + } + + @Automation(state = TO_BE_AUTOMATED) + @Suite(id = SCHEMAS_SUITE_ID) + @QaseId(346) + @Test + public void testCaseK() { + } + + @Automation(state = TO_BE_AUTOMATED) + @Suite(id = TOPICS_PROFILE_SUITE_ID) + @QaseId(347) + @Test + public void testCaseL() { + } } diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/suite/TopicsTest.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/suite/TopicsTest.java index 76f8506deb..758827e21b 100644 --- a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/suite/TopicsTest.java +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/suite/TopicsTest.java @@ -92,4 +92,28 @@ public class TopicsTest extends BaseManualTest { @Test public void testCaseN() { } + + @Automation(state = NOT_AUTOMATED) + @QaseId(337) + @Test + public void testCaseO() { + } + + @Automation(state = NOT_AUTOMATED) + @QaseId(339) + @Test + public void testCaseP() { + } + + @Automation(state = NOT_AUTOMATED) + @QaseId(341) + @Test + public void testCaseQ() { + } + + @Automation(state = NOT_AUTOMATED) + @QaseId(342) + @Test + public void testCaseR() { + } } diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/suite/WizardTest.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/suite/WizardTest.java index 9621104b1a..c74c1ba6f0 100644 --- a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/suite/WizardTest.java +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/manualsuite/suite/WizardTest.java @@ -14,4 +14,16 @@ public class WizardTest extends BaseManualTest { @Test public void testCaseA() { } + + @Automation(state = NOT_AUTOMATED) + @QaseId(338) + @Test + public void testCaseB() { + } + + @Automation(state = NOT_AUTOMATED) + @QaseId(340) + @Test + public void testCaseC() { + } } diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/ksqldb/KsqlDbTest.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/ksqldb/KsqlDbTest.java index c4bbe0def4..22ef931bf1 100644 --- a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/ksqldb/KsqlDbTest.java +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/ksqldb/KsqlDbTest.java @@ -1,5 +1,6 @@ package com.provectus.kafka.ui.smokesuite.ksqldb; +import static com.provectus.kafka.ui.pages.ksqldb.enums.KsqlMenuTabs.STREAMS; import static com.provectus.kafka.ui.pages.ksqldb.enums.KsqlQueryConfig.SHOW_TABLES; import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.KSQL_DB; import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; @@ -39,17 +40,21 @@ public class KsqlDbTest extends BaseTest { FIRST_TABLE.getName(), SECOND_TABLE.getName())); } - @QaseId(86) + @QaseId(284) @Test(priority = 1) - public void clearResultsForExecutedRequest() { - navigateToKsqlDbAndExecuteRequest(SHOW_TABLES.getQuery()); + public void streamsAndTablesVisibilityCheck() { + naviSideBar + .openSideMenu(KSQL_DB); + ksqlDbList + .waitUntilScreenReady(); SoftAssert softly = new SoftAssert(); - softly.assertTrue(ksqlQueryForm.areResultsVisible(), "areResultsVisible()"); - softly.assertAll(); - ksqlQueryForm - .clickClearResultsBtn(); - softly.assertFalse(ksqlQueryForm.areResultsVisible(), "areResultsVisible()"); + softly.assertTrue(ksqlDbList.getTableByName(FIRST_TABLE.getName()).isVisible(), "getTableByName()"); + softly.assertTrue(ksqlDbList.getTableByName(SECOND_TABLE.getName()).isVisible(), "getTableByName()"); softly.assertAll(); + ksqlDbList + .openDetailsTab(STREAMS) + .waitUntilScreenReady(); + Assert.assertTrue(ksqlDbList.getStreamByName(DEFAULT_STREAM.getName()).isVisible(), "getStreamByName()"); } @QaseId(276) @@ -68,11 +73,31 @@ public class KsqlDbTest extends BaseTest { navigateToKsqlDbAndExecuteRequest(SHOW_TABLES.getQuery()); SoftAssert softly = new SoftAssert(); softly.assertTrue(ksqlQueryForm.areResultsVisible(), "areResultsVisible()"); - softly.assertTrue(ksqlQueryForm.getItemByName(FIRST_TABLE.getName()).isVisible(), "getItemByName()"); - softly.assertTrue(ksqlQueryForm.getItemByName(SECOND_TABLE.getName()).isVisible(), "getItemByName()"); + softly.assertTrue(ksqlQueryForm.getItemByName(FIRST_TABLE.getName()).isVisible(), + String.format("getItemByName(%s)", FIRST_TABLE.getName())); + softly.assertTrue(ksqlQueryForm.getItemByName(SECOND_TABLE.getName()).isVisible(), + String.format("getItemByName(%s)", SECOND_TABLE.getName())); softly.assertAll(); } + @QaseId(86) + @Test(priority = 4) + public void clearResultsForExecutedRequest() { + navigateToKsqlDbAndExecuteRequest(SHOW_TABLES.getQuery()); + SoftAssert softly = new SoftAssert(); + softly.assertTrue(ksqlQueryForm.areResultsVisible(), "areResultsVisible()"); + softly.assertAll(); + ksqlQueryForm + .clickClearResultsBtn(); + softly.assertFalse(ksqlQueryForm.areResultsVisible(), "areResultsVisible()"); + softly.assertAll(); + } + + @AfterClass(alwaysRun = true) + public void afterClass() { + TOPIC_NAMES_LIST.forEach(topicName -> apiService.deleteTopic(topicName)); + } + @Step private void navigateToKsqlDbAndExecuteRequest(String query) { naviSideBar @@ -85,9 +110,4 @@ public class KsqlDbTest extends BaseTest { .setQuery(query) .clickExecuteBtn(query); } - - @AfterClass(alwaysRun = true) - public void afterClass() { - TOPIC_NAMES_LIST.forEach(topicName -> apiService.deleteTopic(topicName)); - } } From 1b2827fb2ffd8b0890960845dfaab2ab1f7ebf2e Mon Sep 17 00:00:00 2001 From: David Bejanyan <58771979+David-DB88@users.noreply.github.com> Date: Mon, 24 Apr 2023 14:50:08 +0400 Subject: [PATCH 03/11] FE: Add KC quick actions into the sandwich menu (#3660) Co-authored-by: Roman Zabaluev --- .../components/Connect/List/ActionsCell.tsx | 85 +++++++++++++++++-- .../Connect/List/__tests__/List.spec.tsx | 11 ++- .../common/Dropdown/Dropdown.styled.ts | 2 +- .../src/lib/hooks/api/kafkaConnect.ts | 3 +- 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/kafka-ui-react-app/src/components/Connect/List/ActionsCell.tsx b/kafka-ui-react-app/src/components/Connect/List/ActionsCell.tsx index 30b3df8a56..5b3a24cdb7 100644 --- a/kafka-ui-react-app/src/components/Connect/List/ActionsCell.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/ActionsCell.tsx @@ -1,26 +1,41 @@ import React from 'react'; -import { FullConnectorInfo } from 'generated-sources'; +import { + Action, + ConnectorAction, + ConnectorState, + FullConnectorInfo, + ResourceType, +} from 'generated-sources'; import { CellContext } from '@tanstack/react-table'; import { ClusterNameRoute } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; import { Dropdown, DropdownItem } from 'components/common/Dropdown'; -import { useDeleteConnector } from 'lib/hooks/api/kafkaConnect'; +import { + useDeleteConnector, + useUpdateConnectorState, +} from 'lib/hooks/api/kafkaConnect'; import { useConfirm } from 'lib/hooks/useConfirm'; +import { useIsMutating } from '@tanstack/react-query'; +import { ActionDropdownItem } from 'components/common/ActionComponent'; const ActionsCell: React.FC> = ({ row, }) => { - const { connect, name } = row.original; - + const { connect, name, status } = row.original; const { clusterName } = useAppParams(); - + const mutationsNumber = useIsMutating(); + const isMutating = mutationsNumber > 0; const confirm = useConfirm(); const deleteMutation = useDeleteConnector({ clusterName, connectName: connect, connectorName: name, }); - + const stateMutation = useUpdateConnectorState({ + clusterName, + connectName: connect, + connectorName: name, + }); const handleDelete = () => { confirm( <> @@ -31,8 +46,66 @@ const ActionsCell: React.FC> = ({ } ); }; + // const stateMutation = useUpdateConnectorState(routerProps); + const resumeConnectorHandler = () => + stateMutation.mutateAsync(ConnectorAction.RESUME); + const restartConnectorHandler = () => + stateMutation.mutateAsync(ConnectorAction.RESTART); + + const restartAllTasksHandler = () => + stateMutation.mutateAsync(ConnectorAction.RESTART_ALL_TASKS); + + const restartFailedTasksHandler = () => + stateMutation.mutateAsync(ConnectorAction.RESTART_FAILED_TASKS); + return ( + {status.state === ConnectorState.PAUSED && ( + + Resume + + )} + + Restart Connector + + + Restart All Tasks + + + Restart Failed Tasks + Remove Connector diff --git a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx index 9de28f38ff..82b4aab212 100644 --- a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx @@ -9,7 +9,11 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render, WithRoute } from 'lib/testHelpers'; import { clusterConnectConnectorPath, clusterConnectorsPath } from 'lib/paths'; -import { useConnectors, useDeleteConnector } from 'lib/hooks/api/kafkaConnect'; +import { + useConnectors, + useDeleteConnector, + useUpdateConnectorState, +} from 'lib/hooks/api/kafkaConnect'; const mockedUsedNavigate = jest.fn(); const mockDelete = jest.fn(); @@ -22,6 +26,7 @@ jest.mock('react-router-dom', () => ({ jest.mock('lib/hooks/api/kafkaConnect', () => ({ useConnectors: jest.fn(), useDeleteConnector: jest.fn(), + useUpdateConnectorState: jest.fn(), })); const clusterName = 'local'; @@ -42,6 +47,10 @@ describe('Connectors List', () => { (useConnectors as jest.Mock).mockImplementation(() => ({ data: connectors, })); + const restartConnector = jest.fn(); + (useUpdateConnectorState as jest.Mock).mockImplementation(() => ({ + mutateAsync: restartConnector, + })); }); it('renders', async () => { diff --git a/kafka-ui-react-app/src/components/common/Dropdown/Dropdown.styled.ts b/kafka-ui-react-app/src/components/common/Dropdown/Dropdown.styled.ts index f63fc5fe2a..d7db888a09 100644 --- a/kafka-ui-react-app/src/components/common/Dropdown/Dropdown.styled.ts +++ b/kafka-ui-react-app/src/components/common/Dropdown/Dropdown.styled.ts @@ -70,7 +70,7 @@ export const DropdownButton = styled.button` `; export const DangerItem = styled.div` - color: ${({ theme: { dropdown } }) => dropdown.item.color.normal}; + color: ${({ theme: { dropdown } }) => dropdown.item.color.danger}; `; export const DropdownItemHint = styled.div` diff --git a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts index b8a17c558d..1d01d49195 100644 --- a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts +++ b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts @@ -76,7 +76,8 @@ export function useUpdateConnectorState(props: UseConnectorProps) { return useMutation( (action: ConnectorAction) => api.updateConnectorState({ ...props, action }), { - onSuccess: () => client.invalidateQueries(connectorKey(props)), + onSuccess: () => + client.invalidateQueries(['clusters', props.clusterName, 'connectors']), } ); } From ad9d7dec2cd2effc6f3832e121830c99d98dce0f Mon Sep 17 00:00:00 2001 From: David Bejanyan <58771979+David-DB88@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:19:18 +0400 Subject: [PATCH 04/11] FE: Topics: Fix redirect to Topics on topic delete (#3687) Co-authored-by: Roman Zabaluev --- kafka-ui-react-app/src/components/Topics/Topic/Topic.tsx | 2 +- .../src/components/Topics/Topic/__test__/Topic.spec.tsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Topic.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Topic.tsx index 8945523576..9430e4b749 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Topic.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Topic.tsx @@ -59,7 +59,7 @@ const Topic: React.FC = () => { const deleteTopicHandler = async () => { await deleteTopic.mutateAsync(topicName); - navigate('../..'); + navigate(clusterTopicsPath(clusterName)); }; React.useEffect(() => { diff --git a/kafka-ui-react-app/src/components/Topics/Topic/__test__/Topic.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/__test__/Topic.spec.tsx index 460e4ad5de..4ec45c3a58 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/__test__/Topic.spec.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/__test__/Topic.spec.tsx @@ -10,6 +10,7 @@ import { clusterTopicMessagesPath, clusterTopicPath, clusterTopicSettingsPath, + clusterTopicsPath, clusterTopicStatisticsPath, getNonExactPath, } from 'lib/paths'; @@ -179,7 +180,9 @@ describe('Details', () => { name: 'Confirm', }); await userEvent.click(submitDeleteButton); - expect(mockNavigate).toHaveBeenCalledWith('../..'); + expect(mockNavigate).toHaveBeenCalledWith( + clusterTopicsPath(mockClusterName) + ); }); it('shows a confirmation popup on deleting topic messages', async () => { From eec9fcb5f17ee926f0d4108939d770edd3919386 Mon Sep 17 00:00:00 2001 From: Ilya Kuramshin Date: Mon, 24 Apr 2023 16:02:08 +0400 Subject: [PATCH 05/11] BE: Chore: CVE fixes: spring-core(6.0.7), transitive org.json (#3693) Co-authored-by: iliax Co-authored-by: Roman Zabaluev --- kafka-ui-api/pom.xml | 12 ++++++++++++ pom.xml | 1 + 2 files changed, 13 insertions(+) diff --git a/kafka-ui-api/pom.xml b/kafka-ui-api/pom.xml index e61827fd73..7f2d4c16be 100644 --- a/kafka-ui-api/pom.xml +++ b/kafka-ui-api/pom.xml @@ -21,6 +21,12 @@ + + + org.springframework + spring-core + 6.0.8 + org.springframework.boot spring-boot-starter-webflux @@ -109,6 +115,12 @@ io.projectreactor.addons reactor-extra + + + org.json + json + ${org.json.version} + org.springframework.boot diff --git a/pom.xml b/pom.xml index beb5744e81..a5a450d294 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ 1.0.0 0.1.15 0.1.23 + 20230227 5.9.1 From 0278700edb88cb09c5831675ab4c6d95f27798b1 Mon Sep 17 00:00:00 2001 From: blacktower88 <125452174+blacktower88@users.noreply.github.com> Date: Mon, 24 Apr 2023 14:15:09 +0200 Subject: [PATCH 06/11] FE: Topics: Remove a success message upon creating a topic (#3580) Co-authored-by: Roman Zabaluev Co-authored-by: VladSenyuta --- .../provectus/kafka/ui/smokesuite/topics/TopicsTest.java | 6 +----- kafka-ui-react-app/src/lib/hooks/api/topics.ts | 3 --- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/topics/TopicsTest.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/topics/TopicsTest.java index ad20f595a4..bad6a9fcde 100644 --- a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/topics/TopicsTest.java +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/topics/TopicsTest.java @@ -486,11 +486,7 @@ public class TopicsTest extends BaseTest { topicDetails .waitUntilScreenReady(); TOPIC_LIST.add(topicToCopy); - SoftAssert softly = new SoftAssert(); - softly.assertTrue(topicDetails.isAlertWithMessageVisible(SUCCESS, "Topic successfully created."), - "isAlertWithMessageVisible()"); - softly.assertTrue(topicDetails.isTopicHeaderVisible(topicToCopy.getName()), "isTopicHeaderVisible()"); - softly.assertAll(); + Assert.assertTrue(topicDetails.isTopicHeaderVisible(topicToCopy.getName()), "isTopicHeaderVisible()"); } @AfterClass(alwaysRun = true) diff --git a/kafka-ui-react-app/src/lib/hooks/api/topics.ts b/kafka-ui-react-app/src/lib/hooks/api/topics.ts index f71299f19b..a87673368d 100644 --- a/kafka-ui-react-app/src/lib/hooks/api/topics.ts +++ b/kafka-ui-react-app/src/lib/hooks/api/topics.ts @@ -122,9 +122,6 @@ export function useCreateTopicMutation(clusterName: ClusterName) { }), { onSuccess: () => { - showSuccessAlert({ - message: `Topic successfully created.`, - }); client.invalidateQueries(topicKeys.all(clusterName)); }, } From 039f50273e57ca219748606677539d101040a462 Mon Sep 17 00:00:00 2001 From: Roman Zabaluev Date: Tue, 25 Apr 2023 03:01:32 +0800 Subject: [PATCH 07/11] Infra: Rework issue templates --- .github/ISSUE_TEMPLATE/bug.yml | 92 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 64 ---------------- .github/ISSUE_TEMPLATE/feature.yml | 66 ++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 46 ------------ .github/ISSUE_TEMPLATE/helm.yml | 92 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/k8s.md | 52 ------------- .github/dependabot.yml | 4 - 7 files changed, 250 insertions(+), 166 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/helm.yml delete mode 100644 .github/ISSUE_TEMPLATE/k8s.md diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000000..4ec791ebb9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,92 @@ +name: "\U0001F41E Bug report" +description: File a bug report +labels: ["status/triage", "type/bug"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Hi, thanks for raising the issue(-s), all contributions really matter! + Please, note that we'll close the issue without further explanation if you don't follow + this template and don't provide the information requested within this template. + + - type: checkboxes + id: terms + attributes: + label: Issue submitter TODO list + description: By you checking these checkboxes we can be sure you've done the essential things. + options: + - label: I've looked up my issue in [FAQ](https://docs.kafka-ui.provectus.io/faq/common-problems) + required: true + - label: I've searched for an already existing issues [here](https://github.com/provectus/kafka-ui/issues) + required: true + - label: I've tried running `master`-labeled docker image and the issue still persists there + required: true + - label: I'm running a supported version of the application which is listed [here](https://github.com/provectus/kafka-ui/blob/master/SECURITY.md) + required: true + + - type: textarea + attributes: + label: Describe the bug (actual behavior) + description: A clear and concise description of what the bug is. Use a list, if there is more than one problem + validations: + required: true + + - type: textarea + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen + validations: + required: false + + - type: textarea + attributes: + label: Your installation details + description: | + How do you run the app? Please provide as much info as possible: + 1. App version (commit hash in the top left corner of the UI) + 2. Helm chart version, if you use one + 3. Your application config. Please remove the sensitive info like passwords or API keys. + 4. Any IAAC configs + validations: + required: true + + - type: textarea + attributes: + label: Steps to reproduce + description: | + Please write down the order of the actions required to reproduce the issue. + For the advanced setups/complicated issue, we might need you to provide + a minimal [reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). + validations: + required: true + + - type: textarea + attributes: + label: Screenshots + description: | + If applicable, add screenshots to help explain your problem + validations: + required: false + + - type: textarea + attributes: + label: Logs + description: | + If applicable, *upload* screenshots to help explain your problem + validations: + required: false + + - type: textarea + attributes: + label: Additional context + description: | + Add any other context about the problem here. E.G.: + 1. Are there any alternative scenarios (different data/methods/configuration/setup) you have tried? + Were they successful or the same issue occurred? Please provide steps as well. + 2. Related issues (if there are any). + 3. Logs (if available) + 4. Is there any serious impact or behaviour on the end-user because of this issue, that can be overlooked? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index b6bbcda11a..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -name: "\U0001F41E Bug report" -about: Create a bug report -title: '' -labels: status/triage, type/bug -assignees: '' - ---- - - - - - -**Describe the bug** (Actual behavior) - - -**Expected behavior** - - -**Set up** - - - -**Steps to Reproduce** - - -1. - -**Screenshots** - - - -**Additional context** - diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000000..e52c2b7ae9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,66 @@ +name: "\U0001F680 Feature request" +description: Propose a new feature +labels: ["status/triage", "type/feature"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Hi, thanks for raising the issue(-s), all contributions really matter! + Please, note that we'll close the issue without further explanation if you don't follow + this template and don't provide the information requested within this template. + + - type: checkboxes + id: terms + attributes: + label: Issue submitter TODO list + description: By you checking these checkboxes we can be sure you've done the essential things. + options: + - label: I've searched for an already existing issues [here](https://github.com/provectus/kafka-ui/issues) + required: true + - label: I'm running a supported version of the application which is listed [here](https://github.com/provectus/kafka-ui/blob/master/SECURITY.md) and the feature is not present there + required: true + + - type: textarea + attributes: + label: Is your proposal related to a problem? + description: | + Provide a clear and concise description of what the problem is. + For example, "I'm always frustrated when..." + validations: + required: false + + - type: textarea + attributes: + label: Describe the feature you're interested in + description: | + Provide a clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + attributes: + label: Describe alternatives you've considered + description: | + Let us know about other solutions you've tried or researched. + validations: + required: false + + - type: input + attributes: + label: Version you're running + description: | + Please provide the app version you're currently running: + 1. App version (commit hash in the top left corner of the UI) + validations: + required: true + + - type: textarea + attributes: + label: Additional context + description: | + Is there anything else you can add about the proposal? + You might want to link to related issues here, if you haven't already. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 49a07ae978..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: "\U0001F680 Feature request" -about: Propose a new feature -title: '' -labels: status/triage, type/feature -assignees: '' - ---- - - - -### Which version of the app are you running? - - -### Is your proposal related to a problem? - - - -### Describe the solution you'd like - - - -### Describe alternatives you've considered - - - -### Additional context - - - diff --git a/.github/ISSUE_TEMPLATE/helm.yml b/.github/ISSUE_TEMPLATE/helm.yml new file mode 100644 index 0000000000..b36733504e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/helm.yml @@ -0,0 +1,92 @@ +name: "⎈ K8s/Helm problem report" +description: "Report a problem with k8s/helm charts/etc" +labels: ["status/triage", "scope/k8s"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Hi, thanks for raising the issue(-s), all contributions really matter! + Please, note that we'll close the issue without further explanation if you don't follow + this template and don't provide the information requested within this template. + + - type: checkboxes + id: terms + attributes: + label: Issue submitter TODO list + description: By you checking these checkboxes we can be sure you've done the essential things. + options: + - label: I've looked up my issue in [FAQ](https://docs.kafka-ui.provectus.io/faq/common-problems) + required: true + - label: I've searched for an already existing issues [here](https://github.com/provectus/kafka-ui/issues) + required: true + - label: I've tried running `master`-labeled docker image and the issue still persists there + required: true + - label: I'm running a supported version of the application which is listed [here](https://github.com/provectus/kafka-ui/blob/master/SECURITY.md) + required: true + + - type: textarea + attributes: + label: Describe the bug (actual behavior) + description: A clear and concise description of what the bug is. Use a list, if there is more than one problem + validations: + required: true + + - type: textarea + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen + validations: + required: false + + - type: textarea + attributes: + label: Your installation details + description: | + How do you run the app? Please provide as much info as possible: + 1. App version (commit hash in the top left corner of the UI) + 2. Helm chart version + 3. Your application config. Please remove the sensitive info like passwords or API keys. + 4. Any IAAC configs + validations: + required: true + + - type: textarea + attributes: + label: Steps to reproduce + description: | + Please write down the order of the actions required to reproduce the issue. + For the advanced setups/complicated issue, we might need you to provide + a minimal [reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). + validations: + required: true + + - type: textarea + attributes: + label: Screenshots + description: | + If applicable, add screenshots to help explain your problem + validations: + required: false + + - type: textarea + attributes: + label: Logs + description: | + If applicable, *upload* screenshots to help explain your problem + validations: + required: false + + - type: textarea + attributes: + label: Additional context + description: | + Add any other context about the problem here. E.G.: + 1. Are there any alternative scenarios (different data/methods/configuration/setup) you have tried? + Were they successful or the same issue occurred? Please provide steps as well. + 2. Related issues (if there are any). + 3. Logs (if available) + 4. Is there any serious impact or behaviour on the end-user because of this issue, that can be overlooked? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/k8s.md b/.github/ISSUE_TEMPLATE/k8s.md deleted file mode 100644 index 5f4eb8ca75..0000000000 --- a/.github/ISSUE_TEMPLATE/k8s.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: "⎈ K8s/Helm problem report" -about: Report a problem with k8s/helm charts/etc -title: '' -labels: scope/k8s, status/triage -assignees: azatsafin - ---- - - - -**Describe the bug** - - - -**Set up** - - - -**Steps to Reproduce** -Steps to reproduce the behavior: - -1. - -**Expected behavior** - - -**Screenshots** - - - -**Additional context** - diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 75ea103c31..7e8552962a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,8 +8,6 @@ updates: timezone: Europe/Moscow reviewers: - "Haarolean" - assignees: - - "Haarolean" labels: - "scope/backend" - "type/dependencies" @@ -99,8 +97,6 @@ updates: timezone: Europe/Moscow reviewers: - "Haarolean" - assignees: - - "Haarolean" labels: - "scope/infrastructure" - "type/dependencies" From 5efb380c42a97418b29fd5e72ed8372be7d1b48e Mon Sep 17 00:00:00 2001 From: Winnie Chiu <113582273+winnie-chiu@users.noreply.github.com> Date: Wed, 26 Apr 2023 12:19:41 +0800 Subject: [PATCH 08/11] FE: Make it possible to not close message produce pane upon producing (#2854) Co-authored-by: Roman Zabaluev Co-authored-by: David <58771979+David-DB88@users.noreply.github.com> Co-authored-by: davitbejanyan --- .../Topic/SendMessage/SendMessage.styled.tsx | 26 ++++- .../Topics/Topic/SendMessage/SendMessage.tsx | 106 +++++++++++------- .../SendMessage/__test__/SendMessage.spec.tsx | 2 +- .../src/components/Topics/Topic/Topic.tsx | 2 +- .../SlidingSidebar/SlidingSidebar.styled.ts | 2 +- 5 files changed, 87 insertions(+), 51 deletions(-) diff --git a/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.styled.tsx b/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.styled.tsx index 483c41d053..d2750abf7d 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.styled.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.styled.tsx @@ -8,15 +8,29 @@ export const Wrapper = styled.div` export const Columns = styled.div` margin: -0.75rem; margin-bottom: 0.75rem; + display: flex; + flex-direction: column; + padding: 0.75rem; + gap: 8px; @media screen and (min-width: 769px) { display: flex; } `; - -export const Column = styled.div` - flex-basis: 0; - flex-grow: 1; - flex-shrink: 1; - padding: 0.75rem; +export const Flex = styled.div` + display: flex; + flex-direction: row; + gap: 8px; + @media screen and (max-width: 1200px) { + flex-direction: column; + } +`; +export const FlexItem = styled.div` + width: 18rem; + @media screen and (max-width: 1450px) { + width: 50%; + } + @media screen and (max-width: 1200px) { + width: 100%; + } `; diff --git a/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx b/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx index 9450e512ad..bacfa76c93 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx @@ -4,6 +4,7 @@ import { RouteParamsClusterTopic } from 'lib/paths'; import { Button } from 'components/common/Button/Button'; import Editor from 'components/common/Editor/Editor'; import Select, { SelectOption } from 'components/common/Select/Select'; +import Switch from 'components/common/Switch/Switch'; import useAppParams from 'lib/hooks/useAppParams'; import { showAlert } from 'lib/errorHandling'; import { useSendMessage, useTopicDetails } from 'lib/hooks/api/topics'; @@ -26,9 +27,12 @@ interface FormType { partition: number; keySerde: string; valueSerde: string; + keepContents: boolean; } -const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => { +const SendMessage: React.FC<{ closeSidebar: () => void }> = ({ + closeSidebar, +}) => { const { clusterName, topicName } = useAppParams(); const { data: topic } = useTopicDetails({ clusterName, topicName }); const { data: serdes = {} } = useSerdes({ @@ -47,11 +51,13 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => { handleSubmit, formState: { isSubmitting }, control, + setValue, } = useForm({ mode: 'onChange', defaultValues: { ...defaultValues, partition: Number(partitionOptions[0].value), + keepContents: false, }, }); @@ -62,6 +68,7 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => { content, headers, partition, + keepContents, }: FormType) => { let errors: string[] = []; @@ -110,7 +117,11 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => { keySerde, valueSerde, }); - onSubmit(); + if (!keepContents) { + setValue('key', ''); + setValue('content', ''); + closeSidebar(); + } } catch (e) { // do nothing } @@ -120,7 +131,7 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
- + Partition void }> = ({ onSubmit }) => { /> )} /> - - - Key Serde + + + + Key Serde + ( + + )} + /> + + +
( - - )} - /> - + Keep contents +
- - +
Key void }> = ({ onSubmit }) => { /> )} /> - - +
+
Value void }> = ({ onSubmit }) => { /> )} /> - +
- +
Headers void }> = ({ onSubmit }) => { /> )} /> - +