From 39aca05fe3a754dae09289aa57a20f4bef5a9811 Mon Sep 17 00:00:00 2001 From: Vlad Senyuta <66071557+VladSenyuta@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:22:08 +0300 Subject: [PATCH 01/42] [e2e] Clear entered queue check (#3667) --- .../provectus/kafka/ui/pages/BasePage.java | 6 +- .../kafka/ui/pages/ksqldb/KsqlDbList.java | 37 ++++++++- .../kafka/ui/pages/ksqldb/KsqlQueryForm.java | 23 ++++-- .../kafka/ui/utilities/WebUtils.java | 3 +- .../ui/manualsuite/backlog/SmokeBacklog.java | 21 ++--- .../ui/smokesuite/ksqldb/KsqlDbTest.java | 82 +++++++++++-------- .../ui/smokesuite/topics/MessagesTest.java | 30 +++---- .../src/test/resources/regression.xml | 2 +- .../src/test/resources/sanity.xml | 2 +- .../src/test/resources/smoke.xml | 2 +- 10 files changed, 126 insertions(+), 82 deletions(-) diff --git a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/BasePage.java b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/BasePage.java index fb2e0877e2..8bd7901a63 100644 --- a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/BasePage.java +++ b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/BasePage.java @@ -37,9 +37,13 @@ public abstract class BasePage extends WebUtils { protected String pageTitleFromHeader = "//h1[text()='%s']"; protected String pagePathFromHeader = "//a[text()='%s']/../h1"; + protected boolean isSpinnerVisible(int... timeoutInSeconds) { + return isVisible(loadingSpinner, timeoutInSeconds); + } + protected void waitUntilSpinnerDisappear(int... timeoutInSeconds) { log.debug("\nwaitUntilSpinnerDisappear"); - if (isVisible(loadingSpinner, timeoutInSeconds)) { + if (isSpinnerVisible(timeoutInSeconds)) { loadingSpinner.shouldBe(Condition.disappear, Duration.ofSeconds(60)); } } diff --git a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqldb/KsqlDbList.java b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqldb/KsqlDbList.java index 7eb35d52f3..98980cef4d 100644 --- a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqldb/KsqlDbList.java +++ b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqldb/KsqlDbList.java @@ -1,5 +1,6 @@ package com.provectus.kafka.ui.pages.ksqldb; +import static com.codeborne.selenide.Condition.visible; 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; @@ -10,12 +11,12 @@ 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 java.time.Duration; import java.util.ArrayList; import java.util.List; import org.openqa.selenium.By; 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']"); @@ -76,9 +77,24 @@ public class KsqlDbList extends BasePage { this.element = element; } + private SelenideElement getNameElm() { + return element.$x("./td[1]"); + } + @Step public String getTableName() { - return element.$x("./td[1]").getText().trim(); + return getNameElm().getText().trim(); + } + + @Step + public boolean isVisible() { + boolean isVisible = false; + try { + getNameElm().shouldBe(visible, Duration.ofMillis(500)); + isVisible = true; + } catch (Throwable ignored) { + } + return isVisible; } @Step @@ -110,9 +126,24 @@ public class KsqlDbList extends BasePage { this.element = element; } + private SelenideElement getNameElm() { + return element.$x("./td[1]"); + } + @Step public String getStreamName() { - return element.$x("./td[1]").getText().trim(); + return getNameElm().getText().trim(); + } + + @Step + public boolean isVisible() { + boolean isVisible = false; + try { + getNameElm().shouldBe(visible, Duration.ofMillis(500)); + isVisible = true; + } catch (Throwable ignored) { + } + return isVisible; } @Step diff --git a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqldb/KsqlQueryForm.java b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqldb/KsqlQueryForm.java index ab24cbe9ab..4ce282b6cc 100644 --- a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqldb/KsqlQueryForm.java +++ b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/ksqldb/KsqlQueryForm.java @@ -40,9 +40,14 @@ public class KsqlQueryForm extends BasePage { } @Step - public KsqlQueryForm clickExecuteBtn() { + public String getEnteredQuery() { + return queryAreaValue.getText().trim(); + } + + @Step + public KsqlQueryForm clickExecuteBtn(String query) { clickByActions(executeBtn); - if (queryAreaValue.getText().contains("EMIT CHANGES;")) { + if (query.contains("EMIT CHANGES")) { loadingSpinner.shouldBe(Condition.visible); } else { waitUntilSpinnerDisappear(); @@ -66,19 +71,19 @@ public class KsqlQueryForm extends BasePage { @Step public KsqlQueryForm clickAddStreamProperty() { - clickByJavaScript(addStreamPropertyBtn); + clickByActions(addStreamPropertyBtn); return this; } @Step public KsqlQueryForm setQuery(String query) { queryAreaValue.shouldBe(Condition.visible).click(); - queryArea.setValue(query); + sendKeysByActions(queryArea, query); return this; } @Step - public KsqlQueryForm.KsqlResponseGridItem getTableByName(String name) { + public KsqlQueryForm.KsqlResponseGridItem getItemByName(String name) { return initItems().stream() .filter(e -> e.getName().equalsIgnoreCase(name)) .findFirst().orElseThrow(); @@ -114,16 +119,20 @@ public class KsqlQueryForm extends BasePage { return element.$x("./td[1]").getText().trim(); } + private SelenideElement getNameElm() { + return element.$x("./td[2]"); + } + @Step public String getName() { - return element.$x("./td[2]").scrollTo().getText().trim(); + return getNameElm().scrollTo().getText().trim(); } @Step public boolean isVisible() { boolean isVisible = false; try { - element.$x("./td[2]").shouldBe(visible, Duration.ofMillis(500)); + getNameElm().shouldBe(visible, Duration.ofMillis(500)); isVisible = true; } catch (Throwable ignored) { } diff --git a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/utilities/WebUtils.java b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/utilities/WebUtils.java index fef5ef654a..a1b1523aa5 100644 --- a/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/utilities/WebUtils.java +++ b/kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/utilities/WebUtils.java @@ -95,7 +95,7 @@ public class WebUtils { return isSelected; } - public static boolean selectElement(SelenideElement element, boolean select) { + public static void selectElement(SelenideElement element, boolean select) { if (select) { if (!element.isSelected()) { clickByJavaScript(element); @@ -105,6 +105,5 @@ public class WebUtils { clickByJavaScript(element); } } - return true; } } 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 b89a1d0cf7..d96bbb7f3a 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 @@ -22,57 +22,50 @@ public class SmokeBacklog extends BaseManualTest { @Automation(state = TO_BE_AUTOMATED) @Suite(id = KSQL_DB_SUITE_ID) - @QaseId(276) + @QaseId(277) @Test public void testCaseB() { } @Automation(state = TO_BE_AUTOMATED) @Suite(id = KSQL_DB_SUITE_ID) - @QaseId(277) + @QaseId(278) @Test public void testCaseC() { } - @Automation(state = TO_BE_AUTOMATED) - @Suite(id = KSQL_DB_SUITE_ID) - @QaseId(278) - @Test - public void testCaseD() { - } - @Automation(state = TO_BE_AUTOMATED) @Suite(id = KSQL_DB_SUITE_ID) @QaseId(284) @Test - public void testCaseE() { + public void testCaseD() { } @Automation(state = TO_BE_AUTOMATED) @Suite(id = BROKERS_SUITE_ID) @QaseId(331) @Test - public void testCaseF() { + public void testCaseE() { } @Automation(state = TO_BE_AUTOMATED) @Suite(id = BROKERS_SUITE_ID) @QaseId(332) @Test - public void testCaseG() { + public void testCaseF() { } @Automation(state = TO_BE_AUTOMATED) @Suite(id = TOPICS_PROFILE_SUITE_ID) @QaseId(335) @Test - public void testCaseH() { + public void testCaseG() { } @Automation(state = TO_BE_AUTOMATED) @Suite(id = TOPICS_PROFILE_SUITE_ID) @QaseId(336) @Test - public void testCaseI() { + public void testCaseH() { } } 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 d8bda606dc..c4bbe0def4 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,14 +1,17 @@ package com.provectus.kafka.ui.smokesuite.ksqldb; 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; import com.provectus.kafka.ui.BaseTest; import com.provectus.kafka.ui.pages.ksqldb.models.Stream; import com.provectus.kafka.ui.pages.ksqldb.models.Table; +import io.qameta.allure.Step; import io.qase.api.annotation.QaseId; import java.util.ArrayList; import java.util.List; +import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -16,53 +19,30 @@ import org.testng.asserts.SoftAssert; public class KsqlDbTest extends BaseTest { - private static final Stream STREAM_FOR_CHECK_TABLES = new Stream() - .setName("STREAM_FOR_CHECK_TABLES_" + randomAlphabetic(4).toUpperCase()) - .setTopicName("TOPIC_FOR_STREAM_" + randomAlphabetic(4).toUpperCase()); + private static final Stream DEFAULT_STREAM = new Stream() + .setName("DEFAULT_STREAM_" + randomAlphabetic(4).toUpperCase()) + .setTopicName("DEFAULT_TOPIC_" + randomAlphabetic(4).toUpperCase()); private static final Table FIRST_TABLE = new Table() - .setName("FIRST_TABLE" + randomAlphabetic(4).toUpperCase()) - .setStreamName(STREAM_FOR_CHECK_TABLES.getName()); + .setName("FIRST_TABLE_" + randomAlphabetic(4).toUpperCase()) + .setStreamName(DEFAULT_STREAM.getName()); private static final Table SECOND_TABLE = new Table() - .setName("SECOND_TABLE" + randomAlphabetic(4).toUpperCase()) - .setStreamName(STREAM_FOR_CHECK_TABLES.getName()); + .setName("SECOND_TABLE_" + randomAlphabetic(4).toUpperCase()) + .setStreamName(DEFAULT_STREAM.getName()); private static final List TOPIC_NAMES_LIST = new ArrayList<>(); @BeforeClass(alwaysRun = true) public void beforeClass() { apiService - .createStream(STREAM_FOR_CHECK_TABLES) + .createStream(DEFAULT_STREAM) .createTables(FIRST_TABLE, SECOND_TABLE); - TOPIC_NAMES_LIST.addAll(List.of(STREAM_FOR_CHECK_TABLES.getTopicName(), + TOPIC_NAMES_LIST.addAll(List.of(DEFAULT_STREAM.getTopicName(), FIRST_TABLE.getName(), SECOND_TABLE.getName())); } - @QaseId(41) - @Test(priority = 1) - public void checkShowTablesRequestExecution() { - navigateToKsqlDb(); - ksqlDbList - .clickExecuteKsqlRequestBtn(); - ksqlQueryForm - .waitUntilScreenReady() - .setQuery(SHOW_TABLES.getQuery()) - .clickExecuteBtn(); - SoftAssert softly = new SoftAssert(); - softly.assertTrue(ksqlQueryForm.areResultsVisible(), "areResultsVisible()"); - softly.assertTrue(ksqlQueryForm.getTableByName(FIRST_TABLE.getName()).isVisible(), "getTableName()"); - softly.assertTrue(ksqlQueryForm.getTableByName(SECOND_TABLE.getName()).isVisible(), "getTableName()"); - softly.assertAll(); - } - @QaseId(86) - @Test(priority = 2) + @Test(priority = 1) public void clearResultsForExecutedRequest() { - navigateToKsqlDb(); - ksqlDbList - .clickExecuteKsqlRequestBtn(); - ksqlQueryForm - .waitUntilScreenReady() - .setQuery(SHOW_TABLES.getQuery()) - .clickExecuteBtn(); + navigateToKsqlDbAndExecuteRequest(SHOW_TABLES.getQuery()); SoftAssert softly = new SoftAssert(); softly.assertTrue(ksqlQueryForm.areResultsVisible(), "areResultsVisible()"); softly.assertAll(); @@ -72,6 +52,40 @@ public class KsqlDbTest extends BaseTest { softly.assertAll(); } + @QaseId(276) + @Test(priority = 2) + public void clearEnteredQueryCheck() { + navigateToKsqlDbAndExecuteRequest(SHOW_TABLES.getQuery()); + Assert.assertFalse(ksqlQueryForm.getEnteredQuery().isEmpty(), "getEnteredQuery()"); + ksqlQueryForm + .clickClearBtn(); + Assert.assertTrue(ksqlQueryForm.getEnteredQuery().isEmpty(), "getEnteredQuery()"); + } + + @QaseId(41) + @Test(priority = 3) + public void checkShowTablesRequestExecution() { + 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.assertAll(); + } + + @Step + private void navigateToKsqlDbAndExecuteRequest(String query) { + naviSideBar + .openSideMenu(KSQL_DB); + ksqlDbList + .waitUntilScreenReady() + .clickExecuteKsqlRequestBtn(); + ksqlQueryForm + .waitUntilScreenReady() + .setQuery(query) + .clickExecuteBtn(query); + } + @AfterClass(alwaysRun = true) public void afterClass() { TOPIC_NAMES_LIST.forEach(topicName -> apiService.deleteTopic(topicName)); diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/topics/MessagesTest.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/topics/MessagesTest.java index 3bbc7e7cd3..508a3b95be 100644 --- a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/topics/MessagesTest.java +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/smokesuite/topics/MessagesTest.java @@ -8,7 +8,6 @@ import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; import com.provectus.kafka.ui.BaseTest; import com.provectus.kafka.ui.models.Topic; -import com.provectus.kafka.ui.pages.topics.TopicDetails; import io.qameta.allure.Issue; import io.qameta.allure.Step; import io.qase.api.annotation.QaseId; @@ -140,24 +139,22 @@ public class MessagesTest extends BaseTest { softly.assertAll(); } - @Ignore - @Issue("https://github.com/provectus/kafka-ui/issues/2394") @QaseId(15) @Test(priority = 6) public void checkMessageFilteringByOffset() { navigateToTopicsAndOpenDetails(TOPIC_FOR_CHECK_FILTERS.getName()); - topicDetails - .openDetailsTab(MESSAGES); - TopicDetails.MessageGridItem secondMessage = topicDetails.getMessageByOffset(1); + int nextOffset = topicDetails + .openDetailsTab(MESSAGES) + .getAllMessages().stream() + .findFirst().orElseThrow().getOffset() + 1; topicDetails .selectSeekTypeDdlMessagesTab("Offset") - .setSeekTypeValueFldMessagesTab(String.valueOf(secondMessage.getOffset())) + .setSeekTypeValueFldMessagesTab(String.valueOf(nextOffset)) .clickSubmitFiltersBtnMessagesTab(); SoftAssert softly = new SoftAssert(); topicDetails.getAllMessages().forEach(message -> - softly.assertTrue(message.getOffset() == secondMessage.getOffset() - || message.getOffset() > secondMessage.getOffset(), - String.format("Expected offset is: %s, but found: %s", secondMessage.getOffset(), message.getOffset()))); + softly.assertTrue(message.getOffset() >= nextOffset, + String.format("Expected offset not less: %s, but found: %s", nextOffset, message.getOffset()))); softly.assertAll(); } @@ -168,13 +165,11 @@ public class MessagesTest extends BaseTest { @Test(priority = 7) public void checkMessageFilteringByTimestamp() { navigateToTopicsAndOpenDetails(TOPIC_FOR_CHECK_FILTERS.getName()); - topicDetails - .openDetailsTab(MESSAGES); - LocalDateTime firstTimestamp = topicDetails.getMessageByOffset(0).getTimestamp(); - List nextMessages = topicDetails.getAllMessages().stream() + LocalDateTime firstTimestamp = topicDetails + .openDetailsTab(MESSAGES) + .getMessageByOffset(0).getTimestamp(); + LocalDateTime nextTimestamp = topicDetails.getAllMessages().stream() .filter(message -> message.getTimestamp().getMinute() != firstTimestamp.getMinute()) - .toList(); - LocalDateTime nextTimestamp = nextMessages.stream() .findFirst().orElseThrow().getTimestamp(); topicDetails .selectSeekTypeDdlMessagesTab("Timestamp") @@ -183,8 +178,7 @@ public class MessagesTest extends BaseTest { .clickSubmitFiltersBtnMessagesTab(); SoftAssert softly = new SoftAssert(); topicDetails.getAllMessages().forEach(message -> - softly.assertTrue(message.getTimestamp().isEqual(nextTimestamp) - || message.getTimestamp().isAfter(nextTimestamp), + softly.assertFalse(message.getTimestamp().isBefore(nextTimestamp), String.format("Expected that %s is not before %s.", message.getTimestamp(), nextTimestamp))); softly.assertAll(); } diff --git a/kafka-ui-e2e-checks/src/test/resources/regression.xml b/kafka-ui-e2e-checks/src/test/resources/regression.xml index fe102bae3e..c6461ea14c 100644 --- a/kafka-ui-e2e-checks/src/test/resources/regression.xml +++ b/kafka-ui-e2e-checks/src/test/resources/regression.xml @@ -1,6 +1,6 @@ - + diff --git a/kafka-ui-e2e-checks/src/test/resources/sanity.xml b/kafka-ui-e2e-checks/src/test/resources/sanity.xml index c6b9b06024..bb67922402 100644 --- a/kafka-ui-e2e-checks/src/test/resources/sanity.xml +++ b/kafka-ui-e2e-checks/src/test/resources/sanity.xml @@ -1,6 +1,6 @@ - + diff --git a/kafka-ui-e2e-checks/src/test/resources/smoke.xml b/kafka-ui-e2e-checks/src/test/resources/smoke.xml index ab2929ff34..db93607727 100644 --- a/kafka-ui-e2e-checks/src/test/resources/smoke.xml +++ b/kafka-ui-e2e-checks/src/test/resources/smoke.xml @@ -1,6 +1,6 @@ - + From 0f5a9d7a630129c5b03a3993c24132eeb484c1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ce=C5=A1ek?= Date: Fri, 14 Apr 2023 15:43:03 +0700 Subject: [PATCH 02/42] FE: KSQL: Implement sorting (#3433) * Resolves #3171 - enable ksql tables, streams sorting * enable ksql tables, streams sorting - make memoized onSortingChange, onPaginationChange depend on location * fix linting * edit Table.tsx imports * fix linting --------- Co-authored-by: Roman Zabaluev --- kafka-ui-react-app/src/components/KsqlDb/TableView.tsx | 2 +- .../src/components/common/NewTable/Table.tsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/kafka-ui-react-app/src/components/KsqlDb/TableView.tsx b/kafka-ui-react-app/src/components/KsqlDb/TableView.tsx index d27e4968b7..538345954d 100644 --- a/kafka-ui-react-app/src/components/KsqlDb/TableView.tsx +++ b/kafka-ui-react-app/src/components/KsqlDb/TableView.tsx @@ -31,7 +31,7 @@ const TableView: React.FC = ({ fetching, rows }) => { data={rows || []} columns={columns} emptyMessage={fetching ? 'Loading...' : 'No rows found'} - enableSorting={false} + enableSorting /> ); }; diff --git a/kafka-ui-react-app/src/components/common/NewTable/Table.tsx b/kafka-ui-react-app/src/components/common/NewTable/Table.tsx index 55652df082..da1f2c090a 100644 --- a/kafka-ui-react-app/src/components/common/NewTable/Table.tsx +++ b/kafka-ui-react-app/src/components/common/NewTable/Table.tsx @@ -14,7 +14,7 @@ import type { PaginationState, ColumnDef, } from '@tanstack/react-table'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams, useLocation } from 'react-router-dom'; import { PER_PAGE } from 'lib/constants'; import { Button } from 'components/common/Button/Button'; import Input from 'components/common/Input/Input'; @@ -129,6 +129,7 @@ const Table: React.FC> = ({ onRowClick, }) => { const [searchParams, setSearchParams] = useSearchParams(); + const location = useLocation(); const [rowSelection, setRowSelection] = React.useState({}); const onSortingChange = React.useCallback( (updater: UpdaterFn) => { @@ -136,7 +137,7 @@ const Table: React.FC> = ({ setSearchParams(searchParams); return newState; }, - [searchParams] + [searchParams, location] ); const onPaginationChange = React.useCallback( (updater: UpdaterFn) => { @@ -145,7 +146,7 @@ const Table: React.FC> = ({ setRowSelection({}); return newState; }, - [searchParams] + [searchParams, location] ); const table = useReactTable({ From a640a52fe6775e376cdf9a2f6788a6cd6b894bd4 Mon Sep 17 00:00:00 2001 From: Roman Zabaluev Date: Fri, 14 Apr 2023 17:19:03 +0800 Subject: [PATCH 03/42] Infra: disable creating non-templated issues + add useful links along with issue templates --- .github/ISSUE_TEMPLATE/config.yml | 11 +++++++++++ .github/ISSUE_TEMPLATE/question.md | 16 ---------------- 2 files changed, 11 insertions(+), 16 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..368461c644 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Official documentation + url: https://docs.kafka-ui.provectus.io/ + about: Before reaching out for support, please refer to our documentation. Read "FAQ" and "Common problems", also try using search there. + - name: Community Discord + url: https://discord.gg/4DWzD7pGE5 + about: Chat with other users, get some support or ask questions. + - name: GitHub Discussions + url: https://github.com/provectus/kafka-ui/discussions + about: An alternative place to ask questions or to get some support. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 166f56e660..0000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: "❓ Question" -about: Ask a question -title: '' - ---- - - From 96a577a98c6069376c5d22ed49cffd3739f1bbdc Mon Sep 17 00:00:00 2001 From: Roman Zabaluev Date: Fri, 14 Apr 2023 13:46:49 +0400 Subject: [PATCH 04/42] Update security policy --- SECURITY.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 7e2343711a..af7890e9e7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,9 @@ Following versions of the project are currently being supported with security up | Version | Supported | | ------- | ------------------ | -| 0.5.x | :white_check_mark: | -| 0.4.x | :x: | +| 0.6.x | :white_check_mark: | +| 0.5.x | :x: | +| 0.4.x | :x: | | 0.3.x | :x: | | 0.2.x | :x: | | 0.1.x | :x: | From 40c198f0fc504923449260af2d9a5c78bb825160 Mon Sep 17 00:00:00 2001 From: Ilya Kuramshin Date: Fri, 14 Apr 2023 14:39:17 +0400 Subject: [PATCH 05/42] Config wizard BE: Add remaining cluster properties to wizard API (#3523) * Important @Value annotated properties moved to typed classes --------- Co-authored-by: iliax Co-authored-by: Roman Zabaluev Co-authored-by: VladSenyuta --- .../kafka/ui/config/ClustersProperties.java | 2 ++ .../com/provectus/kafka/ui/config/Config.java | 12 +------ .../kafka/ui/config/WebclientProperties.java | 33 +++++++++++++++++++ .../ui/service/AdminClientServiceImpl.java | 19 ++++++----- .../kafka/ui/service/KafkaClusterFactory.java | 21 +++++++----- .../ui/util/DynamicConfigOperations.java | 6 ++++ .../main/resources/swagger/kafka-ui-api.yaml | 10 ++++++ 7 files changed, 76 insertions(+), 27 deletions(-) create mode 100644 kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/WebclientProperties.java diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java index 15436c1cd8..24b60b5711 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java @@ -27,6 +27,8 @@ public class ClustersProperties { String internalTopicPrefix; + Integer adminClientTimeout; + PollingProperties polling = new PollingProperties(); @Data diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/Config.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/Config.java index 37495b5029..2ad0538c0e 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/Config.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/Config.java @@ -5,7 +5,6 @@ import java.util.Map; import lombok.AllArgsConstructor; import org.openapitools.jackson.nullable.JsonNullableModule; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.context.ApplicationContext; @@ -15,8 +14,6 @@ import org.springframework.http.server.reactive.ContextPathCompositeHandler; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.jmx.export.MBeanExporter; import org.springframework.util.StringUtils; -import org.springframework.util.unit.DataSize; -import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; @Configuration @@ -52,14 +49,7 @@ public class Config { } @Bean - public WebClient webClient( - @Value("${webclient.max-in-memory-buffer-size:20MB}") DataSize maxBuffSize) { - return WebClient.builder() - .codecs(c -> c.defaultCodecs().maxInMemorySize((int) maxBuffSize.toBytes())) - .build(); - } - - @Bean + // will be used by webflux json mapping public JsonNullableModule jsonNullableModule() { return new JsonNullableModule(); } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/WebclientProperties.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/WebclientProperties.java new file mode 100644 index 0000000000..ad7732612d --- /dev/null +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/WebclientProperties.java @@ -0,0 +1,33 @@ +package com.provectus.kafka.ui.config; + +import com.provectus.kafka.ui.exception.ValidationException; +import java.beans.Transient; +import javax.annotation.PostConstruct; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; + +@Configuration +@ConfigurationProperties("webclient") +@Data +public class WebclientProperties { + + String maxInMemoryBufferSize; + + @PostConstruct + public void validate() { + validateAndSetDefaultBufferSize(); + } + + private void validateAndSetDefaultBufferSize() { + if (maxInMemoryBufferSize != null) { + try { + DataSize.parse(maxInMemoryBufferSize); + } catch (Exception e) { + throw new ValidationException("Invalid format for webclient.maxInMemoryBufferSize"); + } + } + } + +} diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/AdminClientServiceImpl.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/AdminClientServiceImpl.java index 886b67b928..1bd4d7e33e 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/AdminClientServiceImpl.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/AdminClientServiceImpl.java @@ -1,33 +1,36 @@ package com.provectus.kafka.ui.service; +import com.provectus.kafka.ui.config.ClustersProperties; import com.provectus.kafka.ui.model.KafkaCluster; import com.provectus.kafka.ui.util.SslPropertiesUtil; import java.io.Closeable; import java.time.Instant; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; -import lombok.RequiredArgsConstructor; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.AdminClientConfig; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @Service -@RequiredArgsConstructor @Slf4j public class AdminClientServiceImpl implements AdminClientService, Closeable { + private static final int DEFAULT_CLIENT_TIMEOUT_MS = 30_000; + private static final AtomicLong CLIENT_ID_SEQ = new AtomicLong(); private final Map adminClientCache = new ConcurrentHashMap<>(); - @Setter // used in tests - @Value("${kafka.admin-client-timeout:30000}") - private int clientTimeout; + private final int clientTimeout; + + public AdminClientServiceImpl(ClustersProperties clustersProperties) { + this.clientTimeout = Optional.ofNullable(clustersProperties.getAdminClientTimeout()) + .orElse(DEFAULT_CLIENT_TIMEOUT_MS); + } @Override public Mono get(KafkaCluster cluster) { @@ -42,7 +45,7 @@ public class AdminClientServiceImpl implements AdminClientService, Closeable { SslPropertiesUtil.addKafkaSslProperties(cluster.getOriginalProperties().getSsl(), properties); properties.putAll(cluster.getProperties()); properties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, cluster.getBootstrapServers()); - properties.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, clientTimeout); + properties.putIfAbsent(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, clientTimeout); properties.putIfAbsent( AdminClientConfig.CLIENT_ID_CONFIG, "kafka-ui-admin-" + Instant.now().getEpochSecond() + "-" + CLIENT_ID_SEQ.incrementAndGet() diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaClusterFactory.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaClusterFactory.java index 357a548a63..964b25473d 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaClusterFactory.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaClusterFactory.java @@ -2,6 +2,7 @@ package com.provectus.kafka.ui.service; import com.provectus.kafka.ui.client.RetryingKafkaConnectClient; import com.provectus.kafka.ui.config.ClustersProperties; +import com.provectus.kafka.ui.config.WebclientProperties; import com.provectus.kafka.ui.connect.api.KafkaConnectClientApi; import com.provectus.kafka.ui.emitter.PollingSettings; import com.provectus.kafka.ui.model.ApplicationPropertyValidationDTO; @@ -22,9 +23,7 @@ import java.util.Optional; import java.util.Properties; import java.util.stream.Stream; import javax.annotation.Nullable; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.unit.DataSize; import org.springframework.web.reactive.function.client.WebClient; @@ -34,12 +33,18 @@ import reactor.util.function.Tuple2; import reactor.util.function.Tuples; @Service -@RequiredArgsConstructor @Slf4j public class KafkaClusterFactory { - @Value("${webclient.max-in-memory-buffer-size:20MB}") - private DataSize maxBuffSize; + private static final DataSize DEFAULT_WEBCLIENT_BUFFER = DataSize.parse("20MB"); + + private final DataSize webClientMaxBuffSize; + + public KafkaClusterFactory(WebclientProperties webclientProperties) { + this.webClientMaxBuffSize = Optional.ofNullable(webclientProperties.getMaxInMemoryBufferSize()) + .map(DataSize::parse) + .orElse(DEFAULT_WEBCLIENT_BUFFER); + } public KafkaCluster create(ClustersProperties properties, ClustersProperties.Cluster clusterProperties) { @@ -140,7 +145,7 @@ public class KafkaClusterFactory { url -> new RetryingKafkaConnectClient( connectCluster.toBuilder().address(url).build(), cluster.getSsl(), - maxBuffSize + webClientMaxBuffSize ), ReactiveFailover.CONNECTION_REFUSED_EXCEPTION_FILTER, "No alive connect instances available", @@ -158,7 +163,7 @@ public class KafkaClusterFactory { WebClient webClient = new WebClientConfigurator() .configureSsl(clusterProperties.getSsl(), clusterProperties.getSchemaRegistrySsl()) .configureBasicAuth(auth.getUsername(), auth.getPassword()) - .configureBufferSize(maxBuffSize) + .configureBufferSize(webClientMaxBuffSize) .build(); return ReactiveFailover.create( parseUrlList(clusterProperties.getSchemaRegistry()), @@ -181,7 +186,7 @@ public class KafkaClusterFactory { clusterProperties.getKsqldbServerAuth(), clusterProperties.getSsl(), clusterProperties.getKsqldbServerSsl(), - maxBuffSize + webClientMaxBuffSize ), ReactiveFailover.CONNECTION_REFUSED_EXCEPTION_FILTER, "No live ksqldb instances available", diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java index 2e1b32d3f1..75c6d25f95 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java @@ -2,6 +2,7 @@ package com.provectus.kafka.ui.util; import com.provectus.kafka.ui.config.ClustersProperties; +import com.provectus.kafka.ui.config.WebclientProperties; import com.provectus.kafka.ui.config.auth.OAuthProperties; import com.provectus.kafka.ui.config.auth.RoleBasedAccessControlProperties; import com.provectus.kafka.ui.exception.FileUploadException; @@ -97,6 +98,7 @@ public class DynamicConfigOperations { .type(ctx.getEnvironment().getProperty("auth.type")) .oauth2(getNullableBean(OAuthProperties.class)) .build()) + .webclient(getNullableBean(WebclientProperties.class)) .build(); } @@ -204,6 +206,7 @@ public class DynamicConfigOperations { private ClustersProperties kafka; private RoleBasedAccessControlProperties rbac; private Auth auth; + private WebclientProperties webclient; @Data @Builder @@ -222,6 +225,9 @@ public class DynamicConfigOperations { Optional.ofNullable(auth) .flatMap(a -> Optional.ofNullable(a.oauth2)) .ifPresent(OAuthProperties::validate); + + Optional.ofNullable(webclient) + .ifPresent(WebclientProperties::validate); } } 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 7b6fd3c113..aef7244466 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 @@ -3467,6 +3467,12 @@ components: type: array items: $ref: '#/components/schemas/Action' + webclient: + type: object + properties: + maxInMemoryBufferSize: + type: string + description: "examples: 20, 12KB, 5MB" kafka: type: object properties: @@ -3479,6 +3485,10 @@ components: type: integer noDataEmptyPolls: type: integer + adminClientTimeout: + type: integer + internalTopicPrefix: + type: string clusters: type: array items: From 814035e2543fc2620ff8e28fd9001210696b79da Mon Sep 17 00:00:00 2001 From: a1tair6 Date: Mon, 17 Apr 2023 19:57:37 +0900 Subject: [PATCH 06/42] FE: RBAC: Fix missing permissions for topic recreation (#3457) * fix missing permission for recreate topic Co-authored-by: jay-choe Co-authored-by: p-eye * fix unnecessary permission * add disable attribute, ActionDropdownItem * remove dropdownitem lib * fix eslint by #3080 --------- Co-authored-by: jay-choe Co-authored-by: p-eye --- .../src/components/Topics/List/ActionsCell.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/kafka-ui-react-app/src/components/Topics/List/ActionsCell.tsx b/kafka-ui-react-app/src/components/Topics/List/ActionsCell.tsx index 2f6fe3f49a..cdd669567f 100644 --- a/kafka-ui-react-app/src/components/Topics/List/ActionsCell.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/ActionsCell.tsx @@ -4,11 +4,7 @@ import { CellContext } from '@tanstack/react-table'; import ClusterContext from 'components/contexts/ClusterContext'; import { ClusterNameRoute } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; -import { - Dropdown, - DropdownItem, - DropdownItemHint, -} from 'components/common/Dropdown'; +import { Dropdown, DropdownItemHint } from 'components/common/Dropdown'; import { useDeleteTopic, useClearTopicMessages, @@ -55,7 +51,8 @@ const ActionsCell: React.FC> = ({ row }) => { with DELETE policy - @@ -63,9 +60,14 @@ const ActionsCell: React.FC> = ({ row }) => { } danger + permission={{ + resource: ResourceType.TOPIC, + action: [Action.VIEW, Action.CREATE, Action.DELETE], + value: name, + }} > Recreate Topic - + deleteTopic.mutateAsync(name)} From 838fb604d569dae18a1a7a85ef28ed2c125df986 Mon Sep 17 00:00:00 2001 From: Roman Zabaluev Date: Mon, 17 Apr 2023 19:28:49 +0800 Subject: [PATCH 07/42] Helm: Bump version to 0.6.2 Co-authored-by: github-actions --- charts/kafka-ui/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/kafka-ui/Chart.yaml b/charts/kafka-ui/Chart.yaml index 9f098c1d92..9337f59774 100644 --- a/charts/kafka-ui/Chart.yaml +++ b/charts/kafka-ui/Chart.yaml @@ -2,6 +2,6 @@ apiVersion: v2 name: kafka-ui description: A Helm chart for kafka-UI type: application -version: 0.6.1 -appVersion: v0.6.1 +version: 0.6.2 +appVersion: v0.6.2 icon: https://github.com/provectus/kafka-ui/raw/master/documentation/images/kafka-ui-logo.png From 47c8f8eeb55316822c2d06aad0cc6188b5c1900b Mon Sep 17 00:00:00 2001 From: Ilya Kuramshin Date: Wed, 19 Apr 2023 12:57:26 +0400 Subject: [PATCH 08/42] BE: Fix protobuf map support (#3683) * ISSUE-3674: Setting untyped object schema for "map" protobuf fields * com.provectus.kafka.ui.util.jsonschema classes visibility refactoring * kafka-ui-serdes.yaml changes rolled back --------- Co-authored-by: iliax --- documentation/compose/proto/values.proto | 2 + .../ui/util/jsonschema/AnyFieldSchema.java | 4 +- .../ui/util/jsonschema/ArrayFieldSchema.java | 4 +- .../ui/util/jsonschema/EnumJsonType.java | 4 +- .../kafka/ui/util/jsonschema/FieldSchema.java | 2 +- .../kafka/ui/util/jsonschema/JsonType.java | 8 +- .../ui/util/jsonschema/MapFieldSchema.java | 14 ++- .../ui/util/jsonschema/ObjectFieldSchema.java | 10 +- .../ui/util/jsonschema/OneOfFieldSchema.java | 5 +- .../jsonschema/ProtobufSchemaConverter.java | 101 ++++++++---------- .../ui/util/jsonschema/RefFieldSchema.java | 6 +- .../ui/util/jsonschema/SimpleFieldSchema.java | 4 +- .../ui/util/jsonschema/SimpleJsonType.java | 6 +- .../ProtobufSchemaConverterTest.java | 8 +- 14 files changed, 86 insertions(+), 92 deletions(-) diff --git a/documentation/compose/proto/values.proto b/documentation/compose/proto/values.proto index fbdd994761..fff8d9bbd9 100644 --- a/documentation/compose/proto/values.proto +++ b/documentation/compose/proto/values.proto @@ -9,4 +9,6 @@ message MySpecificTopicValue { message MyValue { int32 version = 1; string payload = 2; + map intToStringMap = 3; + map strToObjMap = 4; } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/AnyFieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/AnyFieldSchema.java index 6352f04a94..333a6bd633 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/AnyFieldSchema.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/AnyFieldSchema.java @@ -4,9 +4,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; // Specifies field that can contain any kind of value - primitive, complex and nulls -public class AnyFieldSchema implements FieldSchema { +class AnyFieldSchema implements FieldSchema { - public static AnyFieldSchema get() { + static AnyFieldSchema get() { return new AnyFieldSchema(); } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ArrayFieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ArrayFieldSchema.java index c5cefe94c9..b20d09550c 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ArrayFieldSchema.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ArrayFieldSchema.java @@ -4,10 +4,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -public class ArrayFieldSchema implements FieldSchema { +class ArrayFieldSchema implements FieldSchema { private final FieldSchema itemsSchema; - public ArrayFieldSchema(FieldSchema itemsSchema) { + ArrayFieldSchema(FieldSchema itemsSchema) { this.itemsSchema = itemsSchema; } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/EnumJsonType.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/EnumJsonType.java index 715f7d5f44..a43d45cd84 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/EnumJsonType.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/EnumJsonType.java @@ -7,10 +7,10 @@ import java.util.List; import java.util.Map; -public class EnumJsonType extends JsonType { +class EnumJsonType extends JsonType { private final List values; - public EnumJsonType(List values) { + EnumJsonType(List values) { super(Type.ENUM); this.values = values; } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/FieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/FieldSchema.java index 19166bf310..c8ad7e953b 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/FieldSchema.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/FieldSchema.java @@ -3,6 +3,6 @@ package com.provectus.kafka.ui.util.jsonschema; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -public interface FieldSchema { +interface FieldSchema { JsonNode toJsonNode(ObjectMapper mapper); } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/JsonType.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/JsonType.java index 79d73c6813..392a2260c3 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/JsonType.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/JsonType.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; -public abstract class JsonType { +abstract class JsonType { protected final Type type; @@ -12,13 +12,13 @@ public abstract class JsonType { this.type = type; } - public Type getType() { + Type getType() { return type; } - public abstract Map toJsonNode(ObjectMapper mapper); + abstract Map toJsonNode(ObjectMapper mapper); - public enum Type { + enum Type { NULL, BOOLEAN, OBJECT, diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/MapFieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/MapFieldSchema.java index c7c52acbab..6b2422ef7d 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/MapFieldSchema.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/MapFieldSchema.java @@ -2,21 +2,27 @@ package com.provectus.kafka.ui.util.jsonschema; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; +import javax.annotation.Nullable; -public class MapFieldSchema implements FieldSchema { - private final FieldSchema itemSchema; +class MapFieldSchema implements FieldSchema { + private final @Nullable FieldSchema itemSchema; - public MapFieldSchema(FieldSchema itemSchema) { + MapFieldSchema(@Nullable FieldSchema itemSchema) { this.itemSchema = itemSchema; } + MapFieldSchema() { + this(null); + } + @Override public JsonNode toJsonNode(ObjectMapper mapper) { final ObjectNode objectNode = mapper.createObjectNode(); objectNode.set("type", new TextNode(JsonType.Type.OBJECT.getName())); - objectNode.set("additionalProperties", itemSchema.toJsonNode(mapper)); + objectNode.set("additionalProperties", itemSchema != null ? itemSchema.toJsonNode(mapper) : BooleanNode.TRUE); return objectNode; } } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ObjectFieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ObjectFieldSchema.java index 589fe10533..21d3402288 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ObjectFieldSchema.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ObjectFieldSchema.java @@ -9,24 +9,24 @@ import java.util.stream.Collectors; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; -public class ObjectFieldSchema implements FieldSchema { +class ObjectFieldSchema implements FieldSchema { - public static final ObjectFieldSchema EMPTY = new ObjectFieldSchema(Map.of(), List.of()); + static final ObjectFieldSchema EMPTY = new ObjectFieldSchema(Map.of(), List.of()); private final Map properties; private final List required; - public ObjectFieldSchema(Map properties, + ObjectFieldSchema(Map properties, List required) { this.properties = properties; this.required = required; } - public Map getProperties() { + Map getProperties() { return properties; } - public List getRequired() { + List getRequired() { return required; } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/OneOfFieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/OneOfFieldSchema.java index cec8282b70..3f0b11373e 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/OneOfFieldSchema.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/OneOfFieldSchema.java @@ -5,11 +5,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; import java.util.stream.Collectors; -public class OneOfFieldSchema implements FieldSchema { +class OneOfFieldSchema implements FieldSchema { private final List schemaList; - public OneOfFieldSchema( - List schemaList) { + OneOfFieldSchema(List schemaList) { this.schemaList = schemaList; } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ProtobufSchemaConverter.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ProtobufSchemaConverter.java index 219039b31e..86ea8fcf0e 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ProtobufSchemaConverter.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ProtobufSchemaConverter.java @@ -94,6 +94,9 @@ public class ProtobufSchemaConverter implements JsonSchemaConverter new SimpleJsonType( + JsonType.Type.INTEGER, + Map.of( + "maximum", IntNode.valueOf(Integer.MAX_VALUE), + "minimum", IntNode.valueOf(Integer.MIN_VALUE) + ) + ); + case UINT32 -> new SimpleJsonType( + JsonType.Type.INTEGER, + Map.of( + "maximum", LongNode.valueOf(UnsignedInteger.MAX_VALUE.longValue()), + "minimum", IntNode.valueOf(0) + ) + ); //TODO: actually all *64 types will be printed with quotes (as strings), // see JsonFormat::printSingleFieldValue for impl. This can cause problems when you copy-paste from messages // table to `Produce` area - need to think if it is critical or not. - case INT64: - case FIXED64: - case SFIXED64: - case SINT64: - return new SimpleJsonType( - JsonType.Type.INTEGER, - Map.of( - "maximum", LongNode.valueOf(Long.MAX_VALUE), - "minimum", LongNode.valueOf(Long.MIN_VALUE) - ) - ); - case UINT64: - return new SimpleJsonType( - JsonType.Type.INTEGER, - Map.of( - "maximum", new BigIntegerNode(UnsignedLong.MAX_VALUE.bigIntegerValue()), - "minimum", LongNode.valueOf(0) - ) - ); - case MESSAGE: - case GROUP: - return new SimpleJsonType(JsonType.Type.OBJECT); - case ENUM: - return new EnumJsonType( - field.getEnumType().getValues().stream() - .map(Descriptors.EnumValueDescriptor::getName) - .collect(Collectors.toList()) - ); - case BYTES: - case STRING: - return new SimpleJsonType(JsonType.Type.STRING); - case FLOAT: - case DOUBLE: - return new SimpleJsonType(JsonType.Type.NUMBER); - case BOOL: - return new SimpleJsonType(JsonType.Type.BOOLEAN); - default: - return new SimpleJsonType(JsonType.Type.STRING); - } + case INT64, FIXED64, SFIXED64, SINT64 -> new SimpleJsonType( + JsonType.Type.INTEGER, + Map.of( + "maximum", LongNode.valueOf(Long.MAX_VALUE), + "minimum", LongNode.valueOf(Long.MIN_VALUE) + ) + ); + case UINT64 -> new SimpleJsonType( + JsonType.Type.INTEGER, + Map.of( + "maximum", new BigIntegerNode(UnsignedLong.MAX_VALUE.bigIntegerValue()), + "minimum", LongNode.valueOf(0) + ) + ); + case MESSAGE, GROUP -> new SimpleJsonType(JsonType.Type.OBJECT); + case ENUM -> new EnumJsonType( + field.getEnumType().getValues().stream() + .map(Descriptors.EnumValueDescriptor::getName) + .collect(Collectors.toList()) + ); + case BYTES, STRING -> new SimpleJsonType(JsonType.Type.STRING); + case FLOAT, DOUBLE -> new SimpleJsonType(JsonType.Type.NUMBER); + case BOOL -> new SimpleJsonType(JsonType.Type.BOOLEAN); + }; } } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/RefFieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/RefFieldSchema.java index fa122f0f7e..ca8e50a087 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/RefFieldSchema.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/RefFieldSchema.java @@ -4,10 +4,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.TextNode; -public class RefFieldSchema implements FieldSchema { +class RefFieldSchema implements FieldSchema { private final String ref; - public RefFieldSchema(String ref) { + RefFieldSchema(String ref) { this.ref = ref; } @@ -16,7 +16,7 @@ public class RefFieldSchema implements FieldSchema { return mapper.createObjectNode().set("$ref", new TextNode(ref)); } - public String getRef() { + String getRef() { return ref; } } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/SimpleFieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/SimpleFieldSchema.java index 158cceb6bf..339ab4cc86 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/SimpleFieldSchema.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/SimpleFieldSchema.java @@ -3,10 +3,10 @@ package com.provectus.kafka.ui.util.jsonschema; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -public class SimpleFieldSchema implements FieldSchema { +class SimpleFieldSchema implements FieldSchema { private final JsonType type; - public SimpleFieldSchema(JsonType type) { + SimpleFieldSchema(JsonType type) { this.type = type; } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/SimpleJsonType.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/SimpleJsonType.java index 56ab56b48c..b46d3407e3 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/SimpleJsonType.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/SimpleJsonType.java @@ -6,15 +6,15 @@ import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.collect.ImmutableMap; import java.util.Map; -public class SimpleJsonType extends JsonType { +class SimpleJsonType extends JsonType { private final Map additionalTypeProperties; - public SimpleJsonType(Type type) { + SimpleJsonType(Type type) { this(type, Map.of()); } - public SimpleJsonType(Type type, Map additionalTypeProperties) { + SimpleJsonType(Type type, Map additionalTypeProperties) { super(type); this.additionalTypeProperties = additionalTypeProperties; } diff --git a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/jsonschema/ProtobufSchemaConverterTest.java b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/jsonschema/ProtobufSchemaConverterTest.java index 02da33bb12..0416184806 100644 --- a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/jsonschema/ProtobufSchemaConverterTest.java +++ b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/jsonschema/ProtobufSchemaConverterTest.java @@ -59,8 +59,10 @@ class ProtobufSchemaConverterTest { TestMsg outer_ref = 2; EmbeddedMsg self_ref = 3; } - }"""; + map intToStringMap = 21; + map strToObjMap = 22; + }"""; String expectedJsonSchema = """ { @@ -109,7 +111,9 @@ class ProtobufSchemaConverterTest { "v2": { "type": [ "number", "string", "object", "array", "boolean", "null" ] }, "uint32_w_field": { "type": "integer", "maximum": 4294967295, "minimum": 0 }, "bool_w_field": { "type": "boolean" }, - "uint64_w_field": { "type": "integer", "maximum": 18446744073709551615, "minimum": 0 } + "uint64_w_field": { "type": "integer", "maximum": 18446744073709551615, "minimum": 0 }, + "strToObjMap": { "type": "object", "additionalProperties": true }, + "intToStringMap": { "type": "object", "additionalProperties": true } } }, "test.TestMsg.EmbeddedMsg": { From a1f955ab7c0d668e99edf4594312dd28d6c823d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:07:13 +0400 Subject: [PATCH 09/42] Bump docker-maven-plugin from 0.42.0 to 0.42.1 (#3639) Bumps [docker-maven-plugin](https://github.com/fabric8io/docker-maven-plugin) from 0.42.0 to 0.42.1. - [Release notes](https://github.com/fabric8io/docker-maven-plugin/releases) - [Changelog](https://github.com/fabric8io/docker-maven-plugin/blob/master/doc/changelog.md) - [Commits](https://github.com/fabric8io/docker-maven-plugin/compare/v0.42.0...v0.42.1) --- updated-dependencies: - dependency-name: io.fabric8:docker-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a82dc72391..42ebd9addc 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ v7.4.0 - 0.42.0 + 0.42.1 1.12.1 3.2.0 3.10.1 From 73a6d7cade9cd50aa6fba68f7a30950af942477b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:07:53 +0400 Subject: [PATCH 10/42] Bump peter-evans/create-or-update-comment from 2 to 3 (#3638) Bumps [peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment) from 2 to 3. - [Release notes](https://github.com/peter-evans/create-or-update-comment/releases) - [Commits](https://github.com/peter-evans/create-or-update-comment/compare/v2...v3) --- updated-dependencies: - dependency-name: peter-evans/create-or-update-comment dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/branch-deploy.yml | 4 ++-- .github/workflows/branch-remove.yml | 2 +- .github/workflows/build-public-image.yml | 2 +- .github/workflows/delete-public-image.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/branch-deploy.yml b/.github/workflows/branch-deploy.yml index 3039958b5a..4948567147 100644 --- a/.github/workflows/branch-deploy.yml +++ b/.github/workflows/branch-deploy.yml @@ -86,7 +86,7 @@ jobs: - name: make comment with private deployment link if: ${{ github.event.label.name == 'status/feature_testing' }} - uses: peter-evans/create-or-update-comment@v2 + uses: peter-evans/create-or-update-comment@v3 with: issue-number: ${{ github.event.pull_request.number }} body: | @@ -94,7 +94,7 @@ jobs: - name: make comment with public deployment link if: ${{ github.event.label.name == 'status/feature_testing_public' }} - uses: peter-evans/create-or-update-comment@v2 + uses: peter-evans/create-or-update-comment@v3 with: issue-number: ${{ github.event.pull_request.number }} body: | diff --git a/.github/workflows/branch-remove.yml b/.github/workflows/branch-remove.yml index c93fa89eba..8019764f26 100644 --- a/.github/workflows/branch-remove.yml +++ b/.github/workflows/branch-remove.yml @@ -21,7 +21,7 @@ jobs: git add ../kafka-ui-from-branch/ git commit -m "removed env:${{ needs.build.outputs.deploy }}" && git push || true - name: make comment with deployment link - uses: peter-evans/create-or-update-comment@v2 + uses: peter-evans/create-or-update-comment@v3 with: issue-number: ${{ github.event.pull_request.number }} body: | diff --git a/.github/workflows/build-public-image.yml b/.github/workflows/build-public-image.yml index 7a5e3b4ca6..c4698b062c 100644 --- a/.github/workflows/build-public-image.yml +++ b/.github/workflows/build-public-image.yml @@ -65,7 +65,7 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache - name: make comment with private deployment link - uses: peter-evans/create-or-update-comment@v2 + uses: peter-evans/create-or-update-comment@v3 with: issue-number: ${{ github.event.pull_request.number }} body: | diff --git a/.github/workflows/delete-public-image.yml b/.github/workflows/delete-public-image.yml index c335bb8050..1662a4b745 100644 --- a/.github/workflows/delete-public-image.yml +++ b/.github/workflows/delete-public-image.yml @@ -33,7 +33,7 @@ jobs: --image-ids imageTag=${{ steps.extract_branch.outputs.tag }} \ --region us-east-1 - name: make comment with private deployment link - uses: peter-evans/create-or-update-comment@v2 + uses: peter-evans/create-or-update-comment@v3 with: issue-number: ${{ github.event.pull_request.number }} body: | From c89953435a4e3abde3a4fb7073f8aab9bbf702c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:09:09 +0400 Subject: [PATCH 11/42] Bump mheap/github-action-required-labels from 3 to 4 (#3610) Bumps [mheap/github-action-required-labels](https://github.com/mheap/github-action-required-labels) from 3 to 4. - [Release notes](https://github.com/mheap/github-action-required-labels/releases) - [Commits](https://github.com/mheap/github-action-required-labels/compare/v3...v4) --- updated-dependencies: - dependency-name: mheap/github-action-required-labels dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/block_merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/block_merge.yml b/.github/workflows/block_merge.yml index e1cdb3ac8e..766d598eb3 100644 --- a/.github/workflows/block_merge.yml +++ b/.github/workflows/block_merge.yml @@ -6,7 +6,7 @@ jobs: block_merge: runs-on: ubuntu-latest steps: - - uses: mheap/github-action-required-labels@v3 + - uses: mheap/github-action-required-labels@v4 with: mode: exactly count: 0 From bd782213d1a078ecf17fb202f2c13b196ecd3f41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:12:33 +0400 Subject: [PATCH 12/42] Bump actions/stale from 7 to 8 (#3556) Bumps [actions/stale](https://github.com/actions/stale) from 7 to 8. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 0a5d2e064c..dcf39ab003 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v7 + - uses: actions/stale@v8 with: days-before-issue-stale: 7 days-before-issue-close: 3 From 5dd690aa2438182cd2a0deb4f45f4db259618cc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:14:06 +0400 Subject: [PATCH 13/42] Bump aquasecurity/trivy-action from 0.9.2 to 0.10.0 (#3692) Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.9.2 to 0.10.0. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.9.2...0.10.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cve.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cve.yaml b/.github/workflows/cve.yaml index 77eae14bda..9774cca188 100644 --- a/.github/workflows/cve.yaml +++ b/.github/workflows/cve.yaml @@ -55,7 +55,7 @@ jobs: cache-to: type=local,dest=/tmp/.buildx-cache - name: Run CVE checks - uses: aquasecurity/trivy-action@0.9.2 + uses: aquasecurity/trivy-action@0.10.0 with: image-ref: "provectuslabs/kafka-ui:${{ steps.build.outputs.version }}" format: "table" From 8783da313fb342c883a7c54ea98c3a5240650773 Mon Sep 17 00:00:00 2001 From: Roman Zabaluev Date: Thu, 20 Apr 2023 19:18:34 +0800 Subject: [PATCH 14/42] FE: Fix topic messages Invalid size for null key/value messages (#3689) --- .../src/components/Topics/Topic/Messages/Message.tsx | 4 ++++ .../Topic/Messages/MessageContent/MessageContent.tsx | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx index 0282cde2ea..fb4e258cca 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx @@ -29,8 +29,10 @@ const Message: React.FC = ({ timestampType, offset, key, + keySize, partition, content, + valueSize, headers, valueSerde, keySerde, @@ -138,6 +140,8 @@ const Message: React.FC = ({ headers={headers} timestamp={timestamp} timestampType={timestampType} + keySize={keySize} + contentSize={valueSize} /> )} diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/MessageContent.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/MessageContent.tsx index fe472ad3b1..93616ca432 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/MessageContent.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/MessageContent.tsx @@ -15,6 +15,8 @@ export interface MessageContentProps { headers?: { [key: string]: string | undefined }; timestamp?: Date; timestampType?: TopicMessageTimestampTypeEnum; + keySize?: number; + contentSize?: number; } const MessageContent: React.FC = ({ @@ -23,6 +25,8 @@ const MessageContent: React.FC = ({ headers, timestamp, timestampType, + keySize, + contentSize, }) => { const [activeTab, setActiveTab] = React.useState('content'); const [searchParams] = useSearchParams(); @@ -54,8 +58,7 @@ const MessageContent: React.FC = ({ e.preventDefault(); setActiveTab('headers'); }; - const keySize = new TextEncoder().encode(messageKey).length; - const contentSize = new TextEncoder().encode(messageContent).length; + const contentType = messageContent && messageContent.trim().startsWith('{') ? SchemaType.JSON From 734d4ccdf71ee9b5c85570faa0c4767db3624509 Mon Sep 17 00:00:00 2001 From: Nisan Ohana <78907315+nisanohana3@users.noreply.github.com> Date: Thu, 20 Apr 2023 20:13:05 +0300 Subject: [PATCH 15/42] FE: Allow sorting consumer groups by topic num (#3633) Signed-off-by: nisanohana3 Co-authored-by: Roman Zabaluev --- .../kafka/ui/mapper/ConsumerGroupMapper.java | 11 +---- .../kafka/ui/model/InternalConsumerGroup.java | 45 +++++++++++++------ .../ui/service/ConsumerGroupService.java | 43 +++++++++++++----- .../main/resources/swagger/kafka-ui-api.yaml | 1 + .../src/components/ConsumerGroups/List.tsx | 2 +- 5 files changed, 67 insertions(+), 35 deletions(-) diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/mapper/ConsumerGroupMapper.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/mapper/ConsumerGroupMapper.java index 21d9efda9c..4ebbf4c70f 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/mapper/ConsumerGroupMapper.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/mapper/ConsumerGroupMapper.java @@ -11,8 +11,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.kafka.common.Node; import org.apache.kafka.common.TopicPartition; @@ -82,15 +80,8 @@ public class ConsumerGroupMapper { InternalConsumerGroup c, T consumerGroup) { consumerGroup.setGroupId(c.getGroupId()); consumerGroup.setMembers(c.getMembers().size()); - - int numTopics = Stream.concat( - c.getOffsets().keySet().stream().map(TopicPartition::topic), - c.getMembers().stream() - .flatMap(m -> m.getAssignment().stream().map(TopicPartition::topic)) - ).collect(Collectors.toSet()).size(); - consumerGroup.setMessagesBehind(c.getMessagesBehind()); - consumerGroup.setTopics(numTopics); + consumerGroup.setTopics(c.getTopicNum()); consumerGroup.setSimple(c.isSimple()); Optional.ofNullable(c.getState()) diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalConsumerGroup.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalConsumerGroup.java index e8199fa8ef..06de3cb7d6 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalConsumerGroup.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/InternalConsumerGroup.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.Builder; import lombok.Data; import org.apache.kafka.clients.admin.ConsumerGroupDescription; @@ -21,6 +22,7 @@ public class InternalConsumerGroup { private final Map offsets; private final Map endOffsets; private final Long messagesBehind; + private final Integer topicNum; private final String partitionAssignor; private final ConsumerGroupState state; private final Node coordinator; @@ -44,22 +46,12 @@ public class InternalConsumerGroup { builder.simple(description.isSimpleConsumerGroup()); builder.state(description.state()); builder.partitionAssignor(description.partitionAssignor()); - builder.members( - description.members().stream() - .map(m -> - InternalConsumerGroup.InternalMember.builder() - .assignment(m.assignment().topicPartitions()) - .clientId(m.clientId()) - .groupInstanceId(m.groupInstanceId().orElse("")) - .consumerId(m.consumerId()) - .clientId(m.clientId()) - .host(m.host()) - .build() - ).collect(Collectors.toList()) - ); + Collection internalMembers = initInternalMembers(description); + builder.members(internalMembers); builder.offsets(groupOffsets); builder.endOffsets(topicEndOffsets); builder.messagesBehind(calculateMessagesBehind(groupOffsets, topicEndOffsets)); + builder.topicNum(calculateTopicNum(groupOffsets, internalMembers)); Optional.ofNullable(description.coordinator()).ifPresent(builder::coordinator); return builder.build(); } @@ -80,4 +72,31 @@ public class InternalConsumerGroup { return messagesBehind; } + private static Integer calculateTopicNum(Map offsets, Collection members) { + + long topicNum = Stream.concat( + offsets.keySet().stream().map(TopicPartition::topic), + members.stream() + .flatMap(m -> m.getAssignment().stream().map(TopicPartition::topic)) + ).distinct().count(); + + return Integer.valueOf((int) topicNum); + + } + + private static Collection initInternalMembers(ConsumerGroupDescription description) { + return description.members().stream() + .map(m -> + InternalConsumerGroup.InternalMember.builder() + .assignment(m.assignment().topicPartitions()) + .clientId(m.clientId()) + .groupInstanceId(m.groupInstanceId().orElse("")) + .consumerId(m.consumerId()) + .clientId(m.clientId()) + .host(m.host()) + .build() + ).collect(Collectors.toList()); + } + + } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ConsumerGroupService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ConsumerGroupService.java index e848146881..815fdbef6a 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ConsumerGroupService.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ConsumerGroupService.java @@ -101,6 +101,9 @@ public class ConsumerGroupService { public record ConsumerGroupsPage(List consumerGroups, int totalPages) { } + private record GroupWithDescr(InternalConsumerGroup icg, ConsumerGroupDescription cgd) { + } + public Mono getConsumerGroupsPage( KafkaCluster cluster, int pageNum, @@ -159,22 +162,19 @@ public class ConsumerGroupService { sortAndPaginate(descriptions.values(), comparator, pageNum, perPage, sortOrderDto).toList()); } case MESSAGES_BEHIND -> { - record GroupWithDescr(InternalConsumerGroup icg, ConsumerGroupDescription cgd) { } Comparator comparator = Comparator.comparingLong(gwd -> gwd.icg.getMessagesBehind() == null ? 0L : gwd.icg.getMessagesBehind()); - var groupNames = groups.stream().map(ConsumerGroupListing::groupId).toList(); + yield loadDescriptionsByInternalConsumerGroups(ac, groups, comparator, pageNum, perPage, sortOrderDto); + } + + case TOPIC_NUM -> { + + Comparator comparator = Comparator.comparingInt(gwd -> gwd.icg.getTopicNum()); + + yield loadDescriptionsByInternalConsumerGroups(ac, groups, comparator, pageNum, perPage, sortOrderDto); - yield ac.describeConsumerGroups(groupNames) - .flatMap(descriptionsMap -> { - List descriptions = descriptionsMap.values().stream().toList(); - return getConsumerGroups(ac, descriptions) - .map(icg -> Streams.zip(icg.stream(), descriptions.stream(), GroupWithDescr::new).toList()) - .map(gwd -> sortAndPaginate(gwd, comparator, pageNum, perPage, sortOrderDto) - .map(GroupWithDescr::cgd).toList()); - } - ); } }; } @@ -209,6 +209,27 @@ public class ConsumerGroupService { .map(cgs -> new ArrayList<>(cgs.values())); } + + private Mono> loadDescriptionsByInternalConsumerGroups(ReactiveAdminClient ac, + List groups, + Comparator comparator, + int pageNum, + int perPage, + SortOrderDTO sortOrderDto) { + var groupNames = groups.stream().map(ConsumerGroupListing::groupId).toList(); + + return ac.describeConsumerGroups(groupNames) + .flatMap(descriptionsMap -> { + List descriptions = descriptionsMap.values().stream().toList(); + return getConsumerGroups(ac, descriptions) + .map(icg -> Streams.zip(icg.stream(), descriptions.stream(), GroupWithDescr::new).toList()) + .map(gwd -> sortAndPaginate(gwd, comparator, pageNum, perPage, sortOrderDto) + .map(GroupWithDescr::cgd).toList()); + } + ); + + } + public Mono getConsumerGroupDetail(KafkaCluster cluster, String consumerGroupId) { return adminClientService.get(cluster) 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 aef7244466..0d54fa7e79 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 @@ -2441,6 +2441,7 @@ components: - MEMBERS - STATE - MESSAGES_BEHIND + - TOPIC_NUM ConsumerGroupsPageResponse: type: object diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List.tsx index ef8d73f515..795ac75a5e 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List.tsx +++ b/kafka-ui-react-app/src/components/ConsumerGroups/List.tsx @@ -51,9 +51,9 @@ const List = () => { accessorKey: 'members', }, { + id: ConsumerGroupOrdering.TOPIC_NUM, header: 'Num Of Topics', accessorKey: 'topics', - enableSorting: false, }, { id: ConsumerGroupOrdering.MESSAGES_BEHIND, From 7365cfe3948279079f6ca855175f7b596bbd910e Mon Sep 17 00:00:00 2001 From: Ilya Kuramshin Date: Fri, 21 Apr 2023 13:19:38 +0400 Subject: [PATCH 16/42] BE: Make paging sizes configurable (#3685) Co-authored-by: iliax Co-authored-by: Roman Zabaluev --- .../kafka/ui/config/ClustersProperties.java | 2 ++ .../ui/controller/MessagesController.java | 7 +--- .../kafka/ui/service/MessagesService.java | 36 ++++++++++++++++--- .../main/resources/swagger/kafka-ui-api.yaml | 4 +++ 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java index 24b60b5711..1d5cc5393c 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/ClustersProperties.java @@ -58,6 +58,8 @@ public class ClustersProperties { Integer pollTimeoutMs; Integer partitionPollTimeout; Integer noDataEmptyPolls; + Integer maxPageSize; + Integer defaultPageSize; } @Data diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/MessagesController.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/MessagesController.java index 1ba511ab07..aa9d7d5315 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/MessagesController.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/MessagesController.java @@ -43,9 +43,6 @@ import reactor.core.scheduler.Schedulers; @Slf4j public class MessagesController extends AbstractController implements MessagesApi { - private static final int MAX_LOAD_RECORD_LIMIT = 100; - private static final int DEFAULT_LOAD_RECORD_LIMIT = 20; - private final MessagesService messagesService; private final DeserializationService deserializationService; private final AccessControlService accessControlService; @@ -91,8 +88,6 @@ public class MessagesController extends AbstractController implements MessagesAp seekType = seekType != null ? seekType : SeekTypeDTO.BEGINNING; seekDirection = seekDirection != null ? seekDirection : SeekDirectionDTO.FORWARD; filterQueryType = filterQueryType != null ? filterQueryType : MessageFilterTypeDTO.STRING_CONTAINS; - int recordsLimit = - Optional.ofNullable(limit).map(s -> Math.min(s, MAX_LOAD_RECORD_LIMIT)).orElse(DEFAULT_LOAD_RECORD_LIMIT); var positions = new ConsumerPosition( seekType, @@ -103,7 +98,7 @@ public class MessagesController extends AbstractController implements MessagesAp ResponseEntity.ok( messagesService.loadMessages( getCluster(clusterName), topicName, positions, q, filterQueryType, - recordsLimit, seekDirection, keySerde, valueSerde) + limit, seekDirection, keySerde, valueSerde) ) ); diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/MessagesService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/MessagesService.java index 4f9f0f59f4..f6ad42c110 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/MessagesService.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/MessagesService.java @@ -1,6 +1,7 @@ package com.provectus.kafka.ui.service; import com.google.common.util.concurrent.RateLimiter; +import com.provectus.kafka.ui.config.ClustersProperties; import com.provectus.kafka.ui.emitter.BackwardRecordEmitter; import com.provectus.kafka.ui.emitter.ForwardRecordEmitter; import com.provectus.kafka.ui.emitter.MessageFilters; @@ -20,13 +21,13 @@ import com.provectus.kafka.ui.serdes.ProducerRecordCreator; import com.provectus.kafka.ui.util.SslPropertiesUtil; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import javax.annotation.Nullable; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.admin.OffsetSpec; @@ -44,16 +45,35 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @Service -@RequiredArgsConstructor @Slf4j public class MessagesService { + private static final int DEFAULT_MAX_PAGE_SIZE = 500; + private static final int DEFAULT_PAGE_SIZE = 100; // limiting UI messages rate to 20/sec in tailing mode - public static final int TAILING_UI_MESSAGE_THROTTLE_RATE = 20; + private static final int TAILING_UI_MESSAGE_THROTTLE_RATE = 20; private final AdminClientService adminClientService; private final DeserializationService deserializationService; private final ConsumerGroupService consumerGroupService; + private final int maxPageSize; + private final int defaultPageSize; + + public MessagesService(AdminClientService adminClientService, + DeserializationService deserializationService, + ConsumerGroupService consumerGroupService, + ClustersProperties properties) { + this.adminClientService = adminClientService; + this.deserializationService = deserializationService; + this.consumerGroupService = consumerGroupService; + + var pollingProps = Optional.ofNullable(properties.getPolling()) + .orElseGet(ClustersProperties.PollingProperties::new); + this.maxPageSize = Optional.ofNullable(pollingProps.getMaxPageSize()) + .orElse(DEFAULT_MAX_PAGE_SIZE); + this.defaultPageSize = Optional.ofNullable(pollingProps.getDefaultPageSize()) + .orElse(DEFAULT_PAGE_SIZE); + } private Mono withExistingTopic(KafkaCluster cluster, String topicName) { return adminClientService.get(cluster) @@ -139,7 +159,7 @@ public class MessagesService { ConsumerPosition consumerPosition, @Nullable String query, MessageFilterTypeDTO filterQueryType, - int limit, + @Nullable Integer pageSize, SeekDirectionDTO seekDirection, @Nullable String keySerde, @Nullable String valueSerde) { @@ -147,7 +167,13 @@ public class MessagesService { .flux() .publishOn(Schedulers.boundedElastic()) .flatMap(td -> loadMessagesImpl(cluster, topic, consumerPosition, query, - filterQueryType, limit, seekDirection, keySerde, valueSerde)); + filterQueryType, fixPageSize(pageSize), seekDirection, keySerde, valueSerde)); + } + + private int fixPageSize(@Nullable Integer pageSize) { + return Optional.ofNullable(pageSize) + .filter(ps -> ps > 0 && ps <= maxPageSize) + .orElse(defaultPageSize); } private Flux loadMessagesImpl(KafkaCluster cluster, 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 0d54fa7e79..4bd3d2207c 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 @@ -3486,6 +3486,10 @@ components: type: integer noDataEmptyPolls: type: integer + maxPageSize: + type: integer + defaultPageSize: + type: integer adminClientTimeout: type: integer internalTopicPrefix: From 0e1f4ddfcf83d27a3caedc37fb2f4a5e110c36bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 12:01:29 +0000 Subject: [PATCH 17/42] Bump mockito.version from 5.1.1 to 5.3.0 (#3694) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 42ebd9addc..b5fb102355 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ 5.9.1 - 5.1.1 + 5.3.0 4.10.0 1.17.5 From a33e7064ee3231dc729f5cb1d5a299626088202d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 12:28:14 +0000 Subject: [PATCH 18/42] Bump confluent.version from 7.3.0 to 7.3.3 (#3641) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b5fb102355..beb5744e81 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ 3.19.0 1.11.1 1.12.19 - 7.3.0 + 7.3.3 3.1.0 3.0.13 2.14.0 From aed6c16496ddc1dbc83daceaf3c0efc296083a23 Mon Sep 17 00:00:00 2001 From: David Bejanyan <58771979+David-DB88@users.noreply.github.com> Date: Fri, 21 Apr 2023 17:58:11 +0400 Subject: [PATCH 19/42] FE: Chore: Fix TSC error on submit callback type (#3699) --- .../Topics/Topic/Messages/Filters/AddEditFilterContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/AddEditFilterContainer.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/AddEditFilterContainer.tsx index 757b6e171d..557db159ba 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/AddEditFilterContainer.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/AddEditFilterContainer.tsx @@ -27,7 +27,7 @@ export interface AddEditFilterContainerProps { inputDisplayNameDefaultValue?: string; inputCodeDefaultValue?: string; isAdd?: boolean; - submitCallback?: (values: AddMessageFilters) => Promise; + submitCallback?: (values: AddMessageFilters) => void; } const AddEditFilterContainer: React.FC = ({ From 8ecb719e9b762a4bc132997dd660b8519c185ef6 Mon Sep 17 00:00:00 2001 From: Ilya Kuramshin Date: Fri, 21 Apr 2023 21:39:30 +0400 Subject: [PATCH 20/42] 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 21/42] 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 22/42] 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 23/42] 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 24/42] 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 25/42] 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 26/42] 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 27/42] 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 }) => { /> )} /> - +