Merge master
This commit is contained in:
commit
a0b78f68df
40 changed files with 1998 additions and 771 deletions
|
@ -38,8 +38,8 @@
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-security-oauth2-client</artifactId>
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.provectus</groupId>
|
<groupId>com.provectus</groupId>
|
||||||
|
|
|
@ -139,7 +139,9 @@ public class ClusterUtil {
|
||||||
|
|
||||||
public static InternalTopic mapToInternalTopic(TopicDescription topicDescription) {
|
public static InternalTopic mapToInternalTopic(TopicDescription topicDescription) {
|
||||||
var topic = InternalTopic.builder();
|
var topic = InternalTopic.builder();
|
||||||
topic.internal(topicDescription.isInternal());
|
topic.internal(
|
||||||
|
topicDescription.isInternal() || topicDescription.name().startsWith("_")
|
||||||
|
);
|
||||||
topic.name(topicDescription.name());
|
topic.name(topicDescription.name());
|
||||||
|
|
||||||
List<InternalPartition> partitions = topicDescription.partitions().stream().map(
|
List<InternalPartition> partitions = topicDescription.partitions().stream().map(
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
kafka:
|
kafka:
|
||||||
clusters:
|
|
||||||
- name: local
|
|
||||||
bootstrapServers: localhost:9092
|
|
||||||
zookeeper: localhost:2181
|
|
||||||
schemaRegistry: http://localhost:8081
|
|
||||||
admin-client-timeout: 5000
|
admin-client-timeout: 5000
|
||||||
zookeeper:
|
zookeeper:
|
||||||
connection-timeout: 1000
|
connection-timeout: 1000
|
||||||
|
|
3
kafka-ui-e2e-checks/.env.example
Normal file
3
kafka-ui-e2e-checks/.env.example
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
USE_LOCAL_BROWSER=true
|
||||||
|
SHOULD_START_SELENOID=false
|
||||||
|
TURN_OFF_SCREENSHOTS=true
|
6
kafka-ui-e2e-checks/.gitignore
vendored
Normal file
6
kafka-ui-e2e-checks/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.env
|
||||||
|
build/
|
||||||
|
allure-results/
|
||||||
|
selenoid/video/
|
||||||
|
target/
|
||||||
|
selenoid/logs/
|
117
kafka-ui-e2e-checks/README.md
Normal file
117
kafka-ui-e2e-checks/README.md
Normal file
|
@ -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
|
||||||
|
|
25
kafka-ui-e2e-checks/docker/selenoid.yaml
Normal file
25
kafka-ui-e2e-checks/docker/selenoid.yaml
Normal file
|
@ -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" ]
|
180
kafka-ui-e2e-checks/pom.xml
Normal file
180
kafka-ui-e2e-checks/pom.xml
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>kafka-ui</artifactId>
|
||||||
|
<groupId>com.provectus</groupId>
|
||||||
|
<version>0.0.11-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>kafka-ui-e2e-checks</artifactId>
|
||||||
|
<properties>
|
||||||
|
<junit.version>5.7.0</junit.version>
|
||||||
|
<aspectj.version>1.9.6</aspectj.version>
|
||||||
|
<allure.version>2.13.7</allure.version>
|
||||||
|
<testcontainers.version>1.15.2</testcontainers.version>
|
||||||
|
<selenide.version>5.16.2</selenide.version>
|
||||||
|
<assertj.version>3.17.1</assertj.version>
|
||||||
|
<google.auto-service.version>1.0-rc7</google.auto-service.version>
|
||||||
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
|
<slf4j.version>1.7.29</slf4j.version>
|
||||||
|
<testcontainers.junit-jupiter.version>1.15.1</testcontainers.junit-jupiter.version>
|
||||||
|
<allure.java-commons.version>2.13.6</allure.java-commons.version>
|
||||||
|
<dotenv.version>2.2.0</dotenv.version>
|
||||||
|
<junit.platform-launcher.version>1.6.2</junit.platform-launcher.version>
|
||||||
|
<allure.maven-plugin.version>2.6</allure.maven-plugin.version>
|
||||||
|
<ashot.version>1.5.4</ashot.version>
|
||||||
|
<allure.screendiff-plugin.version>2.13.9</allure.screendiff-plugin.version>
|
||||||
|
<maven.surefire-plugin.version>2.22.2</maven.surefire-plugin.version>
|
||||||
|
<allure-maven.version>2.10.0</allure-maven.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kafka</groupId>
|
||||||
|
<artifactId>kafka_2.13</artifactId>
|
||||||
|
<version>${kafka.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>testcontainers</artifactId>
|
||||||
|
<version>${testcontainers.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.qameta.allure</groupId>
|
||||||
|
<artifactId>allure-junit5</artifactId>
|
||||||
|
<version>${allure.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.codeborne</groupId>
|
||||||
|
<artifactId>selenide</artifactId>
|
||||||
|
<version>${selenide.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.qameta.allure</groupId>
|
||||||
|
<artifactId>allure-selenide</artifactId>
|
||||||
|
<version>${allure.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hamcrest</groupId>
|
||||||
|
<artifactId>hamcrest</artifactId>
|
||||||
|
<version>${hamcrest.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>${assertj.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.auto.service</groupId>
|
||||||
|
<artifactId>auto-service</artifactId>
|
||||||
|
<version>${google.auto-service.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${org.projectlombok.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjrt</artifactId>
|
||||||
|
<version>${aspectj.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${testcontainers.junit-jupiter.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.qameta.allure</groupId>
|
||||||
|
<artifactId>allure-java-commons</artifactId>
|
||||||
|
<version>${allure.java-commons.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.cdimascio</groupId>
|
||||||
|
<artifactId>dotenv-java</artifactId>
|
||||||
|
<version>${dotenv.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.platform</groupId>
|
||||||
|
<artifactId>junit-platform-launcher</artifactId>
|
||||||
|
<version>${junit.platform-launcher.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.yandex.qatools.allure</groupId>
|
||||||
|
<artifactId>allure-maven-plugin</artifactId>
|
||||||
|
<version>${allure.maven-plugin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.yandex.qatools.ashot</groupId>
|
||||||
|
<artifactId>ashot</artifactId>
|
||||||
|
<version>${ashot.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-remote-driver</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.qameta.allure.plugins</groupId>
|
||||||
|
<artifactId>screen-diff-plugin</artifactId>
|
||||||
|
<version>${allure.screendiff-plugin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven.surefire-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<argLine>
|
||||||
|
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
|
||||||
|
</argLine>
|
||||||
|
</configuration>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjweaver</artifactId>
|
||||||
|
<version>${aspectj.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.qameta.allure</groupId>
|
||||||
|
<artifactId>allure-maven</artifactId>
|
||||||
|
<version>${allure-maven.version}</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${maven.compiler.source}</source>
|
||||||
|
<target>${maven.compiler.target}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
BIN
kafka-ui-e2e-checks/screenshots/main.png
Normal file
BIN
kafka-ui-e2e-checks/screenshots/main.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 227 KiB |
11
kafka-ui-e2e-checks/selenoid/config/browsers.json
Normal file
11
kafka-ui-e2e-checks/selenoid/config/browsers.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"chrome": {
|
||||||
|
"default": "86.0",
|
||||||
|
"versions": {
|
||||||
|
"86.0": {
|
||||||
|
"image": "selenoid/vnc:chrome_86.0",
|
||||||
|
"port": "4444"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
kafka-ui-e2e-checks/src/main/resources/allure.properties
Normal file
2
kafka-ui-e2e-checks/src/main/resources/allure.properties
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
allure.results.directory=allure-results
|
||||||
|
allure.link.issue.pattern=https://github.com/provectus/kafka-ui/issues/{}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()) + ')';
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
869
kafka-ui-react-app/package-lock.json
generated
869
kafka-ui-react-app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -89,7 +89,7 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^4.20.0",
|
"@typescript-eslint/eslint-plugin": "^4.20.0",
|
||||||
"@typescript-eslint/parser": "^4.20.0",
|
"@typescript-eslint/parser": "^4.20.0",
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.0",
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^9.0.1",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-to-json": "^3.6.1",
|
"enzyme-to-json": "^3.6.1",
|
||||||
"eslint": "^7.22.0",
|
"eslint": "^7.22.0",
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
"fetch-mock-jest": "^1.5.1",
|
"fetch-mock-jest": "^1.5.1",
|
||||||
"husky": "^6.0.0",
|
"husky": "^6.0.0",
|
||||||
"jest-sonar-reporter": "^2.0.0",
|
"jest-sonar-reporter": "^2.0.0",
|
||||||
"lint-staged": "^10.5.4",
|
"lint-staged": "^11.0.0",
|
||||||
"node-sass": "^5.0.0",
|
"node-sass": "^5.0.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
|
|
|
@ -18,9 +18,8 @@ const mapStateToProps = (state: RootState) => ({
|
||||||
isFetching: getIsSchemaListFetching(state),
|
isFetching: getIsSchemaListFetching(state),
|
||||||
schemas: getSchemaList(state),
|
schemas: getSchemaList(state),
|
||||||
globalSchemaCompatibilityLevel: getGlobalSchemaCompatibilityLevel(state),
|
globalSchemaCompatibilityLevel: getGlobalSchemaCompatibilityLevel(state),
|
||||||
isGlobalSchemaCompatibilityLevelFetched: getGlobalSchemaCompatibilityLevelFetched(
|
isGlobalSchemaCompatibilityLevelFetched:
|
||||||
state
|
getGlobalSchemaCompatibilityLevelFetched(state),
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
|
|
@ -23,10 +23,8 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||||
clusterName,
|
clusterName,
|
||||||
clearTopicMessages,
|
clearTopicMessages,
|
||||||
}) => {
|
}) => {
|
||||||
const [
|
const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] =
|
||||||
isDeleteTopicConfirmationVisible,
|
React.useState(false);
|
||||||
setDeleteTopicConfirmationVisible,
|
|
||||||
] = React.useState(false);
|
|
||||||
|
|
||||||
const outOfSyncReplicas = React.useMemo(() => {
|
const outOfSyncReplicas = React.useMemo(() => {
|
||||||
if (partitions === undefined || partitions.length === 0) {
|
if (partitions === undefined || partitions.length === 0) {
|
||||||
|
@ -66,7 +64,9 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||||
{internal ? 'Internal' : 'External'}
|
{internal ? 'Internal' : 'External'}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td className="topic-action-block">
|
||||||
|
{!internal ? (
|
||||||
|
<>
|
||||||
<div className="has-text-right">
|
<div className="has-text-right">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label={
|
label={
|
||||||
|
@ -93,6 +93,8 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||||
>
|
>
|
||||||
Are you sure want to remove <b>{name}</b> topic?
|
Are you sure want to remove <b>{name}</b> topic?
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,28 +28,31 @@ describe('ListItem', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
it('triggers the deleting messages when clicked on the delete messages button', () => {
|
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');
|
component.find('DropdownItem').at(0).simulate('click');
|
||||||
expect(mockDeleteMessages).toBeCalledTimes(1);
|
expect(mockDeleteMessages).toBeCalledTimes(1);
|
||||||
expect(mockDeleteMessages).toBeCalledWith(
|
expect(mockDeleteMessages).toBeCalledWith(
|
||||||
clusterName,
|
clusterName,
|
||||||
internalTopicPayload.name
|
externalTopicPayload.name
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers the deleteTopic when clicked on the delete button', () => {
|
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();
|
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
|
||||||
wrapper.find('DropdownItem').at(1).simulate('click');
|
wrapper.find('DropdownItem').at(1).simulate('click');
|
||||||
const modal = wrapper.find('mock-ConfirmationModal');
|
const modal = wrapper.find('mock-ConfirmationModal');
|
||||||
expect(modal.prop('isOpen')).toBeTruthy();
|
expect(modal.prop('isOpen')).toBeTruthy();
|
||||||
modal.simulate('confirm');
|
modal.simulate('confirm');
|
||||||
expect(mockDelete).toBeCalledTimes(1);
|
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', () => {
|
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();
|
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
|
||||||
wrapper.find('DropdownItem').last().simulate('click');
|
wrapper.find('DropdownItem').last().simulate('click');
|
||||||
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeTruthy();
|
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeTruthy();
|
||||||
|
|
|
@ -19,6 +19,7 @@ import SettingsContainer from './Settings/SettingsContainer';
|
||||||
interface Props extends Topic, TopicDetails {
|
interface Props extends Topic, TopicDetails {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
topicName: TopicName;
|
topicName: TopicName;
|
||||||
|
isInternal: boolean;
|
||||||
deleteTopic: (clusterName: ClusterName, topicName: TopicName) => void;
|
deleteTopic: (clusterName: ClusterName, topicName: TopicName) => void;
|
||||||
clearTopicMessages(clusterName: ClusterName, topicName: TopicName): void;
|
clearTopicMessages(clusterName: ClusterName, topicName: TopicName): void;
|
||||||
}
|
}
|
||||||
|
@ -26,15 +27,14 @@ interface Props extends Topic, TopicDetails {
|
||||||
const Details: React.FC<Props> = ({
|
const Details: React.FC<Props> = ({
|
||||||
clusterName,
|
clusterName,
|
||||||
topicName,
|
topicName,
|
||||||
|
isInternal,
|
||||||
deleteTopic,
|
deleteTopic,
|
||||||
clearTopicMessages,
|
clearTopicMessages,
|
||||||
}) => {
|
}) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { isReadOnly } = React.useContext(ClusterContext);
|
const { isReadOnly } = React.useContext(ClusterContext);
|
||||||
const [
|
const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] =
|
||||||
isDeleteTopicConfirmationVisible,
|
React.useState(false);
|
||||||
setDeleteTopicConfirmationVisible,
|
|
||||||
] = React.useState(false);
|
|
||||||
const deleteTopicHandler = React.useCallback(() => {
|
const deleteTopicHandler = React.useCallback(() => {
|
||||||
deleteTopic(clusterName, topicName);
|
deleteTopic(clusterName, topicName);
|
||||||
history.push(clusterTopicsPath(clusterName));
|
history.push(clusterTopicsPath(clusterName));
|
||||||
|
@ -74,8 +74,8 @@ const Details: React.FC<Props> = ({
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
<div className="navbar-end">
|
<div className="navbar-end">
|
||||||
|
{!isReadOnly && !isInternal ? (
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
{!isReadOnly && (
|
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -107,8 +107,8 @@ const Details: React.FC<Props> = ({
|
||||||
Are you sure want to remove <b>{topicName}</b> topic?
|
Are you sure want to remove <b>{topicName}</b> topic?
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
</>
|
</>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { connect } from 'react-redux';
|
||||||
import { ClusterName, RootState, TopicName } from 'redux/interfaces';
|
import { ClusterName, RootState, TopicName } from 'redux/interfaces';
|
||||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||||
import { deleteTopic, clearTopicMessages } from 'redux/actions';
|
import { deleteTopic, clearTopicMessages } from 'redux/actions';
|
||||||
|
import { getIsTopicInternal } from 'redux/reducers/topics/selectors';
|
||||||
|
|
||||||
import Details from './Details';
|
import Details from './Details';
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ const mapStateToProps = (
|
||||||
) => ({
|
) => ({
|
||||||
clusterName,
|
clusterName,
|
||||||
topicName,
|
topicName,
|
||||||
|
isInternal: getIsTopicInternal(state, topicName),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
|
|
@ -50,9 +50,8 @@ const Messages: React.FC<Props> = ({
|
||||||
fetchTopicMessages,
|
fetchTopicMessages,
|
||||||
}) => {
|
}) => {
|
||||||
const [searchQuery, setSearchQuery] = React.useState<string>('');
|
const [searchQuery, setSearchQuery] = React.useState<string>('');
|
||||||
const [searchTimestamp, setSearchTimestamp] = React.useState<Date | null>(
|
const [searchTimestamp, setSearchTimestamp] =
|
||||||
null
|
React.useState<Date | null>(null);
|
||||||
);
|
|
||||||
const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
|
const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
|
||||||
const [selectedSeekType, setSelectedSeekType] = React.useState<SeekType>(
|
const [selectedSeekType, setSelectedSeekType] = React.useState<SeekType>(
|
||||||
SeekType.OFFSET
|
SeekType.OFFSET
|
||||||
|
|
|
@ -75,6 +75,7 @@ const Overview: React.FC<Props> = ({
|
||||||
<td>{offsetMin}</td>
|
<td>{offsetMin}</td>
|
||||||
<td>{offsetMax}</td>
|
<td>{offsetMax}</td>
|
||||||
<td className="has-text-right">
|
<td className="has-text-right">
|
||||||
|
{!internal ? (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label={
|
label={
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
|
@ -91,6 +92,7 @@ const Overview: React.FC<Props> = ({
|
||||||
<span className="has-text-danger">Clear Messages</span>
|
<span className="has-text-danger">Clear Messages</span>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
) : null}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -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(
|
||||||
|
<Overview
|
||||||
|
name={mockTopicName}
|
||||||
|
partitions={mockPartitions}
|
||||||
|
internal={mockInternal}
|
||||||
|
clusterName={mockClusterName}
|
||||||
|
topicName={mockTopicName}
|
||||||
|
clearTopicMessages={mockClearTopicMessages}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.exists('Dropdown')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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(
|
||||||
|
<StaticRouter>
|
||||||
|
<ClusterContext.Provider
|
||||||
|
value={{
|
||||||
|
isReadOnly: true,
|
||||||
|
hasKafkaConnectConfigured: true,
|
||||||
|
hasSchemaRegistryConfigured: true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Details
|
||||||
|
clusterName={mockClusterName}
|
||||||
|
topicName={internalTopicPayload.name}
|
||||||
|
name={internalTopicPayload.name}
|
||||||
|
isInternal={mockInternalTopicPayload}
|
||||||
|
deleteTopic={mockDelete}
|
||||||
|
clearTopicMessages={mockClearTopicMessages}
|
||||||
|
/>
|
||||||
|
</ClusterContext.Provider>
|
||||||
|
</StaticRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.exists('button')).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when it does not have readonly flag', () => {
|
||||||
|
it('renders the Action button a Topic', () => {
|
||||||
|
const component = mount(
|
||||||
|
<StaticRouter>
|
||||||
|
<ClusterContext.Provider
|
||||||
|
value={{
|
||||||
|
isReadOnly: false,
|
||||||
|
hasKafkaConnectConfigured: true,
|
||||||
|
hasSchemaRegistryConfigured: true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Details
|
||||||
|
clusterName={mockClusterName}
|
||||||
|
topicName={internalTopicPayload.name}
|
||||||
|
name={internalTopicPayload.name}
|
||||||
|
isInternal={mockExternalTopicPayload}
|
||||||
|
deleteTopic={mockDelete}
|
||||||
|
clearTopicMessages={mockClearTopicMessages}
|
||||||
|
/>
|
||||||
|
</ClusterContext.Provider>
|
||||||
|
</StaticRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.exists('button')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -39,9 +39,8 @@ const CustomParamSelect: React.FC<CustomParamSelectProps> = ({
|
||||||
return valid || 'Custom Parameter must be unique';
|
return valid || 'Custom Parameter must be unique';
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChange = (inputName: string) => (
|
const onChange =
|
||||||
event: React.ChangeEvent<HTMLSelectElement>
|
(inputName: string) => (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
) => {
|
|
||||||
trigger(inputName);
|
trigger(inputName);
|
||||||
onNameChange(index, event.target.value);
|
onNameChange(index, event.target.value);
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,10 +35,8 @@ const CustomParams: React.FC<Props> = ({ isSubmitting, config }) => {
|
||||||
)
|
)
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const [
|
const [formCustomParams, setFormCustomParams] =
|
||||||
formCustomParams,
|
React.useState<TopicFormCustomParams>({
|
||||||
setFormCustomParams,
|
|
||||||
] = React.useState<TopicFormCustomParams>({
|
|
||||||
byIndex,
|
byIndex,
|
||||||
allIndexes: Object.keys(byIndex),
|
allIndexes: Object.keys(byIndex),
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,9 +6,9 @@ import * as actions from 'redux/actions/actions';
|
||||||
const apiClientConf = new Configuration(BASE_PARAMS);
|
const apiClientConf = new Configuration(BASE_PARAMS);
|
||||||
export const brokersApiClient = new BrokersApi(apiClientConf);
|
export const brokersApiClient = new BrokersApi(apiClientConf);
|
||||||
|
|
||||||
export const fetchBrokers = (
|
export const fetchBrokers =
|
||||||
clusterName: ClusterName
|
(clusterName: ClusterName): PromiseThunkResult =>
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
async (dispatch) => {
|
||||||
dispatch(actions.fetchBrokersAction.request());
|
dispatch(actions.fetchBrokersAction.request());
|
||||||
try {
|
try {
|
||||||
const payload = await brokersApiClient.getBrokers({ clusterName });
|
const payload = await brokersApiClient.getBrokers({ clusterName });
|
||||||
|
@ -18,10 +18,9 @@ export const fetchBrokers = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchBrokerMetrics = (
|
export const fetchBrokerMetrics =
|
||||||
clusterName: ClusterName,
|
(clusterName: ClusterName, brokerId: BrokerId): PromiseThunkResult =>
|
||||||
brokerId: BrokerId
|
async (dispatch) => {
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
|
||||||
dispatch(actions.fetchBrokerMetricsAction.request());
|
dispatch(actions.fetchBrokerMetricsAction.request());
|
||||||
try {
|
try {
|
||||||
const payload = await brokersApiClient.getBrokersMetrics({
|
const payload = await brokersApiClient.getBrokersMetrics({
|
||||||
|
|
|
@ -16,9 +16,9 @@ export const fetchClustersList = (): PromiseThunkResult => async (dispatch) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchClusterStats = (
|
export const fetchClusterStats =
|
||||||
clusterName: ClusterName
|
(clusterName: ClusterName): PromiseThunkResult =>
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
async (dispatch) => {
|
||||||
dispatch(actions.fetchClusterStatsAction.request());
|
dispatch(actions.fetchClusterStatsAction.request());
|
||||||
try {
|
try {
|
||||||
const payload = await clustersApiClient.getClusterStats({ clusterName });
|
const payload = await clustersApiClient.getClusterStats({ clusterName });
|
||||||
|
@ -28,12 +28,14 @@ export const fetchClusterStats = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchClusterMetrics = (
|
export const fetchClusterMetrics =
|
||||||
clusterName: ClusterName
|
(clusterName: ClusterName): PromiseThunkResult =>
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
async (dispatch) => {
|
||||||
dispatch(actions.fetchClusterMetricsAction.request());
|
dispatch(actions.fetchClusterMetricsAction.request());
|
||||||
try {
|
try {
|
||||||
const payload = await clustersApiClient.getClusterMetrics({ clusterName });
|
const payload = await clustersApiClient.getClusterMetrics({
|
||||||
|
clusterName,
|
||||||
|
});
|
||||||
dispatch(actions.fetchClusterMetricsAction.success(payload));
|
dispatch(actions.fetchClusterMetricsAction.success(payload));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(actions.fetchClusterMetricsAction.failure());
|
dispatch(actions.fetchClusterMetricsAction.failure());
|
||||||
|
|
|
@ -10,9 +10,9 @@ import * as actions from 'redux/actions/actions';
|
||||||
const apiClientConf = new Configuration(BASE_PARAMS);
|
const apiClientConf = new Configuration(BASE_PARAMS);
|
||||||
export const consumerGroupsApiClient = new ConsumerGroupsApi(apiClientConf);
|
export const consumerGroupsApiClient = new ConsumerGroupsApi(apiClientConf);
|
||||||
|
|
||||||
export const fetchConsumerGroupsList = (
|
export const fetchConsumerGroupsList =
|
||||||
clusterName: ClusterName
|
(clusterName: ClusterName): PromiseThunkResult =>
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
async (dispatch) => {
|
||||||
dispatch(actions.fetchConsumerGroupsAction.request());
|
dispatch(actions.fetchConsumerGroupsAction.request());
|
||||||
try {
|
try {
|
||||||
const consumerGroups = await consumerGroupsApiClient.getConsumerGroups({
|
const consumerGroups = await consumerGroupsApiClient.getConsumerGroups({
|
||||||
|
@ -24,18 +24,19 @@ export const fetchConsumerGroupsList = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchConsumerGroupDetails = (
|
export const fetchConsumerGroupDetails =
|
||||||
|
(
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
consumerGroupID: ConsumerGroupID
|
consumerGroupID: ConsumerGroupID
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
): PromiseThunkResult =>
|
||||||
|
async (dispatch) => {
|
||||||
dispatch(actions.fetchConsumerGroupDetailsAction.request());
|
dispatch(actions.fetchConsumerGroupDetailsAction.request());
|
||||||
try {
|
try {
|
||||||
const consumerGroupDetails = await consumerGroupsApiClient.getConsumerGroup(
|
const consumerGroupDetails =
|
||||||
{
|
await consumerGroupsApiClient.getConsumerGroup({
|
||||||
clusterName,
|
clusterName,
|
||||||
id: consumerGroupID,
|
id: consumerGroupID,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
dispatch(
|
dispatch(
|
||||||
actions.fetchConsumerGroupDetailsAction.success({
|
actions.fetchConsumerGroupDetailsAction.success({
|
||||||
consumerGroupID,
|
consumerGroupID,
|
||||||
|
|
|
@ -20,9 +20,9 @@ import { isEqual } from 'lodash';
|
||||||
const apiClientConf = new Configuration(BASE_PARAMS);
|
const apiClientConf = new Configuration(BASE_PARAMS);
|
||||||
export const schemasApiClient = new SchemasApi(apiClientConf);
|
export const schemasApiClient = new SchemasApi(apiClientConf);
|
||||||
|
|
||||||
export const fetchSchemasByClusterName = (
|
export const fetchSchemasByClusterName =
|
||||||
clusterName: ClusterName
|
(clusterName: ClusterName): PromiseThunkResult<void> =>
|
||||||
): PromiseThunkResult<void> => async (dispatch) => {
|
async (dispatch) => {
|
||||||
dispatch(actions.fetchSchemasByClusterNameAction.request());
|
dispatch(actions.fetchSchemasByClusterNameAction.request());
|
||||||
try {
|
try {
|
||||||
const schemas = await schemasApiClient.getSchemas({ clusterName });
|
const schemas = await schemasApiClient.getSchemas({ clusterName });
|
||||||
|
@ -32,10 +32,9 @@ export const fetchSchemasByClusterName = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchSchemaVersions = (
|
export const fetchSchemaVersions =
|
||||||
clusterName: ClusterName,
|
(clusterName: ClusterName, subject: SchemaName): PromiseThunkResult<void> =>
|
||||||
subject: SchemaName
|
async (dispatch) => {
|
||||||
): PromiseThunkResult<void> => async (dispatch) => {
|
|
||||||
if (!subject) return;
|
if (!subject) return;
|
||||||
dispatch(actions.fetchSchemaVersionsAction.request());
|
dispatch(actions.fetchSchemaVersionsAction.request());
|
||||||
try {
|
try {
|
||||||
|
@ -49,9 +48,9 @@ export const fetchSchemaVersions = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchGlobalSchemaCompatibilityLevel = (
|
export const fetchGlobalSchemaCompatibilityLevel =
|
||||||
clusterName: ClusterName
|
(clusterName: ClusterName): PromiseThunkResult<void> =>
|
||||||
): PromiseThunkResult<void> => async (dispatch) => {
|
async (dispatch) => {
|
||||||
dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.request());
|
dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.request());
|
||||||
try {
|
try {
|
||||||
const result = await schemasApiClient.getGlobalSchemaCompatibilityLevel({
|
const result = await schemasApiClient.getGlobalSchemaCompatibilityLevel({
|
||||||
|
@ -67,10 +66,12 @@ export const fetchGlobalSchemaCompatibilityLevel = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateGlobalSchemaCompatibilityLevel = (
|
export const updateGlobalSchemaCompatibilityLevel =
|
||||||
|
(
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
compatibilityLevel: CompatibilityLevelCompatibilityEnum
|
compatibilityLevel: CompatibilityLevelCompatibilityEnum
|
||||||
): PromiseThunkResult<void> => async (dispatch) => {
|
): PromiseThunkResult<void> =>
|
||||||
|
async (dispatch) => {
|
||||||
dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.request());
|
dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.request());
|
||||||
try {
|
try {
|
||||||
await schemasApiClient.updateGlobalSchemaCompatibilityLevel({
|
await schemasApiClient.updateGlobalSchemaCompatibilityLevel({
|
||||||
|
@ -87,10 +88,12 @@ export const updateGlobalSchemaCompatibilityLevel = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createSchema = (
|
export const createSchema =
|
||||||
|
(
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
newSchemaSubject: NewSchemaSubject
|
newSchemaSubject: NewSchemaSubject
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
): PromiseThunkResult =>
|
||||||
|
async (dispatch) => {
|
||||||
dispatch(actions.createSchemaAction.request());
|
dispatch(actions.createSchemaAction.request());
|
||||||
try {
|
try {
|
||||||
const schema: SchemaSubject = await schemasApiClient.createNewSchema({
|
const schema: SchemaSubject = await schemasApiClient.createNewSchema({
|
||||||
|
@ -110,14 +113,16 @@ export const createSchema = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateSchema = (
|
export const updateSchema =
|
||||||
|
(
|
||||||
latestSchema: SchemaSubject,
|
latestSchema: SchemaSubject,
|
||||||
newSchema: string,
|
newSchema: string,
|
||||||
newSchemaType: SchemaType,
|
newSchemaType: SchemaType,
|
||||||
newCompatibilityLevel: CompatibilityLevelCompatibilityEnum,
|
newCompatibilityLevel: CompatibilityLevelCompatibilityEnum,
|
||||||
clusterName: string,
|
clusterName: string,
|
||||||
subject: string
|
subject: string
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
): PromiseThunkResult =>
|
||||||
|
async (dispatch) => {
|
||||||
dispatch(actions.updateSchemaAction.request());
|
dispatch(actions.updateSchemaAction.request());
|
||||||
try {
|
try {
|
||||||
let schema: SchemaSubject = latestSchema;
|
let schema: SchemaSubject = latestSchema;
|
||||||
|
@ -156,10 +161,9 @@ export const updateSchema = (
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const deleteSchema = (
|
export const deleteSchema =
|
||||||
clusterName: ClusterName,
|
(clusterName: ClusterName, subject: string): PromiseThunkResult =>
|
||||||
subject: string
|
async (dispatch) => {
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
|
||||||
dispatch(actions.deleteSchemaAction.request());
|
dispatch(actions.deleteSchemaAction.request());
|
||||||
try {
|
try {
|
||||||
await schemasApiClient.deleteSchema({
|
await schemasApiClient.deleteSchema({
|
||||||
|
|
|
@ -32,9 +32,9 @@ export interface FetchTopicsListParams {
|
||||||
perPage?: number;
|
perPage?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchTopicsList = (
|
export const fetchTopicsList =
|
||||||
params: FetchTopicsListParams
|
(params: FetchTopicsListParams): PromiseThunkResult =>
|
||||||
): PromiseThunkResult => async (dispatch, getState) => {
|
async (dispatch, getState) => {
|
||||||
dispatch(actions.fetchTopicsListAction.request());
|
dispatch(actions.fetchTopicsListAction.request());
|
||||||
try {
|
try {
|
||||||
const { topics, pageCount } = await topicsApiClient.getTopics(params);
|
const { topics, pageCount } = await topicsApiClient.getTopics(params);
|
||||||
|
@ -63,11 +63,13 @@ export const fetchTopicsList = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTopicMessages = (
|
export const fetchTopicMessages =
|
||||||
|
(
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
topicName: TopicName,
|
topicName: TopicName,
|
||||||
queryParams: Partial<TopicMessageQueryParams>
|
queryParams: Partial<TopicMessageQueryParams>
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
): PromiseThunkResult =>
|
||||||
|
async (dispatch) => {
|
||||||
dispatch(actions.fetchTopicMessagesAction.request());
|
dispatch(actions.fetchTopicMessagesAction.request());
|
||||||
try {
|
try {
|
||||||
const messages = await messagesApiClient.getTopicMessages({
|
const messages = await messagesApiClient.getTopicMessages({
|
||||||
|
@ -81,11 +83,13 @@ export const fetchTopicMessages = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearTopicMessages = (
|
export const clearTopicMessages =
|
||||||
|
(
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
topicName: TopicName,
|
topicName: TopicName,
|
||||||
partitions?: number[]
|
partitions?: number[]
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
): PromiseThunkResult =>
|
||||||
|
async (dispatch) => {
|
||||||
dispatch(actions.clearMessagesTopicAction.request());
|
dispatch(actions.clearMessagesTopicAction.request());
|
||||||
try {
|
try {
|
||||||
await messagesApiClient.deleteTopicMessages({
|
await messagesApiClient.deleteTopicMessages({
|
||||||
|
@ -105,10 +109,9 @@ export const clearTopicMessages = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTopicDetails = (
|
export const fetchTopicDetails =
|
||||||
clusterName: ClusterName,
|
(clusterName: ClusterName, topicName: TopicName): PromiseThunkResult =>
|
||||||
topicName: TopicName
|
async (dispatch, getState) => {
|
||||||
): PromiseThunkResult => async (dispatch, getState) => {
|
|
||||||
dispatch(actions.fetchTopicDetailsAction.request());
|
dispatch(actions.fetchTopicDetailsAction.request());
|
||||||
try {
|
try {
|
||||||
const topicDetails = await topicsApiClient.getTopicDetails({
|
const topicDetails = await topicsApiClient.getTopicDetails({
|
||||||
|
@ -132,10 +135,9 @@ export const fetchTopicDetails = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTopicConfig = (
|
export const fetchTopicConfig =
|
||||||
clusterName: ClusterName,
|
(clusterName: ClusterName, topicName: TopicName): PromiseThunkResult =>
|
||||||
topicName: TopicName
|
async (dispatch, getState) => {
|
||||||
): PromiseThunkResult => async (dispatch, getState) => {
|
|
||||||
dispatch(actions.fetchTopicConfigAction.request());
|
dispatch(actions.fetchTopicConfigAction.request());
|
||||||
try {
|
try {
|
||||||
const config = await topicsApiClient.getTopicConfigs({
|
const config = await topicsApiClient.getTopicConfigs({
|
||||||
|
@ -229,10 +231,9 @@ const formatTopicUpdate = (form: TopicFormDataRaw): TopicUpdate => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTopic = (
|
export const createTopic =
|
||||||
clusterName: ClusterName,
|
(clusterName: ClusterName, form: TopicFormDataRaw): PromiseThunkResult =>
|
||||||
form: TopicFormDataRaw
|
async (dispatch, getState) => {
|
||||||
): PromiseThunkResult => async (dispatch, getState) => {
|
|
||||||
dispatch(actions.createTopicAction.request());
|
dispatch(actions.createTopicAction.request());
|
||||||
try {
|
try {
|
||||||
const topic: Topic = await topicsApiClient.createTopic({
|
const topic: Topic = await topicsApiClient.createTopic({
|
||||||
|
@ -264,11 +265,13 @@ export const createTopic = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateTopic = (
|
export const updateTopic =
|
||||||
|
(
|
||||||
clusterName: ClusterName,
|
clusterName: ClusterName,
|
||||||
topicName: TopicName,
|
topicName: TopicName,
|
||||||
form: TopicFormDataRaw
|
form: TopicFormDataRaw
|
||||||
): PromiseThunkResult => async (dispatch, getState) => {
|
): PromiseThunkResult =>
|
||||||
|
async (dispatch, getState) => {
|
||||||
dispatch(actions.updateTopicAction.request());
|
dispatch(actions.updateTopicAction.request());
|
||||||
try {
|
try {
|
||||||
const topic: Topic = await topicsApiClient.updateTopic({
|
const topic: Topic = await topicsApiClient.updateTopic({
|
||||||
|
@ -295,10 +298,9 @@ export const updateTopic = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteTopic = (
|
export const deleteTopic =
|
||||||
clusterName: ClusterName,
|
(clusterName: ClusterName, topicName: TopicName): PromiseThunkResult =>
|
||||||
topicName: TopicName
|
async (dispatch) => {
|
||||||
): PromiseThunkResult => async (dispatch) => {
|
|
||||||
dispatch(actions.deleteTopicAction.request());
|
dispatch(actions.deleteTopicAction.request());
|
||||||
try {
|
try {
|
||||||
await topicsApiClient.deleteTopic({
|
await topicsApiClient.deleteTopic({
|
||||||
|
|
|
@ -18,12 +18,10 @@ export const getTopicListTotalPages = (state: RootState) =>
|
||||||
topicsState(state).totalPages;
|
topicsState(state).totalPages;
|
||||||
|
|
||||||
const getTopicListFetchingStatus = createFetchingSelector('GET_TOPICS');
|
const getTopicListFetchingStatus = createFetchingSelector('GET_TOPICS');
|
||||||
const getTopicDetailsFetchingStatus = createFetchingSelector(
|
const getTopicDetailsFetchingStatus =
|
||||||
'GET_TOPIC_DETAILS'
|
createFetchingSelector('GET_TOPIC_DETAILS');
|
||||||
);
|
const getTopicMessagesFetchingStatus =
|
||||||
const getTopicMessagesFetchingStatus = createFetchingSelector(
|
createFetchingSelector('GET_TOPIC_MESSAGES');
|
||||||
'GET_TOPIC_MESSAGES'
|
|
||||||
);
|
|
||||||
const getTopicConfigFetchingStatus = createFetchingSelector('GET_TOPIC_CONFIG');
|
const getTopicConfigFetchingStatus = createFetchingSelector('GET_TOPIC_CONFIG');
|
||||||
const getTopicCreationStatus = createFetchingSelector('POST_TOPIC');
|
const getTopicCreationStatus = createFetchingSelector('POST_TOPIC');
|
||||||
const getTopicUpdateStatus = createFetchingSelector('PATCH_TOPIC');
|
const getTopicUpdateStatus = createFetchingSelector('PATCH_TOPIC');
|
||||||
|
@ -123,3 +121,8 @@ export const getTopicConfigByParamName = createSelector(
|
||||||
return byParamName;
|
return byParamName;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getIsTopicInternal = createSelector(
|
||||||
|
getTopicByName,
|
||||||
|
({ internal }) => !!internal
|
||||||
|
);
|
||||||
|
|
|
@ -6,9 +6,7 @@ import { RootState, Action } from 'redux/interfaces';
|
||||||
const middlewares: Array<Middleware> = [thunk];
|
const middlewares: Array<Middleware> = [thunk];
|
||||||
type DispatchExts = ThunkDispatch<RootState, undefined, Action>;
|
type DispatchExts = ThunkDispatch<RootState, undefined, Action>;
|
||||||
|
|
||||||
const mockStoreCreator: MockStoreCreator<
|
const mockStoreCreator: MockStoreCreator<RootState, DispatchExts> =
|
||||||
RootState,
|
configureMockStore<RootState, DispatchExts>(middlewares);
|
||||||
DispatchExts
|
|
||||||
> = configureMockStore<RootState, DispatchExts>(middlewares);
|
|
||||||
|
|
||||||
export default mockStoreCreator();
|
export default mockStoreCreator();
|
||||||
|
|
1
pom.xml
1
pom.xml
|
@ -6,6 +6,7 @@
|
||||||
<modules>
|
<modules>
|
||||||
<module>kafka-ui-contract</module>
|
<module>kafka-ui-contract</module>
|
||||||
<module>kafka-ui-api</module>
|
<module>kafka-ui-api</module>
|
||||||
|
<module>kafka-ui-e2e-checks</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|
Loading…
Add table
Reference in a new issue