diff --git a/kafka-ui-api/pom.xml b/kafka-ui-api/pom.xml index a16cb80345..bd1447b1ca 100644 --- a/kafka-ui-api/pom.xml +++ b/kafka-ui-api/pom.xml @@ -38,8 +38,8 @@ spring-boot-starter-security - org.springframework.security - spring-security-oauth2-client + org.springframework.boot + spring-boot-starter-oauth2-client com.provectus diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/ClusterUtil.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/ClusterUtil.java index 7abd4b64dd..889079eaef 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/ClusterUtil.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/ClusterUtil.java @@ -139,7 +139,9 @@ public class ClusterUtil { public static InternalTopic mapToInternalTopic(TopicDescription topicDescription) { var topic = InternalTopic.builder(); - topic.internal(topicDescription.isInternal()); + topic.internal( + topicDescription.isInternal() || topicDescription.name().startsWith("_") + ); topic.name(topicDescription.name()); List partitions = topicDescription.partitions().stream().map( diff --git a/kafka-ui-api/src/main/resources/application.yml b/kafka-ui-api/src/main/resources/application.yml index 0bb3c197c5..21dc097584 100644 --- a/kafka-ui-api/src/main/resources/application.yml +++ b/kafka-ui-api/src/main/resources/application.yml @@ -1,9 +1,4 @@ kafka: - clusters: - - name: local - bootstrapServers: localhost:9092 - zookeeper: localhost:2181 - schemaRegistry: http://localhost:8081 admin-client-timeout: 5000 zookeeper: connection-timeout: 1000 diff --git a/kafka-ui-e2e-checks/.env.example b/kafka-ui-e2e-checks/.env.example new file mode 100644 index 0000000000..de43f7b2dc --- /dev/null +++ b/kafka-ui-e2e-checks/.env.example @@ -0,0 +1,3 @@ +USE_LOCAL_BROWSER=true +SHOULD_START_SELENOID=false +TURN_OFF_SCREENSHOTS=true diff --git a/kafka-ui-e2e-checks/.gitignore b/kafka-ui-e2e-checks/.gitignore new file mode 100644 index 0000000000..e1f37b83c8 --- /dev/null +++ b/kafka-ui-e2e-checks/.gitignore @@ -0,0 +1,6 @@ +.env +build/ +allure-results/ +selenoid/video/ +target/ +selenoid/logs/ diff --git a/kafka-ui-e2e-checks/README.md b/kafka-ui-e2e-checks/README.md new file mode 100644 index 0000000000..25be125c30 --- /dev/null +++ b/kafka-ui-e2e-checks/README.md @@ -0,0 +1,117 @@ +### E2E UI automation for Kafka-ui + +This repository is for E2E UI automation. + +### Table of Contents + +- [Prerequisites](#prerequisites) +- [How to install](#how-to-install) +- [Environment variables](#environment-variables) +- [How to run checks](#how-to-run-checks) +- [Reporting](#reporting) +- [Environments setup](#environments-setup) +- [Test Data](#test-data) +- [Actions](#actions) +- [Checks](#checks) +- [Parallelization](#parallelization) +- [How to develop](#how-to-develop) + +### Prerequisites +- Docker & Docker-compose +- Java +- Maven + +### How to install +``` +git clone https://github.com/provectus/kafka-ui.git +cd kafka-ui-e2e-checks +docker pull selenoid/vnc:chrome_86.0 +``` +### Environment variables + +|Name | Default | Description +|---------------------------------------|-------------|--------------------- +|`USE_LOCAL_BROWSER` | `true` | clear reports dir on startup +|`CLEAR_REPORTS_DIR` | `true` | clear reports dir on startup +|`SHOULD_START_SELENOID` | `false` | starts selenoid container on startup +|`SELENOID_URL` | `http://localhost:4444/wd/hub` | URL of remote selenoid instance +|`BASE_URL` | `http://192.168.1.2:8080/` | base url for selenide configuration +|`PIXELS_THRESHOLD` | `200` | Amount of pixels, that should be different to fail screenshot check +|`SCREENSHOTS_FOLDER` | `screenshots/` | folder for keeping reference screenshots +|`DIFF_SCREENSHOTS_FOLDER` | `build/__diff__/` | folder for keeping screenshots diffs +|`ACTUAL_SCREENSHOTS_FOLDER` | `build/__actual__/` | folder for keeping actual screenshots(during checks) +|`SHOULD_SAVE_SCREENSHOTS_IF_NOT_EXIST` | `true` | folder for keeping actual screenshots(during checks) +|`TURN_OFF_SCREENSHOTS` | `false` | If true, `compareScreenshots` will not fail on different screenshots. Useful for functional debugging on local machine, while preserving golden screenshots made in selenoid + +### How to run checks + +1. Run `kafka-ui` +``` +cd docker +docker-compose -f kafka-ui.yaml up -d +``` +2. Run `selenoid-ui` +``` +cd kafka-ui-e2e-checks/docker +docker-compose -f selenoid.yaml up -d +``` +3. Run checks +``` +cd kafka-ui-e2e-checks +mvn test +``` + +* There are several ways to run checks + +1. If you don't have selenoid run on your machine +``` + mvn test -DSHOULD_START_SELENOID=true +``` +⚠️ If you want to run checks in IDE with this approach, you'd need to set up +environment variable(`SHOULD_START_SELENOID=true`) in `Run/Edit Configurations..` + +2. For development purposes it is better to just start separate selenoid in docker-compose +Do it in separate window +``` +cd docker +docker-compose -f selenoid.yaml up +``` +Then you can just `mvn test`. By default, `SELENOID_URL` will resolve to `http://localhost:4444/wd/hub` + +It's preferred way to run. + +* If you have remote selenoid instance, set + +`SELENOID_URL` environment variable + +Example: +`mvn test -DSELENOID_URL=http://localhost:4444/wd/hub` +That's the way to run tests in CI with selenoid set up somewhere in cloud + +### Reporting + +Reports are in `allure-results` folder. +If you have installed allure commandline(e.g. like [here](https://docs.qameta.io/allure/#_installing_a_commandline) or [here](https://www.npmjs.com/package/allure-commandline)) +You can see allure report with command: +``` +allure serve +``` +### Screenshots + +Reference screenshots are in `SCREENSHOTS_FOLDER` (default,`kafka-ui-e2e-checks/screenshots`) + +### How to develop +> ⚠️ todo +### Setting for different environments +> ⚠️ todo +### Test Data +> ⚠️ todo +### Actions +> ⚠️ todo +### Checks +> ⚠️ todo +### Parallelization +> ⚠️ todo +### Tips + - install `Selenium UI Testing plugin` in IDEA + diff --git a/kafka-ui-e2e-checks/docker/selenoid.yaml b/kafka-ui-e2e-checks/docker/selenoid.yaml new file mode 100644 index 0000000000..5e9bc170b2 --- /dev/null +++ b/kafka-ui-e2e-checks/docker/selenoid.yaml @@ -0,0 +1,25 @@ +version: '3' + +services: + selenoid: + network_mode: bridge + image: aerokube/selenoid:1.10.3 + volumes: + - "../selenoid/config:/etc/selenoid" + - "/var/run/docker.sock:/var/run/docker.sock" + - "../selenoid/video:/video" + - "../selenoid/logs:/opt/selenoid/logs" + environment: + - OVERRIDE_VIDEO_OUTPUT_DIR=video + command: [ "-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs" ] + ports: + - "4444:4444" + + selenoid-ui: + network_mode: bridge + image: aerokube/selenoid-ui:latest-release + links: + - selenoid + ports: + - "8081:8080" + command: [ "--selenoid-uri", "http://selenoid:4444" ] diff --git a/kafka-ui-e2e-checks/pom.xml b/kafka-ui-e2e-checks/pom.xml new file mode 100644 index 0000000000..a26da3e456 --- /dev/null +++ b/kafka-ui-e2e-checks/pom.xml @@ -0,0 +1,180 @@ + + + + kafka-ui + com.provectus + 0.0.11-SNAPSHOT + + 4.0.0 + + kafka-ui-e2e-checks + + 5.7.0 + 1.9.6 + 2.13.7 + 1.15.2 + 5.16.2 + 3.17.1 + 1.0-rc7 + 2.2 + 1.7.29 + 1.15.1 + 2.13.6 + 2.2.0 + 1.6.2 + 2.6 + 1.5.4 + 2.13.9 + 2.22.2 + 2.10.0 + + + + + org.apache.kafka + kafka_2.13 + ${kafka.version} + + + org.testcontainers + testcontainers + ${testcontainers.version} + + + + io.qameta.allure + allure-junit5 + ${allure.version} + + + com.codeborne + selenide + ${selenide.version} + + + io.qameta.allure + allure-selenide + ${allure.version} + + + org.hamcrest + hamcrest + ${hamcrest.version} + + + org.assertj + assertj-core + ${assertj.version} + + + com.google.auto.service + auto-service + ${google.auto-service.version} + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + org.projectlombok + lombok + ${org.projectlombok.version} + + + org.aspectj + aspectjrt + ${aspectj.version} + + + + org.testcontainers + junit-jupiter + ${testcontainers.junit-jupiter.version} + + + io.qameta.allure + allure-java-commons + ${allure.java-commons.version} + + + io.github.cdimascio + dotenv-java + ${dotenv.version} + + + org.junit.platform + junit-platform-launcher + ${junit.platform-launcher.version} + + + ru.yandex.qatools.allure + allure-maven-plugin + ${allure.maven-plugin.version} + + + ru.yandex.qatools.ashot + ashot + ${ashot.version} + + + org.seleniumhq.selenium + selenium-remote-driver + + + + + io.qameta.allure.plugins + screen-diff-plugin + ${allure.screendiff-plugin.version} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire-plugin.version} + + + -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" + + + + + org.aspectj + aspectjweaver + ${aspectj.version} + + + + + + io.qameta.allure + allure-maven + ${allure-maven.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + diff --git a/kafka-ui-e2e-checks/screenshots/main.png b/kafka-ui-e2e-checks/screenshots/main.png new file mode 100644 index 0000000000..694cc5cb1d Binary files /dev/null and b/kafka-ui-e2e-checks/screenshots/main.png differ diff --git a/kafka-ui-e2e-checks/selenoid/config/browsers.json b/kafka-ui-e2e-checks/selenoid/config/browsers.json new file mode 100644 index 0000000000..2645f5980e --- /dev/null +++ b/kafka-ui-e2e-checks/selenoid/config/browsers.json @@ -0,0 +1,11 @@ +{ + "chrome": { + "default": "86.0", + "versions": { + "86.0": { + "image": "selenoid/vnc:chrome_86.0", + "port": "4444" + } + } + } +} diff --git a/kafka-ui-e2e-checks/src/main/resources/allure.properties b/kafka-ui-e2e-checks/src/main/resources/allure.properties new file mode 100644 index 0000000000..3238f97015 --- /dev/null +++ b/kafka-ui-e2e-checks/src/main/resources/allure.properties @@ -0,0 +1,2 @@ +allure.results.directory=allure-results +allure.link.issue.pattern=https://github.com/provectus/kafka-ui/issues/{} diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/SmokeTests.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/SmokeTests.java new file mode 100644 index 0000000000..7f0b2bbf9c --- /dev/null +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/SmokeTests.java @@ -0,0 +1,21 @@ +package com.provectus.kafka.ui; + +import com.provectus.kafka.ui.base.BaseTest; +import io.qameta.allure.Issue; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class SmokeTests extends BaseTest { + + @Disabled("till we get tests in ci run") + @SneakyThrows + @DisplayName("main page should load") + @Issue("380") + void mainPageLoads() { + pages.goTo("") + .mainPage.shouldBeOnPage(); + compareScreenshots("main"); + } +} diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/BaseTest.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/BaseTest.java new file mode 100644 index 0000000000..3315a2ffdc --- /dev/null +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/BaseTest.java @@ -0,0 +1,107 @@ +package com.provectus.kafka.ui.base; + +import com.codeborne.selenide.Configuration; +import com.codeborne.selenide.logevents.SelenideLogger; +import com.provectus.kafka.ui.pages.Pages; +import com.provectus.kafka.ui.screenshots.Screenshooter; +import io.github.cdimascio.dotenv.Dotenv; +import io.qameta.allure.selenide.AllureSelenide; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +import java.io.File; +import java.util.Arrays; + +import static com.codeborne.selenide.Selenide.closeWebDriver; + +@Slf4j +@DisplayNameGeneration(CamelCaseToSpacedDisplayNameGenerator.class) +public class BaseTest { + + protected Pages pages = Pages.INSTANCE; + + private Screenshooter screenshooter = new Screenshooter(); + + public void compareScreenshots(String name) { + screenshooter.compareScreenshots(name); + } + + public void compareScreenshots(String name, Boolean shouldUpdateScreenshots) { + screenshooter.compareScreenshots(name, shouldUpdateScreenshots); + } + + public static GenericContainer selenoid = + new GenericContainer(DockerImageName.parse("aerokube/selenoid:latest-release")) + .withExposedPorts(4444) + .withFileSystemBind("selenoid/config/", "/etc/selenoid", BindMode.READ_WRITE) + .withFileSystemBind("/var/run/docker.sock", "/var/run/docker.sock", BindMode.READ_WRITE) + .withFileSystemBind("selenoid/video", "/opt/selenoid/video", BindMode.READ_WRITE) + .withFileSystemBind("selenoid/logs", "/opt/selenoid/logs", BindMode.READ_WRITE) + .withEnv("OVERRIDE_VIDEO_OUTPUT_DIR", "/opt/selenoid/video") + .withCommand( + "-conf", "/etc/selenoid/browsers.json", "-log-output-dir", "/opt/selenoid/logs"); + + static { + if (new File("./.env").exists()) { + Dotenv.load().entries().forEach(env -> System.setProperty(env.getKey(), env.getValue())); + } + if (TestConfiguration.CLEAR_REPORTS_DIR) { + clearReports(); + } + setupSelenoid(); + } + + @AfterAll + public static void afterAll() { + closeWebDriver(); + selenoid.close(); + } + + @SneakyThrows + private static void setupSelenoid() { + String remote = TestConfiguration.SELENOID_URL; + if (TestConfiguration.SHOULD_START_SELENOID) { + selenoid.start(); + remote = + "http://%s:%s/wd/hub" + .formatted(selenoid.getContainerIpAddress(), selenoid.getMappedPort(4444)); + } + + Configuration.reportsFolder = TestConfiguration.REPORTS_FOLDER; + if (!TestConfiguration.USE_LOCAL_BROWSER) { + Configuration.remote = remote; + TestConfiguration.BASE_URL = + TestConfiguration.BASE_URL.replace("localhost", "host.docker.internal"); + } + Configuration.screenshots = TestConfiguration.SCREENSHOTS; + Configuration.savePageSource = TestConfiguration.SAVE_PAGE_SOURCE; + Configuration.reopenBrowserOnFail = TestConfiguration.REOPEN_BROWSER_ON_FAIL; + Configuration.browser = TestConfiguration.BROWSER; + Configuration.baseUrl = TestConfiguration.BASE_URL; + Configuration.browserSize = TestConfiguration.BROWSER_SIZE; + var capabilities = new DesiredCapabilities(); + capabilities.setCapability("enableVNC", TestConfiguration.ENABLE_VNC); + Configuration.browserCapabilities = capabilities; + + SelenideLogger.addListener("allure", new AllureSelenide().savePageSource(false)); + } + + public static void clearReports() { + log.info("Clearing reports dir [%s]...".formatted(TestConfiguration.REPORTS_FOLDER)); + File allureResults = new File(TestConfiguration.REPORTS_FOLDER); + if (allureResults.isDirectory()) { + File[] list = allureResults.listFiles(); + if (list != null) + Arrays.stream(list) + .sequential() + .filter(e -> !e.getName().equals("categories.json")) + .forEach(File::delete); + } + } +} diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/CamelCaseToSpacedDisplayNameGenerator.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/CamelCaseToSpacedDisplayNameGenerator.java new file mode 100644 index 0000000000..105f15e83a --- /dev/null +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/CamelCaseToSpacedDisplayNameGenerator.java @@ -0,0 +1,34 @@ +package com.provectus.kafka.ui.base; + +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.Preconditions; + +import java.lang.reflect.Method; + +public class CamelCaseToSpacedDisplayNameGenerator implements DisplayNameGenerator { + @Override + public String generateDisplayNameForClass(Class testClass) { + String name = testClass.getName(); + int lastDot = name.lastIndexOf('.'); + return name.substring(lastDot + 1).replaceAll("([A-Z])", " $1").toLowerCase(); + } + + @Override + public String generateDisplayNameForNestedClass(Class nestedClass) { + return nestedClass.getSimpleName(); + } + + @Override + public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + return testMethod.getName().replaceAll("([A-Z])", " $1").toLowerCase() + + parameterTypesAsString(testMethod); + } + + static String parameterTypesAsString(Method method) { + Preconditions.notNull(method, "Method must not be null"); + return method.getParameterTypes().length == 0 + ? "" + : '(' + ClassUtils.nullSafeToString(Class::getSimpleName, method.getParameterTypes()) + ')'; + } +} diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/TestConfiguration.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/TestConfiguration.java new file mode 100644 index 0000000000..09c85db1c0 --- /dev/null +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/base/TestConfiguration.java @@ -0,0 +1,27 @@ +package com.provectus.kafka.ui.base; + +public class TestConfiguration { + public static boolean CLEAR_REPORTS_DIR = + Boolean.parseBoolean(System.getProperty("CLEAR_REPORTS_DIR", "true")); + + public static boolean SHOULD_START_SELENOID = + Boolean.parseBoolean(System.getProperty("SHOULD_START_SELENOID", "false")); + + public static String BASE_URL = System.getProperty("BASE_URL", "http://localhost:8080/"); + + public static boolean USE_LOCAL_BROWSER = + Boolean.parseBoolean(System.getProperty("USE_LOCAL_BROWSER", "true")); + + public static String SELENOID_URL = + System.getProperty("SELENOID_URL", "http://localhost:4444/wd/hub"); + public static String REPORTS_FOLDER = System.getProperty("REPORTS_FOLDER", "allure-results"); + public static Boolean SCREENSHOTS = + Boolean.parseBoolean(System.getProperty("SCREENSHOTS", "false")); + public static Boolean SAVE_PAGE_SOURCE = + Boolean.parseBoolean(System.getProperty("SAVE_PAGE_SOURCE", "false")); + public static Boolean REOPEN_BROWSER_ON_FAIL = + Boolean.parseBoolean(System.getProperty("REOPEN_BROWSER_ON_FAIL", "true")); + public static String BROWSER = System.getProperty("BROWSER", "chrome"); + public static String BROWSER_SIZE = System.getProperty("BROWSER_SIZE", "1920x1080"); + public static Boolean ENABLE_VNC = Boolean.parseBoolean(System.getProperty("ENABLE_VNC", "true")); +} diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/pages/MainPage.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/pages/MainPage.java new file mode 100644 index 0000000000..0e02934a2f --- /dev/null +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/pages/MainPage.java @@ -0,0 +1,16 @@ +package com.provectus.kafka.ui.pages; + +import com.codeborne.selenide.Condition; +import io.qameta.allure.Step; +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Selenide.$; + +public class MainPage { + + @Step + public void shouldBeOnPage(){ + $(By.xpath("//*[contains(text(),'Loading')]")).shouldBe(Condition.disappear); + $(By.xpath("//h5[text()='Clusters']")).shouldBe(Condition.visible); + } +} diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/pages/Pages.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/pages/Pages.java new file mode 100644 index 0000000000..eabad8d721 --- /dev/null +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/pages/Pages.java @@ -0,0 +1,16 @@ +package com.provectus.kafka.ui.pages; + +import com.codeborne.selenide.Selenide; +import com.provectus.kafka.ui.base.TestConfiguration; + +public class Pages { + + public static Pages INSTANCE = new Pages(); + + public MainPage mainPage = new MainPage(); + + public Pages goTo(String path) { + Selenide.open(TestConfiguration.BASE_URL+path); + return this; + } +} diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/screenshots/NoReferenceScreenshotFoundException.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/screenshots/NoReferenceScreenshotFoundException.java new file mode 100644 index 0000000000..1c2df03bc3 --- /dev/null +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/screenshots/NoReferenceScreenshotFoundException.java @@ -0,0 +1,7 @@ +package com.provectus.kafka.ui.screenshots; + +public class NoReferenceScreenshotFoundException extends Throwable { + public NoReferenceScreenshotFoundException(String name) { + super(("no reference screenshot found for %s".formatted(name))); + } +} diff --git a/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/screenshots/Screenshooter.java b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/screenshots/Screenshooter.java new file mode 100644 index 0000000000..4b328e0265 --- /dev/null +++ b/kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/screenshots/Screenshooter.java @@ -0,0 +1,150 @@ +package com.provectus.kafka.ui.screenshots; + +import io.qameta.allure.Allure; +import io.qameta.allure.Attachment; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.yandex.qatools.ashot.AShot; +import ru.yandex.qatools.ashot.Screenshot; +import ru.yandex.qatools.ashot.comparison.ImageDiff; +import ru.yandex.qatools.ashot.comparison.ImageDiffer; +import ru.yandex.qatools.ashot.coordinates.WebDriverCoordsProvider; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.nio.file.FileSystems; +import java.util.List; + +import static com.codeborne.selenide.WebDriverRunner.getWebDriver; +import static org.junit.jupiter.api.Assertions.fail; + +public class Screenshooter { + + public static Logger log = LoggerFactory.getLogger(Screenshooter.class); + + private static int PIXELS_THRESHOLD = + Integer.parseInt(System.getProperty("PIXELS_THRESHOLD", "200")); + private static String SCREENSHOTS_FOLDER = + System.getProperty("SCREENSHOTS_FOLDER", "screenshots/"); + private static String DIFF_SCREENSHOTS_FOLDER = + System.getProperty("DIFF_SCREENSHOTS_FOLDER", "build/__diff__/"); + private static String ACTUAL_SCREENSHOTS_FOLDER = + System.getProperty("ACTUAL_SCREENSHOTS_FOLDER", "build/__actual__/"); + private static boolean SHOULD_SAVE_SCREENSHOTS_IF_NOT_EXIST = + Boolean.parseBoolean(System.getProperty("SHOULD_SAVE_SCREENSHOTS_IF_NOT_EXIST", "true")); + private static boolean TURN_OFF_SCREENSHOTS = + Boolean.parseBoolean(System.getProperty("TURN_OFF_SCREENSHOTS", "false")); + + private File newFile(String name) { + var file = new File(name); + if (!file.exists()) { + file.mkdirs(); + } + return file; + } + + public Screenshooter() { + List.of(SCREENSHOTS_FOLDER, DIFF_SCREENSHOTS_FOLDER, ACTUAL_SCREENSHOTS_FOLDER) + .forEach(this::newFile); + } + + public void compareScreenshots(String name) { + compareScreenshots(name, false); + } + + @SneakyThrows + public void compareScreenshots(String name, boolean shouldUpdateScreenshotIfDiffer) { + if (TURN_OFF_SCREENSHOTS) { + return; + } + if (!doesScreenshotExist(name)) { + if (SHOULD_SAVE_SCREENSHOTS_IF_NOT_EXIST) { + updateActualScreenshot(name); + } else { + throw new NoReferenceScreenshotFoundException(name); + } + } else { + makeImageDiff(name, shouldUpdateScreenshotIfDiffer); + } + } + + @SneakyThrows + private void updateActualScreenshot(String name) { + Screenshot actual = + new AShot().coordsProvider(new WebDriverCoordsProvider()).takeScreenshot(getWebDriver()); + File file= newFile(SCREENSHOTS_FOLDER + name + ".png"); + ImageIO.write(actual.getImage(), "png", file); + log.debug("created screenshot: %s \n at $s".formatted(name,file.getAbsolutePath())); + } + + private static boolean doesScreenshotExist(String name) { + return new File(SCREENSHOTS_FOLDER + name + ".png").exists(); + } + + @SneakyThrows + private void makeImageDiff(String expectedName, boolean shouldUpdateScreenshotIfDiffer) { + String fullPathNameExpected = SCREENSHOTS_FOLDER + expectedName + ".png"; + String fullPathNameActual = ACTUAL_SCREENSHOTS_FOLDER + expectedName + ".png"; + String fullPathNameDiff = DIFF_SCREENSHOTS_FOLDER + expectedName + ".png"; + + // activating allure plugin for showing diffs in report + Allure.label("testType", "screenshotDiff"); + + Screenshot actual = + new AShot().coordsProvider(new WebDriverCoordsProvider()).takeScreenshot(getWebDriver()); + ImageIO.write(actual.getImage(), "png", newFile(fullPathNameActual)); + + Screenshot expected = new Screenshot(ImageIO.read(newFile(fullPathNameExpected))); + ImageDiff diff = new ImageDiffer().makeDiff(actual, expected); + BufferedImage diffImage = diff.getMarkedImage(); + ImageIO.write(diffImage, "png", newFile(fullPathNameDiff)); + // adding to report + diff(fullPathNameDiff); + // adding to report + actual(fullPathNameActual); + // adding to report + expected(fullPathNameExpected); + + if (shouldUpdateScreenshotIfDiffer) { + if (diff.getDiffSize() > PIXELS_THRESHOLD) { + updateActualScreenshot(expectedName); + } + } else { + Assertions.assertTrue( + PIXELS_THRESHOLD >= diff.getDiffSize(), + ("Amount of differing pixels should be less or equals than %s, actual %s\n"+ + "diff file: %s") + .formatted(PIXELS_THRESHOLD, diff.getDiffSize(), FileSystems.getDefault().getPath(fullPathNameDiff).normalize().toAbsolutePath().toString())); + } + } + + @SneakyThrows + private byte[] imgToBytes(String filename) { + BufferedImage bImage2 = ImageIO.read(new File(filename)); + var bos2 = new ByteArrayOutputStream(); + ImageIO.write(bImage2, "png", bos2); + return bos2.toByteArray(); + } + + @SneakyThrows + @Attachment + private byte[] actual(String actualFileName) { + return imgToBytes(actualFileName); + } + + @SneakyThrows + @Attachment + private byte[] expected(String expectedFileName) { + return imgToBytes(expectedFileName); + } + + @SneakyThrows + @Attachment + private byte[] diff(String diffFileName) { + return imgToBytes(diffFileName); + } +} diff --git a/kafka-ui-react-app/package-lock.json b/kafka-ui-react-app/package-lock.json index ed450f5f23..9e5971de3c 100644 --- a/kafka-ui-react-app/package-lock.json +++ b/kafka-ui-react-app/package-lock.json @@ -60,7 +60,7 @@ "@typescript-eslint/eslint-plugin": "^4.20.0", "@typescript-eslint/parser": "^4.20.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.0", - "dotenv": "^8.2.0", + "dotenv": "^9.0.1", "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.1", "eslint": "^7.22.0", @@ -76,7 +76,7 @@ "fetch-mock-jest": "^1.5.1", "husky": "^6.0.0", "jest-sonar-reporter": "^2.0.0", - "lint-staged": "^10.5.4", + "lint-staged": "^11.0.0", "node-sass": "^5.0.0", "prettier": "^2.2.1", "react-scripts": "4.0.3", @@ -1566,9 +1566,9 @@ "dev": true }, "node_modules/@eslint/eslintrc": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", - "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", + "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -1637,6 +1637,7 @@ "version": "5.15.3", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz", "integrity": "sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w==", + "hasInstallScript": true, "engines": { "node": ">=6" } @@ -2307,6 +2308,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-7.6.15.tgz", "integrity": "sha512-8CrL/iY5Gt4HJfyDg1PgPalhT7tVRT643f2mGMgPum/P/e94uuwEYBNIgsMEVOJUrOAWZkNIN60uEf8JkH6GWw==", "dev": true, + "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.0.7", @@ -2408,15 +2410,16 @@ } }, "node_modules/@openapitools/openapi-generator-cli": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.2.5.tgz", - "integrity": "sha512-8eTiw9U5PWYZx41RDIrJ6iZ4XCteD6ljupbJS6/ichZEUwa1Pv78y14QSLVfky+1KpJsq/RlqrgjnPm6yMHo6g==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.2.6.tgz", + "integrity": "sha512-TFKHY1lcknzg6IhPe9f8wWUO11PeKBPnrM40jZjZNUHlQRvh7WLsW6vNlbGcm78eBYTcyY0cKqr1QXUpB9Ez2Q==", "dev": true, + "hasInstallScript": true, "dependencies": { "@nestjs/common": "7.6.15", "@nestjs/core": "7.6.15", "@nuxtjs/opencollective": "0.3.2", - "chalk": "4.1.0", + "chalk": "4.1.1", "commander": "6.2.1", "compare-versions": "3.6.0", "concurrently": "5.3.0", @@ -2436,6 +2439,19 @@ "node": ">=10.0.0" } }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.3.tgz", @@ -2509,9 +2525,9 @@ "dev": true }, "node_modules/@rooks/use-outside-click-ref": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rooks/use-outside-click-ref/-/use-outside-click-ref-4.11.0.tgz", - "integrity": "sha512-GhqUO3u4I8APlEa+e0s+fIPtMV1C00K1voTnALjrLs8VIdeemJKvGPecm2RV+uwvsUbs/Lj1z6u75B4dgPl0nw==" + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@rooks/use-outside-click-ref/-/use-outside-click-ref-4.11.2.tgz", + "integrity": "sha512-w2bCW69zcpLh0KmN/odAuBsQ3sps+73KEu7zMOi0o4YMfDo+tXcqwlTJiLYysd0BEoQC9pNIklzZmI9zZep69g==" }, "node_modules/@sinonjs/commons": { "version": "1.8.2", @@ -2716,9 +2732,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "5.11.10", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.10.tgz", - "integrity": "sha512-FuKiq5xuk44Fqm0000Z9w0hjOdwZRNzgx7xGGxQYepWFZy+OYUMOT/wPI4nLYXCaVltNVpU1W/qmD88wLWDsqQ==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.12.0.tgz", + "integrity": "sha512-N9Y82b2Z3j6wzIoAqajlKVF1Zt7sOH0pPee0sUHXHc5cv2Fdn23r+vpWm0MBBoGJtPOly5+Bdx1lnc3CD+A+ow==", "dev": true, "dependencies": { "@babel/runtime": "^7.9.2", @@ -2806,10 +2822,13 @@ } }, "node_modules/@types/classnames": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz", - "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==", - "dev": true + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==", + "dev": true, + "dependencies": { + "classnames": "*" + } }, "node_modules/@types/connect": { "version": "3.4.34", @@ -2922,9 +2941,9 @@ } }, "node_modules/@types/jest": { - "version": "26.0.22", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz", - "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==", + "version": "26.0.23", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz", + "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==", "dev": true, "dependencies": { "jest-diff": "^26.0.0", @@ -3011,9 +3030,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.3.tgz", - "integrity": "sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.5.tgz", + "integrity": "sha512-bj4biDB9ZJmGAYTWSKJly6bMr4BLUiBrx9ujiJEoP9XIDY9CTaPGxE5QWN/1WjpPLzYF7/jRNnV2nNxNe970sw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3196,13 +3215,13 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz", - "integrity": "sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.0.tgz", + "integrity": "sha512-U8SP9VOs275iDXaL08Ln1Fa/wLXfj5aTr/1c0t0j6CdbOnxh+TruXu1p4I0NAvdPBQgoPjHsgKn28mOi0FzfoA==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "4.21.0", - "@typescript-eslint/scope-manager": "4.21.0", + "@typescript-eslint/experimental-utils": "4.22.0", + "@typescript-eslint/scope-manager": "4.22.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", @@ -3214,6 +3233,76 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/experimental-utils": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.22.0.tgz", + "integrity": "sha512-xJXHHl6TuAxB5AWiVrGhvbGL8/hbiCQ8FiWwObO3r0fnvBdrbWEDy1hlvGQOAWc6qsCWuWMKdVWlLAEMpxnddg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.22.0", + "@typescript-eslint/types": "4.22.0", + "@typescript-eslint/typescript-estree": "4.22.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.22.0.tgz", + "integrity": "sha512-OcCO7LTdk6ukawUM40wo61WdeoA7NM/zaoq1/2cs13M7GyiF+T4rxuA4xM+6LeHWjWbss7hkGXjFDRcKD4O04Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.22.0", + "@typescript-eslint/visitor-keys": "4.22.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.22.0.tgz", + "integrity": "sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.0.tgz", + "integrity": "sha512-TkIFeu5JEeSs5ze/4NID+PIcVjgoU3cUQUIZnH3Sb1cEn1lBo7StSV5bwPuJQuoxKXlzAObjYTilOEKRuhR5yg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.22.0", + "@typescript-eslint/visitor-keys": "4.22.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.0.tgz", + "integrity": "sha512-nnMu4F+s4o0sll6cBSsTeVsT4cwxB7zECK3dFxzEjPBii9xLpq4yqqsy/FU5zMfan6G60DKZSCXAa3sHJZrcYw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.22.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + } + }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -3265,20 +3354,73 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.21.0.tgz", - "integrity": "sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.22.1.tgz", + "integrity": "sha512-l+sUJFInWhuMxA6rtirzjooh8cM/AATAe3amvIkqKFeMzkn85V+eLzb1RyuXkHak4dLfYzOmF6DXPyflJvjQnw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "4.21.0", - "@typescript-eslint/types": "4.21.0", - "@typescript-eslint/typescript-estree": "4.21.0", + "@typescript-eslint/scope-manager": "4.22.1", + "@typescript-eslint/types": "4.22.1", + "@typescript-eslint/typescript-estree": "4.22.1", "debug": "^4.1.1" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.22.1.tgz", + "integrity": "sha512-d5bAiPBiessSmNi8Amq/RuLslvcumxLmyhf1/Xa9IuaoFJ0YtshlJKxhlbY7l2JdEk3wS0EnmnfeJWSvADOe0g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.22.1", + "@typescript-eslint/visitor-keys": "4.22.1" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.22.1.tgz", + "integrity": "sha512-2HTkbkdAeI3OOcWbqA8hWf/7z9c6gkmnWNGz0dKSLYLWywUlkOAQ2XcjhlKLj5xBFDf8FgAOF5aQbnLRvgNbCw==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.1.tgz", + "integrity": "sha512-p3We0pAPacT+onSGM+sPR+M9CblVqdA9F1JEdIqRVlxK5Qth4ochXQgIyb9daBomyQKAXbygxp1aXQRV0GC79A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.22.1", + "@typescript-eslint/visitor-keys": "4.22.1", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.1.tgz", + "integrity": "sha512-WPkOrIRm+WCLZxXQHCi+WG8T2MMTUFR70rWjdWYddLT7cEfb2P4a3O/J2U1FBVsSFTocXLCoXWY6MZGejeStvQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.22.1", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + } + }, "node_modules/@typescript-eslint/parser/node_modules/debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -3297,6 +3439,21 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz", @@ -4865,7 +5022,8 @@ "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true + "dev": true, + "hasInstallScript": true }, "node_modules/babel-runtime/node_modules/regenerator-runtime": { "version": "0.11.1", @@ -6326,7 +6484,8 @@ "version": "3.10.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.10.0.tgz", "integrity": "sha512-MQx/7TLgmmDVamSyfE+O+5BHvG1aUGj/gHhLn1wVtm2B5u1eVIPvh7vkfjwWKNCjrTJB8+He99IntSQ1qP+vYQ==", - "dev": true + "dev": true, + "hasInstallScript": true }, "node_modules/core-js-compat": { "version": "3.10.0", @@ -6351,7 +6510,8 @@ "version": "3.10.0", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.10.0.tgz", "integrity": "sha512-CC582enhrFZStO4F8lGI7QL3SYx7/AIRc+IdSi3btrQGrVsTawo5K/crmKbRrQ+MOMhNX4v+PATn0k2NN6wI7A==", - "dev": true + "dev": true, + "hasInstallScript": true }, "node_modules/core-util-is": { "version": "1.0.2", @@ -6889,9 +7049,9 @@ "dev": true }, "node_modules/csstype": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", - "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" }, "node_modules/currently-unhandled": { "version": "0.4.1", @@ -6989,9 +7149,9 @@ } }, "node_modules/date-fns": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.20.1.tgz", - "integrity": "sha512-8P5M8Kxbnovd0zfvOs7ipkiVJ3/zZQ0F/nrBW4x5E+I0uAZVZ80h6CKd24fSXQ5TLK5hXMtI4yb2O5rEZdUt2A==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.21.3.tgz", + "integrity": "sha512-HeYdzCaFflc1i4tGbj7JKMjM4cKGYoyxwcIIkHzNgCkX8xXDNJDZXgDDVchIWpN4eQc3lH37WarduXFZJOtxfw==", "engines": { "node": ">=0.11" } @@ -7517,12 +7677,12 @@ } }, "node_modules/dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.1.tgz", + "integrity": "sha512-W8FNeNnnvJoYfgkFRKzp8kTgz0T2YY4TJ9xy1Ma0hSebPTK8iquRtpG12TUrSTX5zIN9D/wSLEEuI+Ad35tlyw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/dotenv-expand": { @@ -7579,6 +7739,7 @@ "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", "dev": true, + "hasInstallScript": true, "engines": { "node": ">=0.10.0" } @@ -7989,13 +8150,13 @@ } }, "node_modules/eslint": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.24.0.tgz", - "integrity": "sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.26.0.tgz", + "integrity": "sha512-4R1ieRf52/izcZE7AlLy56uIHHDLT74Yzz2Iv2l6kDaYvEu9x+wMB5dZArVL8SYGXSYV2YAg70FcW5Y5nGGNIg==", "dev": true, "dependencies": { "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.0", + "@eslint/eslintrc": "^0.4.1", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -8079,9 +8240,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", - "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -8249,9 +8410,9 @@ "dev": true }, "node_modules/eslint-plugin-prettier": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz", - "integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0" @@ -13822,22 +13983,22 @@ "dev": true }, "node_modules/lint-staged": { - "version": "10.5.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz", - "integrity": "sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-11.0.0.tgz", + "integrity": "sha512-3rsRIoyaE8IphSUtO1RVTFl1e0SLBtxxUOPBtHxQgBHS5/i6nqvjcUfNioMa4BU9yGnPzbO+xkfLtXtxBpCzjw==", "dev": true, "dependencies": { - "chalk": "^4.1.0", + "chalk": "^4.1.1", "cli-truncate": "^2.1.0", - "commander": "^6.2.0", + "commander": "^7.2.0", "cosmiconfig": "^7.0.0", - "debug": "^4.2.0", + "debug": "^4.3.1", "dedent": "^0.7.0", "enquirer": "^2.3.6", - "execa": "^4.1.0", - "listr2": "^3.2.2", - "log-symbols": "^4.0.0", - "micromatch": "^4.0.2", + "execa": "^5.0.0", + "listr2": "^3.8.2", + "log-symbols": "^4.1.0", + "micromatch": "^4.0.4", "normalize-path": "^3.0.0", "please-upgrade-node": "^3.2.0", "string-argv": "0.3.1", @@ -13847,6 +14008,28 @@ "lint-staged": "bin/lint-staged.js" } }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/lint-staged/node_modules/debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -13860,19 +14043,19 @@ } }, "node_modules/lint-staged/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" }, "engines": { @@ -13880,15 +14063,21 @@ } }, "node_modules/lint-staged/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" } }, "node_modules/lint-staged/node_modules/is-stream": { @@ -13900,6 +14089,19 @@ "node": ">=8" } }, + "node_modules/lint-staged/node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/lint-staged/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -13927,13 +14129,22 @@ "node": ">=8" } }, + "node_modules/lint-staged/node_modules/picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true, + "engines": { + "node": ">=8.6" + } + }, "node_modules/listr2": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.6.2.tgz", - "integrity": "sha512-B2vlu7Zx/2OAMVUovJ7Tv1kQ2v2oXd0nZKzkSAcRCej269d8gkS/gupDEdNl23KQ3ZjVD8hQmifrrBFbx8F9LA==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.8.2.tgz", + "integrity": "sha512-E28Fw7Zd3HQlCJKzb9a8C8M0HtFWQeucE+S8YrSrqZObuCLPRHMRrR8gNmYt65cU9orXYHwvN5agXC36lYt7VQ==", "dev": true, "dependencies": { - "chalk": "^4.1.0", + "chalk": "^4.1.1", "cli-truncate": "^2.1.0", "figures": "^3.2.0", "indent-string": "^4.0.0", @@ -13956,6 +14167,19 @@ "node": ">=8" } }, + "node_modules/listr2/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/listr2/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -14132,12 +14356,6 @@ "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", "dev": true }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -15300,6 +15518,7 @@ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-5.0.0.tgz", "integrity": "sha512-opNgmlu83ZCF792U281Ry7tak9IbVC+AKnXGovcQ8LG8wFaJv6cLnRlc6DIHlmNxWEexB5bZxi9SZ9JyUuOYjw==", "dev": true, + "hasInstallScript": true, "dependencies": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -17639,9 +17858,9 @@ } }, "node_modules/prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz", + "integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -18058,9 +18277,9 @@ } }, "node_modules/react-datepicker": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.7.0.tgz", - "integrity": "sha512-O7bUv5zbjQ/sMItH6f2Sh7eHxqKN3VT3qT5naIIXv8el+GixsjmwOORDltlxcT0+ynkRUG6VeL70whbvyGPLTA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.8.0.tgz", + "integrity": "sha512-iFVNEp8DJoX5yEvEiciM7sJKmLGrvE70U38KhpG13XrulNSijeHw1RZkhd/0UmuXR71dcZB/kdfjiidifstZjw==", "dependencies": { "classnames": "^2.2.6", "date-fns": "^2.0.1", @@ -18307,9 +18526,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-multi-select-component": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/react-multi-select-component/-/react-multi-select-component-4.0.0.tgz", - "integrity": "sha512-JcMoqy68KN8cSE7JopbLwVWXftkhBuHHwKtPCmkdQHFW8yyC+GaNxqmuStYJuNByPOIrwo+fj+Bin0GAQ0d1dA==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-multi-select-component/-/react-multi-select-component-4.0.1.tgz", + "integrity": "sha512-4VcYmc4TvWVQP1tdzE+z6guPPCDzVPqwVdrvLNnOnCuZM9a0sz5RYBKV2SOyywgwRuYvS/YdvUhrp/2J0Llc8g==" }, "node_modules/react-onclickoutside": { "version": "6.10.0", @@ -18331,9 +18550,9 @@ } }, "node_modules/react-redux": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.3.tgz", - "integrity": "sha512-ZhAmQ1lrK+Pyi0ZXNMUZuYxYAZd59wFuVDGUt536kSGdD0ya9Q7BfsE95E3TsFLE3kOSFp5m6G5qbatE+Ic1+w==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", + "integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==", "dependencies": { "@babel/runtime": "^7.12.1", "@types/react-redux": "^7.1.16", @@ -18515,6 +18734,15 @@ "node": ">=6.0" } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/react-scripts/node_modules/json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -19841,9 +20069,6 @@ "lodash": "^4.0.0", "scss-tokenizer": "^0.2.3", "yargs": "^13.3.2" - }, - "bin": { - "sassgraph": "bin/sassgraph" } }, "node_modules/sass-loader": { @@ -20652,11 +20877,6 @@ "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, "engines": { "node": ">=0.10.0" } @@ -21236,29 +21456,26 @@ "dev": true }, "node_modules/table": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz", - "integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.0.tgz", + "integrity": "sha512-SAM+5p6V99gYiiy2gT5ArdzgM1dLDed0nkrWmG6Fry/bUS/m9x83BwpJUOf1Qj/x2qJd+thL6IkIx7qPGRxqBw==", "dev": true, "dependencies": { "ajv": "^8.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", "lodash.clonedeep": "^4.5.0", - "lodash.flatten": "^4.4.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10.0.0" } }, "node_modules/table/node_modules/ajv": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz", - "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.3.0.tgz", + "integrity": "sha512-RYE7B5An83d7eWnDR8kbdaIFqmKCNsP16ay1hDbJEU+sa0e3H9SebskCt0Uufem6cfAVu7Col6ubcn/W+Sm8/Q==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -21842,9 +22059,9 @@ "dev": true }, "node_modules/ts-jest": { - "version": "26.5.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.4.tgz", - "integrity": "sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg==", + "version": "26.5.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.5.tgz", + "integrity": "sha512-7tP4m+silwt1NHqzNRAPjW1BswnAhopTdc2K3HEkRZjF0ZG2F/e/ypVH0xiZIMfItFtD3CX0XFbwPzp9fIEUVg==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -22667,6 +22884,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -23085,6 +23303,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -25719,9 +25938,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", - "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", + "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -26412,15 +26631,15 @@ } }, "@openapitools/openapi-generator-cli": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.2.5.tgz", - "integrity": "sha512-8eTiw9U5PWYZx41RDIrJ6iZ4XCteD6ljupbJS6/ichZEUwa1Pv78y14QSLVfky+1KpJsq/RlqrgjnPm6yMHo6g==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.2.6.tgz", + "integrity": "sha512-TFKHY1lcknzg6IhPe9f8wWUO11PeKBPnrM40jZjZNUHlQRvh7WLsW6vNlbGcm78eBYTcyY0cKqr1QXUpB9Ez2Q==", "dev": true, "requires": { "@nestjs/common": "7.6.15", "@nestjs/core": "7.6.15", "@nuxtjs/opencollective": "0.3.2", - "chalk": "4.1.0", + "chalk": "4.1.1", "commander": "6.2.1", "compare-versions": "3.6.0", "concurrently": "5.3.0", @@ -26432,6 +26651,18 @@ "reflect-metadata": "0.1.13", "rxjs": "6.6.7", "tslib": "1.13.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@pmmmwh/react-refresh-webpack-plugin": { @@ -26499,9 +26730,9 @@ } }, "@rooks/use-outside-click-ref": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rooks/use-outside-click-ref/-/use-outside-click-ref-4.11.0.tgz", - "integrity": "sha512-GhqUO3u4I8APlEa+e0s+fIPtMV1C00K1voTnALjrLs8VIdeemJKvGPecm2RV+uwvsUbs/Lj1z6u75B4dgPl0nw==" + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@rooks/use-outside-click-ref/-/use-outside-click-ref-4.11.2.tgz", + "integrity": "sha512-w2bCW69zcpLh0KmN/odAuBsQ3sps+73KEu7zMOi0o4YMfDo+tXcqwlTJiLYysd0BEoQC9pNIklzZmI9zZep69g==" }, "@sinonjs/commons": { "version": "1.8.2", @@ -26663,9 +26894,9 @@ } }, "@testing-library/jest-dom": { - "version": "5.11.10", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.10.tgz", - "integrity": "sha512-FuKiq5xuk44Fqm0000Z9w0hjOdwZRNzgx7xGGxQYepWFZy+OYUMOT/wPI4nLYXCaVltNVpU1W/qmD88wLWDsqQ==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.12.0.tgz", + "integrity": "sha512-N9Y82b2Z3j6wzIoAqajlKVF1Zt7sOH0pPee0sUHXHc5cv2Fdn23r+vpWm0MBBoGJtPOly5+Bdx1lnc3CD+A+ow==", "dev": true, "requires": { "@babel/runtime": "^7.9.2", @@ -26747,10 +26978,13 @@ } }, "@types/classnames": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz", - "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==", - "dev": true + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==", + "dev": true, + "requires": { + "classnames": "*" + } }, "@types/connect": { "version": "3.4.34", @@ -26863,9 +27097,9 @@ } }, "@types/jest": { - "version": "26.0.22", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz", - "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==", + "version": "26.0.23", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz", + "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==", "dev": true, "requires": { "jest-diff": "^26.0.0", @@ -26952,9 +27186,9 @@ "dev": true }, "@types/react": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.3.tgz", - "integrity": "sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.5.tgz", + "integrity": "sha512-bj4biDB9ZJmGAYTWSKJly6bMr4BLUiBrx9ujiJEoP9XIDY9CTaPGxE5QWN/1WjpPLzYF7/jRNnV2nNxNe970sw==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -27136,13 +27370,13 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz", - "integrity": "sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.0.tgz", + "integrity": "sha512-U8SP9VOs275iDXaL08Ln1Fa/wLXfj5aTr/1c0t0j6CdbOnxh+TruXu1p4I0NAvdPBQgoPjHsgKn28mOi0FzfoA==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.21.0", - "@typescript-eslint/scope-manager": "4.21.0", + "@typescript-eslint/experimental-utils": "4.22.0", + "@typescript-eslint/scope-manager": "4.22.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", @@ -27151,6 +27385,61 @@ "tsutils": "^3.17.1" }, "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.22.0.tgz", + "integrity": "sha512-xJXHHl6TuAxB5AWiVrGhvbGL8/hbiCQ8FiWwObO3r0fnvBdrbWEDy1hlvGQOAWc6qsCWuWMKdVWlLAEMpxnddg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.22.0", + "@typescript-eslint/types": "4.22.0", + "@typescript-eslint/typescript-estree": "4.22.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.22.0.tgz", + "integrity": "sha512-OcCO7LTdk6ukawUM40wo61WdeoA7NM/zaoq1/2cs13M7GyiF+T4rxuA4xM+6LeHWjWbss7hkGXjFDRcKD4O04Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.22.0", + "@typescript-eslint/visitor-keys": "4.22.0" + } + }, + "@typescript-eslint/types": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.22.0.tgz", + "integrity": "sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.0.tgz", + "integrity": "sha512-TkIFeu5JEeSs5ze/4NID+PIcVjgoU3cUQUIZnH3Sb1cEn1lBo7StSV5bwPuJQuoxKXlzAObjYTilOEKRuhR5yg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.22.0", + "@typescript-eslint/visitor-keys": "4.22.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.0.tgz", + "integrity": "sha512-nnMu4F+s4o0sll6cBSsTeVsT4cwxB7zECK3dFxzEjPBii9xLpq4yqqsy/FU5zMfan6G60DKZSCXAa3sHJZrcYw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.22.0", + "eslint-visitor-keys": "^2.0.0" + } + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -27192,17 +27481,58 @@ } }, "@typescript-eslint/parser": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.21.0.tgz", - "integrity": "sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.22.1.tgz", + "integrity": "sha512-l+sUJFInWhuMxA6rtirzjooh8cM/AATAe3amvIkqKFeMzkn85V+eLzb1RyuXkHak4dLfYzOmF6DXPyflJvjQnw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.21.0", - "@typescript-eslint/types": "4.21.0", - "@typescript-eslint/typescript-estree": "4.21.0", + "@typescript-eslint/scope-manager": "4.22.1", + "@typescript-eslint/types": "4.22.1", + "@typescript-eslint/typescript-estree": "4.22.1", "debug": "^4.1.1" }, "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.22.1.tgz", + "integrity": "sha512-d5bAiPBiessSmNi8Amq/RuLslvcumxLmyhf1/Xa9IuaoFJ0YtshlJKxhlbY7l2JdEk3wS0EnmnfeJWSvADOe0g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.22.1", + "@typescript-eslint/visitor-keys": "4.22.1" + } + }, + "@typescript-eslint/types": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.22.1.tgz", + "integrity": "sha512-2HTkbkdAeI3OOcWbqA8hWf/7z9c6gkmnWNGz0dKSLYLWywUlkOAQ2XcjhlKLj5xBFDf8FgAOF5aQbnLRvgNbCw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.1.tgz", + "integrity": "sha512-p3We0pAPacT+onSGM+sPR+M9CblVqdA9F1JEdIqRVlxK5Qth4ochXQgIyb9daBomyQKAXbygxp1aXQRV0GC79A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.22.1", + "@typescript-eslint/visitor-keys": "4.22.1", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.1.tgz", + "integrity": "sha512-WPkOrIRm+WCLZxXQHCi+WG8T2MMTUFR70rWjdWYddLT7cEfb2P4a3O/J2U1FBVsSFTocXLCoXWY6MZGejeStvQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.22.1", + "eslint-visitor-keys": "^2.0.0" + } + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -27217,6 +27547,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -30320,9 +30659,9 @@ } }, "csstype": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", - "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" }, "currently-unhandled": { "version": "0.4.1", @@ -30404,9 +30743,9 @@ } }, "date-fns": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.20.1.tgz", - "integrity": "sha512-8P5M8Kxbnovd0zfvOs7ipkiVJ3/zZQ0F/nrBW4x5E+I0uAZVZ80h6CKd24fSXQ5TLK5hXMtI4yb2O5rEZdUt2A==" + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.21.3.tgz", + "integrity": "sha512-HeYdzCaFflc1i4tGbj7JKMjM4cKGYoyxwcIIkHzNgCkX8xXDNJDZXgDDVchIWpN4eQc3lH37WarduXFZJOtxfw==" }, "debug": { "version": "2.6.9", @@ -30848,9 +31187,9 @@ } }, "dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.1.tgz", + "integrity": "sha512-W8FNeNnnvJoYfgkFRKzp8kTgz0T2YY4TJ9xy1Ma0hSebPTK8iquRtpG12TUrSTX5zIN9D/wSLEEuI+Ad35tlyw==", "dev": true }, "dotenv-expand": { @@ -31261,13 +31600,13 @@ } }, "eslint": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.24.0.tgz", - "integrity": "sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.26.0.tgz", + "integrity": "sha512-4R1ieRf52/izcZE7AlLy56uIHHDLT74Yzz2Iv2l6kDaYvEu9x+wMB5dZArVL8SYGXSYV2YAg70FcW5Y5nGGNIg==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.0", + "@eslint/eslintrc": "^0.4.1", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -31386,9 +31725,9 @@ } }, "eslint-config-prettier": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", - "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", "dev": true }, "eslint-config-react-app": { @@ -31532,9 +31871,9 @@ } }, "eslint-plugin-prettier": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz", - "integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" @@ -36017,28 +36356,44 @@ "dev": true }, "lint-staged": { - "version": "10.5.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz", - "integrity": "sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-11.0.0.tgz", + "integrity": "sha512-3rsRIoyaE8IphSUtO1RVTFl1e0SLBtxxUOPBtHxQgBHS5/i6nqvjcUfNioMa4BU9yGnPzbO+xkfLtXtxBpCzjw==", "dev": true, "requires": { - "chalk": "^4.1.0", + "chalk": "^4.1.1", "cli-truncate": "^2.1.0", - "commander": "^6.2.0", + "commander": "^7.2.0", "cosmiconfig": "^7.0.0", - "debug": "^4.2.0", + "debug": "^4.3.1", "dedent": "^0.7.0", "enquirer": "^2.3.6", - "execa": "^4.1.0", - "listr2": "^3.2.2", - "log-symbols": "^4.0.0", - "micromatch": "^4.0.2", + "execa": "^5.0.0", + "listr2": "^3.8.2", + "log-symbols": "^4.1.0", + "micromatch": "^4.0.4", "normalize-path": "^3.0.0", "please-upgrade-node": "^3.2.0", "string-argv": "0.3.1", "stringify-object": "^3.3.0" }, "dependencies": { + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -36049,30 +36404,33 @@ } }, "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true }, "is-stream": { "version": "2.0.0", @@ -36080,6 +36438,16 @@ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -36100,16 +36468,22 @@ "requires": { "path-key": "^3.0.0" } + }, + "picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true } } }, "listr2": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.6.2.tgz", - "integrity": "sha512-B2vlu7Zx/2OAMVUovJ7Tv1kQ2v2oXd0nZKzkSAcRCej269d8gkS/gupDEdNl23KQ3ZjVD8hQmifrrBFbx8F9LA==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.8.2.tgz", + "integrity": "sha512-E28Fw7Zd3HQlCJKzb9a8C8M0HtFWQeucE+S8YrSrqZObuCLPRHMRrR8gNmYt65cU9orXYHwvN5agXC36lYt7VQ==", "dev": true, "requires": { - "chalk": "^4.1.0", + "chalk": "^4.1.1", "cli-truncate": "^2.1.0", "figures": "^3.2.0", "indent-string": "^4.0.0", @@ -36126,6 +36500,16 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -36272,12 +36656,6 @@ "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", "dev": true }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -39197,9 +39575,9 @@ "dev": true }, "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz", + "integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==", "dev": true }, "prettier-linter-helpers": { @@ -39554,9 +39932,9 @@ } }, "react-datepicker": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.7.0.tgz", - "integrity": "sha512-O7bUv5zbjQ/sMItH6f2Sh7eHxqKN3VT3qT5naIIXv8el+GixsjmwOORDltlxcT0+ynkRUG6VeL70whbvyGPLTA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.8.0.tgz", + "integrity": "sha512-iFVNEp8DJoX5yEvEiciM7sJKmLGrvE70U38KhpG13XrulNSijeHw1RZkhd/0UmuXR71dcZB/kdfjiidifstZjw==", "requires": { "classnames": "^2.2.6", "date-fns": "^2.0.1", @@ -39759,9 +40137,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-multi-select-component": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/react-multi-select-component/-/react-multi-select-component-4.0.0.tgz", - "integrity": "sha512-JcMoqy68KN8cSE7JopbLwVWXftkhBuHHwKtPCmkdQHFW8yyC+GaNxqmuStYJuNByPOIrwo+fj+Bin0GAQ0d1dA==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-multi-select-component/-/react-multi-select-component-4.0.1.tgz", + "integrity": "sha512-4VcYmc4TvWVQP1tdzE+z6guPPCDzVPqwVdrvLNnOnCuZM9a0sz5RYBKV2SOyywgwRuYvS/YdvUhrp/2J0Llc8g==" }, "react-onclickoutside": { "version": "6.10.0", @@ -39783,9 +40161,9 @@ } }, "react-redux": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.3.tgz", - "integrity": "sha512-ZhAmQ1lrK+Pyi0ZXNMUZuYxYAZd59wFuVDGUt536kSGdD0ya9Q7BfsE95E3TsFLE3kOSFp5m6G5qbatE+Ic1+w==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", + "integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==", "requires": { "@babel/runtime": "^7.12.1", "@types/react-redux": "^7.1.16", @@ -39946,6 +40324,12 @@ "ms": "2.1.2" } }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true + }, "json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -42287,26 +42671,23 @@ "dev": true }, "table": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz", - "integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.0.tgz", + "integrity": "sha512-SAM+5p6V99gYiiy2gT5ArdzgM1dLDed0nkrWmG6Fry/bUS/m9x83BwpJUOf1Qj/x2qJd+thL6IkIx7qPGRxqBw==", "dev": true, "requires": { "ajv": "^8.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", "lodash.clonedeep": "^4.5.0", - "lodash.flatten": "^4.4.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" }, "dependencies": { "ajv": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz", - "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.3.0.tgz", + "integrity": "sha512-RYE7B5An83d7eWnDR8kbdaIFqmKCNsP16ay1hDbJEU+sa0e3H9SebskCt0Uufem6cfAVu7Col6ubcn/W+Sm8/Q==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -42783,9 +43164,9 @@ "dev": true }, "ts-jest": { - "version": "26.5.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.4.tgz", - "integrity": "sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg==", + "version": "26.5.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.5.tgz", + "integrity": "sha512-7tP4m+silwt1NHqzNRAPjW1BswnAhopTdc2K3HEkRZjF0ZG2F/e/ypVH0xiZIMfItFtD3CX0XFbwPzp9fIEUVg==", "dev": true, "requires": { "bs-logger": "0.x", diff --git a/kafka-ui-react-app/package.json b/kafka-ui-react-app/package.json index 1c433a9839..e66c65b02b 100644 --- a/kafka-ui-react-app/package.json +++ b/kafka-ui-react-app/package.json @@ -89,7 +89,7 @@ "@typescript-eslint/eslint-plugin": "^4.20.0", "@typescript-eslint/parser": "^4.20.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.0", - "dotenv": "^8.2.0", + "dotenv": "^9.0.1", "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.1", "eslint": "^7.22.0", @@ -105,7 +105,7 @@ "fetch-mock-jest": "^1.5.1", "husky": "^6.0.0", "jest-sonar-reporter": "^2.0.0", - "lint-staged": "^10.5.4", + "lint-staged": "^11.0.0", "node-sass": "^5.0.0", "prettier": "^2.2.1", "react-scripts": "4.0.3", diff --git a/kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx b/kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx index 1019a1bca3..85b65d016d 100644 --- a/kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx +++ b/kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx @@ -18,9 +18,8 @@ const mapStateToProps = (state: RootState) => ({ isFetching: getIsSchemaListFetching(state), schemas: getSchemaList(state), globalSchemaCompatibilityLevel: getGlobalSchemaCompatibilityLevel(state), - isGlobalSchemaCompatibilityLevelFetched: getGlobalSchemaCompatibilityLevelFetched( - state - ), + isGlobalSchemaCompatibilityLevelFetched: + getGlobalSchemaCompatibilityLevelFetched(state), }); const mapDispatchToProps = { diff --git a/kafka-ui-react-app/src/components/Topics/List/ListItem.tsx b/kafka-ui-react-app/src/components/Topics/List/ListItem.tsx index fac54b8a94..ca92c22f5a 100644 --- a/kafka-ui-react-app/src/components/Topics/List/ListItem.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/ListItem.tsx @@ -23,10 +23,8 @@ const ListItem: React.FC = ({ clusterName, clearTopicMessages, }) => { - const [ - isDeleteTopicConfirmationVisible, - setDeleteTopicConfirmationVisible, - ] = React.useState(false); + const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] = + React.useState(false); const outOfSyncReplicas = React.useMemo(() => { if (partitions === undefined || partitions.length === 0) { @@ -66,33 +64,37 @@ const ListItem: React.FC = ({ {internal ? 'Internal' : 'External'} - -
- - - - } - right - > - - Clear Messages - - setDeleteTopicConfirmationVisible(true)} + + {!internal ? ( + <> +
+ + + + } + right + > + + Clear Messages + + setDeleteTopicConfirmationVisible(true)} + > + Remove Topic + + +
+ setDeleteTopicConfirmationVisible(false)} + onConfirm={deleteTopicHandler} > - Remove Topic -
-
-
- setDeleteTopicConfirmationVisible(false)} - onConfirm={deleteTopicHandler} - > - Are you sure want to remove {name} topic? - + Are you sure want to remove {name} topic? + + + ) : null} ); diff --git a/kafka-ui-react-app/src/components/Topics/List/__tests__/ListItem.spec.tsx b/kafka-ui-react-app/src/components/Topics/List/__tests__/ListItem.spec.tsx index 6aa399efa6..f652155374 100644 --- a/kafka-ui-react-app/src/components/Topics/List/__tests__/ListItem.spec.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/__tests__/ListItem.spec.tsx @@ -28,28 +28,31 @@ describe('ListItem', () => { ); it('triggers the deleting messages when clicked on the delete messages button', () => { - const component = shallow(setupComponent()); + const component = shallow(setupComponent({ topic: externalTopicPayload })); + expect(component.exists('.topic-action-block')).toBeTruthy(); component.find('DropdownItem').at(0).simulate('click'); expect(mockDeleteMessages).toBeCalledTimes(1); expect(mockDeleteMessages).toBeCalledWith( clusterName, - internalTopicPayload.name + externalTopicPayload.name ); }); it('triggers the deleteTopic when clicked on the delete button', () => { - const wrapper = shallow(setupComponent()); + const wrapper = shallow(setupComponent({ topic: externalTopicPayload })); + expect(wrapper.exists('.topic-action-block')).toBeTruthy(); expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy(); wrapper.find('DropdownItem').at(1).simulate('click'); const modal = wrapper.find('mock-ConfirmationModal'); expect(modal.prop('isOpen')).toBeTruthy(); modal.simulate('confirm'); expect(mockDelete).toBeCalledTimes(1); - expect(mockDelete).toBeCalledWith(clusterName, internalTopicPayload.name); + expect(mockDelete).toBeCalledWith(clusterName, externalTopicPayload.name); }); it('closes ConfirmationModal when clicked on the cancel button', () => { - const wrapper = shallow(setupComponent()); + const wrapper = shallow(setupComponent({ topic: externalTopicPayload })); + expect(wrapper.exists('.topic-action-block')).toBeTruthy(); expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy(); wrapper.find('DropdownItem').last().simulate('click'); expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeTruthy(); diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx index 379ffc53a6..62fcca3882 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx @@ -19,6 +19,7 @@ import SettingsContainer from './Settings/SettingsContainer'; interface Props extends Topic, TopicDetails { clusterName: ClusterName; topicName: TopicName; + isInternal: boolean; deleteTopic: (clusterName: ClusterName, topicName: TopicName) => void; clearTopicMessages(clusterName: ClusterName, topicName: TopicName): void; } @@ -26,15 +27,14 @@ interface Props extends Topic, TopicDetails { const Details: React.FC = ({ clusterName, topicName, + isInternal, deleteTopic, clearTopicMessages, }) => { const history = useHistory(); const { isReadOnly } = React.useContext(ClusterContext); - const [ - isDeleteTopicConfirmationVisible, - setDeleteTopicConfirmationVisible, - ] = React.useState(false); + const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] = + React.useState(false); const deleteTopicHandler = React.useCallback(() => { deleteTopic(clusterName, topicName); history.push(clusterTopicsPath(clusterName)); @@ -74,8 +74,8 @@ const Details: React.FC = ({
-
- {!isReadOnly && ( + {!isReadOnly && !isInternal ? ( +
<>
+
+ ) : null}

diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/DetailsContainer.ts b/kafka-ui-react-app/src/components/Topics/Topic/Details/DetailsContainer.ts index b704926fc8..475f138bd1 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/DetailsContainer.ts +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/DetailsContainer.ts @@ -2,6 +2,7 @@ import { connect } from 'react-redux'; import { ClusterName, RootState, TopicName } from 'redux/interfaces'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { deleteTopic, clearTopicMessages } from 'redux/actions'; +import { getIsTopicInternal } from 'redux/reducers/topics/selectors'; import Details from './Details'; @@ -22,6 +23,7 @@ const mapStateToProps = ( ) => ({ clusterName, topicName, + isInternal: getIsTopicInternal(state, topicName), }); const mapDispatchToProps = { diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx index fd0d4499f8..034d942c68 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx @@ -50,9 +50,8 @@ const Messages: React.FC = ({ fetchTopicMessages, }) => { const [searchQuery, setSearchQuery] = React.useState(''); - const [searchTimestamp, setSearchTimestamp] = React.useState( - null - ); + const [searchTimestamp, setSearchTimestamp] = + React.useState(null); const [filterProps, setFilterProps] = React.useState([]); const [selectedSeekType, setSelectedSeekType] = React.useState( SeekType.OFFSET diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/Overview.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/Overview.tsx index 02f235475b..52811ac353 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/Overview.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/Overview.tsx @@ -75,22 +75,24 @@ const Overview: React.FC = ({ {offsetMin} {offsetMax} - - - - } - right - > - - clearTopicMessages(clusterName, topicName, [partition]) + {!internal ? ( + + + } + right > - Clear Messages - - + + clearTopicMessages(clusterName, topicName, [partition]) + } + > + Clear Messages + + + ) : null} ))} diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/__test__/Overview.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/__test__/Overview.spec.tsx new file mode 100644 index 0000000000..824e327fb5 --- /dev/null +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/__test__/Overview.spec.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Overview from 'components/Topics/Topic/Details/Overview/Overview'; + +describe('Overview', () => { + const mockInternal = false; + const mockClusterName = 'local'; + const mockTopicName = 'topic'; + const mockClearTopicMessages = jest.fn(); + const mockPartitions = [ + { + partition: 1, + leader: 1, + replicas: [ + { + broker: 1, + leader: false, + inSync: true, + }, + ], + offsetMax: 0, + offsetMin: 0, + }, + ]; + + describe('when it has internal flag', () => { + it('does not render the Action button a Topic', () => { + const component = shallow( + + ); + + expect(component.exists('Dropdown')).toBeTruthy(); + }); + }); +}); diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/__test__/Details.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/__test__/Details.spec.tsx new file mode 100644 index 0000000000..a32b88a95e --- /dev/null +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/__test__/Details.spec.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { StaticRouter } from 'react-router-dom'; +import ClusterContext from 'components/contexts/ClusterContext'; +import Details from 'components/Topics/Topic/Details/Details'; +import { + internalTopicPayload, + externalTopicPayload, +} from 'redux/reducers/topics/__test__/fixtures'; + +describe('Details', () => { + const mockDelete = jest.fn(); + const mockClusterName = 'local'; + const mockClearTopicMessages = jest.fn(); + const mockInternalTopicPayload = internalTopicPayload.internal; + const mockExternalTopicPayload = externalTopicPayload.internal; + + describe('when it has readonly flag', () => { + it('does not render the Action button a Topic', () => { + const component = mount( + + +
+ + + ); + + expect(component.exists('button')).toBeFalsy(); + }); + }); + + describe('when it does not have readonly flag', () => { + it('renders the Action button a Topic', () => { + const component = mount( + + +
+ + + ); + + expect(component.exists('button')).toBeTruthy(); + }); + }); +}); diff --git a/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx b/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx index 31965e372b..2eb11cf8b5 100644 --- a/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx +++ b/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx @@ -39,12 +39,11 @@ const CustomParamSelect: React.FC = ({ return valid || 'Custom Parameter must be unique'; }; - const onChange = (inputName: string) => ( - event: React.ChangeEvent - ) => { - trigger(inputName); - onNameChange(index, event.target.value); - }; + const onChange = + (inputName: string) => (event: React.ChangeEvent) => { + trigger(inputName); + onNameChange(index, event.target.value); + }; return ( <> diff --git a/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx b/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx index 570d0154f4..f05e33382a 100644 --- a/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx +++ b/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx @@ -35,13 +35,11 @@ const CustomParams: React.FC = ({ isSubmitting, config }) => { ) : {}; - const [ - formCustomParams, - setFormCustomParams, - ] = React.useState({ - byIndex, - allIndexes: Object.keys(byIndex), - }); + const [formCustomParams, setFormCustomParams] = + React.useState({ + byIndex, + allIndexes: Object.keys(byIndex), + }); const onAdd = (event: React.MouseEvent) => { event.preventDefault(); diff --git a/kafka-ui-react-app/src/redux/actions/thunks/brokers.ts b/kafka-ui-react-app/src/redux/actions/thunks/brokers.ts index f698f45c29..025929b08f 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/brokers.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/brokers.ts @@ -6,30 +6,29 @@ import * as actions from 'redux/actions/actions'; const apiClientConf = new Configuration(BASE_PARAMS); export const brokersApiClient = new BrokersApi(apiClientConf); -export const fetchBrokers = ( - clusterName: ClusterName -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchBrokersAction.request()); - try { - const payload = await brokersApiClient.getBrokers({ clusterName }); - dispatch(actions.fetchBrokersAction.success(payload)); - } catch (e) { - dispatch(actions.fetchBrokersAction.failure()); - } -}; +export const fetchBrokers = + (clusterName: ClusterName): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchBrokersAction.request()); + try { + const payload = await brokersApiClient.getBrokers({ clusterName }); + dispatch(actions.fetchBrokersAction.success(payload)); + } catch (e) { + dispatch(actions.fetchBrokersAction.failure()); + } + }; -export const fetchBrokerMetrics = ( - clusterName: ClusterName, - brokerId: BrokerId -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchBrokerMetricsAction.request()); - try { - const payload = await brokersApiClient.getBrokersMetrics({ - clusterName, - id: brokerId, - }); - dispatch(actions.fetchBrokerMetricsAction.success(payload)); - } catch (e) { - dispatch(actions.fetchBrokerMetricsAction.failure()); - } -}; +export const fetchBrokerMetrics = + (clusterName: ClusterName, brokerId: BrokerId): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchBrokerMetricsAction.request()); + try { + const payload = await brokersApiClient.getBrokersMetrics({ + clusterName, + id: brokerId, + }); + dispatch(actions.fetchBrokerMetricsAction.success(payload)); + } catch (e) { + dispatch(actions.fetchBrokerMetricsAction.failure()); + } + }; diff --git a/kafka-ui-react-app/src/redux/actions/thunks/clusters.ts b/kafka-ui-react-app/src/redux/actions/thunks/clusters.ts index 8d7f2030e7..14d04b01f8 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/clusters.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/clusters.ts @@ -16,26 +16,28 @@ export const fetchClustersList = (): PromiseThunkResult => async (dispatch) => { } }; -export const fetchClusterStats = ( - clusterName: ClusterName -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchClusterStatsAction.request()); - try { - const payload = await clustersApiClient.getClusterStats({ clusterName }); - dispatch(actions.fetchClusterStatsAction.success(payload)); - } catch (e) { - dispatch(actions.fetchClusterStatsAction.failure()); - } -}; +export const fetchClusterStats = + (clusterName: ClusterName): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchClusterStatsAction.request()); + try { + const payload = await clustersApiClient.getClusterStats({ clusterName }); + dispatch(actions.fetchClusterStatsAction.success(payload)); + } catch (e) { + dispatch(actions.fetchClusterStatsAction.failure()); + } + }; -export const fetchClusterMetrics = ( - clusterName: ClusterName -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchClusterMetricsAction.request()); - try { - const payload = await clustersApiClient.getClusterMetrics({ clusterName }); - dispatch(actions.fetchClusterMetricsAction.success(payload)); - } catch (e) { - dispatch(actions.fetchClusterMetricsAction.failure()); - } -}; +export const fetchClusterMetrics = + (clusterName: ClusterName): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchClusterMetricsAction.request()); + try { + const payload = await clustersApiClient.getClusterMetrics({ + clusterName, + }); + dispatch(actions.fetchClusterMetricsAction.success(payload)); + } catch (e) { + dispatch(actions.fetchClusterMetricsAction.failure()); + } + }; diff --git a/kafka-ui-react-app/src/redux/actions/thunks/consumerGroups.ts b/kafka-ui-react-app/src/redux/actions/thunks/consumerGroups.ts index 4a5445cb3b..37ce7b9aed 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/consumerGroups.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/consumerGroups.ts @@ -10,39 +10,40 @@ import * as actions from 'redux/actions/actions'; const apiClientConf = new Configuration(BASE_PARAMS); export const consumerGroupsApiClient = new ConsumerGroupsApi(apiClientConf); -export const fetchConsumerGroupsList = ( - clusterName: ClusterName -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchConsumerGroupsAction.request()); - try { - const consumerGroups = await consumerGroupsApiClient.getConsumerGroups({ - clusterName, - }); - dispatch(actions.fetchConsumerGroupsAction.success(consumerGroups)); - } catch (e) { - dispatch(actions.fetchConsumerGroupsAction.failure()); - } -}; - -export const fetchConsumerGroupDetails = ( - clusterName: ClusterName, - consumerGroupID: ConsumerGroupID -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchConsumerGroupDetailsAction.request()); - try { - const consumerGroupDetails = await consumerGroupsApiClient.getConsumerGroup( - { +export const fetchConsumerGroupsList = + (clusterName: ClusterName): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchConsumerGroupsAction.request()); + try { + const consumerGroups = await consumerGroupsApiClient.getConsumerGroups({ clusterName, - id: consumerGroupID, - } - ); - dispatch( - actions.fetchConsumerGroupDetailsAction.success({ - consumerGroupID, - details: consumerGroupDetails, - }) - ); - } catch (e) { - dispatch(actions.fetchConsumerGroupDetailsAction.failure()); - } -}; + }); + dispatch(actions.fetchConsumerGroupsAction.success(consumerGroups)); + } catch (e) { + dispatch(actions.fetchConsumerGroupsAction.failure()); + } + }; + +export const fetchConsumerGroupDetails = + ( + clusterName: ClusterName, + consumerGroupID: ConsumerGroupID + ): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchConsumerGroupDetailsAction.request()); + try { + const consumerGroupDetails = + await consumerGroupsApiClient.getConsumerGroup({ + clusterName, + id: consumerGroupID, + }); + dispatch( + actions.fetchConsumerGroupDetailsAction.success({ + consumerGroupID, + details: consumerGroupDetails, + }) + ); + } catch (e) { + dispatch(actions.fetchConsumerGroupDetailsAction.failure()); + } + }; diff --git a/kafka-ui-react-app/src/redux/actions/thunks/schemas.ts b/kafka-ui-react-app/src/redux/actions/thunks/schemas.ts index bc98688afb..03f1c20960 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/schemas.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/schemas.ts @@ -20,160 +20,164 @@ import { isEqual } from 'lodash'; const apiClientConf = new Configuration(BASE_PARAMS); export const schemasApiClient = new SchemasApi(apiClientConf); -export const fetchSchemasByClusterName = ( - clusterName: ClusterName -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchSchemasByClusterNameAction.request()); - try { - const schemas = await schemasApiClient.getSchemas({ clusterName }); - dispatch(actions.fetchSchemasByClusterNameAction.success(schemas)); - } catch (e) { - dispatch(actions.fetchSchemasByClusterNameAction.failure()); - } -}; - -export const fetchSchemaVersions = ( - clusterName: ClusterName, - subject: SchemaName -): PromiseThunkResult => async (dispatch) => { - if (!subject) return; - dispatch(actions.fetchSchemaVersionsAction.request()); - try { - const versions = await schemasApiClient.getAllVersionsBySubject({ - clusterName, - subject, - }); - dispatch(actions.fetchSchemaVersionsAction.success(versions)); - } catch (e) { - dispatch(actions.fetchSchemaVersionsAction.failure()); - } -}; - -export const fetchGlobalSchemaCompatibilityLevel = ( - clusterName: ClusterName -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.request()); - try { - const result = await schemasApiClient.getGlobalSchemaCompatibilityLevel({ - clusterName, - }); - dispatch( - actions.fetchGlobalSchemaCompatibilityLevelAction.success( - result.compatibility - ) - ); - } catch (e) { - dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.failure()); - } -}; - -export const updateGlobalSchemaCompatibilityLevel = ( - clusterName: ClusterName, - compatibilityLevel: CompatibilityLevelCompatibilityEnum -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.request()); - try { - await schemasApiClient.updateGlobalSchemaCompatibilityLevel({ - clusterName, - compatibilityLevel: { compatibility: compatibilityLevel }, - }); - dispatch( - actions.updateGlobalSchemaCompatibilityLevelAction.success( - compatibilityLevel - ) - ); - } catch (e) { - dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.failure()); - } -}; - -export const createSchema = ( - clusterName: ClusterName, - newSchemaSubject: NewSchemaSubject -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.createSchemaAction.request()); - try { - const schema: SchemaSubject = await schemasApiClient.createNewSchema({ - clusterName, - newSchemaSubject, - }); - dispatch(actions.createSchemaAction.success(schema)); - } catch (error) { - const response = await getResponse(error); - const alert: FailurePayload = { - subject: ['schema', newSchemaSubject.subject].join('-'), - title: `Schema ${newSchemaSubject.subject}`, - response, - }; - dispatch(actions.createSchemaAction.failure({ alert })); - throw error; - } -}; - -export const updateSchema = ( - latestSchema: SchemaSubject, - newSchema: string, - newSchemaType: SchemaType, - newCompatibilityLevel: CompatibilityLevelCompatibilityEnum, - clusterName: string, - subject: string -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.updateSchemaAction.request()); - try { - let schema: SchemaSubject = latestSchema; - if ( - (newSchema && - !isEqual(JSON.parse(latestSchema.schema), JSON.parse(newSchema))) || - newSchemaType !== latestSchema.schemaType - ) { - schema = await schemasApiClient.createNewSchema({ - clusterName, - newSchemaSubject: { - ...latestSchema, - schema: newSchema || latestSchema.schema, - schemaType: newSchemaType || latestSchema.schemaType, - }, - }); +export const fetchSchemasByClusterName = + (clusterName: ClusterName): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchSchemasByClusterNameAction.request()); + try { + const schemas = await schemasApiClient.getSchemas({ clusterName }); + dispatch(actions.fetchSchemasByClusterNameAction.success(schemas)); + } catch (e) { + dispatch(actions.fetchSchemasByClusterNameAction.failure()); } - if (newCompatibilityLevel !== latestSchema.compatibilityLevel) { - await schemasApiClient.updateSchemaCompatibilityLevel({ + }; + +export const fetchSchemaVersions = + (clusterName: ClusterName, subject: SchemaName): PromiseThunkResult => + async (dispatch) => { + if (!subject) return; + dispatch(actions.fetchSchemaVersionsAction.request()); + try { + const versions = await schemasApiClient.getAllVersionsBySubject({ clusterName, subject, - compatibilityLevel: { - compatibility: newCompatibilityLevel, - }, }); + dispatch(actions.fetchSchemaVersionsAction.success(versions)); + } catch (e) { + dispatch(actions.fetchSchemaVersionsAction.failure()); } - actions.updateSchemaAction.success(schema); - } catch (e) { - const response = await getResponse(e); - const alert: FailurePayload = { - subject: ['schema', subject].join('-'), - title: `Schema ${subject}`, - response, - }; - dispatch(actions.updateSchemaAction.failure({ alert })); - throw e; - } -}; -export const deleteSchema = ( - clusterName: ClusterName, - subject: string -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.deleteSchemaAction.request()); - try { - await schemasApiClient.deleteSchema({ - clusterName, - subject, - }); - dispatch(actions.deleteSchemaAction.success(subject)); - } catch (error) { - const response = await getResponse(error); - const alert: FailurePayload = { - subject: ['schema', subject].join('-'), - title: `Schema ${subject}`, - response, - }; - dispatch(actions.deleteSchemaAction.failure({ alert })); - } -}; + }; + +export const fetchGlobalSchemaCompatibilityLevel = + (clusterName: ClusterName): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.request()); + try { + const result = await schemasApiClient.getGlobalSchemaCompatibilityLevel({ + clusterName, + }); + dispatch( + actions.fetchGlobalSchemaCompatibilityLevelAction.success( + result.compatibility + ) + ); + } catch (e) { + dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.failure()); + } + }; + +export const updateGlobalSchemaCompatibilityLevel = + ( + clusterName: ClusterName, + compatibilityLevel: CompatibilityLevelCompatibilityEnum + ): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.request()); + try { + await schemasApiClient.updateGlobalSchemaCompatibilityLevel({ + clusterName, + compatibilityLevel: { compatibility: compatibilityLevel }, + }); + dispatch( + actions.updateGlobalSchemaCompatibilityLevelAction.success( + compatibilityLevel + ) + ); + } catch (e) { + dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.failure()); + } + }; + +export const createSchema = + ( + clusterName: ClusterName, + newSchemaSubject: NewSchemaSubject + ): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.createSchemaAction.request()); + try { + const schema: SchemaSubject = await schemasApiClient.createNewSchema({ + clusterName, + newSchemaSubject, + }); + dispatch(actions.createSchemaAction.success(schema)); + } catch (error) { + const response = await getResponse(error); + const alert: FailurePayload = { + subject: ['schema', newSchemaSubject.subject].join('-'), + title: `Schema ${newSchemaSubject.subject}`, + response, + }; + dispatch(actions.createSchemaAction.failure({ alert })); + throw error; + } + }; + +export const updateSchema = + ( + latestSchema: SchemaSubject, + newSchema: string, + newSchemaType: SchemaType, + newCompatibilityLevel: CompatibilityLevelCompatibilityEnum, + clusterName: string, + subject: string + ): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.updateSchemaAction.request()); + try { + let schema: SchemaSubject = latestSchema; + if ( + (newSchema && + !isEqual(JSON.parse(latestSchema.schema), JSON.parse(newSchema))) || + newSchemaType !== latestSchema.schemaType + ) { + schema = await schemasApiClient.createNewSchema({ + clusterName, + newSchemaSubject: { + ...latestSchema, + schema: newSchema || latestSchema.schema, + schemaType: newSchemaType || latestSchema.schemaType, + }, + }); + } + if (newCompatibilityLevel !== latestSchema.compatibilityLevel) { + await schemasApiClient.updateSchemaCompatibilityLevel({ + clusterName, + subject, + compatibilityLevel: { + compatibility: newCompatibilityLevel, + }, + }); + } + actions.updateSchemaAction.success(schema); + } catch (e) { + const response = await getResponse(e); + const alert: FailurePayload = { + subject: ['schema', subject].join('-'), + title: `Schema ${subject}`, + response, + }; + dispatch(actions.updateSchemaAction.failure({ alert })); + throw e; + } + }; +export const deleteSchema = + (clusterName: ClusterName, subject: string): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.deleteSchemaAction.request()); + try { + await schemasApiClient.deleteSchema({ + clusterName, + subject, + }); + dispatch(actions.deleteSchemaAction.success(subject)); + } catch (error) { + const response = await getResponse(error); + const alert: FailurePayload = { + subject: ['schema', subject].join('-'), + title: `Schema ${subject}`, + response, + }; + dispatch(actions.deleteSchemaAction.failure({ alert })); + } + }; diff --git a/kafka-ui-react-app/src/redux/actions/thunks/topics.ts b/kafka-ui-react-app/src/redux/actions/thunks/topics.ts index 7ce6bbe6a9..22e9fb359e 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/topics.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/topics.ts @@ -32,136 +32,138 @@ export interface FetchTopicsListParams { perPage?: number; } -export const fetchTopicsList = ( - params: FetchTopicsListParams -): PromiseThunkResult => async (dispatch, getState) => { - dispatch(actions.fetchTopicsListAction.request()); - try { - const { topics, pageCount } = await topicsApiClient.getTopics(params); - const newState = (topics || []).reduce( - (memo: TopicsState, topic) => ({ - ...memo, +export const fetchTopicsList = + (params: FetchTopicsListParams): PromiseThunkResult => + async (dispatch, getState) => { + dispatch(actions.fetchTopicsListAction.request()); + try { + const { topics, pageCount } = await topicsApiClient.getTopics(params); + const newState = (topics || []).reduce( + (memo: TopicsState, topic) => ({ + ...memo, + byName: { + ...memo.byName, + [topic.name]: { + ...memo.byName[topic.name], + ...topic, + id: v4(), + }, + }, + allNames: [...memo.allNames, topic.name], + }), + { + ...getState().topics, + allNames: [], + totalPages: pageCount || 1, + } + ); + dispatch(actions.fetchTopicsListAction.success(newState)); + } catch (e) { + dispatch(actions.fetchTopicsListAction.failure()); + } + }; + +export const fetchTopicMessages = + ( + clusterName: ClusterName, + topicName: TopicName, + queryParams: Partial + ): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.fetchTopicMessagesAction.request()); + try { + const messages = await messagesApiClient.getTopicMessages({ + clusterName, + topicName, + ...queryParams, + }); + dispatch(actions.fetchTopicMessagesAction.success(messages)); + } catch (e) { + dispatch(actions.fetchTopicMessagesAction.failure()); + } + }; + +export const clearTopicMessages = + ( + clusterName: ClusterName, + topicName: TopicName, + partitions?: number[] + ): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.clearMessagesTopicAction.request()); + try { + await messagesApiClient.deleteTopicMessages({ + clusterName, + topicName, + partitions, + }); + dispatch(actions.clearMessagesTopicAction.success(topicName)); + } catch (e) { + const response = await getResponse(e); + const alert: FailurePayload = { + subject: [clusterName, topicName, partitions].join('-'), + title: `Clear Topic Messages`, + response, + }; + dispatch(actions.clearMessagesTopicAction.failure({ alert })); + } + }; + +export const fetchTopicDetails = + (clusterName: ClusterName, topicName: TopicName): PromiseThunkResult => + async (dispatch, getState) => { + dispatch(actions.fetchTopicDetailsAction.request()); + try { + const topicDetails = await topicsApiClient.getTopicDetails({ + clusterName, + topicName, + }); + const state = getState().topics; + const newState = { + ...state, byName: { - ...memo.byName, - [topic.name]: { - ...memo.byName[topic.name], - ...topic, - id: v4(), + ...state.byName, + [topicName]: { + ...state.byName[topicName], + ...topicDetails, }, }, - allNames: [...memo.allNames, topic.name], - }), - { - ...getState().topics, - allNames: [], - totalPages: pageCount || 1, - } - ); - dispatch(actions.fetchTopicsListAction.success(newState)); - } catch (e) { - dispatch(actions.fetchTopicsListAction.failure()); - } -}; + }; + dispatch(actions.fetchTopicDetailsAction.success(newState)); + } catch (e) { + dispatch(actions.fetchTopicDetailsAction.failure()); + } + }; -export const fetchTopicMessages = ( - clusterName: ClusterName, - topicName: TopicName, - queryParams: Partial -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.fetchTopicMessagesAction.request()); - try { - const messages = await messagesApiClient.getTopicMessages({ - clusterName, - topicName, - ...queryParams, - }); - dispatch(actions.fetchTopicMessagesAction.success(messages)); - } catch (e) { - dispatch(actions.fetchTopicMessagesAction.failure()); - } -}; +export const fetchTopicConfig = + (clusterName: ClusterName, topicName: TopicName): PromiseThunkResult => + async (dispatch, getState) => { + dispatch(actions.fetchTopicConfigAction.request()); + try { + const config = await topicsApiClient.getTopicConfigs({ + clusterName, + topicName, + }); -export const clearTopicMessages = ( - clusterName: ClusterName, - topicName: TopicName, - partitions?: number[] -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.clearMessagesTopicAction.request()); - try { - await messagesApiClient.deleteTopicMessages({ - clusterName, - topicName, - partitions, - }); - dispatch(actions.clearMessagesTopicAction.success(topicName)); - } catch (e) { - const response = await getResponse(e); - const alert: FailurePayload = { - subject: [clusterName, topicName, partitions].join('-'), - title: `Clear Topic Messages`, - response, - }; - dispatch(actions.clearMessagesTopicAction.failure({ alert })); - } -}; - -export const fetchTopicDetails = ( - clusterName: ClusterName, - topicName: TopicName -): PromiseThunkResult => async (dispatch, getState) => { - dispatch(actions.fetchTopicDetailsAction.request()); - try { - const topicDetails = await topicsApiClient.getTopicDetails({ - clusterName, - topicName, - }); - const state = getState().topics; - const newState = { - ...state, - byName: { - ...state.byName, - [topicName]: { - ...state.byName[topicName], - ...topicDetails, + const state = getState().topics; + const newState = { + ...state, + byName: { + ...state.byName, + [topicName]: { + ...state.byName[topicName], + config: config.map((inputConfig) => ({ + ...inputConfig, + })), + }, }, - }, - }; - dispatch(actions.fetchTopicDetailsAction.success(newState)); - } catch (e) { - dispatch(actions.fetchTopicDetailsAction.failure()); - } -}; + }; -export const fetchTopicConfig = ( - clusterName: ClusterName, - topicName: TopicName -): PromiseThunkResult => async (dispatch, getState) => { - dispatch(actions.fetchTopicConfigAction.request()); - try { - const config = await topicsApiClient.getTopicConfigs({ - clusterName, - topicName, - }); - - const state = getState().topics; - const newState = { - ...state, - byName: { - ...state.byName, - [topicName]: { - ...state.byName[topicName], - config: config.map((inputConfig) => ({ - ...inputConfig, - })), - }, - }, - }; - - dispatch(actions.fetchTopicConfigAction.success(newState)); - } catch (e) { - dispatch(actions.fetchTopicConfigAction.failure()); - } -}; + dispatch(actions.fetchTopicConfigAction.success(newState)); + } catch (e) { + dispatch(actions.fetchTopicConfigAction.failure()); + } + }; const formatTopicCreation = (form: TopicFormDataRaw): TopicCreation => { const { @@ -229,84 +231,84 @@ const formatTopicUpdate = (form: TopicFormDataRaw): TopicUpdate => { }; }; -export const createTopic = ( - clusterName: ClusterName, - form: TopicFormDataRaw -): PromiseThunkResult => async (dispatch, getState) => { - dispatch(actions.createTopicAction.request()); - try { - const topic: Topic = await topicsApiClient.createTopic({ - clusterName, - topicCreation: formatTopicCreation(form), - }); +export const createTopic = + (clusterName: ClusterName, form: TopicFormDataRaw): PromiseThunkResult => + async (dispatch, getState) => { + dispatch(actions.createTopicAction.request()); + try { + const topic: Topic = await topicsApiClient.createTopic({ + clusterName, + topicCreation: formatTopicCreation(form), + }); - const state = getState().topics; - const newState = { - ...state, - byName: { - ...state.byName, - [topic.name]: { - ...topic, + const state = getState().topics; + const newState = { + ...state, + byName: { + ...state.byName, + [topic.name]: { + ...topic, + }, }, - }, - allNames: [...state.allNames, topic.name], - }; + allNames: [...state.allNames, topic.name], + }; - dispatch(actions.createTopicAction.success(newState)); - } catch (error) { - const response = await getResponse(error); - const alert: FailurePayload = { - subject: ['schema', form.name].join('-'), - title: `Schema ${form.name}`, - response, - }; - dispatch(actions.createTopicAction.failure({ alert })); - } -}; + dispatch(actions.createTopicAction.success(newState)); + } catch (error) { + const response = await getResponse(error); + const alert: FailurePayload = { + subject: ['schema', form.name].join('-'), + title: `Schema ${form.name}`, + response, + }; + dispatch(actions.createTopicAction.failure({ alert })); + } + }; -export const updateTopic = ( - clusterName: ClusterName, - topicName: TopicName, - form: TopicFormDataRaw -): PromiseThunkResult => async (dispatch, getState) => { - dispatch(actions.updateTopicAction.request()); - try { - const topic: Topic = await topicsApiClient.updateTopic({ - clusterName, - topicName, - topicUpdate: formatTopicUpdate(form), - }); +export const updateTopic = + ( + clusterName: ClusterName, + topicName: TopicName, + form: TopicFormDataRaw + ): PromiseThunkResult => + async (dispatch, getState) => { + dispatch(actions.updateTopicAction.request()); + try { + const topic: Topic = await topicsApiClient.updateTopic({ + clusterName, + topicName, + topicUpdate: formatTopicUpdate(form), + }); - const state = getState().topics; - const newState = { - ...state, - byName: { - ...state.byName, - [topic.name]: { - ...state.byName[topic.name], - ...topic, + const state = getState().topics; + const newState = { + ...state, + byName: { + ...state.byName, + [topic.name]: { + ...state.byName[topic.name], + ...topic, + }, }, - }, - }; + }; - dispatch(actions.updateTopicAction.success(newState)); - } catch (e) { - dispatch(actions.updateTopicAction.failure()); - } -}; + dispatch(actions.updateTopicAction.success(newState)); + } catch (e) { + dispatch(actions.updateTopicAction.failure()); + } + }; -export const deleteTopic = ( - clusterName: ClusterName, - topicName: TopicName -): PromiseThunkResult => async (dispatch) => { - dispatch(actions.deleteTopicAction.request()); - try { - await topicsApiClient.deleteTopic({ - clusterName, - topicName, - }); - dispatch(actions.deleteTopicAction.success(topicName)); - } catch (e) { - dispatch(actions.deleteTopicAction.failure()); - } -}; +export const deleteTopic = + (clusterName: ClusterName, topicName: TopicName): PromiseThunkResult => + async (dispatch) => { + dispatch(actions.deleteTopicAction.request()); + try { + await topicsApiClient.deleteTopic({ + clusterName, + topicName, + }); + dispatch(actions.deleteTopicAction.success(topicName)); + } catch (e) { + dispatch(actions.deleteTopicAction.failure()); + } + }; diff --git a/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts b/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts index 1da0cf9110..6aa5a6f228 100644 --- a/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts +++ b/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts @@ -18,12 +18,10 @@ export const getTopicListTotalPages = (state: RootState) => topicsState(state).totalPages; const getTopicListFetchingStatus = createFetchingSelector('GET_TOPICS'); -const getTopicDetailsFetchingStatus = createFetchingSelector( - 'GET_TOPIC_DETAILS' -); -const getTopicMessagesFetchingStatus = createFetchingSelector( - 'GET_TOPIC_MESSAGES' -); +const getTopicDetailsFetchingStatus = + createFetchingSelector('GET_TOPIC_DETAILS'); +const getTopicMessagesFetchingStatus = + createFetchingSelector('GET_TOPIC_MESSAGES'); const getTopicConfigFetchingStatus = createFetchingSelector('GET_TOPIC_CONFIG'); const getTopicCreationStatus = createFetchingSelector('POST_TOPIC'); const getTopicUpdateStatus = createFetchingSelector('PATCH_TOPIC'); @@ -123,3 +121,8 @@ export const getTopicConfigByParamName = createSelector( return byParamName; } ); + +export const getIsTopicInternal = createSelector( + getTopicByName, + ({ internal }) => !!internal +); diff --git a/kafka-ui-react-app/src/redux/store/configureStore/mockStoreCreator.ts b/kafka-ui-react-app/src/redux/store/configureStore/mockStoreCreator.ts index 373edbe3a0..9c20ad7f1a 100644 --- a/kafka-ui-react-app/src/redux/store/configureStore/mockStoreCreator.ts +++ b/kafka-ui-react-app/src/redux/store/configureStore/mockStoreCreator.ts @@ -6,9 +6,7 @@ import { RootState, Action } from 'redux/interfaces'; const middlewares: Array = [thunk]; type DispatchExts = ThunkDispatch; -const mockStoreCreator: MockStoreCreator< - RootState, - DispatchExts -> = configureMockStore(middlewares); +const mockStoreCreator: MockStoreCreator = + configureMockStore(middlewares); export default mockStoreCreator(); diff --git a/pom.xml b/pom.xml index 95895193a4..6d83ebfbbd 100644 --- a/pom.xml +++ b/pom.xml @@ -6,6 +6,7 @@ kafka-ui-contract kafka-ui-api + kafka-ui-e2e-checks