Merge branch 'master' of github.com:provectus/kafka-ui into ISSUE_754_acl
Conflicts: kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ReactiveAdminClient.java
This commit is contained in:
commit
a58c2055f5
182 changed files with 6467 additions and 5149 deletions
36
.devcontainer/devcontainer.json
Normal file
36
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "Java",
|
||||||
|
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/java:0-17",
|
||||||
|
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/java:1": {
|
||||||
|
"version": "none",
|
||||||
|
"installMaven": "true",
|
||||||
|
"installGradle": "false"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "java -version",
|
||||||
|
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions" : [
|
||||||
|
"vscjava.vscode-java-pack",
|
||||||
|
"vscjava.vscode-maven",
|
||||||
|
"vscjava.vscode-java-debug",
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"ms-azuretools.vscode-docker",
|
||||||
|
"antfu.vite",
|
||||||
|
"ms-kubernetes-tools.vscode-kubernetes-tools",
|
||||||
|
"github.vscode-pull-request-github"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
.github/workflows/e2e-automation.yml
vendored
5
.github/workflows/e2e-automation.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- name: Pull with Docker
|
- name: Pull with Docker
|
||||||
id: pull_chrome
|
id: pull_chrome
|
||||||
run: |
|
run: |
|
||||||
docker pull selenium/standalone-chrome:103.0
|
docker pull selenoid/vnc_chrome:103.0
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
|
@ -52,6 +52,7 @@ jobs:
|
||||||
id: compose_app
|
id: compose_app
|
||||||
# use the following command until #819 will be fixed
|
# use the following command until #819 will be fixed
|
||||||
run: |
|
run: |
|
||||||
|
docker-compose -f kafka-ui-e2e-checks/docker/selenoid-git.yaml up -d
|
||||||
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
|
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
|
||||||
- name: Run test suite
|
- name: Run test suite
|
||||||
run: |
|
run: |
|
||||||
|
@ -78,7 +79,7 @@ jobs:
|
||||||
uses: Sibz/github-status-action@v1.1.6
|
uses: Sibz/github-status-action@v1.1.6
|
||||||
with:
|
with:
|
||||||
authToken: ${{secrets.GITHUB_TOKEN}}
|
authToken: ${{secrets.GITHUB_TOKEN}}
|
||||||
context: "Test report"
|
context: "Click Details button to open Allure report"
|
||||||
state: "success"
|
state: "success"
|
||||||
sha: ${{ github.sha }}
|
sha: ${{ github.sha }}
|
||||||
target_url: http://kafkaui-allure-reports.s3-website.eu-central-1.amazonaws.com/${{ github.run_number }}
|
target_url: http://kafkaui-allure-reports.s3-website.eu-central-1.amazonaws.com/${{ github.run_number }}
|
||||||
|
|
15
.github/workflows/e2e-checks.yaml
vendored
15
.github/workflows/e2e-checks.yaml
vendored
|
@ -15,20 +15,20 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: Configure AWS credentials for Kafka-UI account
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
uses: aws-actions/configure-aws-credentials@v2
|
||||||
with:
|
with:
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
aws-region: eu-central-1
|
aws-region: eu-central-1
|
||||||
- name: Set the values
|
- name: Set up environment
|
||||||
id: set_env_values
|
id: set_env_values
|
||||||
run: |
|
run: |
|
||||||
cat "./kafka-ui-e2e-checks/.env.ci" >> "./kafka-ui-e2e-checks/.env"
|
cat "./kafka-ui-e2e-checks/.env.ci" >> "./kafka-ui-e2e-checks/.env"
|
||||||
- name: pull docker
|
- name: Pull with Docker
|
||||||
id: pull_chrome
|
id: pull_chrome
|
||||||
run: |
|
run: |
|
||||||
docker pull selenium/standalone-chrome:103.0
|
docker pull selenoid/vnc_chrome:103.0
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
|
@ -40,12 +40,13 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
./mvnw -B -ntp versions:set -DnewVersion=${{ github.event.pull_request.head.sha }}
|
./mvnw -B -ntp versions:set -DnewVersion=${{ github.event.pull_request.head.sha }}
|
||||||
./mvnw -B -V -ntp clean install -Pprod -Dmaven.test.skip=true ${{ github.event.inputs.extraMavenOptions }}
|
./mvnw -B -V -ntp clean install -Pprod -Dmaven.test.skip=true ${{ github.event.inputs.extraMavenOptions }}
|
||||||
- name: compose app
|
- name: Compose with Docker
|
||||||
id: compose_app
|
id: compose_app
|
||||||
# use the following command until #819 will be fixed
|
# use the following command until #819 will be fixed
|
||||||
run: |
|
run: |
|
||||||
|
docker-compose -f kafka-ui-e2e-checks/docker/selenoid-git.yaml up -d
|
||||||
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
|
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
|
||||||
- name: e2e run
|
- name: Run test suite
|
||||||
run: |
|
run: |
|
||||||
./mvnw -B -ntp versions:set -DnewVersion=${{ github.event.pull_request.head.sha }}
|
./mvnw -B -ntp versions:set -DnewVersion=${{ github.event.pull_request.head.sha }}
|
||||||
./mvnw -B -V -ntp -Dsurefire.suiteXmlFiles='src/test/resources/smoke.xml' -f 'kafka-ui-e2e-checks' test -Pprod
|
./mvnw -B -V -ntp -Dsurefire.suiteXmlFiles='src/test/resources/smoke.xml' -f 'kafka-ui-e2e-checks' test -Pprod
|
||||||
|
@ -65,7 +66,7 @@ jobs:
|
||||||
AWS_S3_BUCKET: 'kafkaui-allure-reports'
|
AWS_S3_BUCKET: 'kafkaui-allure-reports'
|
||||||
AWS_REGION: 'eu-central-1'
|
AWS_REGION: 'eu-central-1'
|
||||||
SOURCE_DIR: 'allure-history/allure-results'
|
SOURCE_DIR: 'allure-history/allure-results'
|
||||||
- name: Post the link to allure report
|
- name: Deploy report to Amazon S3
|
||||||
if: always()
|
if: always()
|
||||||
uses: Sibz/github-status-action@v1.1.6
|
uses: Sibz/github-status-action@v1.1.6
|
||||||
with:
|
with:
|
||||||
|
|
5
.github/workflows/e2e-weekly.yml
vendored
5
.github/workflows/e2e-weekly.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
||||||
- name: Pull with Docker
|
- name: Pull with Docker
|
||||||
id: pull_chrome
|
id: pull_chrome
|
||||||
run: |
|
run: |
|
||||||
docker pull selenium/standalone-chrome:103.0
|
docker pull selenoid/vnc_chrome:103.0
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
|
@ -39,6 +39,7 @@ jobs:
|
||||||
id: compose_app
|
id: compose_app
|
||||||
# use the following command until #819 will be fixed
|
# use the following command until #819 will be fixed
|
||||||
run: |
|
run: |
|
||||||
|
docker-compose -f kafka-ui-e2e-checks/docker/selenoid-git.yaml up -d
|
||||||
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
|
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
|
||||||
- name: Run test suite
|
- name: Run test suite
|
||||||
run: |
|
run: |
|
||||||
|
@ -65,7 +66,7 @@ jobs:
|
||||||
uses: Sibz/github-status-action@v1.1.6
|
uses: Sibz/github-status-action@v1.1.6
|
||||||
with:
|
with:
|
||||||
authToken: ${{secrets.GITHUB_TOKEN}}
|
authToken: ${{secrets.GITHUB_TOKEN}}
|
||||||
context: "Test report"
|
context: "Click Details button to open Allure report"
|
||||||
state: "success"
|
state: "success"
|
||||||
sha: ${{ github.sha }}
|
sha: ${{ github.sha }}
|
||||||
target_url: http://kafkaui-allure-reports.s3-website.eu-central-1.amazonaws.com/${{ github.run_number }}
|
target_url: http://kafkaui-allure-reports.s3-website.eu-central-1.amazonaws.com/${{ github.run_number }}
|
||||||
|
|
|
@ -11,14 +11,14 @@ services:
|
||||||
test: wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health
|
test: wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 10
|
||||||
depends_on:
|
depends_on:
|
||||||
kafka0:
|
kafka0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
schemaregistry0:
|
schemaregistry0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
kafka-connect0:
|
kafka-connect0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
KAFKA_CLUSTERS_0_NAME: local
|
KAFKA_CLUSTERS_0_NAME: local
|
||||||
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092
|
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092
|
||||||
|
@ -33,10 +33,10 @@ services:
|
||||||
hostname: kafka0
|
hostname: kafka0
|
||||||
container_name: kafka0
|
container_name: kafka0
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: unset JMX_PORT && KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka0 -Dcom.sun.management.jmxremote.rmi.port=9999" && kafka-broker-api-versions --bootstrap-server=localhost:9092
|
test: unset JMX_PORT && KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka0 -Dcom.sun.management.jmxremote.rmi.port=9999" && kafka-broker-api-versions --bootstrap-server=localhost:9092
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 10
|
||||||
ports:
|
ports:
|
||||||
- "9092:9092"
|
- "9092:9092"
|
||||||
- "9997:9997"
|
- "9997:9997"
|
||||||
|
@ -68,12 +68,12 @@ services:
|
||||||
- 8085:8085
|
- 8085:8085
|
||||||
depends_on:
|
depends_on:
|
||||||
kafka0:
|
kafka0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "timeout", "1", "curl", "--silent", "--fail", "http://schemaregistry0:8085/subjects"]
|
test: [ "CMD", "timeout", "1", "curl", "--silent", "--fail", "http://schemaregistry0:8085/subjects" ]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 10
|
||||||
environment:
|
environment:
|
||||||
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092
|
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092
|
||||||
SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT
|
SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT
|
||||||
|
@ -93,11 +93,11 @@ services:
|
||||||
- 8083:8083
|
- 8083:8083
|
||||||
depends_on:
|
depends_on:
|
||||||
kafka0:
|
kafka0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
schemaregistry0:
|
schemaregistry0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "nc", "127.0.0.1", "8083"]
|
test: [ "CMD", "nc", "127.0.0.1", "8083" ]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
@ -118,8 +118,8 @@ services:
|
||||||
CONNECT_INTERNAL_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
|
CONNECT_INTERNAL_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
|
||||||
CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect0
|
CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect0
|
||||||
CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components"
|
CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components"
|
||||||
# AWS_ACCESS_KEY_ID: ""
|
# AWS_ACCESS_KEY_ID: ""
|
||||||
# AWS_SECRET_ACCESS_KEY: ""
|
# AWS_SECRET_ACCESS_KEY: ""
|
||||||
|
|
||||||
kafka-init-topics:
|
kafka-init-topics:
|
||||||
image: confluentinc/cp-kafka:7.2.1
|
image: confluentinc/cp-kafka:7.2.1
|
||||||
|
@ -127,7 +127,7 @@ services:
|
||||||
- ./message.json:/data/message.json
|
- ./message.json:/data/message.json
|
||||||
depends_on:
|
depends_on:
|
||||||
kafka0:
|
kafka0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
command: "bash -c 'echo Waiting for Kafka to be ready... && \
|
command: "bash -c 'echo Waiting for Kafka to be ready... && \
|
||||||
cub kafka-ready -b kafka0:29092 1 30 && \
|
cub kafka-ready -b kafka0:29092 1 30 && \
|
||||||
kafka-topics --create --topic users --partitions 3 --replication-factor 1 --if-not-exists --bootstrap-server kafka0:29092 && \
|
kafka-topics --create --topic users --partitions 3 --replication-factor 1 --if-not-exists --bootstrap-server kafka0:29092 && \
|
||||||
|
@ -142,10 +142,10 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U dev_user"]
|
test: [ "CMD-SHELL", "pg_isready -U dev_user" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: 'dev_user'
|
POSTGRES_USER: 'dev_user'
|
||||||
POSTGRES_PASSWORD: '12345'
|
POSTGRES_PASSWORD: '12345'
|
||||||
|
@ -154,7 +154,7 @@ services:
|
||||||
image: ellerbrock/alpine-bash-curl-ssl
|
image: ellerbrock/alpine-bash-curl-ssl
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres-db:
|
postgres-db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
kafka-connect0:
|
kafka-connect0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -164,7 +164,7 @@ services:
|
||||||
ksqldb:
|
ksqldb:
|
||||||
image: confluentinc/ksqldb-server:0.18.0
|
image: confluentinc/ksqldb-server:0.18.0
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "timeout", "1", "curl", "--silent", "--fail", "http://localhost:8088/info"]
|
test: [ "CMD", "timeout", "1", "curl", "--silent", "--fail", "http://localhost:8088/info" ]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
@ -174,7 +174,7 @@ services:
|
||||||
kafka-connect0:
|
kafka-connect0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
schemaregistry0:
|
schemaregistry0:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- 8088:8088
|
- 8088:8088
|
||||||
environment:
|
environment:
|
||||||
|
|
333
etc/checkstyle/checkstyle-e2e.xml
Normal file
333
etc/checkstyle/checkstyle-e2e.xml
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||||
|
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Checkstyle configuration that checks the Google coding conventions from Google Java Style
|
||||||
|
that can be found at https://google.github.io/styleguide/javaguide.html
|
||||||
|
|
||||||
|
Checkstyle is very configurable. Be sure to read the documentation at
|
||||||
|
http://checkstyle.org (or in your downloaded distribution).
|
||||||
|
|
||||||
|
To completely disable a check, just comment it out or delete it from the file.
|
||||||
|
To suppress certain violations please review suppression filters.
|
||||||
|
|
||||||
|
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name = "Checker">
|
||||||
|
<property name="charset" value="UTF-8"/>
|
||||||
|
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
|
||||||
|
<property name="fileExtensions" value="java, properties, xml"/>
|
||||||
|
<!-- Excludes all 'module-info.java' files -->
|
||||||
|
<!-- See https://checkstyle.org/config_filefilters.html -->
|
||||||
|
<module name="BeforeExecutionExclusionFileFilter">
|
||||||
|
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||||
|
</module>
|
||||||
|
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
|
||||||
|
<module name="SuppressionFilter">
|
||||||
|
<property name="file" value="${org.checkstyle.google.suppressionfilter.config}"
|
||||||
|
default="checkstyle-suppressions.xml" />
|
||||||
|
<property name="optional" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Checks for whitespace -->
|
||||||
|
<!-- See http://checkstyle.org/config_whitespace.html -->
|
||||||
|
<module name="FileTabCharacter">
|
||||||
|
<property name="eachLine" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="LineLength">
|
||||||
|
<property name="fileExtensions" value="java"/>
|
||||||
|
<property name="max" value="120"/>
|
||||||
|
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="TreeWalker">
|
||||||
|
<module name="OuterTypeFilename"/>
|
||||||
|
<module name="IllegalTokenText">
|
||||||
|
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
||||||
|
<property name="format"
|
||||||
|
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
|
||||||
|
<property name="message"
|
||||||
|
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
|
||||||
|
</module>
|
||||||
|
<module name="AvoidEscapedUnicodeCharacters">
|
||||||
|
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||||
|
<property name="allowByTailComment" value="true"/>
|
||||||
|
<property name="allowNonPrintableEscapes" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="AvoidStarImport"/>
|
||||||
|
<module name="OneTopLevelClass"/>
|
||||||
|
<module name="NoLineWrap">
|
||||||
|
<property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
|
||||||
|
</module>
|
||||||
|
<module name="EmptyBlock">
|
||||||
|
<property name="option" value="TEXT"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
|
||||||
|
</module>
|
||||||
|
<module name="NeedBraces">
|
||||||
|
<property name="tokens"
|
||||||
|
value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/>
|
||||||
|
</module>
|
||||||
|
<module name="LeftCurly">
|
||||||
|
<property name="tokens"
|
||||||
|
value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,
|
||||||
|
INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT,
|
||||||
|
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,
|
||||||
|
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,
|
||||||
|
OBJBLOCK, STATIC_INIT"/>
|
||||||
|
</module>
|
||||||
|
<module name="RightCurly">
|
||||||
|
<property name="id" value="RightCurlySame"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
|
||||||
|
LITERAL_DO"/>
|
||||||
|
</module>
|
||||||
|
<module name="RightCurly">
|
||||||
|
<property name="id" value="RightCurlyAlone"/>
|
||||||
|
<property name="option" value="alone"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
|
||||||
|
INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="SuppressionXpathSingleFilter">
|
||||||
|
<!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 -->
|
||||||
|
<property name="id" value="RightCurlyAlone"/>
|
||||||
|
<property name="query" value="//RCURLY[parent::SLIST[count(./*)=1]
|
||||||
|
or preceding-sibling::*[last()][self::LCURLY]]"/>
|
||||||
|
</module>
|
||||||
|
<module name="WhitespaceAfter">
|
||||||
|
<property name="tokens"
|
||||||
|
value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE,
|
||||||
|
LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
|
||||||
|
</module>
|
||||||
|
<module name="WhitespaceAround">
|
||||||
|
<property name="allowEmptyConstructors" value="true"/>
|
||||||
|
<property name="allowEmptyLambdas" value="true"/>
|
||||||
|
<property name="allowEmptyMethods" value="true"/>
|
||||||
|
<property name="allowEmptyTypes" value="true"/>
|
||||||
|
<property name="allowEmptyLoops" value="true"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
|
||||||
|
BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
|
||||||
|
LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
|
||||||
|
LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
|
||||||
|
LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
|
||||||
|
NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
|
||||||
|
SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
|
||||||
|
<message key="ws.notFollowed"
|
||||||
|
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
|
||||||
|
<message key="ws.notPreceded"
|
||||||
|
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
|
||||||
|
</module>
|
||||||
|
<module name="OneStatementPerLine"/>
|
||||||
|
<!-- <module name="MultipleVariableDeclarations"/>-->
|
||||||
|
<module name="ArrayTypeStyle"/>
|
||||||
|
<module name="MissingSwitchDefault"/>
|
||||||
|
<module name="FallThrough"/>
|
||||||
|
<module name="UpperEll"/>
|
||||||
|
<module name="ModifierOrder"/>
|
||||||
|
<module name="EmptyLineSeparator">
|
||||||
|
<property name="tokens"
|
||||||
|
value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||||
|
STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||||
|
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapDot"/>
|
||||||
|
<property name="tokens" value="DOT"/>
|
||||||
|
<property name="option" value="nl"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapComma"/>
|
||||||
|
<property name="tokens" value="COMMA"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
|
||||||
|
<property name="id" value="SeparatorWrapEllipsis"/>
|
||||||
|
<property name="tokens" value="ELLIPSIS"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
|
||||||
|
<property name="id" value="SeparatorWrapArrayDeclarator"/>
|
||||||
|
<property name="tokens" value="ARRAY_DECLARATOR"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapMethodRef"/>
|
||||||
|
<property name="tokens" value="METHOD_REF"/>
|
||||||
|
<property name="option" value="nl"/>
|
||||||
|
</module>
|
||||||
|
<module name="PackageName">
|
||||||
|
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Package name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="TypeName">
|
||||||
|
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="MemberName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Member name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="ParameterName">
|
||||||
|
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="LambdaParameterName">
|
||||||
|
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="CatchParameterName">
|
||||||
|
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="LocalVariableName">
|
||||||
|
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="ClassTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Class type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Method type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="InterfaceTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="NoFinalizer"/>
|
||||||
|
<module name="GenericWhitespace">
|
||||||
|
<message key="ws.followed"
|
||||||
|
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
|
||||||
|
<message key="ws.preceded"
|
||||||
|
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
|
||||||
|
<message key="ws.illegalFollow"
|
||||||
|
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
|
||||||
|
<message key="ws.notPreceded"
|
||||||
|
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
|
||||||
|
</module>
|
||||||
|
<module name="Indentation">
|
||||||
|
<property name="basicOffset" value="2"/>
|
||||||
|
<property name="braceAdjustment" value="0"/>
|
||||||
|
<property name="caseIndent" value="2"/>
|
||||||
|
<property name="throwsIndent" value="4"/>
|
||||||
|
<property name="lineWrappingIndentation" value="4"/>
|
||||||
|
<property name="arrayInitIndent" value="2"/>
|
||||||
|
</module>
|
||||||
|
<module name="AbbreviationAsWordInName">
|
||||||
|
<property name="ignoreFinal" value="false"/>
|
||||||
|
<property name="allowedAbbreviationLength" value="1"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
|
||||||
|
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="OverloadMethodsDeclarationOrder"/>
|
||||||
|
<!-- <module name="VariableDeclarationUsageDistance"/>-->
|
||||||
|
<module name="CustomImportOrder">
|
||||||
|
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||||
|
<property name="separateLineBetweenGroups" value="true"/>
|
||||||
|
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
|
||||||
|
<property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodParamPad">
|
||||||
|
<property name="tokens"
|
||||||
|
value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF,
|
||||||
|
SUPER_CTOR_CALL, ENUM_CONSTANT_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="NoWhitespaceBefore">
|
||||||
|
<property name="tokens"
|
||||||
|
value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS,
|
||||||
|
LABELED_STAT, METHOD_REF"/>
|
||||||
|
<property name="allowLineBreaks" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="ParenPad">
|
||||||
|
<property name="tokens"
|
||||||
|
value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,
|
||||||
|
EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,
|
||||||
|
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,
|
||||||
|
METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA"/>
|
||||||
|
</module>
|
||||||
|
<module name="OperatorWrap">
|
||||||
|
<property name="option" value="NL"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
|
||||||
|
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
|
||||||
|
</module>
|
||||||
|
<module name="AnnotationLocation">
|
||||||
|
<property name="id" value="AnnotationLocationMostCases"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="AnnotationLocation">
|
||||||
|
<property name="id" value="AnnotationLocationVariables"/>
|
||||||
|
<property name="tokens" value="VARIABLE_DEF"/>
|
||||||
|
<property name="allowSamelineMultipleAnnotations" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="NonEmptyAtclauseDescription"/>
|
||||||
|
<module name="InvalidJavadocPosition"/>
|
||||||
|
<module name="JavadocTagContinuationIndentation"/>
|
||||||
|
<module name="SummaryJavadoc">
|
||||||
|
<property name="forbiddenSummaryFragments"
|
||||||
|
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
||||||
|
</module>
|
||||||
|
<module name="JavadocParagraph"/>
|
||||||
|
<module name="AtclauseOrder">
|
||||||
|
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||||
|
<property name="target"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="JavadocMethod">
|
||||||
|
<property name="accessModifiers" value="public"/>
|
||||||
|
<property name="allowMissingParamTags" value="true"/>
|
||||||
|
<property name="allowMissingReturnTag" value="true"/>
|
||||||
|
<property name="allowedAnnotations" value="Override, Test"/>
|
||||||
|
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF"/>
|
||||||
|
</module>
|
||||||
|
<!-- <module name="MissingJavadocMethod">-->
|
||||||
|
<!-- <property name="scope" value="public"/>-->
|
||||||
|
<!-- <property name="minLineCount" value="2"/>-->
|
||||||
|
<!-- <property name="allowedAnnotations" value="Override, Test"/>-->
|
||||||
|
<!-- <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF"/>-->
|
||||||
|
<!-- </module>-->
|
||||||
|
<module name="MethodName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Method name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="SingleLineJavadoc">
|
||||||
|
<property name="ignoreInlineTags" value="false"/>
|
||||||
|
</module>
|
||||||
|
<module name="EmptyCatchBlock">
|
||||||
|
<property name="exceptionVariableName" value="ignored"/>
|
||||||
|
</module>
|
||||||
|
<module name="CommentsIndentation">
|
||||||
|
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
|
||||||
|
</module>
|
||||||
|
<!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->
|
||||||
|
<module name="SuppressionXpathFilter">
|
||||||
|
<property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}"
|
||||||
|
default="checkstyle-xpath-suppressions.xml" />
|
||||||
|
<property name="optional" value="true"/>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
|
</module>
|
|
@ -318,7 +318,7 @@
|
||||||
<property name="ignoreInlineTags" value="false"/>
|
<property name="ignoreInlineTags" value="false"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="EmptyCatchBlock">
|
<module name="EmptyCatchBlock">
|
||||||
<property name="exceptionVariableName" value="expected"/>
|
<property name="exceptionVariableName" value="ignored"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="CommentsIndentation">
|
<module name="CommentsIndentation">
|
||||||
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
|
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
|
||||||
|
@ -330,4 +330,4 @@
|
||||||
<property name="optional" value="true"/>
|
<property name="optional" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
</module>
|
</module>
|
||||||
</module>
|
</module>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
FROM azul/zulu-openjdk-alpine:17-jre
|
#FROM azul/zulu-openjdk-alpine:17-jre-headless
|
||||||
|
FROM azul/zulu-openjdk-alpine@sha256:a36679ac0d28cb835e2a8c00e1e0d95509c6c51c5081c7782b85edb1f37a771a
|
||||||
|
|
||||||
RUN apk add --no-cache gcompat # need to make snappy codec work
|
RUN apk add --no-cache gcompat # need to make snappy codec work
|
||||||
RUN addgroup -S kafkaui && adduser -S kafkaui -G kafkaui
|
RUN addgroup -S kafkaui && adduser -S kafkaui -G kafkaui
|
||||||
|
|
|
@ -6,7 +6,13 @@ import com.provectus.kafka.ui.config.ClustersProperties;
|
||||||
import com.provectus.kafka.ui.connect.ApiClient;
|
import com.provectus.kafka.ui.connect.ApiClient;
|
||||||
import com.provectus.kafka.ui.connect.api.KafkaConnectClientApi;
|
import com.provectus.kafka.ui.connect.api.KafkaConnectClientApi;
|
||||||
import com.provectus.kafka.ui.connect.model.Connector;
|
import com.provectus.kafka.ui.connect.model.Connector;
|
||||||
|
import com.provectus.kafka.ui.connect.model.ConnectorPlugin;
|
||||||
|
import com.provectus.kafka.ui.connect.model.ConnectorPluginConfigValidationResponse;
|
||||||
|
import com.provectus.kafka.ui.connect.model.ConnectorStatus;
|
||||||
|
import com.provectus.kafka.ui.connect.model.ConnectorTask;
|
||||||
|
import com.provectus.kafka.ui.connect.model.ConnectorTopics;
|
||||||
import com.provectus.kafka.ui.connect.model.NewConnector;
|
import com.provectus.kafka.ui.connect.model.NewConnector;
|
||||||
|
import com.provectus.kafka.ui.connect.model.TaskStatus;
|
||||||
import com.provectus.kafka.ui.exception.KafkaConnectConflictReponseException;
|
import com.provectus.kafka.ui.exception.KafkaConnectConflictReponseException;
|
||||||
import com.provectus.kafka.ui.exception.ValidationException;
|
import com.provectus.kafka.ui.exception.ValidationException;
|
||||||
import com.provectus.kafka.ui.util.WebClientConfigurator;
|
import com.provectus.kafka.ui.util.WebClientConfigurator;
|
||||||
|
@ -15,11 +21,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.util.unit.DataSize;
|
import org.springframework.util.unit.DataSize;
|
||||||
import org.springframework.web.client.RestClientException;
|
import org.springframework.web.client.RestClientException;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
@ -79,6 +81,176 @@ public class RetryingKafkaConnectClient extends KafkaConnectClientApi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Connector>> createConnectorWithHttpInfo(NewConnector newConnector)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.createConnectorWithHttpInfo(newConnector));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteConnector(String connectorName) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.deleteConnector(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Void>> deleteConnectorWithHttpInfo(String connectorName)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.deleteConnectorWithHttpInfo(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Connector> getConnector(String connectorName) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnector(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Connector>> getConnectorWithHttpInfo(String connectorName)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorWithHttpInfo(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Map<String, Object>> getConnectorConfig(String connectorName) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorConfig(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Map<String, Object>>> getConnectorConfigWithHttpInfo(String connectorName)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorConfigWithHttpInfo(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<ConnectorPlugin> getConnectorPlugins() throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorPlugins());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<List<ConnectorPlugin>>> getConnectorPluginsWithHttpInfo()
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorPluginsWithHttpInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ConnectorStatus> getConnectorStatus(String connectorName) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorStatus(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<ConnectorStatus>> getConnectorStatusWithHttpInfo(String connectorName)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorStatusWithHttpInfo(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<TaskStatus> getConnectorTaskStatus(String connectorName, Integer taskId)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorTaskStatus(connectorName, taskId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<TaskStatus>> getConnectorTaskStatusWithHttpInfo(String connectorName, Integer taskId)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorTaskStatusWithHttpInfo(connectorName, taskId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<ConnectorTask> getConnectorTasks(String connectorName) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorTasks(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<List<ConnectorTask>>> getConnectorTasksWithHttpInfo(String connectorName)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorTasksWithHttpInfo(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Map<String, ConnectorTopics>> getConnectorTopics(String connectorName) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorTopics(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Map<String, ConnectorTopics>>> getConnectorTopicsWithHttpInfo(String connectorName)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorTopicsWithHttpInfo(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<String> getConnectors(String search) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectors(search));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<List<String>>> getConnectorsWithHttpInfo(String search) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.getConnectorsWithHttpInfo(search));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> pauseConnector(String connectorName) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.pauseConnector(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Void>> pauseConnectorWithHttpInfo(String connectorName) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.pauseConnectorWithHttpInfo(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> restartConnector(String connectorName, Boolean includeTasks, Boolean onlyFailed)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.restartConnector(connectorName, includeTasks, onlyFailed));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Void>> restartConnectorWithHttpInfo(String connectorName, Boolean includeTasks,
|
||||||
|
Boolean onlyFailed) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.restartConnectorWithHttpInfo(connectorName, includeTasks, onlyFailed));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> restartConnectorTask(String connectorName, Integer taskId) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.restartConnectorTask(connectorName, taskId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Void>> restartConnectorTaskWithHttpInfo(String connectorName, Integer taskId)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.restartConnectorTaskWithHttpInfo(connectorName, taskId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> resumeConnector(String connectorName) throws WebClientResponseException {
|
||||||
|
return super.resumeConnector(connectorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Void>> resumeConnectorWithHttpInfo(String connectorName)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.resumeConnectorWithHttpInfo(connectorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<Connector>> setConnectorConfigWithHttpInfo(String connectorName,
|
||||||
|
Map<String, Object> requestBody)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.setConnectorConfigWithHttpInfo(connectorName, requestBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ConnectorPluginConfigValidationResponse> validateConnectorPluginConfig(String pluginName,
|
||||||
|
Map<String, Object> requestBody)
|
||||||
|
throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.validateConnectorPluginConfig(pluginName, requestBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ResponseEntity<ConnectorPluginConfigValidationResponse>> validateConnectorPluginConfigWithHttpInfo(
|
||||||
|
String pluginName, Map<String, Object> requestBody) throws WebClientResponseException {
|
||||||
|
return withRetryOnConflict(super.validateConnectorPluginConfigWithHttpInfo(pluginName, requestBody));
|
||||||
|
}
|
||||||
|
|
||||||
private static class RetryingApiClient extends ApiClient {
|
private static class RetryingApiClient extends ApiClient {
|
||||||
|
|
||||||
public RetryingApiClient(ConnectCluster config,
|
public RetryingApiClient(ConnectCluster config,
|
||||||
|
@ -108,35 +280,5 @@ public class RetryingKafkaConnectClient extends KafkaConnectClientApi {
|
||||||
.configureBufferSize(maxBuffSize)
|
.configureBufferSize(maxBuffSize)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> Mono<T> invokeAPI(String path, HttpMethod method, Map<String, Object> pathParams,
|
|
||||||
MultiValueMap<String, String> queryParams, Object body,
|
|
||||||
HttpHeaders headerParams,
|
|
||||||
MultiValueMap<String, String> cookieParams,
|
|
||||||
MultiValueMap<String, Object> formParams, List<MediaType> accept,
|
|
||||||
MediaType contentType, String[] authNames,
|
|
||||||
ParameterizedTypeReference<T> returnType)
|
|
||||||
throws RestClientException {
|
|
||||||
return withRetryOnConflict(
|
|
||||||
super.invokeAPI(path, method, pathParams, queryParams, body, headerParams, cookieParams,
|
|
||||||
formParams, accept, contentType, authNames, returnType)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> Flux<T> invokeFluxAPI(String path, HttpMethod method, Map<String, Object> pathParams,
|
|
||||||
MultiValueMap<String, String> queryParams, Object body,
|
|
||||||
HttpHeaders headerParams,
|
|
||||||
MultiValueMap<String, String> cookieParams,
|
|
||||||
MultiValueMap<String, Object> formParams,
|
|
||||||
List<MediaType> accept, MediaType contentType,
|
|
||||||
String[] authNames, ParameterizedTypeReference<T> returnType)
|
|
||||||
throws RestClientException {
|
|
||||||
return withRetryOnConflict(
|
|
||||||
super.invokeFluxAPI(path, method, pathParams, queryParams, body, headerParams,
|
|
||||||
cookieParams, formParams, accept, contentType, authNames, returnType)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.provectus.kafka.ui.config;
|
package com.provectus.kafka.ui.config;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.model.MetricsConfig;
|
import com.provectus.kafka.ui.model.MetricsConfig;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -8,7 +9,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package com.provectus.kafka.ui.config.auth;
|
package com.provectus.kafka.ui.config.auth;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
|
@ -13,12 +13,12 @@ import com.provectus.kafka.ui.model.ClusterConfigValidationDTO;
|
||||||
import com.provectus.kafka.ui.model.RestartRequestDTO;
|
import com.provectus.kafka.ui.model.RestartRequestDTO;
|
||||||
import com.provectus.kafka.ui.model.UploadedFileInfoDTO;
|
import com.provectus.kafka.ui.model.UploadedFileInfoDTO;
|
||||||
import com.provectus.kafka.ui.model.rbac.AccessContext;
|
import com.provectus.kafka.ui.model.rbac.AccessContext;
|
||||||
|
import com.provectus.kafka.ui.service.ApplicationInfoService;
|
||||||
import com.provectus.kafka.ui.service.KafkaClusterFactory;
|
import com.provectus.kafka.ui.service.KafkaClusterFactory;
|
||||||
import com.provectus.kafka.ui.service.rbac.AccessControlService;
|
import com.provectus.kafka.ui.service.rbac.AccessControlService;
|
||||||
import com.provectus.kafka.ui.util.ApplicationRestarter;
|
import com.provectus.kafka.ui.util.ApplicationRestarter;
|
||||||
import com.provectus.kafka.ui.util.DynamicConfigOperations;
|
import com.provectus.kafka.ui.util.DynamicConfigOperations;
|
||||||
import com.provectus.kafka.ui.util.DynamicConfigOperations.PropertiesStructure;
|
import com.provectus.kafka.ui.util.DynamicConfigOperations.PropertiesStructure;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
@ -53,18 +53,11 @@ public class ApplicationConfigController implements ApplicationConfigApi {
|
||||||
private final DynamicConfigOperations dynamicConfigOperations;
|
private final DynamicConfigOperations dynamicConfigOperations;
|
||||||
private final ApplicationRestarter restarter;
|
private final ApplicationRestarter restarter;
|
||||||
private final KafkaClusterFactory kafkaClusterFactory;
|
private final KafkaClusterFactory kafkaClusterFactory;
|
||||||
|
private final ApplicationInfoService applicationInfoService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ResponseEntity<ApplicationInfoDTO>> getApplicationInfo(ServerWebExchange exchange) {
|
public Mono<ResponseEntity<ApplicationInfoDTO>> getApplicationInfo(ServerWebExchange exchange) {
|
||||||
return Mono.just(
|
return Mono.just(applicationInfoService.getApplicationInfo()).map(ResponseEntity::ok);
|
||||||
new ApplicationInfoDTO()
|
|
||||||
.enabledFeatures(
|
|
||||||
dynamicConfigOperations.dynamicConfigEnabled()
|
|
||||||
? List.of(ApplicationInfoDTO.EnabledFeaturesEnum.DYNAMIC_CONFIG)
|
|
||||||
: List.of()
|
|
||||||
)
|
|
||||||
).map(ResponseEntity::ok);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -149,10 +149,9 @@ public class KafkaConnectController extends AbstractController implements KafkaC
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ResponseEntity<ConnectorDTO>> setConnectorConfig(String clusterName,
|
public Mono<ResponseEntity<ConnectorDTO>> setConnectorConfig(String clusterName, String connectName,
|
||||||
String connectName,
|
|
||||||
String connectorName,
|
String connectorName,
|
||||||
@Valid Mono<Object> requestBody,
|
Mono<Map<String, Object>> requestBody,
|
||||||
ServerWebExchange exchange) {
|
ServerWebExchange exchange) {
|
||||||
|
|
||||||
Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
||||||
|
@ -164,8 +163,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
|
||||||
return validateAccess.then(
|
return validateAccess.then(
|
||||||
kafkaConnectService
|
kafkaConnectService
|
||||||
.setConnectorConfig(getCluster(clusterName), connectName, connectorName, requestBody)
|
.setConnectorConfig(getCluster(clusterName), connectName, connectorName, requestBody)
|
||||||
.map(ResponseEntity::ok)
|
.map(ResponseEntity::ok));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -242,7 +240,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ResponseEntity<ConnectorPluginConfigValidationResponseDTO>> validateConnectorPluginConfig(
|
public Mono<ResponseEntity<ConnectorPluginConfigValidationResponseDTO>> validateConnectorPluginConfig(
|
||||||
String clusterName, String connectName, String pluginName, @Valid Mono<Object> requestBody,
|
String clusterName, String connectName, String pluginName, @Valid Mono<Map<String, Object>> requestBody,
|
||||||
ServerWebExchange exchange) {
|
ServerWebExchange exchange) {
|
||||||
return kafkaConnectService
|
return kafkaConnectService
|
||||||
.validateConnectorPluginConfig(
|
.validateConnectorPluginConfig(
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package com.provectus.kafka.ui.emitter;
|
package com.provectus.kafka.ui.emitter;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.model.TopicMessageDTO;
|
|
||||||
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
||||||
import com.provectus.kafka.ui.model.TopicMessagePhaseDTO;
|
|
||||||
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import org.apache.kafka.clients.consumer.Consumer;
|
import org.apache.kafka.clients.consumer.Consumer;
|
||||||
|
@ -14,13 +11,12 @@ import reactor.core.publisher.FluxSink;
|
||||||
|
|
||||||
public abstract class AbstractEmitter {
|
public abstract class AbstractEmitter {
|
||||||
|
|
||||||
private final ConsumerRecordDeserializer recordDeserializer;
|
private final MessagesProcessing messagesProcessing;
|
||||||
private final ConsumingStats consumingStats = new ConsumingStats();
|
|
||||||
private final PollingThrottler throttler;
|
private final PollingThrottler throttler;
|
||||||
protected final PollingSettings pollingSettings;
|
protected final PollingSettings pollingSettings;
|
||||||
|
|
||||||
protected AbstractEmitter(ConsumerRecordDeserializer recordDeserializer, PollingSettings pollingSettings) {
|
protected AbstractEmitter(MessagesProcessing messagesProcessing, PollingSettings pollingSettings) {
|
||||||
this.recordDeserializer = recordDeserializer;
|
this.messagesProcessing = messagesProcessing;
|
||||||
this.pollingSettings = pollingSettings;
|
this.pollingSettings = pollingSettings;
|
||||||
this.throttler = pollingSettings.getPollingThrottler();
|
this.throttler = pollingSettings.getPollingThrottler();
|
||||||
}
|
}
|
||||||
|
@ -40,39 +36,27 @@ public abstract class AbstractEmitter {
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean sendLimitReached() {
|
||||||
|
return messagesProcessing.limitReached();
|
||||||
|
}
|
||||||
|
|
||||||
protected void sendMessage(FluxSink<TopicMessageEventDTO> sink,
|
protected void sendMessage(FluxSink<TopicMessageEventDTO> sink,
|
||||||
ConsumerRecord<Bytes, Bytes> msg) {
|
ConsumerRecord<Bytes, Bytes> msg) {
|
||||||
final TopicMessageDTO topicMessage = recordDeserializer.deserialize(msg);
|
messagesProcessing.sendMsg(sink, msg);
|
||||||
sink.next(
|
|
||||||
new TopicMessageEventDTO()
|
|
||||||
.type(TopicMessageEventDTO.TypeEnum.MESSAGE)
|
|
||||||
.message(topicMessage)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendPhase(FluxSink<TopicMessageEventDTO> sink, String name) {
|
protected void sendPhase(FluxSink<TopicMessageEventDTO> sink, String name) {
|
||||||
sink.next(
|
messagesProcessing.sendPhase(sink, name);
|
||||||
new TopicMessageEventDTO()
|
|
||||||
.type(TopicMessageEventDTO.TypeEnum.PHASE)
|
|
||||||
.phase(new TopicMessagePhaseDTO().name(name))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int sendConsuming(FluxSink<TopicMessageEventDTO> sink,
|
protected int sendConsuming(FluxSink<TopicMessageEventDTO> sink,
|
||||||
ConsumerRecords<Bytes, Bytes> records,
|
ConsumerRecords<Bytes, Bytes> records,
|
||||||
long elapsed) {
|
long elapsed) {
|
||||||
return consumingStats.sendConsumingEvt(sink, records, elapsed, getFilterApplyErrors(sink));
|
return messagesProcessing.sentConsumingInfo(sink, records, elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendFinishStatsAndCompleteSink(FluxSink<TopicMessageEventDTO> sink) {
|
protected void sendFinishStatsAndCompleteSink(FluxSink<TopicMessageEventDTO> sink) {
|
||||||
consumingStats.sendFinishEvent(sink, getFilterApplyErrors(sink));
|
messagesProcessing.sendFinishEvent(sink);
|
||||||
sink.complete();
|
sink.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Number getFilterApplyErrors(FluxSink<?> sink) {
|
|
||||||
return sink.contextView()
|
|
||||||
.<MessageFilterStats>getOrEmpty(MessageFilterStats.class)
|
|
||||||
.<Number>map(MessageFilterStats::getFilterApplyErrors)
|
|
||||||
.orElse(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.provectus.kafka.ui.emitter;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.model.ConsumerPosition;
|
import com.provectus.kafka.ui.model.ConsumerPosition;
|
||||||
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
||||||
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
@ -31,9 +30,9 @@ public class BackwardRecordEmitter
|
||||||
Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
|
Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
|
||||||
ConsumerPosition consumerPosition,
|
ConsumerPosition consumerPosition,
|
||||||
int messagesPerPage,
|
int messagesPerPage,
|
||||||
ConsumerRecordDeserializer recordDeserializer,
|
MessagesProcessing messagesProcessing,
|
||||||
PollingSettings pollingSettings) {
|
PollingSettings pollingSettings) {
|
||||||
super(recordDeserializer, pollingSettings);
|
super(messagesProcessing, pollingSettings);
|
||||||
this.consumerPosition = consumerPosition;
|
this.consumerPosition = consumerPosition;
|
||||||
this.messagesPerPage = messagesPerPage;
|
this.messagesPerPage = messagesPerPage;
|
||||||
this.consumerSupplier = consumerSupplier;
|
this.consumerSupplier = consumerSupplier;
|
||||||
|
@ -52,7 +51,7 @@ public class BackwardRecordEmitter
|
||||||
int msgsToPollPerPartition = (int) Math.ceil((double) messagesPerPage / readUntilOffsets.size());
|
int msgsToPollPerPartition = (int) Math.ceil((double) messagesPerPage / readUntilOffsets.size());
|
||||||
log.debug("'Until' offsets for polling: {}", readUntilOffsets);
|
log.debug("'Until' offsets for polling: {}", readUntilOffsets);
|
||||||
|
|
||||||
while (!sink.isCancelled() && !readUntilOffsets.isEmpty()) {
|
while (!sink.isCancelled() && !readUntilOffsets.isEmpty() && !sendLimitReached()) {
|
||||||
new TreeMap<>(readUntilOffsets).forEach((tp, readToOffset) -> {
|
new TreeMap<>(readUntilOffsets).forEach((tp, readToOffset) -> {
|
||||||
if (sink.isCancelled()) {
|
if (sink.isCancelled()) {
|
||||||
return; //fast return in case of sink cancellation
|
return; //fast return in case of sink cancellation
|
||||||
|
@ -61,8 +60,6 @@ public class BackwardRecordEmitter
|
||||||
long readFromOffset = Math.max(beginOffset, readToOffset - msgsToPollPerPartition);
|
long readFromOffset = Math.max(beginOffset, readToOffset - msgsToPollPerPartition);
|
||||||
|
|
||||||
partitionPollIteration(tp, readFromOffset, readToOffset, consumer, sink)
|
partitionPollIteration(tp, readFromOffset, readToOffset, consumer, sink)
|
||||||
.stream()
|
|
||||||
.filter(r -> !sink.isCancelled())
|
|
||||||
.forEach(r -> sendMessage(sink, r));
|
.forEach(r -> sendMessage(sink, r));
|
||||||
|
|
||||||
if (beginOffset == readFromOffset) {
|
if (beginOffset == readFromOffset) {
|
||||||
|
@ -106,6 +103,7 @@ public class BackwardRecordEmitter
|
||||||
|
|
||||||
EmptyPollsCounter emptyPolls = pollingSettings.createEmptyPollsCounter();
|
EmptyPollsCounter emptyPolls = pollingSettings.createEmptyPollsCounter();
|
||||||
while (!sink.isCancelled()
|
while (!sink.isCancelled()
|
||||||
|
&& !sendLimitReached()
|
||||||
&& recordsToSend.size() < desiredMsgsToPoll
|
&& recordsToSend.size() < desiredMsgsToPoll
|
||||||
&& !emptyPolls.noDataEmptyPollsReached()) {
|
&& !emptyPolls.noDataEmptyPollsReached()) {
|
||||||
var polledRecords = poll(sink, consumer, pollingSettings.getPartitionPollTimeout());
|
var polledRecords = poll(sink, consumer, pollingSettings.getPartitionPollTimeout());
|
||||||
|
|
|
@ -19,7 +19,7 @@ class ConsumingStats {
|
||||||
int sendConsumingEvt(FluxSink<TopicMessageEventDTO> sink,
|
int sendConsumingEvt(FluxSink<TopicMessageEventDTO> sink,
|
||||||
ConsumerRecords<Bytes, Bytes> polledRecords,
|
ConsumerRecords<Bytes, Bytes> polledRecords,
|
||||||
long elapsed,
|
long elapsed,
|
||||||
Number filterApplyErrors) {
|
int filterApplyErrors) {
|
||||||
int polledBytes = ConsumerRecordsUtil.calculatePolledSize(polledRecords);
|
int polledBytes = ConsumerRecordsUtil.calculatePolledSize(polledRecords);
|
||||||
bytes += polledBytes;
|
bytes += polledBytes;
|
||||||
this.records += polledRecords.count();
|
this.records += polledRecords.count();
|
||||||
|
@ -32,7 +32,7 @@ class ConsumingStats {
|
||||||
return polledBytes;
|
return polledBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendFinishEvent(FluxSink<TopicMessageEventDTO> sink, Number filterApplyErrors) {
|
void sendFinishEvent(FluxSink<TopicMessageEventDTO> sink, int filterApplyErrors) {
|
||||||
sink.next(
|
sink.next(
|
||||||
new TopicMessageEventDTO()
|
new TopicMessageEventDTO()
|
||||||
.type(TopicMessageEventDTO.TypeEnum.DONE)
|
.type(TopicMessageEventDTO.TypeEnum.DONE)
|
||||||
|
@ -41,12 +41,12 @@ class ConsumingStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
private TopicMessageConsumingDTO createConsumingStats(FluxSink<TopicMessageEventDTO> sink,
|
private TopicMessageConsumingDTO createConsumingStats(FluxSink<TopicMessageEventDTO> sink,
|
||||||
Number filterApplyErrors) {
|
int filterApplyErrors) {
|
||||||
return new TopicMessageConsumingDTO()
|
return new TopicMessageConsumingDTO()
|
||||||
.bytesConsumed(this.bytes)
|
.bytesConsumed(this.bytes)
|
||||||
.elapsedMs(this.elapsed)
|
.elapsedMs(this.elapsed)
|
||||||
.isCancelled(sink.isCancelled())
|
.isCancelled(sink.isCancelled())
|
||||||
.filterApplyErrors(filterApplyErrors.intValue())
|
.filterApplyErrors(filterApplyErrors)
|
||||||
.messagesConsumed(this.records);
|
.messagesConsumed(this.records);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.provectus.kafka.ui.emitter;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.model.ConsumerPosition;
|
import com.provectus.kafka.ui.model.ConsumerPosition;
|
||||||
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
||||||
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||||
|
@ -23,9 +22,9 @@ public class ForwardRecordEmitter
|
||||||
public ForwardRecordEmitter(
|
public ForwardRecordEmitter(
|
||||||
Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
|
Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
|
||||||
ConsumerPosition position,
|
ConsumerPosition position,
|
||||||
ConsumerRecordDeserializer recordDeserializer,
|
MessagesProcessing messagesProcessing,
|
||||||
PollingSettings pollingSettings) {
|
PollingSettings pollingSettings) {
|
||||||
super(recordDeserializer, pollingSettings);
|
super(messagesProcessing, pollingSettings);
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.consumerSupplier = consumerSupplier;
|
this.consumerSupplier = consumerSupplier;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +39,7 @@ public class ForwardRecordEmitter
|
||||||
|
|
||||||
EmptyPollsCounter emptyPolls = pollingSettings.createEmptyPollsCounter();
|
EmptyPollsCounter emptyPolls = pollingSettings.createEmptyPollsCounter();
|
||||||
while (!sink.isCancelled()
|
while (!sink.isCancelled()
|
||||||
|
&& !sendLimitReached()
|
||||||
&& !seekOperations.assignedPartitionsFullyPolled()
|
&& !seekOperations.assignedPartitionsFullyPolled()
|
||||||
&& !emptyPolls.noDataEmptyPollsReached()) {
|
&& !emptyPolls.noDataEmptyPollsReached()) {
|
||||||
|
|
||||||
|
@ -50,11 +50,7 @@ public class ForwardRecordEmitter
|
||||||
log.debug("{} records polled", records.count());
|
log.debug("{} records polled", records.count());
|
||||||
|
|
||||||
for (ConsumerRecord<Bytes, Bytes> msg : records) {
|
for (ConsumerRecord<Bytes, Bytes> msg : records) {
|
||||||
if (!sink.isCancelled()) {
|
sendMessage(sink, msg);
|
||||||
sendMessage(sink, msg);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendFinishStatsAndCompleteSink(sink);
|
sendFinishStatsAndCompleteSink(sink);
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
package com.provectus.kafka.ui.emitter;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
public class MessageFilterStats {
|
|
||||||
|
|
||||||
@Getter(AccessLevel.PACKAGE)
|
|
||||||
private final AtomicLong filterApplyErrors = new AtomicLong();
|
|
||||||
|
|
||||||
public final void incrementApplyErrors() {
|
|
||||||
filterApplyErrors.incrementAndGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package com.provectus.kafka.ui.emitter;
|
||||||
|
|
||||||
|
import com.provectus.kafka.ui.model.TopicMessageDTO;
|
||||||
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
||||||
|
import com.provectus.kafka.ui.model.TopicMessagePhaseDTO;
|
||||||
|
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||||
|
import org.apache.kafka.common.utils.Bytes;
|
||||||
|
import reactor.core.publisher.FluxSink;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class MessagesProcessing {
|
||||||
|
|
||||||
|
private final ConsumingStats consumingStats = new ConsumingStats();
|
||||||
|
private long sentMessages = 0;
|
||||||
|
private int filterApplyErrors = 0;
|
||||||
|
|
||||||
|
private final ConsumerRecordDeserializer deserializer;
|
||||||
|
private final Predicate<TopicMessageDTO> filter;
|
||||||
|
private final @Nullable Integer limit;
|
||||||
|
|
||||||
|
public MessagesProcessing(ConsumerRecordDeserializer deserializer,
|
||||||
|
Predicate<TopicMessageDTO> filter,
|
||||||
|
@Nullable Integer limit) {
|
||||||
|
this.deserializer = deserializer;
|
||||||
|
this.filter = filter;
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean limitReached() {
|
||||||
|
return limit != null && sentMessages >= limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendMsg(FluxSink<TopicMessageEventDTO> sink, ConsumerRecord<Bytes, Bytes> rec) {
|
||||||
|
if (!sink.isCancelled() && !limitReached()) {
|
||||||
|
TopicMessageDTO topicMessage = deserializer.deserialize(rec);
|
||||||
|
try {
|
||||||
|
if (filter.test(topicMessage)) {
|
||||||
|
sink.next(
|
||||||
|
new TopicMessageEventDTO()
|
||||||
|
.type(TopicMessageEventDTO.TypeEnum.MESSAGE)
|
||||||
|
.message(topicMessage)
|
||||||
|
);
|
||||||
|
sentMessages++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
filterApplyErrors++;
|
||||||
|
log.trace("Error applying filter for message {}", topicMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int sentConsumingInfo(FluxSink<TopicMessageEventDTO> sink,
|
||||||
|
ConsumerRecords<Bytes, Bytes> polledRecords,
|
||||||
|
long elapsed) {
|
||||||
|
if (!sink.isCancelled()) {
|
||||||
|
return consumingStats.sendConsumingEvt(sink, polledRecords, elapsed, filterApplyErrors);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendFinishEvent(FluxSink<TopicMessageEventDTO> sink) {
|
||||||
|
if (!sink.isCancelled()) {
|
||||||
|
consumingStats.sendFinishEvent(sink, filterApplyErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPhase(FluxSink<TopicMessageEventDTO> sink, String name) {
|
||||||
|
if (!sink.isCancelled()) {
|
||||||
|
sink.next(
|
||||||
|
new TopicMessageEventDTO()
|
||||||
|
.type(TopicMessageEventDTO.TypeEnum.PHASE)
|
||||||
|
.phase(new TopicMessagePhaseDTO().name(name))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package com.provectus.kafka.ui.emitter;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.model.ConsumerPosition;
|
import com.provectus.kafka.ui.model.ConsumerPosition;
|
||||||
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
||||||
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -20,9 +19,9 @@ public class TailingEmitter extends AbstractEmitter
|
||||||
|
|
||||||
public TailingEmitter(Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
|
public TailingEmitter(Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
|
||||||
ConsumerPosition consumerPosition,
|
ConsumerPosition consumerPosition,
|
||||||
ConsumerRecordDeserializer recordDeserializer,
|
MessagesProcessing messagesProcessing,
|
||||||
PollingSettings pollingSettings) {
|
PollingSettings pollingSettings) {
|
||||||
super(recordDeserializer, pollingSettings);
|
super(messagesProcessing, pollingSettings);
|
||||||
this.consumerSupplier = consumerSupplier;
|
this.consumerSupplier = consumerSupplier;
|
||||||
this.consumerPosition = consumerPosition;
|
this.consumerPosition = consumerPosition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHan
|
||||||
.timestamp(currentTimestamp())
|
.timestamp(currentTimestamp())
|
||||||
.stackTrace(Throwables.getStackTraceAsString(exception));
|
.stackTrace(Throwables.getStackTraceAsString(exception));
|
||||||
return ServerResponse
|
return ServerResponse
|
||||||
.status(exception.getStatus())
|
.status(exception.getStatusCode())
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.bodyValue(response);
|
.bodyValue(response);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.provectus.kafka.ui.model.rbac;
|
package com.provectus.kafka.ui.model.rbac;
|
||||||
|
|
||||||
|
import static com.provectus.kafka.ui.model.rbac.Resource.APPLICATIONCONFIG;
|
||||||
import static com.provectus.kafka.ui.model.rbac.Resource.CLUSTERCONFIG;
|
import static com.provectus.kafka.ui.model.rbac.Resource.CLUSTERCONFIG;
|
||||||
import static com.provectus.kafka.ui.model.rbac.Resource.KSQL;
|
import static com.provectus.kafka.ui.model.rbac.Resource.KSQL;
|
||||||
|
|
||||||
|
@ -26,6 +27,8 @@ import org.springframework.util.Assert;
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class Permission {
|
public class Permission {
|
||||||
|
|
||||||
|
private static final List<Resource> RBAC_ACTION_EXEMPT_LIST = List.of(KSQL, CLUSTERCONFIG, APPLICATIONCONFIG);
|
||||||
|
|
||||||
Resource resource;
|
Resource resource;
|
||||||
List<String> actions;
|
List<String> actions;
|
||||||
|
|
||||||
|
@ -51,7 +54,7 @@ public class Permission {
|
||||||
|
|
||||||
public void validate() {
|
public void validate() {
|
||||||
Assert.notNull(resource, "resource cannot be null");
|
Assert.notNull(resource, "resource cannot be null");
|
||||||
if (!List.of(KSQL, CLUSTERCONFIG).contains(this.resource)) {
|
if (!RBAC_ACTION_EXEMPT_LIST.contains(this.resource)) {
|
||||||
Assert.notNull(value, "permission value can't be empty for resource " + resource);
|
Assert.notNull(value, "permission value can't be empty for resource " + resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.provectus.kafka.ui.service;
|
||||||
|
|
||||||
|
import static com.provectus.kafka.ui.model.ApplicationInfoDTO.EnabledFeaturesEnum;
|
||||||
|
|
||||||
|
import com.provectus.kafka.ui.model.ApplicationInfoBuildDTO;
|
||||||
|
import com.provectus.kafka.ui.model.ApplicationInfoDTO;
|
||||||
|
import com.provectus.kafka.ui.model.ApplicationInfoLatestReleaseDTO;
|
||||||
|
import com.provectus.kafka.ui.util.DynamicConfigOperations;
|
||||||
|
import com.provectus.kafka.ui.util.GithubReleaseInfo;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Properties;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.info.BuildProperties;
|
||||||
|
import org.springframework.boot.info.GitProperties;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ApplicationInfoService {
|
||||||
|
|
||||||
|
private final GithubReleaseInfo githubReleaseInfo = new GithubReleaseInfo();
|
||||||
|
|
||||||
|
private final DynamicConfigOperations dynamicConfigOperations;
|
||||||
|
private final BuildProperties buildProperties;
|
||||||
|
private final GitProperties gitProperties;
|
||||||
|
|
||||||
|
public ApplicationInfoService(DynamicConfigOperations dynamicConfigOperations,
|
||||||
|
@Autowired(required = false) BuildProperties buildProperties,
|
||||||
|
@Autowired(required = false) GitProperties gitProperties) {
|
||||||
|
this.dynamicConfigOperations = dynamicConfigOperations;
|
||||||
|
this.buildProperties = Optional.ofNullable(buildProperties).orElse(new BuildProperties(new Properties()));
|
||||||
|
this.gitProperties = Optional.ofNullable(gitProperties).orElse(new GitProperties(new Properties()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplicationInfoDTO getApplicationInfo() {
|
||||||
|
var releaseInfo = githubReleaseInfo.get();
|
||||||
|
return new ApplicationInfoDTO()
|
||||||
|
.build(getBuildInfo(releaseInfo))
|
||||||
|
.enabledFeatures(getEnabledFeatures())
|
||||||
|
.latestRelease(convert(releaseInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApplicationInfoLatestReleaseDTO convert(GithubReleaseInfo.GithubReleaseDto releaseInfo) {
|
||||||
|
return new ApplicationInfoLatestReleaseDTO()
|
||||||
|
.htmlUrl(releaseInfo.html_url())
|
||||||
|
.publishedAt(releaseInfo.published_at())
|
||||||
|
.versionTag(releaseInfo.tag_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApplicationInfoBuildDTO getBuildInfo(GithubReleaseInfo.GithubReleaseDto release) {
|
||||||
|
return new ApplicationInfoBuildDTO()
|
||||||
|
.isLatestRelease(release.tag_name() != null && release.tag_name().equals(buildProperties.getVersion()))
|
||||||
|
.commitId(gitProperties.getShortCommitId())
|
||||||
|
.version(buildProperties.getVersion())
|
||||||
|
.buildTime(buildProperties.getTime() != null
|
||||||
|
? DateTimeFormatter.ISO_INSTANT.format(buildProperties.getTime()) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EnabledFeaturesEnum> getEnabledFeatures() {
|
||||||
|
var enabledFeatures = new ArrayList<EnabledFeaturesEnum>();
|
||||||
|
if (dynamicConfigOperations.dynamicConfigEnabled()) {
|
||||||
|
enabledFeatures.add(EnabledFeaturesEnum.DYNAMIC_CONFIG);
|
||||||
|
}
|
||||||
|
return enabledFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
// updating on startup and every hour
|
||||||
|
@Scheduled(fixedRateString = "${github-release-info-update-rate:3600000}")
|
||||||
|
public void updateGithubReleaseInfo() {
|
||||||
|
githubReleaseInfo.refresh().block();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,38 +1,58 @@
|
||||||
package com.provectus.kafka.ui.service;
|
package com.provectus.kafka.ui.service;
|
||||||
|
|
||||||
|
import static java.util.regex.Pattern.CASE_INSENSITIVE;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.apache.kafka.common.config.ConfigDef;
|
import org.apache.kafka.common.config.ConfigDef;
|
||||||
import org.apache.kafka.common.config.SaslConfigs;
|
import org.apache.kafka.common.config.SaslConfigs;
|
||||||
import org.apache.kafka.common.config.SslConfigs;
|
import org.apache.kafka.common.config.SslConfigs;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class KafkaConfigSanitizer extends Sanitizer {
|
class KafkaConfigSanitizer {
|
||||||
private static final List<String> DEFAULT_PATTERNS_TO_SANITIZE = Arrays.asList(
|
|
||||||
"basic.auth.user.info", /* For Schema Registry credentials */
|
private static final String SANITIZED_VALUE = "******";
|
||||||
"password", "secret", "token", "key", ".*credentials.*", /* General credential patterns */
|
|
||||||
"aws.access.*", "aws.secret.*", "aws.session.*" /* AWS-related credential patterns */
|
private static final String[] REGEX_PARTS = {"*", "$", "^", "+"};
|
||||||
);
|
|
||||||
|
private static final List<String> DEFAULT_PATTERNS_TO_SANITIZE = ImmutableList.<String>builder()
|
||||||
|
.addAll(kafkaConfigKeysToSanitize())
|
||||||
|
.add(
|
||||||
|
"basic.auth.user.info", /* For Schema Registry credentials */
|
||||||
|
"password", "secret", "token", "key", ".*credentials.*", /* General credential patterns */
|
||||||
|
"aws.access.*", "aws.secret.*", "aws.session.*" /* AWS-related credential patterns */
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final List<Pattern> sanitizeKeysPatterns;
|
||||||
|
|
||||||
KafkaConfigSanitizer(
|
KafkaConfigSanitizer(
|
||||||
@Value("${kafka.config.sanitizer.enabled:true}") boolean enabled,
|
@Value("${kafka.config.sanitizer.enabled:true}") boolean enabled,
|
||||||
@Value("${kafka.config.sanitizer.patterns:}") List<String> patternsToSanitize
|
@Value("${kafka.config.sanitizer.patterns:}") List<String> patternsToSanitize
|
||||||
) {
|
) {
|
||||||
if (!enabled) {
|
this.sanitizeKeysPatterns = enabled
|
||||||
setKeysToSanitize();
|
? compile(patternsToSanitize.isEmpty() ? DEFAULT_PATTERNS_TO_SANITIZE : patternsToSanitize)
|
||||||
} else {
|
: List.of();
|
||||||
var keysToSanitize = new HashSet<>(
|
}
|
||||||
patternsToSanitize.isEmpty() ? DEFAULT_PATTERNS_TO_SANITIZE : patternsToSanitize);
|
|
||||||
keysToSanitize.addAll(kafkaConfigKeysToSanitize());
|
private static List<Pattern> compile(Collection<String> patternStrings) {
|
||||||
setKeysToSanitize(keysToSanitize.toArray(new String[] {}));
|
return patternStrings.stream()
|
||||||
}
|
.map(p -> isRegex(p)
|
||||||
|
? Pattern.compile(p, CASE_INSENSITIVE)
|
||||||
|
: Pattern.compile(".*" + p + "$", CASE_INSENSITIVE))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isRegex(String str) {
|
||||||
|
return Arrays.stream(REGEX_PARTS).anyMatch(str::contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> kafkaConfigKeysToSanitize() {
|
private static Set<String> kafkaConfigKeysToSanitize() {
|
||||||
|
@ -45,4 +65,17 @@ class KafkaConfigSanitizer extends Sanitizer {
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Object sanitize(String key, Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (Pattern pattern : sanitizeKeysPatterns) {
|
||||||
|
if (pattern.matcher(key).matches()) {
|
||||||
|
return SANITIZED_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,11 +225,11 @@ public class KafkaConnectService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<ConnectorDTO> setConnectorConfig(KafkaCluster cluster, String connectName,
|
public Mono<ConnectorDTO> setConnectorConfig(KafkaCluster cluster, String connectName,
|
||||||
String connectorName, Mono<Object> requestBody) {
|
String connectorName, Mono<Map<String, Object>> requestBody) {
|
||||||
return api(cluster, connectName)
|
return api(cluster, connectName)
|
||||||
.mono(c ->
|
.mono(c ->
|
||||||
requestBody
|
requestBody
|
||||||
.flatMap(body -> c.setConnectorConfig(connectorName, (Map<String, Object>) body))
|
.flatMap(body -> c.setConnectorConfig(connectorName, body))
|
||||||
.map(kafkaConnectMapper::fromClient));
|
.map(kafkaConnectMapper::fromClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,12 +298,12 @@ public class KafkaConnectService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<ConnectorPluginConfigValidationResponseDTO> validateConnectorPluginConfig(
|
public Mono<ConnectorPluginConfigValidationResponseDTO> validateConnectorPluginConfig(
|
||||||
KafkaCluster cluster, String connectName, String pluginName, Mono<Object> requestBody) {
|
KafkaCluster cluster, String connectName, String pluginName, Mono<Map<String, Object>> requestBody) {
|
||||||
return api(cluster, connectName)
|
return api(cluster, connectName)
|
||||||
.mono(client ->
|
.mono(client ->
|
||||||
requestBody
|
requestBody
|
||||||
.flatMap(body ->
|
.flatMap(body ->
|
||||||
client.validateConnectorPluginConfig(pluginName, (Map<String, Object>) body))
|
client.validateConnectorPluginConfig(pluginName, body))
|
||||||
.map(kafkaConnectMapper::fromClient)
|
.map(kafkaConnectMapper::fromClient)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,8 @@ package com.provectus.kafka.ui.service;
|
||||||
import com.google.common.util.concurrent.RateLimiter;
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.provectus.kafka.ui.emitter.BackwardRecordEmitter;
|
import com.provectus.kafka.ui.emitter.BackwardRecordEmitter;
|
||||||
import com.provectus.kafka.ui.emitter.ForwardRecordEmitter;
|
import com.provectus.kafka.ui.emitter.ForwardRecordEmitter;
|
||||||
import com.provectus.kafka.ui.emitter.MessageFilterStats;
|
|
||||||
import com.provectus.kafka.ui.emitter.MessageFilters;
|
import com.provectus.kafka.ui.emitter.MessageFilters;
|
||||||
import com.provectus.kafka.ui.emitter.ResultSizeLimiter;
|
import com.provectus.kafka.ui.emitter.MessagesProcessing;
|
||||||
import com.provectus.kafka.ui.emitter.TailingEmitter;
|
import com.provectus.kafka.ui.emitter.TailingEmitter;
|
||||||
import com.provectus.kafka.ui.exception.TopicNotFoundException;
|
import com.provectus.kafka.ui.exception.TopicNotFoundException;
|
||||||
import com.provectus.kafka.ui.exception.ValidationException;
|
import com.provectus.kafka.ui.exception.ValidationException;
|
||||||
|
@ -14,9 +13,9 @@ import com.provectus.kafka.ui.model.CreateTopicMessageDTO;
|
||||||
import com.provectus.kafka.ui.model.KafkaCluster;
|
import com.provectus.kafka.ui.model.KafkaCluster;
|
||||||
import com.provectus.kafka.ui.model.MessageFilterTypeDTO;
|
import com.provectus.kafka.ui.model.MessageFilterTypeDTO;
|
||||||
import com.provectus.kafka.ui.model.SeekDirectionDTO;
|
import com.provectus.kafka.ui.model.SeekDirectionDTO;
|
||||||
|
import com.provectus.kafka.ui.model.TopicMessageDTO;
|
||||||
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
||||||
import com.provectus.kafka.ui.serde.api.Serde;
|
import com.provectus.kafka.ui.serde.api.Serde;
|
||||||
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
|
|
||||||
import com.provectus.kafka.ui.serdes.ProducerRecordCreator;
|
import com.provectus.kafka.ui.serdes.ProducerRecordCreator;
|
||||||
import com.provectus.kafka.ui.util.SslPropertiesUtil;
|
import com.provectus.kafka.ui.util.SslPropertiesUtil;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -162,13 +161,18 @@ public class MessagesService {
|
||||||
@Nullable String valueSerde) {
|
@Nullable String valueSerde) {
|
||||||
|
|
||||||
java.util.function.Consumer<? super FluxSink<TopicMessageEventDTO>> emitter;
|
java.util.function.Consumer<? super FluxSink<TopicMessageEventDTO>> emitter;
|
||||||
ConsumerRecordDeserializer recordDeserializer =
|
|
||||||
deserializationService.deserializerFor(cluster, topic, keySerde, valueSerde);
|
var processing = new MessagesProcessing(
|
||||||
|
deserializationService.deserializerFor(cluster, topic, keySerde, valueSerde),
|
||||||
|
getMsgFilter(query, filterQueryType),
|
||||||
|
seekDirection == SeekDirectionDTO.TAILING ? null : limit
|
||||||
|
);
|
||||||
|
|
||||||
if (seekDirection.equals(SeekDirectionDTO.FORWARD)) {
|
if (seekDirection.equals(SeekDirectionDTO.FORWARD)) {
|
||||||
emitter = new ForwardRecordEmitter(
|
emitter = new ForwardRecordEmitter(
|
||||||
() -> consumerGroupService.createConsumer(cluster),
|
() -> consumerGroupService.createConsumer(cluster),
|
||||||
consumerPosition,
|
consumerPosition,
|
||||||
recordDeserializer,
|
processing,
|
||||||
cluster.getPollingSettings()
|
cluster.getPollingSettings()
|
||||||
);
|
);
|
||||||
} else if (seekDirection.equals(SeekDirectionDTO.BACKWARD)) {
|
} else if (seekDirection.equals(SeekDirectionDTO.BACKWARD)) {
|
||||||
|
@ -176,33 +180,22 @@ public class MessagesService {
|
||||||
() -> consumerGroupService.createConsumer(cluster),
|
() -> consumerGroupService.createConsumer(cluster),
|
||||||
consumerPosition,
|
consumerPosition,
|
||||||
limit,
|
limit,
|
||||||
recordDeserializer,
|
processing,
|
||||||
cluster.getPollingSettings()
|
cluster.getPollingSettings()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
emitter = new TailingEmitter(
|
emitter = new TailingEmitter(
|
||||||
() -> consumerGroupService.createConsumer(cluster),
|
() -> consumerGroupService.createConsumer(cluster),
|
||||||
consumerPosition,
|
consumerPosition,
|
||||||
recordDeserializer,
|
processing,
|
||||||
cluster.getPollingSettings()
|
cluster.getPollingSettings()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
MessageFilterStats filterStats = new MessageFilterStats();
|
|
||||||
return Flux.create(emitter)
|
return Flux.create(emitter)
|
||||||
.contextWrite(ctx -> ctx.put(MessageFilterStats.class, filterStats))
|
|
||||||
.filter(getMsgFilter(query, filterQueryType, filterStats))
|
|
||||||
.map(getDataMasker(cluster, topic))
|
.map(getDataMasker(cluster, topic))
|
||||||
.takeWhile(createTakeWhilePredicate(seekDirection, limit))
|
|
||||||
.map(throttleUiPublish(seekDirection));
|
.map(throttleUiPublish(seekDirection));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate<TopicMessageEventDTO> createTakeWhilePredicate(
|
|
||||||
SeekDirectionDTO seekDirection, int limit) {
|
|
||||||
return seekDirection == SeekDirectionDTO.TAILING
|
|
||||||
? evt -> true // no limit for tailing
|
|
||||||
: new ResultSizeLimiter(limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnaryOperator<TopicMessageEventDTO> getDataMasker(KafkaCluster cluster, String topicName) {
|
private UnaryOperator<TopicMessageEventDTO> getDataMasker(KafkaCluster cluster, String topicName) {
|
||||||
var keyMasker = cluster.getMasking().getMaskingFunction(topicName, Serde.Target.KEY);
|
var keyMasker = cluster.getMasking().getMaskingFunction(topicName, Serde.Target.KEY);
|
||||||
var valMasker = cluster.getMasking().getMaskingFunction(topicName, Serde.Target.VALUE);
|
var valMasker = cluster.getMasking().getMaskingFunction(topicName, Serde.Target.VALUE);
|
||||||
|
@ -211,32 +204,18 @@ public class MessagesService {
|
||||||
return evt;
|
return evt;
|
||||||
}
|
}
|
||||||
return evt.message(
|
return evt.message(
|
||||||
evt.getMessage()
|
evt.getMessage()
|
||||||
.key(keyMasker.apply(evt.getMessage().getKey()))
|
.key(keyMasker.apply(evt.getMessage().getKey()))
|
||||||
.content(valMasker.apply(evt.getMessage().getContent())));
|
.content(valMasker.apply(evt.getMessage().getContent())));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate<TopicMessageEventDTO> getMsgFilter(String query,
|
private Predicate<TopicMessageDTO> getMsgFilter(String query,
|
||||||
MessageFilterTypeDTO filterQueryType,
|
MessageFilterTypeDTO filterQueryType) {
|
||||||
MessageFilterStats filterStats) {
|
|
||||||
if (StringUtils.isEmpty(query)) {
|
if (StringUtils.isEmpty(query)) {
|
||||||
return evt -> true;
|
return evt -> true;
|
||||||
}
|
}
|
||||||
var messageFilter = MessageFilters.createMsgFilter(query, filterQueryType);
|
return MessageFilters.createMsgFilter(query, filterQueryType);
|
||||||
return evt -> {
|
|
||||||
// we only apply filter for message events
|
|
||||||
if (evt.getType() == TopicMessageEventDTO.TypeEnum.MESSAGE) {
|
|
||||||
try {
|
|
||||||
return messageFilter.test(evt.getMessage());
|
|
||||||
} catch (Exception e) {
|
|
||||||
filterStats.incrementApplyErrors();
|
|
||||||
log.trace("Error applying filter '{}' for message {}", query, evt.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> UnaryOperator<T> throttleUiPublish(SeekDirectionDTO seekDirection) {
|
private <T> UnaryOperator<T> throttleUiPublish(SeekDirectionDTO seekDirection) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import static java.util.stream.Collectors.toList;
|
||||||
import static java.util.stream.Collectors.toMap;
|
import static java.util.stream.Collectors.toMap;
|
||||||
import static org.apache.kafka.clients.admin.ListOffsetsResult.ListOffsetsResultInfo;
|
import static org.apache.kafka.clients.admin.ListOffsetsResult.ListOffsetsResultInfo;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableTable;
|
import com.google.common.collect.ImmutableTable;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
@ -514,6 +515,14 @@ public class ReactiveAdminClient implements Closeable {
|
||||||
.flatMap(parts -> listOffsetsUnsafe(parts, offsetSpec));
|
.flatMap(parts -> listOffsetsUnsafe(parts, offsetSpec));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List offset for the specified topics, skipping no-leader partitions.
|
||||||
|
*/
|
||||||
|
public Mono<Map<TopicPartition, Long>> listOffsets(Collection<TopicDescription> topicDescriptions,
|
||||||
|
OffsetSpec offsetSpec) {
|
||||||
|
return listOffsetsUnsafe(filterPartitionsWithLeaderCheck(topicDescriptions, p -> true, false), offsetSpec);
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<Collection<TopicPartition>> filterPartitionsWithLeaderCheck(Collection<TopicPartition> partitions,
|
private Mono<Collection<TopicPartition>> filterPartitionsWithLeaderCheck(Collection<TopicPartition> partitions,
|
||||||
boolean failOnUnknownLeader) {
|
boolean failOnUnknownLeader) {
|
||||||
var targetTopics = partitions.stream().map(TopicPartition::topic).collect(Collectors.toSet());
|
var targetTopics = partitions.stream().map(TopicPartition::topic).collect(Collectors.toSet());
|
||||||
|
@ -523,34 +532,44 @@ public class ReactiveAdminClient implements Closeable {
|
||||||
descriptions.values(), partitions::contains, failOnUnknownLeader));
|
descriptions.values(), partitions::contains, failOnUnknownLeader));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<TopicPartition> filterPartitionsWithLeaderCheck(Collection<TopicDescription> topicDescriptions,
|
@VisibleForTesting
|
||||||
|
static Set<TopicPartition> filterPartitionsWithLeaderCheck(Collection<TopicDescription> topicDescriptions,
|
||||||
Predicate<TopicPartition> partitionPredicate,
|
Predicate<TopicPartition> partitionPredicate,
|
||||||
boolean failOnUnknownLeader) {
|
boolean failOnUnknownLeader) {
|
||||||
var goodPartitions = new HashSet<TopicPartition>();
|
var goodPartitions = new HashSet<TopicPartition>();
|
||||||
for (TopicDescription description : topicDescriptions) {
|
for (TopicDescription description : topicDescriptions) {
|
||||||
|
var goodTopicPartitions = new ArrayList<TopicPartition>();
|
||||||
for (TopicPartitionInfo partitionInfo : description.partitions()) {
|
for (TopicPartitionInfo partitionInfo : description.partitions()) {
|
||||||
TopicPartition topicPartition = new TopicPartition(description.name(), partitionInfo.partition());
|
TopicPartition topicPartition = new TopicPartition(description.name(), partitionInfo.partition());
|
||||||
if (!partitionPredicate.test(topicPartition)) {
|
if (partitionInfo.leader() == null) {
|
||||||
continue;
|
if (failOnUnknownLeader) {
|
||||||
|
throw new ValidationException(String.format("Topic partition %s has no leader", topicPartition));
|
||||||
|
} else {
|
||||||
|
// if ANY of topic partitions has no leader - we have to skip all topic partitions
|
||||||
|
goodTopicPartitions.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (partitionInfo.leader() != null) {
|
if (partitionPredicate.test(topicPartition)) {
|
||||||
goodPartitions.add(topicPartition);
|
goodTopicPartitions.add(topicPartition);
|
||||||
} else if (failOnUnknownLeader) {
|
|
||||||
throw new ValidationException(String.format("Topic partition %s has no leader", topicPartition));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
goodPartitions.addAll(goodTopicPartitions);
|
||||||
}
|
}
|
||||||
return goodPartitions;
|
return goodPartitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. NOTE(!): should only apply for partitions with existing leader,
|
// 1. NOTE(!): should only apply for partitions from topics where all partitions have leaders,
|
||||||
// otherwise AdminClient will try to fetch topic metadata, fail and retry infinitely (until timeout)
|
// otherwise AdminClient will try to fetch topic metadata, fail and retry infinitely (until timeout)
|
||||||
// 2. NOTE(!): Skips partitions that were not initialized yet
|
// 2. NOTE(!): Skips partitions that were not initialized yet
|
||||||
// (UnknownTopicOrPartitionException thrown, ex. after topic creation)
|
// (UnknownTopicOrPartitionException thrown, ex. after topic creation)
|
||||||
// 3. TODO: check if it is a bug that AdminClient never throws LeaderNotAvailableException and just retrying instead
|
// 3. TODO: check if it is a bug that AdminClient never throws LeaderNotAvailableException and just retrying instead
|
||||||
@KafkaClientInternalsDependant
|
@KafkaClientInternalsDependant
|
||||||
public Mono<Map<TopicPartition, Long>> listOffsetsUnsafe(Collection<TopicPartition> partitions,
|
@VisibleForTesting
|
||||||
OffsetSpec offsetSpec) {
|
Mono<Map<TopicPartition, Long>> listOffsetsUnsafe(Collection<TopicPartition> partitions, OffsetSpec offsetSpec) {
|
||||||
|
if (partitions.isEmpty()) {
|
||||||
|
return Mono.just(Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
Function<Collection<TopicPartition>, Mono<Map<TopicPartition, Long>>> call =
|
Function<Collection<TopicPartition>, Mono<Map<TopicPartition, Long>>> call =
|
||||||
parts -> {
|
parts -> {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.provectus.kafka.ui.service;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
import static java.util.stream.Collectors.toMap;
|
import static java.util.stream.Collectors.toMap;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.provectus.kafka.ui.config.ClustersProperties;
|
import com.provectus.kafka.ui.config.ClustersProperties;
|
||||||
import com.provectus.kafka.ui.exception.TopicMetadataException;
|
import com.provectus.kafka.ui.exception.TopicMetadataException;
|
||||||
import com.provectus.kafka.ui.exception.TopicNotFoundException;
|
import com.provectus.kafka.ui.exception.TopicNotFoundException;
|
||||||
|
@ -136,22 +137,14 @@ public class TopicsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<InternalPartitionsOffsets> getPartitionOffsets(Map<String, TopicDescription>
|
private Mono<InternalPartitionsOffsets> getPartitionOffsets(Map<String, TopicDescription>
|
||||||
descriptions,
|
descriptionsMap,
|
||||||
ReactiveAdminClient ac) {
|
ReactiveAdminClient ac) {
|
||||||
var topicPartitions = descriptions.values().stream()
|
var descriptions = descriptionsMap.values();
|
||||||
.flatMap(desc ->
|
return ac.listOffsets(descriptions, OffsetSpec.earliest())
|
||||||
desc.partitions().stream()
|
.zipWith(ac.listOffsets(descriptions, OffsetSpec.latest()),
|
||||||
// list offsets should only be applied to partitions with existing leader
|
|
||||||
// (see ReactiveAdminClient.listOffsetsUnsafe(..) docs)
|
|
||||||
.filter(tp -> tp.leader() != null)
|
|
||||||
.map(p -> new TopicPartition(desc.name(), p.partition())))
|
|
||||||
.collect(toList());
|
|
||||||
|
|
||||||
return ac.listOffsetsUnsafe(topicPartitions, OffsetSpec.earliest())
|
|
||||||
.zipWith(ac.listOffsetsUnsafe(topicPartitions, OffsetSpec.latest()),
|
|
||||||
(earliest, latest) ->
|
(earliest, latest) ->
|
||||||
topicPartitions.stream()
|
Sets.intersection(earliest.keySet(), latest.keySet())
|
||||||
.filter(tp -> earliest.containsKey(tp) && latest.containsKey(tp))
|
.stream()
|
||||||
.map(tp ->
|
.map(tp ->
|
||||||
Map.entry(tp,
|
Map.entry(tp,
|
||||||
new InternalPartitionsOffsets.Offsets(
|
new InternalPartitionsOffsets.Offsets(
|
||||||
|
|
|
@ -2,7 +2,7 @@ package com.provectus.kafka.ui.service.analyze;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.model.TopicAnalysisSizeStatsDTO;
|
import com.provectus.kafka.ui.model.TopicAnalysisSizeStatsDTO;
|
||||||
import com.provectus.kafka.ui.model.TopicAnalysisStatsDTO;
|
import com.provectus.kafka.ui.model.TopicAnalysisStatsDTO;
|
||||||
import com.provectus.kafka.ui.model.TopicAnalysisStatsHourlyMsgCountsDTO;
|
import com.provectus.kafka.ui.model.TopicAnalysisStatsHourlyMsgCountsInnerDTO;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
@ -78,10 +78,10 @@ class TopicAnalysisStats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TopicAnalysisStatsHourlyMsgCountsDTO> toDto() {
|
List<TopicAnalysisStatsHourlyMsgCountsInnerDTO> toDto() {
|
||||||
return hourlyStats.entrySet().stream()
|
return hourlyStats.entrySet().stream()
|
||||||
.sorted(Comparator.comparingLong(Map.Entry::getKey))
|
.sorted(Comparator.comparingLong(Map.Entry::getKey))
|
||||||
.map(e -> new TopicAnalysisStatsHourlyMsgCountsDTO()
|
.map(e -> new TopicAnalysisStatsHourlyMsgCountsInnerDTO()
|
||||||
.hourStart(e.getKey())
|
.hourStart(e.getKey())
|
||||||
.count(e.getValue()))
|
.count(e.getValue()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
|
@ -52,7 +52,10 @@ public class KsqlApiClient {
|
||||||
boolean error;
|
boolean error;
|
||||||
|
|
||||||
public Optional<JsonNode> getColumnValue(List<JsonNode> row, String column) {
|
public Optional<JsonNode> getColumnValue(List<JsonNode> row, String column) {
|
||||||
return Optional.ofNullable(row.get(columnNames.indexOf(column)));
|
int colIdx = columnNames.indexOf(column);
|
||||||
|
return colIdx >= 0
|
||||||
|
? Optional.ofNullable(row.get(colIdx))
|
||||||
|
: Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,14 @@ public class KsqlServiceV2 {
|
||||||
.name(resp.getColumnValue(row, "name").map(JsonNode::asText).orElse(null))
|
.name(resp.getColumnValue(row, "name").map(JsonNode::asText).orElse(null))
|
||||||
.topic(resp.getColumnValue(row, "topic").map(JsonNode::asText).orElse(null))
|
.topic(resp.getColumnValue(row, "topic").map(JsonNode::asText).orElse(null))
|
||||||
.keyFormat(resp.getColumnValue(row, "keyFormat").map(JsonNode::asText).orElse(null))
|
.keyFormat(resp.getColumnValue(row, "keyFormat").map(JsonNode::asText).orElse(null))
|
||||||
.valueFormat(resp.getColumnValue(row, "valueFormat").map(JsonNode::asText).orElse(null)))
|
.valueFormat(
|
||||||
|
// for old versions (<0.13) "format" column is filled,
|
||||||
|
// for new version "keyFormat" & "valueFormat" columns should be filled
|
||||||
|
resp.getColumnValue(row, "valueFormat")
|
||||||
|
.or(() -> resp.getColumnValue(row, "format"))
|
||||||
|
.map(JsonNode::asText)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.provectus.kafka.ui.service.rbac.extractor.GithubAuthorityExtractor;
|
||||||
import com.provectus.kafka.ui.service.rbac.extractor.GoogleAuthorityExtractor;
|
import com.provectus.kafka.ui.service.rbac.extractor.GoogleAuthorityExtractor;
|
||||||
import com.provectus.kafka.ui.service.rbac.extractor.LdapAuthorityExtractor;
|
import com.provectus.kafka.ui.service.rbac.extractor.LdapAuthorityExtractor;
|
||||||
import com.provectus.kafka.ui.service.rbac.extractor.ProviderAuthorityExtractor;
|
import com.provectus.kafka.ui.service.rbac.extractor.ProviderAuthorityExtractor;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -28,7 +29,6 @@ import java.util.function.Predicate;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package com.provectus.kafka.ui.service.rbac.extractor;
|
package com.provectus.kafka.ui.service.rbac.extractor;
|
||||||
|
|
||||||
import com.nimbusds.jose.shaded.json.JSONArray;
|
|
||||||
import com.provectus.kafka.ui.model.rbac.Role;
|
import com.provectus.kafka.ui.model.rbac.Role;
|
||||||
import com.provectus.kafka.ui.model.rbac.provider.Provider;
|
import com.provectus.kafka.ui.model.rbac.provider.Provider;
|
||||||
import com.provectus.kafka.ui.service.rbac.AccessControlService;
|
import com.provectus.kafka.ui.service.rbac.AccessControlService;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -44,7 +44,7 @@ public class CognitoAuthorityExtractor implements ProviderAuthorityExtractor {
|
||||||
.map(Role::getName)
|
.map(Role::getName)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
JSONArray groups = principal.getAttribute(COGNITO_GROUPS_ATTRIBUTE_NAME);
|
List<String> groups = principal.getAttribute(COGNITO_GROUPS_ATTRIBUTE_NAME);
|
||||||
if (groups == null) {
|
if (groups == null) {
|
||||||
log.debug("Cognito groups param is not present");
|
log.debug("Cognito groups param is not present");
|
||||||
return Mono.just(groupsByUsername);
|
return Mono.just(groupsByUsername);
|
||||||
|
@ -56,9 +56,8 @@ public class CognitoAuthorityExtractor implements ProviderAuthorityExtractor {
|
||||||
.stream()
|
.stream()
|
||||||
.filter(s -> s.getProvider().equals(Provider.OAUTH_COGNITO))
|
.filter(s -> s.getProvider().equals(Provider.OAUTH_COGNITO))
|
||||||
.filter(s -> s.getType().equals("group"))
|
.filter(s -> s.getType().equals("group"))
|
||||||
.anyMatch(subject -> Stream.of(groups.toArray())
|
.anyMatch(subject -> Stream.of(groups)
|
||||||
.map(Object::toString)
|
.map(Object::toString)
|
||||||
.distinct()
|
|
||||||
.anyMatch(cognitoGroup -> cognitoGroup.equals(subject.getValue()))
|
.anyMatch(cognitoGroup -> cognitoGroup.equals(subject.getValue()))
|
||||||
))
|
))
|
||||||
.map(Role::getName)
|
.map(Role::getName)
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.provectus.kafka.ui.util;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import java.time.Duration;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class GithubReleaseInfo {
|
||||||
|
|
||||||
|
private static final String GITHUB_LATEST_RELEASE_RETRIEVAL_URL =
|
||||||
|
"https://api.github.com/repos/provectus/kafka-ui/releases/latest";
|
||||||
|
|
||||||
|
private static final Duration GITHUB_API_MAX_WAIT_TIME = Duration.ofSeconds(2);
|
||||||
|
|
||||||
|
public record GithubReleaseDto(String html_url, String tag_name, String published_at) {
|
||||||
|
|
||||||
|
static GithubReleaseDto empty() {
|
||||||
|
return new GithubReleaseDto(null, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile GithubReleaseDto release = GithubReleaseDto.empty();
|
||||||
|
|
||||||
|
private final Mono<Void> refreshMono;
|
||||||
|
|
||||||
|
public GithubReleaseInfo() {
|
||||||
|
this(GITHUB_LATEST_RELEASE_RETRIEVAL_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
GithubReleaseInfo(String url) {
|
||||||
|
this.refreshMono = WebClient.create()
|
||||||
|
.get()
|
||||||
|
.uri(url)
|
||||||
|
.exchangeToMono(resp -> resp.bodyToMono(GithubReleaseDto.class))
|
||||||
|
.timeout(GITHUB_API_MAX_WAIT_TIME)
|
||||||
|
.doOnError(th -> log.trace("Error getting latest github release info", th))
|
||||||
|
.onErrorResume(th -> true, th -> Mono.just(GithubReleaseDto.empty()))
|
||||||
|
.doOnNext(release -> this.release = release)
|
||||||
|
.then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GithubReleaseDto get() {
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<Void> refresh() {
|
||||||
|
return refreshMono;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ import org.springframework.context.ApplicationContextInitializer;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.util.SocketUtils;
|
import org.springframework.test.util.TestSocketUtils;
|
||||||
import org.testcontainers.containers.KafkaContainer;
|
import org.testcontainers.containers.KafkaContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.utility.DockerImageName;
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
@ -61,7 +61,7 @@ public abstract class AbstractIntegrationTest {
|
||||||
System.setProperty("kafka.clusters.0.bootstrapServers", kafka.getBootstrapServers());
|
System.setProperty("kafka.clusters.0.bootstrapServers", kafka.getBootstrapServers());
|
||||||
// List unavailable hosts to verify failover
|
// List unavailable hosts to verify failover
|
||||||
System.setProperty("kafka.clusters.0.schemaRegistry", String.format("http://localhost:%1$s,http://localhost:%1$s,%2$s",
|
System.setProperty("kafka.clusters.0.schemaRegistry", String.format("http://localhost:%1$s,http://localhost:%1$s,%2$s",
|
||||||
SocketUtils.findAvailableTcpPort(), schemaRegistry.getUrl()));
|
TestSocketUtils.findAvailableTcpPort(), schemaRegistry.getUrl()));
|
||||||
System.setProperty("kafka.clusters.0.kafkaConnect.0.name", "kafka-connect");
|
System.setProperty("kafka.clusters.0.kafkaConnect.0.name", "kafka-connect");
|
||||||
System.setProperty("kafka.clusters.0.kafkaConnect.0.userName", "kafka-connect");
|
System.setProperty("kafka.clusters.0.kafkaConnect.0.userName", "kafka-connect");
|
||||||
System.setProperty("kafka.clusters.0.kafkaConnect.0.password", "kafka-connect");
|
System.setProperty("kafka.clusters.0.kafkaConnect.0.password", "kafka-connect");
|
||||||
|
|
|
@ -5,13 +5,12 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
|
||||||
|
|
||||||
class KafkaConfigSanitizerTest {
|
class KafkaConfigSanitizerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void doNothingIfEnabledPropertySetToFalse() {
|
void doNothingIfEnabledPropertySetToFalse() {
|
||||||
final Sanitizer sanitizer = new KafkaConfigSanitizer(false, Collections.emptyList());
|
final var sanitizer = new KafkaConfigSanitizer(false, Collections.emptyList());
|
||||||
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("secret");
|
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("secret");
|
||||||
assertThat(sanitizer.sanitize("sasl.jaas.config", "secret")).isEqualTo("secret");
|
assertThat(sanitizer.sanitize("sasl.jaas.config", "secret")).isEqualTo("secret");
|
||||||
assertThat(sanitizer.sanitize("database.password", "secret")).isEqualTo("secret");
|
assertThat(sanitizer.sanitize("database.password", "secret")).isEqualTo("secret");
|
||||||
|
@ -19,7 +18,7 @@ class KafkaConfigSanitizerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void obfuscateCredentials() {
|
void obfuscateCredentials() {
|
||||||
final Sanitizer sanitizer = new KafkaConfigSanitizer(true, Collections.emptyList());
|
final var sanitizer = new KafkaConfigSanitizer(true, Collections.emptyList());
|
||||||
assertThat(sanitizer.sanitize("sasl.jaas.config", "secret")).isEqualTo("******");
|
assertThat(sanitizer.sanitize("sasl.jaas.config", "secret")).isEqualTo("******");
|
||||||
assertThat(sanitizer.sanitize("consumer.sasl.jaas.config", "secret")).isEqualTo("******");
|
assertThat(sanitizer.sanitize("consumer.sasl.jaas.config", "secret")).isEqualTo("******");
|
||||||
assertThat(sanitizer.sanitize("producer.sasl.jaas.config", "secret")).isEqualTo("******");
|
assertThat(sanitizer.sanitize("producer.sasl.jaas.config", "secret")).isEqualTo("******");
|
||||||
|
@ -37,7 +36,7 @@ class KafkaConfigSanitizerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void notObfuscateNormalConfigs() {
|
void notObfuscateNormalConfigs() {
|
||||||
final Sanitizer sanitizer = new KafkaConfigSanitizer(true, Collections.emptyList());
|
final var sanitizer = new KafkaConfigSanitizer(true, Collections.emptyList());
|
||||||
assertThat(sanitizer.sanitize("security.protocol", "SASL_SSL")).isEqualTo("SASL_SSL");
|
assertThat(sanitizer.sanitize("security.protocol", "SASL_SSL")).isEqualTo("SASL_SSL");
|
||||||
final String[] bootstrapServer = new String[] {"test1:9092", "test2:9092"};
|
final String[] bootstrapServer = new String[] {"test1:9092", "test2:9092"};
|
||||||
assertThat(sanitizer.sanitize("bootstrap.servers", bootstrapServer)).isEqualTo(bootstrapServer);
|
assertThat(sanitizer.sanitize("bootstrap.servers", bootstrapServer)).isEqualTo(bootstrapServer);
|
||||||
|
@ -45,7 +44,7 @@ class KafkaConfigSanitizerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void obfuscateCredentialsWithDefinedPatterns() {
|
void obfuscateCredentialsWithDefinedPatterns() {
|
||||||
final Sanitizer sanitizer = new KafkaConfigSanitizer(true, Arrays.asList("kafka.ui", ".*test.*"));
|
final var sanitizer = new KafkaConfigSanitizer(true, Arrays.asList("kafka.ui", ".*test.*"));
|
||||||
assertThat(sanitizer.sanitize("consumer.kafka.ui", "secret")).isEqualTo("******");
|
assertThat(sanitizer.sanitize("consumer.kafka.ui", "secret")).isEqualTo("******");
|
||||||
assertThat(sanitizer.sanitize("this.is.test.credentials", "secret")).isEqualTo("******");
|
assertThat(sanitizer.sanitize("this.is.test.credentials", "secret")).isEqualTo("******");
|
||||||
assertThat(sanitizer.sanitize("this.is.not.credential", "not.credential"))
|
assertThat(sanitizer.sanitize("this.is.not.credential", "not.credential"))
|
||||||
|
|
|
@ -4,8 +4,11 @@ import static com.provectus.kafka.ui.service.ReactiveAdminClient.toMonoWithExcep
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.apache.kafka.clients.admin.ListOffsetsResult.ListOffsetsResultInfo;
|
import static org.apache.kafka.clients.admin.ListOffsetsResult.ListOffsetsResultInfo;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.AbstractIntegrationTest;
|
import com.provectus.kafka.ui.AbstractIntegrationTest;
|
||||||
|
import com.provectus.kafka.ui.exception.ValidationException;
|
||||||
import com.provectus.kafka.ui.producer.KafkaTestProducer;
|
import com.provectus.kafka.ui.producer.KafkaTestProducer;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -22,16 +25,20 @@ import org.apache.kafka.clients.admin.Config;
|
||||||
import org.apache.kafka.clients.admin.ConfigEntry;
|
import org.apache.kafka.clients.admin.ConfigEntry;
|
||||||
import org.apache.kafka.clients.admin.NewTopic;
|
import org.apache.kafka.clients.admin.NewTopic;
|
||||||
import org.apache.kafka.clients.admin.OffsetSpec;
|
import org.apache.kafka.clients.admin.OffsetSpec;
|
||||||
|
import org.apache.kafka.clients.admin.TopicDescription;
|
||||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||||
import org.apache.kafka.clients.consumer.KafkaConsumer;
|
import org.apache.kafka.clients.consumer.KafkaConsumer;
|
||||||
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
|
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
|
||||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||||
import org.apache.kafka.common.KafkaFuture;
|
import org.apache.kafka.common.KafkaFuture;
|
||||||
|
import org.apache.kafka.common.Node;
|
||||||
import org.apache.kafka.common.TopicPartition;
|
import org.apache.kafka.common.TopicPartition;
|
||||||
|
import org.apache.kafka.common.TopicPartitionInfo;
|
||||||
import org.apache.kafka.common.config.ConfigResource;
|
import org.apache.kafka.common.config.ConfigResource;
|
||||||
import org.apache.kafka.common.errors.UnknownTopicOrPartitionException;
|
import org.apache.kafka.common.errors.UnknownTopicOrPartitionException;
|
||||||
import org.apache.kafka.common.internals.KafkaFutureImpl;
|
import org.apache.kafka.common.internals.KafkaFutureImpl;
|
||||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||||
|
import org.assertj.core.api.ThrowableAssert;
|
||||||
import org.junit.function.ThrowingRunnable;
|
import org.junit.function.ThrowingRunnable;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
@ -133,6 +140,56 @@ class ReactiveAdminClientTest extends AbstractIntegrationTest {
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void filterPartitionsWithLeaderCheckSkipsPartitionsFromTopicWhereSomePartitionsHaveNoLeader() {
|
||||||
|
var filteredPartitions = ReactiveAdminClient.filterPartitionsWithLeaderCheck(
|
||||||
|
List.of(
|
||||||
|
// contains partitions with no leader
|
||||||
|
new TopicDescription("noLeaderTopic", false,
|
||||||
|
List.of(
|
||||||
|
new TopicPartitionInfo(0, new Node(1, "n1", 9092), List.of(), List.of()),
|
||||||
|
new TopicPartitionInfo(1, null, List.of(), List.of()))),
|
||||||
|
// should be skipped by predicate
|
||||||
|
new TopicDescription("skippingByPredicate", false,
|
||||||
|
List.of(
|
||||||
|
new TopicPartitionInfo(0, new Node(1, "n1", 9092), List.of(), List.of()))),
|
||||||
|
// good topic
|
||||||
|
new TopicDescription("good", false,
|
||||||
|
List.of(
|
||||||
|
new TopicPartitionInfo(0, new Node(1, "n1", 9092), List.of(), List.of()),
|
||||||
|
new TopicPartitionInfo(1, new Node(2, "n2", 9092), List.of(), List.of()))
|
||||||
|
)),
|
||||||
|
p -> !p.topic().equals("skippingByPredicate"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(filteredPartitions)
|
||||||
|
.containsExactlyInAnyOrder(
|
||||||
|
new TopicPartition("good", 0),
|
||||||
|
new TopicPartition("good", 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void filterPartitionsWithLeaderCheckThrowExceptionIfThereIsSomePartitionsWithoutLeaderAndFlagSet() {
|
||||||
|
ThrowingCallable call = () -> ReactiveAdminClient.filterPartitionsWithLeaderCheck(
|
||||||
|
List.of(
|
||||||
|
// contains partitions with no leader
|
||||||
|
new TopicDescription("t1", false,
|
||||||
|
List.of(
|
||||||
|
new TopicPartitionInfo(0, new Node(1, "n1", 9092), List.of(), List.of()),
|
||||||
|
new TopicPartitionInfo(1, null, List.of(), List.of()))),
|
||||||
|
new TopicDescription("t2", false,
|
||||||
|
List.of(
|
||||||
|
new TopicPartitionInfo(0, new Node(1, "n1", 9092), List.of(), List.of()))
|
||||||
|
)),
|
||||||
|
p -> true,
|
||||||
|
// setting failOnNoLeader flag
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assertThatThrownBy(call).isInstanceOf(ValidationException.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testListOffsetsUnsafe() {
|
void testListOffsetsUnsafe() {
|
||||||
String topic = UUID.randomUUID().toString();
|
String topic = UUID.randomUUID().toString();
|
||||||
|
|
|
@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import com.provectus.kafka.ui.AbstractIntegrationTest;
|
import com.provectus.kafka.ui.AbstractIntegrationTest;
|
||||||
import com.provectus.kafka.ui.emitter.BackwardRecordEmitter;
|
import com.provectus.kafka.ui.emitter.BackwardRecordEmitter;
|
||||||
import com.provectus.kafka.ui.emitter.ForwardRecordEmitter;
|
import com.provectus.kafka.ui.emitter.ForwardRecordEmitter;
|
||||||
|
import com.provectus.kafka.ui.emitter.MessagesProcessing;
|
||||||
import com.provectus.kafka.ui.emitter.PollingSettings;
|
import com.provectus.kafka.ui.emitter.PollingSettings;
|
||||||
import com.provectus.kafka.ui.model.ConsumerPosition;
|
import com.provectus.kafka.ui.model.ConsumerPosition;
|
||||||
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
||||||
|
@ -106,12 +107,16 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MessagesProcessing createMessagesProcessing() {
|
||||||
|
return new MessagesProcessing(RECORD_DESERIALIZER, msg -> true, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void pollNothingOnEmptyTopic() {
|
void pollNothingOnEmptyTopic() {
|
||||||
var forwardEmitter = new ForwardRecordEmitter(
|
var forwardEmitter = new ForwardRecordEmitter(
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(BEGINNING, EMPTY_TOPIC, null),
|
new ConsumerPosition(BEGINNING, EMPTY_TOPIC, null),
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -119,7 +124,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(BEGINNING, EMPTY_TOPIC, null),
|
new ConsumerPosition(BEGINNING, EMPTY_TOPIC, null),
|
||||||
100,
|
100,
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -141,7 +146,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
var forwardEmitter = new ForwardRecordEmitter(
|
var forwardEmitter = new ForwardRecordEmitter(
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(BEGINNING, TOPIC, null),
|
new ConsumerPosition(BEGINNING, TOPIC, null),
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -149,7 +154,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(LATEST, TOPIC, null),
|
new ConsumerPosition(LATEST, TOPIC, null),
|
||||||
PARTITIONS * MSGS_PER_PARTITION,
|
PARTITIONS * MSGS_PER_PARTITION,
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -170,7 +175,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
var forwardEmitter = new ForwardRecordEmitter(
|
var forwardEmitter = new ForwardRecordEmitter(
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -178,7 +183,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
||||||
PARTITIONS * MSGS_PER_PARTITION,
|
PARTITIONS * MSGS_PER_PARTITION,
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -215,7 +220,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
var forwardEmitter = new ForwardRecordEmitter(
|
var forwardEmitter = new ForwardRecordEmitter(
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(TIMESTAMP, TOPIC, targetTimestamps),
|
new ConsumerPosition(TIMESTAMP, TOPIC, targetTimestamps),
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -223,7 +228,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(TIMESTAMP, TOPIC, targetTimestamps),
|
new ConsumerPosition(TIMESTAMP, TOPIC, targetTimestamps),
|
||||||
PARTITIONS * MSGS_PER_PARTITION,
|
PARTITIONS * MSGS_PER_PARTITION,
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -254,7 +259,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
||||||
numMessages,
|
numMessages,
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -280,7 +285,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
||||||
this::createConsumer,
|
this::createConsumer,
|
||||||
new ConsumerPosition(OFFSET, TOPIC, offsets),
|
new ConsumerPosition(OFFSET, TOPIC, offsets),
|
||||||
100,
|
100,
|
||||||
RECORD_DESERIALIZER,
|
createMessagesProcessing(),
|
||||||
PollingSettings.createDefault()
|
PollingSettings.createDefault()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.util.unit.DataSize;
|
|
||||||
import org.testcontainers.utility.DockerImageName;
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
class KsqlServiceV2Test extends AbstractIntegrationTest {
|
class KsqlServiceV2Test extends AbstractIntegrationTest {
|
||||||
|
@ -27,8 +26,6 @@ class KsqlServiceV2Test extends AbstractIntegrationTest {
|
||||||
private static final Set<String> STREAMS_TO_DELETE = new CopyOnWriteArraySet<>();
|
private static final Set<String> STREAMS_TO_DELETE = new CopyOnWriteArraySet<>();
|
||||||
private static final Set<String> TABLES_TO_DELETE = new CopyOnWriteArraySet<>();
|
private static final Set<String> TABLES_TO_DELETE = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
private static final DataSize maxBuffSize = DataSize.ofMegabytes(20);
|
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void init() {
|
static void init() {
|
||||||
KSQL_DB.start();
|
KSQL_DB.start();
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.provectus.kafka.ui.util;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
|
class GithubReleaseInfoTest {
|
||||||
|
|
||||||
|
private final MockWebServer mockWebServer = new MockWebServer();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void startMockServer() throws IOException {
|
||||||
|
mockWebServer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void stopMockServer() throws IOException {
|
||||||
|
mockWebServer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test() {
|
||||||
|
mockWebServer.enqueue(new MockResponse()
|
||||||
|
.addHeader("content-type: application/json")
|
||||||
|
.setBody("""
|
||||||
|
{
|
||||||
|
"published_at": "2023-03-09T16:11:31Z",
|
||||||
|
"tag_name": "v0.6.0",
|
||||||
|
"html_url": "https://github.com/provectus/kafka-ui/releases/tag/v0.6.0",
|
||||||
|
"some_unused_prop": "ololo"
|
||||||
|
}
|
||||||
|
"""));
|
||||||
|
var url = mockWebServer.url("repos/provectus/kafka-ui/releases/latest").toString();
|
||||||
|
|
||||||
|
var infoHolder = new GithubReleaseInfo(url);
|
||||||
|
infoHolder.refresh().block();
|
||||||
|
|
||||||
|
var i = infoHolder.get();
|
||||||
|
assertThat(i.html_url())
|
||||||
|
.isEqualTo("https://github.com/provectus/kafka-ui/releases/tag/v0.6.0");
|
||||||
|
assertThat(i.published_at())
|
||||||
|
.isEqualTo("2023-03-09T16:11:31Z");
|
||||||
|
assertThat(i.tag_name())
|
||||||
|
.isEqualTo("v0.6.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,20 +27,24 @@
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.swagger</groupId>
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
<artifactId>swagger-annotations</artifactId>
|
<artifactId>swagger-integration-jakarta</artifactId>
|
||||||
<version>${swagger-annotations.version}</version>
|
<version>2.2.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openapitools</groupId>
|
<groupId>org.openapitools</groupId>
|
||||||
<artifactId>jackson-databind-nullable</artifactId>
|
<artifactId>jackson-databind-nullable</artifactId>
|
||||||
<version>${jackson-databind-nullable.version}</version>
|
<version>0.2.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.findbugs</groupId>
|
<groupId>jakarta.annotation</groupId>
|
||||||
<artifactId>jsr305</artifactId>
|
<artifactId>jakarta.annotation-api</artifactId>
|
||||||
<version>3.0.2</version>
|
<version>2.1.1</version>
|
||||||
<scope>provided</scope>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.annotation</groupId>
|
||||||
|
<artifactId>javax.annotation-api</artifactId>
|
||||||
|
<version>1.3.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -71,6 +75,7 @@
|
||||||
<library>webclient</library>
|
<library>webclient</library>
|
||||||
<useBeanValidation>true</useBeanValidation>
|
<useBeanValidation>true</useBeanValidation>
|
||||||
<dateLibrary>java8</dateLibrary>
|
<dateLibrary>java8</dateLibrary>
|
||||||
|
<useJakartaEe>true</useJakartaEe>
|
||||||
</configOptions>
|
</configOptions>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
@ -80,8 +85,7 @@
|
||||||
<goal>generate</goal>
|
<goal>generate</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<inputSpec>${project.basedir}/src/main/resources/swagger/kafka-ui-api.yaml
|
<inputSpec>${project.basedir}/src/main/resources/swagger/kafka-ui-api.yaml</inputSpec>
|
||||||
</inputSpec>
|
|
||||||
<output>${project.build.directory}/generated-sources/api</output>
|
<output>${project.build.directory}/generated-sources/api</output>
|
||||||
<generatorName>spring</generatorName>
|
<generatorName>spring</generatorName>
|
||||||
<modelNameSuffix>DTO</modelNameSuffix>
|
<modelNameSuffix>DTO</modelNameSuffix>
|
||||||
|
@ -89,14 +93,12 @@
|
||||||
<modelPackage>com.provectus.kafka.ui.model</modelPackage>
|
<modelPackage>com.provectus.kafka.ui.model</modelPackage>
|
||||||
<apiPackage>com.provectus.kafka.ui.api</apiPackage>
|
<apiPackage>com.provectus.kafka.ui.api</apiPackage>
|
||||||
<sourceFolder>kafka-ui-contract</sourceFolder>
|
<sourceFolder>kafka-ui-contract</sourceFolder>
|
||||||
|
|
||||||
<reactive>true</reactive>
|
<reactive>true</reactive>
|
||||||
|
|
||||||
<interfaceOnly>true</interfaceOnly>
|
<interfaceOnly>true</interfaceOnly>
|
||||||
<skipDefaultInterface>true</skipDefaultInterface>
|
<skipDefaultInterface>true</skipDefaultInterface>
|
||||||
<useBeanValidation>true</useBeanValidation>
|
<useBeanValidation>true</useBeanValidation>
|
||||||
<useTags>true</useTags>
|
<useTags>true</useTags>
|
||||||
|
<useSpringBoot3>true</useSpringBoot3>
|
||||||
<dateLibrary>java8</dateLibrary>
|
<dateLibrary>java8</dateLibrary>
|
||||||
</configOptions>
|
</configOptions>
|
||||||
<typeMappings>
|
<typeMappings>
|
||||||
|
@ -116,15 +118,13 @@
|
||||||
<generatorName>java</generatorName>
|
<generatorName>java</generatorName>
|
||||||
<generateApiTests>false</generateApiTests>
|
<generateApiTests>false</generateApiTests>
|
||||||
<generateModelTests>false</generateModelTests>
|
<generateModelTests>false</generateModelTests>
|
||||||
|
|
||||||
<configOptions>
|
<configOptions>
|
||||||
<modelPackage>com.provectus.kafka.ui.connect.model</modelPackage>
|
<modelPackage>com.provectus.kafka.ui.connect.model</modelPackage>
|
||||||
<apiPackage>com.provectus.kafka.ui.connect.api</apiPackage>
|
<apiPackage>com.provectus.kafka.ui.connect.api</apiPackage>
|
||||||
<sourceFolder>kafka-connect-client</sourceFolder>
|
<sourceFolder>kafka-connect-client</sourceFolder>
|
||||||
|
|
||||||
<asyncNative>true</asyncNative>
|
<asyncNative>true</asyncNative>
|
||||||
<library>webclient</library>
|
<library>webclient</library>
|
||||||
|
<useJakartaEe>true</useJakartaEe>
|
||||||
<useBeanValidation>true</useBeanValidation>
|
<useBeanValidation>true</useBeanValidation>
|
||||||
<dateLibrary>java8</dateLibrary>
|
<dateLibrary>java8</dateLibrary>
|
||||||
</configOptions>
|
</configOptions>
|
||||||
|
@ -142,15 +142,13 @@
|
||||||
<generatorName>java</generatorName>
|
<generatorName>java</generatorName>
|
||||||
<generateApiTests>false</generateApiTests>
|
<generateApiTests>false</generateApiTests>
|
||||||
<generateModelTests>false</generateModelTests>
|
<generateModelTests>false</generateModelTests>
|
||||||
|
|
||||||
<configOptions>
|
<configOptions>
|
||||||
<modelPackage>com.provectus.kafka.ui.sr.model</modelPackage>
|
<modelPackage>com.provectus.kafka.ui.sr.model</modelPackage>
|
||||||
<apiPackage>com.provectus.kafka.ui.sr.api</apiPackage>
|
<apiPackage>com.provectus.kafka.ui.sr.api</apiPackage>
|
||||||
<sourceFolder>kafka-sr-client</sourceFolder>
|
<sourceFolder>kafka-sr-client</sourceFolder>
|
||||||
|
|
||||||
<asyncNative>true</asyncNative>
|
<asyncNative>true</asyncNative>
|
||||||
<library>webclient</library>
|
<library>webclient</library>
|
||||||
|
<useJakartaEe>true</useJakartaEe>
|
||||||
<useBeanValidation>true</useBeanValidation>
|
<useBeanValidation>true</useBeanValidation>
|
||||||
<dateLibrary>java8</dateLibrary>
|
<dateLibrary>java8</dateLibrary>
|
||||||
</configOptions>
|
</configOptions>
|
||||||
|
|
|
@ -2021,6 +2021,26 @@ components:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- DYNAMIC_CONFIG
|
- DYNAMIC_CONFIG
|
||||||
|
build:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
commitId:
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
buildTime:
|
||||||
|
type: string
|
||||||
|
isLatestRelease:
|
||||||
|
type: boolean
|
||||||
|
latestRelease:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
versionTag:
|
||||||
|
type: string
|
||||||
|
publishedAt:
|
||||||
|
type: string
|
||||||
|
htmlUrl:
|
||||||
|
type: string
|
||||||
|
|
||||||
Cluster:
|
Cluster:
|
||||||
type: object
|
type: object
|
||||||
|
@ -2493,6 +2513,10 @@ components:
|
||||||
- UNKNOWN
|
- UNKNOWN
|
||||||
|
|
||||||
ConsumerGroup:
|
ConsumerGroup:
|
||||||
|
discriminator:
|
||||||
|
propertyName: inherit
|
||||||
|
mapping:
|
||||||
|
details: "#/components/schemas/ConsumerGroupDetails"
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
groupId:
|
groupId:
|
||||||
|
|
|
@ -27,7 +27,7 @@ This repository is for E2E UI automation.
|
||||||
```
|
```
|
||||||
git clone https://github.com/provectus/kafka-ui.git
|
git clone https://github.com/provectus/kafka-ui.git
|
||||||
cd kafka-ui-e2e-checks
|
cd kafka-ui-e2e-checks
|
||||||
docker pull selenoid/vnc:chrome_86.0
|
docker pull selenoid/vnc_chrome:103.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### How to run checks
|
### How to run checks
|
||||||
|
@ -36,6 +36,7 @@ docker pull selenoid/vnc:chrome_86.0
|
||||||
|
|
||||||
```
|
```
|
||||||
cd kafka-ui
|
cd kafka-ui
|
||||||
|
docker-compose -f kafka-ui-e2e-checks/docker/selenoid-local.yaml up -d
|
||||||
docker-compose -f documentation/compose/e2e-tests.yaml up -d
|
docker-compose -f documentation/compose/e2e-tests.yaml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -51,6 +52,14 @@ docker-compose -f documentation/compose/e2e-tests.yaml up -d
|
||||||
-Dbrowser=local
|
-Dbrowser=local
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Expected Location of Chrome
|
||||||
|
```
|
||||||
|
Linux: /usr/bin/google-chrome1
|
||||||
|
Mac: /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome
|
||||||
|
Windows XP: %HOMEPATH%\Local Settings\Application Data\Google\Chrome\Application\chrome.exe
|
||||||
|
Windows Vista and newer: C:\Users%USERNAME%\AppData\Local\Google\Chrome\Application\chrome.exe
|
||||||
|
```
|
||||||
|
|
||||||
### Qase integration
|
### Qase integration
|
||||||
|
|
||||||
Found instruction for Qase.io integration (for internal use only) at `kafka-ui-e2e-checks/QASE.md`
|
Found instruction for Qase.io integration (for internal use only) at `kafka-ui-e2e-checks/QASE.md`
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
|
---
|
||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
selenoid:
|
selenoid:
|
||||||
network_mode: bridge
|
network_mode: bridge
|
||||||
image: aerokube/selenoid:1.10.7
|
image: aerokube/selenoid:1.10.7
|
||||||
volumes:
|
volumes:
|
||||||
- "../selenoid/config:/etc/selenoid"
|
- "../selenoid/config:/etc/selenoid"
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||||
- "../selenoid/video:/video"
|
- "../selenoid/video:/opt/selenoid/video"
|
||||||
- "../selenoid/logs:/opt/selenoid/logs"
|
- "../selenoid/logs:/opt/selenoid/logs"
|
||||||
environment:
|
environment:
|
||||||
- OVERRIDE_VIDEO_OUTPUT_DIR=video
|
- OVERRIDE_VIDEO_OUTPUT_DIR=../selenoid/video
|
||||||
command: [ "-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs" ]
|
command: [ "-conf", "/etc/selenoid/browsersGit.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs" ]
|
||||||
ports:
|
ports:
|
||||||
- "4444:4444"
|
- "4444:4444"
|
||||||
|
|
||||||
|
@ -22,10 +24,10 @@ services:
|
||||||
- selenoid
|
- selenoid
|
||||||
ports:
|
ports:
|
||||||
- "8081:8080"
|
- "8081:8080"
|
||||||
command: [ "--selenoid-uri", "http://localhost:4444" ]
|
command: [ "--selenoid-uri", "http://selenoid:4444" ]
|
||||||
|
|
||||||
selenoid-chrome:
|
selenoid-chrome:
|
||||||
network_mode: bridge
|
network_mode: bridge
|
||||||
image: selenoid/vnc:chrome_96.0
|
image: selenoid/vnc_chrome:103.0
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
33
kafka-ui-e2e-checks/docker/selenoid-local.yaml
Normal file
33
kafka-ui-e2e-checks/docker/selenoid-local.yaml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
selenoid:
|
||||||
|
network_mode: bridge
|
||||||
|
image: aerokube/selenoid:1.10.7
|
||||||
|
volumes:
|
||||||
|
- "../selenoid/config:/etc/selenoid"
|
||||||
|
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||||
|
- "../selenoid/video:/opt/selenoid/video"
|
||||||
|
- "../selenoid/logs:/opt/selenoid/logs"
|
||||||
|
environment:
|
||||||
|
- OVERRIDE_VIDEO_OUTPUT_DIR=../selenoid/video
|
||||||
|
command: [ "-conf", "/etc/selenoid/browsersLocal.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" ]
|
||||||
|
|
||||||
|
selenoid-chrome:
|
||||||
|
network_mode: bridge
|
||||||
|
image: selenoid/vnc_chrome:103.0
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
|
@ -17,15 +17,14 @@
|
||||||
<testcontainers.version>1.17.6</testcontainers.version>
|
<testcontainers.version>1.17.6</testcontainers.version>
|
||||||
<httpcomponents.version>5.2.1</httpcomponents.version>
|
<httpcomponents.version>5.2.1</httpcomponents.version>
|
||||||
<selenium.version>4.8.1</selenium.version>
|
<selenium.version>4.8.1</selenium.version>
|
||||||
<selenide.version>6.11.2</selenide.version>
|
<selenide.version>6.12.3</selenide.version>
|
||||||
<testng.version>7.7.0</testng.version>
|
<testng.version>7.7.0</testng.version>
|
||||||
<allure.version>2.21.0</allure.version>
|
<allure.version>2.21.0</allure.version>
|
||||||
<qase.io.version>3.0.3</qase.io.version>
|
<qase.io.version>3.0.4</qase.io.version>
|
||||||
<aspectj.version>1.9.9.1</aspectj.version>
|
<aspectj.version>1.9.9.1</aspectj.version>
|
||||||
<assertj.version>3.24.2</assertj.version>
|
<assertj.version>3.24.2</assertj.version>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
<slf4j.version>1.7.36</slf4j.version>
|
<slf4j.version>2.0.5</slf4j.version>
|
||||||
<dotenv.version>2.3.1</dotenv.version>
|
|
||||||
<kafka.version>3.3.1</kafka.version>
|
<kafka.version>3.3.1</kafka.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
@ -122,6 +121,11 @@
|
||||||
<artifactId>selenium</artifactId>
|
<artifactId>selenium</artifactId>
|
||||||
<version>${testcontainers.version}</version>
|
<version>${testcontainers.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${org.projectlombok.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
<groupId>org.apache.httpcomponents.core5</groupId>
|
||||||
<artifactId>httpcore5</artifactId>
|
<artifactId>httpcore5</artifactId>
|
||||||
|
@ -132,6 +136,11 @@
|
||||||
<artifactId>httpclient5</artifactId>
|
<artifactId>httpclient5</artifactId>
|
||||||
<version>${httpcomponents.version}</version>
|
<version>${httpcomponents.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-http-jdk-client</artifactId>
|
||||||
|
<version>${selenium.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.seleniumhq.selenium</groupId>
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
<artifactId>selenium-http</artifactId>
|
<artifactId>selenium-http</artifactId>
|
||||||
|
@ -187,16 +196,6 @@
|
||||||
<artifactId>slf4j-simple</artifactId>
|
<artifactId>slf4j-simple</artifactId>
|
||||||
<version>${slf4j.version}</version>
|
<version>${slf4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<version>${org.projectlombok.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.github.cdimascio</groupId>
|
|
||||||
<artifactId>dotenv-java</artifactId>
|
|
||||||
<version>${dotenv.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.provectus</groupId>
|
<groupId>com.provectus</groupId>
|
||||||
<artifactId>kafka-ui-contract</artifactId>
|
<artifactId>kafka-ui-contract</artifactId>
|
||||||
|
@ -265,6 +264,37 @@
|
||||||
<artifactId>allure-maven</artifactId>
|
<artifactId>allure-maven</artifactId>
|
||||||
<version>2.10.0</version>
|
<version>2.10.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.puppycrawl.tools</groupId>
|
||||||
|
<artifactId>checkstyle</artifactId>
|
||||||
|
<version>10.3.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>checkstyle</id>
|
||||||
|
<phase>validate</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<violationSeverity>warning</violationSeverity>
|
||||||
|
<failOnViolation>true</failOnViolation>
|
||||||
|
<failsOnError>true</failsOnError>
|
||||||
|
<includeTestSourceDirectory>true</includeTestSourceDirectory>
|
||||||
|
<configLocation>file:${basedir}/../etc/checkstyle/checkstyle-e2e.xml</configLocation>
|
||||||
|
<headerLocation>file:${basedir}/../etc/checkstyle/apache-header.txt</headerLocation>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
|
||||||
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
15
kafka-ui-e2e-checks/selenoid/config/browsersGit.json
Normal file
15
kafka-ui-e2e-checks/selenoid/config/browsersGit.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"chrome": {
|
||||||
|
"default": "103.0",
|
||||||
|
"versions": {
|
||||||
|
"103.0": {
|
||||||
|
"image": "selenoid/vnc_chrome:103.0",
|
||||||
|
"hosts": [
|
||||||
|
"host.docker.internal:172.17.0.1"
|
||||||
|
],
|
||||||
|
"port": "4444",
|
||||||
|
"path": "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"chrome": {
|
"chrome": {
|
||||||
"default": "96.0",
|
"default": "103.0",
|
||||||
"versions": {
|
"versions": {
|
||||||
"96.0": {
|
"103.0": {
|
||||||
"image": "selenoid/vnc_chrome:96.0",
|
"image": "selenoid/vnc_chrome:103.0",
|
||||||
"port": "4444",
|
"port": "4444",
|
||||||
"path": "/"
|
"path": "/"
|
||||||
}
|
}
|
|
@ -7,5 +7,5 @@ import lombok.experimental.Accessors;
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class Connector {
|
public class Connector {
|
||||||
|
|
||||||
private String name, config;
|
private String name, config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,34 @@
|
||||||
package com.provectus.kafka.ui.models;
|
package com.provectus.kafka.ui.models;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.api.model.SchemaType;
|
import com.provectus.kafka.ui.api.model.SchemaType;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class Schema {
|
public class Schema {
|
||||||
|
|
||||||
private String name, valuePath;
|
private String name, valuePath;
|
||||||
private SchemaType type;
|
private SchemaType type;
|
||||||
|
|
||||||
public static Schema createSchemaAvro() {
|
public static Schema createSchemaAvro() {
|
||||||
return new Schema().setName("schema_avro-" + randomAlphabetic(5))
|
return new Schema().setName("schema_avro-" + randomAlphabetic(5))
|
||||||
.setType(SchemaType.AVRO)
|
.setType(SchemaType.AVRO)
|
||||||
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_avro_value.json");
|
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_avro_value.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Schema createSchemaJson() {
|
public static Schema createSchemaJson() {
|
||||||
return new Schema().setName("schema_json-" + randomAlphabetic(5))
|
return new Schema().setName("schema_json-" + randomAlphabetic(5))
|
||||||
.setType(SchemaType.JSON)
|
.setType(SchemaType.JSON)
|
||||||
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_json_Value.json");
|
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_json_Value.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Schema createSchemaProtobuf() {
|
public static Schema createSchemaProtobuf() {
|
||||||
return new Schema().setName("schema_protobuf-" + randomAlphabetic(5))
|
return new Schema().setName("schema_protobuf-" + randomAlphabetic(5))
|
||||||
.setType(SchemaType.PROTOBUF)
|
.setType(SchemaType.PROTOBUF)
|
||||||
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_protobuf_value.txt");
|
.setValuePath(
|
||||||
}
|
System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_protobuf_value.txt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ import lombok.experimental.Accessors;
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class Topic {
|
public class Topic {
|
||||||
|
|
||||||
private String name, timeToRetainData, maxMessageBytes, messageKey, messageContent, customParameterValue;
|
private String name, timeToRetainData, maxMessageBytes, messageKey, messageValue, customParameterValue;
|
||||||
private int numberOfPartitions;
|
private int numberOfPartitions;
|
||||||
private CustomParameterType customParameterType;
|
private CustomParameterType customParameterType;
|
||||||
private CleanupPolicyValue cleanupPolicyValue;
|
private CleanupPolicyValue cleanupPolicyValue;
|
||||||
private MaxSizeOnDisk maxSizeOnDisk;
|
private MaxSizeOnDisk maxSizeOnDisk;
|
||||||
private TimeToRetain timeToRetain;
|
private TimeToRetain timeToRetain;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,135 +1,142 @@
|
||||||
package com.provectus.kafka.ui.pages;
|
package com.provectus.kafka.ui.pages;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$$x;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.ElementsCollection;
|
import com.codeborne.selenide.ElementsCollection;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.codeborne.selenide.WebDriverRunner;
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
|
import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
|
||||||
import com.provectus.kafka.ui.utilities.WebUtils;
|
import com.provectus.kafka.ui.utilities.WebUtils;
|
||||||
|
import java.time.Duration;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.openqa.selenium.Keys;
|
import org.openqa.selenium.Keys;
|
||||||
import org.openqa.selenium.interactions.Actions;
|
import org.openqa.selenium.interactions.Actions;
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$$x;
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class BasePage extends WebUtils {
|
public abstract class BasePage extends WebUtils {
|
||||||
|
|
||||||
protected SelenideElement loadingSpinner = $x("//div[@role='progressbar']");
|
protected SelenideElement loadingSpinner = $x("//div[@role='progressbar']");
|
||||||
protected SelenideElement submitBtn = $x("//button[@type='submit']");
|
protected SelenideElement submitBtn = $x("//button[@type='submit']");
|
||||||
protected SelenideElement tableGrid = $x("//table");
|
protected SelenideElement tableGrid = $x("//table");
|
||||||
protected SelenideElement dotMenuBtn = $x("//button[@aria-label='Dropdown Toggle']");
|
protected SelenideElement searchFld = $x("//input[@type='text'][contains(@id, ':r')]");
|
||||||
protected SelenideElement alertHeader = $x("//div[@role='alert']//div[@role='heading']");
|
protected SelenideElement dotMenuBtn = $x("//button[@aria-label='Dropdown Toggle']");
|
||||||
protected SelenideElement alertMessage = $x("//div[@role='alert']//div[@role='contentinfo']");
|
protected SelenideElement alertHeader = $x("//div[@role='alert']//div[@role='heading']");
|
||||||
protected SelenideElement confirmationMdl = $x("//div[text()= 'Confirm the action']/..");
|
protected SelenideElement alertMessage = $x("//div[@role='alert']//div[@role='contentinfo']");
|
||||||
protected SelenideElement confirmBtn = $x("//button[contains(text(),'Confirm')]");
|
protected SelenideElement confirmationMdl = $x("//div[text()= 'Confirm the action']/..");
|
||||||
protected SelenideElement cancelBtn = $x("//button[contains(text(),'Cancel')]");
|
protected SelenideElement confirmBtn = $x("//button[contains(text(),'Confirm')]");
|
||||||
protected SelenideElement backBtn = $x("//button[contains(text(),'Back')]");
|
protected SelenideElement cancelBtn = $x("//button[contains(text(),'Cancel')]");
|
||||||
protected SelenideElement nextBtn = $x("//button[contains(text(),'Next')]");
|
protected SelenideElement backBtn = $x("//button[contains(text(),'Back')]");
|
||||||
protected ElementsCollection ddlOptions = $$x("//li[@value]");
|
protected SelenideElement nextBtn = $x("//button[contains(text(),'Next')]");
|
||||||
protected ElementsCollection gridItems = $$x("//tr[@class]");
|
protected ElementsCollection ddlOptions = $$x("//li[@value]");
|
||||||
protected String summaryCellLocator = "//div[contains(text(),'%s')]";
|
protected ElementsCollection gridItems = $$x("//tr[@class]");
|
||||||
protected String tableElementNameLocator = "//tbody//a[contains(text(),'%s')]";
|
protected String summaryCellLocator = "//div[contains(text(),'%s')]";
|
||||||
protected String columnHeaderLocator = "//table//tr/th//div[text()='%s']";
|
protected String tableElementNameLocator = "//tbody//a[contains(text(),'%s')]";
|
||||||
protected String pageTitleFromHeader = "//h1[text()='%s']";
|
protected String columnHeaderLocator = "//table//tr/th//div[text()='%s']";
|
||||||
protected String pagePathFromHeader = "//a[text()='%s']/../h1";
|
protected String pageTitleFromHeader = "//h1[text()='%s']";
|
||||||
|
protected String pagePathFromHeader = "//a[text()='%s']/../h1";
|
||||||
|
|
||||||
protected void waitUntilSpinnerDisappear() {
|
protected void waitUntilSpinnerDisappear(int... timeoutInSeconds) {
|
||||||
log.debug("\nwaitUntilSpinnerDisappear");
|
log.debug("\nwaitUntilSpinnerDisappear");
|
||||||
if (isVisible(loadingSpinner)) {
|
if (isVisible(loadingSpinner, timeoutInSeconds)) {
|
||||||
loadingSpinner.shouldBe(Condition.disappear, Duration.ofSeconds(60));
|
loadingSpinner.shouldBe(Condition.disappear, Duration.ofSeconds(60));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void searchItem(String tag) {
|
||||||
|
log.debug("\nsearchItem: {}", tag);
|
||||||
|
sendKeysAfterClear(searchFld, tag);
|
||||||
|
searchFld.pressEnter().shouldHave(Condition.value(tag));
|
||||||
|
waitUntilSpinnerDisappear(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SelenideElement getPageTitleFromHeader(MenuItem menuItem) {
|
||||||
|
return $x(String.format(pageTitleFromHeader, menuItem.getPageTitle()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SelenideElement getPagePathFromHeader(MenuItem menuItem) {
|
||||||
|
return $x(String.format(pagePathFromHeader, menuItem.getPageTitle()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clickSubmitBtn() {
|
||||||
|
clickByJavaScript(submitBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setJsonInputValue(SelenideElement jsonInput, String jsonConfig) {
|
||||||
|
sendKeysByActions(jsonInput, jsonConfig.replace(" ", ""));
|
||||||
|
new Actions(WebDriverRunner.getWebDriver())
|
||||||
|
.keyDown(Keys.SHIFT)
|
||||||
|
.sendKeys(Keys.PAGE_DOWN)
|
||||||
|
.keyUp(Keys.SHIFT)
|
||||||
|
.sendKeys(Keys.DELETE)
|
||||||
|
.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SelenideElement getTableElement(String elementName) {
|
||||||
|
log.debug("\ngetTableElement: {}", elementName);
|
||||||
|
return $x(String.format(tableElementNameLocator, elementName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ElementsCollection getDdlOptions() {
|
||||||
|
return ddlOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getAlertHeader() {
|
||||||
|
log.debug("\ngetAlertHeader");
|
||||||
|
String result = alertHeader.shouldBe(Condition.visible).getText();
|
||||||
|
log.debug("-> {}", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getAlertMessage() {
|
||||||
|
log.debug("\ngetAlertMessage");
|
||||||
|
String result = alertMessage.shouldBe(Condition.visible).getText();
|
||||||
|
log.debug("-> {}", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isAlertVisible(AlertHeader header) {
|
||||||
|
log.debug("\nisAlertVisible: {}", header.toString());
|
||||||
|
boolean result = getAlertHeader().equals(header.toString());
|
||||||
|
log.debug("-> {}", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isAlertVisible(AlertHeader header, String message) {
|
||||||
|
log.debug("\nisAlertVisible: {} {}", header, message);
|
||||||
|
boolean result = isAlertVisible(header) && getAlertMessage().equals(message);
|
||||||
|
log.debug("-> {}", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clickConfirmButton() {
|
||||||
|
confirmBtn.shouldBe(Condition.enabled).click();
|
||||||
|
confirmBtn.shouldBe(Condition.disappear);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clickCancelButton() {
|
||||||
|
cancelBtn.shouldBe(Condition.enabled).click();
|
||||||
|
cancelBtn.shouldBe(Condition.disappear);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isConfirmationModalVisible() {
|
||||||
|
return isVisible(confirmationMdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AlertHeader {
|
||||||
|
SUCCESS("Success"),
|
||||||
|
VALIDATION_ERROR("Validation Error"),
|
||||||
|
BAD_REQUEST("400 Bad Request");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
AlertHeader(String value) {
|
||||||
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SelenideElement getPageTitleFromHeader(MenuItem menuItem) {
|
public String toString() {
|
||||||
return $x(String.format(pageTitleFromHeader, menuItem.getPageTitle()));
|
return value;
|
||||||
}
|
|
||||||
|
|
||||||
protected SelenideElement getPagePathFromHeader(MenuItem menuItem) {
|
|
||||||
return $x(String.format(pagePathFromHeader, menuItem.getPageTitle()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void clickSubmitBtn() {
|
|
||||||
clickByJavaScript(submitBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setJsonInputValue(SelenideElement jsonInput, String jsonConfig) {
|
|
||||||
sendKeysByActions(jsonInput, jsonConfig.replace(" ", ""));
|
|
||||||
new Actions(WebDriverRunner.getWebDriver())
|
|
||||||
.keyDown(Keys.SHIFT)
|
|
||||||
.sendKeys(Keys.PAGE_DOWN)
|
|
||||||
.keyUp(Keys.SHIFT)
|
|
||||||
.sendKeys(Keys.DELETE)
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SelenideElement getTableElement(String elementName) {
|
|
||||||
log.debug("\ngetTableElement: {}", elementName);
|
|
||||||
return $x(String.format(tableElementNameLocator, elementName));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ElementsCollection getDdlOptions() {
|
|
||||||
return ddlOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getAlertHeader() {
|
|
||||||
log.debug("\ngetAlertHeader");
|
|
||||||
String result = alertHeader.shouldBe(Condition.visible).getText();
|
|
||||||
log.debug("-> {}", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getAlertMessage() {
|
|
||||||
log.debug("\ngetAlertMessage");
|
|
||||||
String result = alertMessage.shouldBe(Condition.visible).getText();
|
|
||||||
log.debug("-> {}", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isAlertVisible(AlertHeader header) {
|
|
||||||
log.debug("\nisAlertVisible: {}", header.toString());
|
|
||||||
boolean result = getAlertHeader().equals(header.toString());
|
|
||||||
log.debug("-> {}", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isAlertVisible(AlertHeader header, String message) {
|
|
||||||
log.debug("\nisAlertVisible: {} {}", header, message);
|
|
||||||
boolean result = isAlertVisible(header) && getAlertMessage().equals(message);
|
|
||||||
log.debug("-> {}", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void clickConfirmButton() {
|
|
||||||
confirmBtn.shouldBe(Condition.enabled).click();
|
|
||||||
confirmBtn.shouldBe(Condition.disappear);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void clickCancelButton() {
|
|
||||||
cancelBtn.shouldBe(Condition.enabled).click();
|
|
||||||
cancelBtn.shouldBe(Condition.disappear);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isConfirmationModalVisible() {
|
|
||||||
return isVisible(confirmationMdl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum AlertHeader {
|
|
||||||
SUCCESS("Success"),
|
|
||||||
VALIDATION_ERROR("Validation Error"),
|
|
||||||
BAD_REQUEST("400 Bad Request");
|
|
||||||
|
|
||||||
private final String value;
|
|
||||||
|
|
||||||
AlertHeader(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
package com.provectus.kafka.ui.pages.brokers;
|
package com.provectus.kafka.ui.pages.brokers;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$$x;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$$x;
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
public class BrokersConfigTab extends BasePage {
|
public class BrokersConfigTab extends BasePage {
|
||||||
|
|
||||||
protected List<SelenideElement> editBtn = $$x("//button[@aria-label='editAction']");
|
protected List<SelenideElement> editBtn = $$x("//button[@aria-label='editAction']");
|
||||||
protected SelenideElement searchByKeyField = $x("//input[@placeholder='Search by Key']");
|
protected SelenideElement searchByKeyField = $x("//input[@placeholder='Search by Key']");
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public BrokersConfigTab waitUntilScreenReady() {
|
public BrokersConfigTab waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
searchByKeyField.shouldBe(Condition.visible);
|
searchByKeyField.shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isSearchByKeyVisible() {
|
public boolean isSearchByKeyVisible() {
|
||||||
return isVisible(searchByKeyField);
|
return isVisible(searchByKeyField);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SelenideElement> getColumnHeaders() {
|
public List<SelenideElement> getColumnHeaders() {
|
||||||
return Stream.of("Key", "Value", "Source")
|
return Stream.of("Key", "Value", "Source")
|
||||||
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SelenideElement> getEditButtons() {
|
public List<SelenideElement> getEditButtons() {
|
||||||
return editBtn;
|
return editBtn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,92 +1,91 @@
|
||||||
package com.provectus.kafka.ui.pages.brokers;
|
package com.provectus.kafka.ui.pages.brokers;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
import org.openqa.selenium.By;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
import static com.codeborne.selenide.Selenide.$;
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
public class BrokersDetails extends BasePage {
|
public class BrokersDetails extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement logDirectoriesTab = $x("//a[text()='Log directories']");
|
protected SelenideElement logDirectoriesTab = $x("//a[text()='Log directories']");
|
||||||
protected SelenideElement metricsTab = $x("//a[text()='Metrics']");
|
protected SelenideElement metricsTab = $x("//a[text()='Metrics']");
|
||||||
protected String brokersTabLocator = "//a[text()='%s']";
|
protected String brokersTabLocator = "//a[text()='%s']";
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public BrokersDetails waitUntilScreenReady() {
|
public BrokersDetails waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
Arrays.asList(logDirectoriesTab, metricsTab).forEach(element -> element.shouldBe(Condition.visible));
|
Arrays.asList(logDirectoriesTab, metricsTab).forEach(element -> element.shouldBe(Condition.visible));
|
||||||
return this;
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public BrokersDetails openDetailsTab(DetailsTab menu) {
|
||||||
|
$(By.linkText(menu.toString())).shouldBe(Condition.enabled).click();
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getVisibleColumnHeaders() {
|
||||||
|
return Stream.of("Name", "Topics", "Error", "Partitions")
|
||||||
|
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getEnabledColumnHeaders() {
|
||||||
|
return Stream.of("Name", "Error")
|
||||||
|
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getVisibleSummaryCells() {
|
||||||
|
return Stream.of("Segment Size", "Segment Count", "Port", "Host")
|
||||||
|
.map(name -> $x(String.format(summaryCellLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getDetailsTabs() {
|
||||||
|
return Stream.of(DetailsTab.values())
|
||||||
|
.map(name -> $x(String.format(brokersTabLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<SelenideElement> getAllEnabledElements() {
|
||||||
|
List<SelenideElement> enabledElements = new ArrayList<>(getEnabledColumnHeaders());
|
||||||
|
enabledElements.addAll(getDetailsTabs());
|
||||||
|
return enabledElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<SelenideElement> getAllVisibleElements() {
|
||||||
|
List<SelenideElement> visibleElements = new ArrayList<>(getVisibleSummaryCells());
|
||||||
|
visibleElements.addAll(getVisibleColumnHeaders());
|
||||||
|
visibleElements.addAll(getDetailsTabs());
|
||||||
|
return visibleElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DetailsTab {
|
||||||
|
LOG_DIRECTORIES("Log directories"),
|
||||||
|
CONFIGS("Configs"),
|
||||||
|
METRICS("Metrics");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
DetailsTab(String value) {
|
||||||
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
public String toString() {
|
||||||
public BrokersDetails openDetailsTab(DetailsTab menu) {
|
return value;
|
||||||
$(By.linkText(menu.toString())).shouldBe(Condition.enabled).click();
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getVisibleColumnHeaders() {
|
|
||||||
return Stream.of("Name", "Topics", "Error", "Partitions")
|
|
||||||
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getEnabledColumnHeaders() {
|
|
||||||
return Stream.of("Name", "Error")
|
|
||||||
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getVisibleSummaryCells() {
|
|
||||||
return Stream.of("Segment Size", "Segment Count", "Port", "Host")
|
|
||||||
.map(name -> $x(String.format(summaryCellLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getDetailsTabs() {
|
|
||||||
return Stream.of(DetailsTab.values())
|
|
||||||
.map(name -> $x(String.format(brokersTabLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public List<SelenideElement> getAllEnabledElements() {
|
|
||||||
List<SelenideElement> enabledElements = new ArrayList<>(getEnabledColumnHeaders());
|
|
||||||
enabledElements.addAll(getDetailsTabs());
|
|
||||||
return enabledElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public List<SelenideElement> getAllVisibleElements() {
|
|
||||||
List<SelenideElement> visibleElements = new ArrayList<>(getVisibleSummaryCells());
|
|
||||||
visibleElements.addAll(getVisibleColumnHeaders());
|
|
||||||
visibleElements.addAll(getDetailsTabs());
|
|
||||||
return visibleElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DetailsTab {
|
|
||||||
LOG_DIRECTORIES("Log directories"),
|
|
||||||
CONFIGS("Configs"),
|
|
||||||
METRICS("Metrics");
|
|
||||||
|
|
||||||
private final String value;
|
|
||||||
|
|
||||||
DetailsTab(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,123 +1,122 @@
|
||||||
package com.provectus.kafka.ui.pages.brokers;
|
package com.provectus.kafka.ui.pages.brokers;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.BROKERS;
|
||||||
|
|
||||||
import com.codeborne.selenide.CollectionCondition;
|
import com.codeborne.selenide.CollectionCondition;
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.BROKERS;
|
|
||||||
|
|
||||||
public class BrokersList extends BasePage {
|
public class BrokersList extends BasePage {
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public BrokersList waitUntilScreenReady() {
|
public BrokersList waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
getPageTitleFromHeader(BROKERS).shouldBe(Condition.visible);
|
getPageTitleFromHeader(BROKERS).shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public BrokersList openBroker(int brokerId) {
|
||||||
|
getBrokerItem(brokerId).openItem();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getUptimeSummaryCells() {
|
||||||
|
return Stream.of("Broker Count", "Active Controller", "Version")
|
||||||
|
.map(name -> $x(String.format(summaryCellLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getPartitionsSummaryCells() {
|
||||||
|
return Stream.of("Online", "URP", "In Sync Replicas", "Out Of Sync Replicas")
|
||||||
|
.map(name -> $x(String.format(summaryCellLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<SelenideElement> getAllVisibleElements() {
|
||||||
|
List<SelenideElement> visibleElements = new ArrayList<>(getUptimeSummaryCells());
|
||||||
|
visibleElements.addAll(getPartitionsSummaryCells());
|
||||||
|
return visibleElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getEnabledColumnHeaders() {
|
||||||
|
return Stream.of("Broker ID", "Segment Size", "Segment Count", "Port", "Host")
|
||||||
|
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<SelenideElement> getAllEnabledElements() {
|
||||||
|
return getEnabledColumnHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BrokersList.BrokerGridItem> initGridItems() {
|
||||||
|
List<BrokersList.BrokerGridItem> gridItemList = new ArrayList<>();
|
||||||
|
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
||||||
|
.forEach(item -> gridItemList.add(new BrokersList.BrokerGridItem(item)));
|
||||||
|
return gridItemList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public BrokerGridItem getBrokerItem(int id) {
|
||||||
|
return initGridItems().stream()
|
||||||
|
.filter(e -> e.getId() == id)
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<BrokerGridItem> getAllBrokers() {
|
||||||
|
return initGridItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BrokerGridItem extends BasePage {
|
||||||
|
|
||||||
|
private final SelenideElement element;
|
||||||
|
|
||||||
|
public BrokerGridItem(SelenideElement element) {
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SelenideElement getIdElm() {
|
||||||
|
return element.$x("./td[1]/div/a");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public BrokersList openBroker(int brokerId) {
|
public int getId() {
|
||||||
getBrokerItem(brokerId).openItem();
|
return Integer.parseInt(getIdElm().getText().trim());
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getUptimeSummaryCells() {
|
|
||||||
return Stream.of("Broker Count", "Active Controller", "Version")
|
|
||||||
.map(name -> $x(String.format(summaryCellLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getPartitionsSummaryCells() {
|
|
||||||
return Stream.of("Online", "URP", "In Sync Replicas", "Out Of Sync Replicas")
|
|
||||||
.map(name -> $x(String.format(summaryCellLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public List<SelenideElement> getAllVisibleElements() {
|
public void openItem() {
|
||||||
List<SelenideElement> visibleElements = new ArrayList<>(getUptimeSummaryCells());
|
getIdElm().click();
|
||||||
visibleElements.addAll(getPartitionsSummaryCells());
|
|
||||||
return visibleElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getEnabledColumnHeaders() {
|
|
||||||
return Stream.of("Broker ID", "Segment Size", "Segment Count", "Port", "Host")
|
|
||||||
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public List<SelenideElement> getAllEnabledElements() {
|
public int getSegmentSize() {
|
||||||
return getEnabledColumnHeaders();
|
return Integer.parseInt(element.$x("./td[2]").getText().trim());
|
||||||
}
|
|
||||||
|
|
||||||
private List<BrokersList.BrokerGridItem> initGridItems() {
|
|
||||||
List<BrokersList.BrokerGridItem> gridItemList = new ArrayList<>();
|
|
||||||
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
|
||||||
.forEach(item -> gridItemList.add(new BrokersList.BrokerGridItem(item)));
|
|
||||||
return gridItemList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public BrokerGridItem getBrokerItem(int id) {
|
public int getSegmentCount() {
|
||||||
return initGridItems().stream()
|
return Integer.parseInt(element.$x("./td[3]").getText().trim());
|
||||||
.filter(e -> e.getId() == id)
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public List<BrokerGridItem> getAllBrokers() {
|
public int getPort() {
|
||||||
return initGridItems();
|
return Integer.parseInt(element.$x("./td[4]").getText().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BrokerGridItem extends BasePage {
|
@Step
|
||||||
|
public String getHost() {
|
||||||
private final SelenideElement element;
|
return element.$x("./td[5]").getText().trim();
|
||||||
|
|
||||||
public BrokerGridItem(SelenideElement element) {
|
|
||||||
this.element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SelenideElement getIdElm() {
|
|
||||||
return element.$x("./td[1]/div/a");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getId() {
|
|
||||||
return Integer.parseInt(getIdElm().getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public void openItem() {
|
|
||||||
getIdElm().click();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getSegmentSize() {
|
|
||||||
return Integer.parseInt(element.$x("./td[2]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getSegmentCount() {
|
|
||||||
return Integer.parseInt(element.$x("./td[3]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getPort() {
|
|
||||||
return Integer.parseInt(element.$x("./td[4]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getHost() {
|
|
||||||
return element.$x("./td[5]").getText().trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
package com.provectus.kafka.ui.pages.connectors;
|
package com.provectus.kafka.ui.pages.connectors;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
public class ConnectorCreateForm extends BasePage {
|
public class ConnectorCreateForm extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement nameField = $x("//input[@name='name']");
|
protected SelenideElement nameField = $x("//input[@name='name']");
|
||||||
protected SelenideElement contentTextArea = $x("//textarea[@class='ace_text-input']");
|
protected SelenideElement contentTextArea = $x("//textarea[@class='ace_text-input']");
|
||||||
protected SelenideElement configField = $x("//div[@id='config']");
|
protected SelenideElement configField = $x("//div[@id='config']");
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorCreateForm waitUntilScreenReady() {
|
public ConnectorCreateForm waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
nameField.shouldBe(Condition.visible);
|
nameField.shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorCreateForm setName(String connectName) {
|
public ConnectorCreateForm setName(String connectName) {
|
||||||
nameField.shouldBe(Condition.enabled).setValue(connectName);
|
nameField.shouldBe(Condition.enabled).setValue(connectName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorCreateForm setConfig(String configJson) {
|
public ConnectorCreateForm setConfig(String configJson) {
|
||||||
configField.shouldBe(Condition.enabled).click();
|
configField.shouldBe(Condition.enabled).click();
|
||||||
setJsonInputValue(contentTextArea, configJson);
|
setJsonInputValue(contentTextArea, configJson);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorCreateForm setConnectorDetails(String connectName, String configJson) {
|
public ConnectorCreateForm setConnectorDetails(String connectName, String configJson) {
|
||||||
setName(connectName);
|
setName(connectName);
|
||||||
setConfig(configJson);
|
setConfig(configJson);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorCreateForm clickSubmitButton() {
|
public ConnectorCreateForm clickSubmitButton() {
|
||||||
clickSubmitBtn();
|
clickSubmitBtn();
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,84 +1,84 @@
|
||||||
package com.provectus.kafka.ui.pages.connectors;
|
package com.provectus.kafka.ui.pages.connectors;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
public class ConnectorDetails extends BasePage {
|
public class ConnectorDetails extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement deleteBtn = $x("//li/div[contains(text(),'Delete')]");
|
protected SelenideElement deleteBtn = $x("//li/div[contains(text(),'Delete')]");
|
||||||
protected SelenideElement confirmBtnMdl = $x("//div[@role='dialog']//button[contains(text(),'Confirm')]");
|
protected SelenideElement confirmBtnMdl = $x("//div[@role='dialog']//button[contains(text(),'Confirm')]");
|
||||||
protected SelenideElement contentTextArea = $x("//textarea[@class='ace_text-input']");
|
protected SelenideElement contentTextArea = $x("//textarea[@class='ace_text-input']");
|
||||||
protected SelenideElement taskTab = $x("//a[contains(text(),'Tasks')]");
|
protected SelenideElement taskTab = $x("//a[contains(text(),'Tasks')]");
|
||||||
protected SelenideElement configTab = $x("//a[contains(text(),'Config')]");
|
protected SelenideElement configTab = $x("//a[contains(text(),'Config')]");
|
||||||
protected SelenideElement configField = $x("//div[@id='config']");
|
protected SelenideElement configField = $x("//div[@id='config']");
|
||||||
protected String connectorHeaderLocator = "//h1[contains(text(),'%s')]";
|
protected String connectorHeaderLocator = "//h1[contains(text(),'%s')]";
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorDetails waitUntilScreenReady() {
|
public ConnectorDetails waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
dotMenuBtn.shouldBe(Condition.visible);
|
dotMenuBtn.shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorDetails openConfigTab() {
|
public ConnectorDetails openConfigTab() {
|
||||||
clickByJavaScript(configTab);
|
clickByJavaScript(configTab);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorDetails setConfig(String configJson) {
|
public ConnectorDetails setConfig(String configJson) {
|
||||||
configField.shouldBe(Condition.enabled).click();
|
configField.shouldBe(Condition.enabled).click();
|
||||||
clearByKeyboard(contentTextArea);
|
clearByKeyboard(contentTextArea);
|
||||||
contentTextArea.setValue(configJson);
|
contentTextArea.setValue(configJson);
|
||||||
configField.shouldBe(Condition.enabled).click();
|
configField.shouldBe(Condition.enabled).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorDetails clickSubmitButton() {
|
public ConnectorDetails clickSubmitButton() {
|
||||||
clickSubmitBtn();
|
clickSubmitBtn();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorDetails openDotMenu() {
|
public ConnectorDetails openDotMenu() {
|
||||||
clickByJavaScript(dotMenuBtn);
|
clickByJavaScript(dotMenuBtn);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorDetails clickDeleteBtn() {
|
public ConnectorDetails clickDeleteBtn() {
|
||||||
clickByJavaScript(deleteBtn);
|
clickByJavaScript(deleteBtn);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorDetails clickConfirmBtn() {
|
public ConnectorDetails clickConfirmBtn() {
|
||||||
confirmBtnMdl.shouldBe(Condition.enabled).click();
|
confirmBtnMdl.shouldBe(Condition.enabled).click();
|
||||||
confirmBtnMdl.shouldBe(Condition.disappear);
|
confirmBtnMdl.shouldBe(Condition.disappear);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConnectorDetails deleteConnector() {
|
public ConnectorDetails deleteConnector() {
|
||||||
openDotMenu();
|
openDotMenu();
|
||||||
clickDeleteBtn();
|
clickDeleteBtn();
|
||||||
clickConfirmBtn();
|
clickConfirmBtn();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isConnectorHeaderVisible(String connectorName) {
|
public boolean isConnectorHeaderVisible(String connectorName) {
|
||||||
return isVisible($x(String.format(connectorHeaderLocator, connectorName)));
|
return isVisible($x(String.format(connectorHeaderLocator, connectorName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
|
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
|
||||||
return isAlertVisible(header, message);
|
return isAlertVisible(header, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
package com.provectus.kafka.ui.pages.connectors;
|
package com.provectus.kafka.ui.pages.connectors;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.KAFKA_CONNECT;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.KAFKA_CONNECT;
|
|
||||||
|
|
||||||
|
|
||||||
public class KafkaConnectList extends BasePage {
|
public class KafkaConnectList extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement createConnectorBtn = $x("//button[contains(text(),'Create Connector')]");
|
protected SelenideElement createConnectorBtn = $x("//button[contains(text(),'Create Connector')]");
|
||||||
|
|
||||||
public KafkaConnectList() {
|
public KafkaConnectList() {
|
||||||
tableElementNameLocator = "//tbody//td[contains(text(),'%s')]";
|
tableElementNameLocator = "//tbody//td[contains(text(),'%s')]";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public KafkaConnectList waitUntilScreenReady() {
|
public KafkaConnectList waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
getPageTitleFromHeader(KAFKA_CONNECT).shouldBe(Condition.visible);
|
getPageTitleFromHeader(KAFKA_CONNECT).shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public KafkaConnectList clickCreateConnectorBtn() {
|
public KafkaConnectList clickCreateConnectorBtn() {
|
||||||
clickByJavaScript(createConnectorBtn);
|
clickByJavaScript(createConnectorBtn);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public KafkaConnectList openConnector(String connectorName) {
|
public KafkaConnectList openConnector(String connectorName) {
|
||||||
getTableElement(connectorName).shouldBe(Condition.enabled).click();
|
getTableElement(connectorName).shouldBe(Condition.enabled).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isConnectorVisible(String connectorName) {
|
public boolean isConnectorVisible(String connectorName) {
|
||||||
tableGrid.shouldBe(Condition.visible);
|
tableGrid.shouldBe(Condition.visible);
|
||||||
return isVisible(getTableElement(connectorName));
|
return isVisible(getTableElement(connectorName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
package com.provectus.kafka.ui.pages.consumers;
|
package com.provectus.kafka.ui.pages.consumers;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
public class ConsumersDetails extends BasePage {
|
public class ConsumersDetails extends BasePage {
|
||||||
|
|
||||||
protected String consumerIdHeaderLocator = "//h1[contains(text(),'%s')]";
|
protected String consumerIdHeaderLocator = "//h1[contains(text(),'%s')]";
|
||||||
protected String topicElementLocator = "//tbody//td//a[text()='%s']";
|
protected String topicElementLocator = "//tbody//td//a[text()='%s']";
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConsumersDetails waitUntilScreenReady() {
|
public ConsumersDetails waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
tableGrid.shouldBe(Condition.visible);
|
tableGrid.shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isRedirectedConsumerTitleVisible(String consumerGroupId) {
|
public boolean isRedirectedConsumerTitleVisible(String consumerGroupId) {
|
||||||
return isVisible($x(String.format(consumerIdHeaderLocator, consumerGroupId)));
|
return isVisible($x(String.format(consumerIdHeaderLocator, consumerGroupId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isTopicInConsumersDetailsVisible(String topicName) {
|
public boolean isTopicInConsumersDetailsVisible(String topicName) {
|
||||||
tableGrid.shouldBe(Condition.visible);
|
tableGrid.shouldBe(Condition.visible);
|
||||||
return isVisible($x(String.format(topicElementLocator, topicName)));
|
return isVisible($x(String.format(topicElementLocator, topicName)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
package com.provectus.kafka.ui.pages.consumers;
|
package com.provectus.kafka.ui.pages.consumers;
|
||||||
|
|
||||||
|
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.CONSUMERS;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.CONSUMERS;
|
|
||||||
|
|
||||||
public class ConsumersList extends BasePage {
|
public class ConsumersList extends BasePage {
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ConsumersList waitUntilScreenReady() {
|
public ConsumersList waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
getPageTitleFromHeader(CONSUMERS).shouldBe(Condition.visible);
|
getPageTitleFromHeader(CONSUMERS).shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
package com.provectus.kafka.ui.pages.ksqlDb;
|
|
||||||
|
|
||||||
import com.codeborne.selenide.CollectionCondition;
|
|
||||||
import com.codeborne.selenide.Condition;
|
|
||||||
import com.codeborne.selenide.SelenideElement;
|
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
|
||||||
import com.provectus.kafka.ui.pages.ksqlDb.enums.KsqlMenuTabs;
|
|
||||||
import io.qameta.allure.Step;
|
|
||||||
import org.openqa.selenium.By;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$;
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.KSQL_DB;
|
|
||||||
|
|
||||||
public class KsqlDbList extends BasePage {
|
|
||||||
|
|
||||||
protected SelenideElement executeKsqlBtn = $x("//button[text()='Execute KSQL Request']");
|
|
||||||
protected SelenideElement tablesTab = $x("//nav[@role='navigation']/a[text()='Tables']");
|
|
||||||
protected SelenideElement streamsTab = $x("//nav[@role='navigation']/a[text()='Streams']");
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlDbList waitUntilScreenReady() {
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
getPageTitleFromHeader(KSQL_DB).shouldBe(Condition.visible);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlDbList clickExecuteKsqlRequestBtn() {
|
|
||||||
clickByJavaScript(executeKsqlBtn);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlDbList openDetailsTab(KsqlMenuTabs menu) {
|
|
||||||
$(By.linkText(menu.toString())).shouldBe(Condition.visible).click();
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<KsqlDbList.KsqlTablesGridItem> initTablesItems() {
|
|
||||||
List<KsqlDbList.KsqlTablesGridItem> gridItemList = new ArrayList<>();
|
|
||||||
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
|
||||||
.forEach(item -> gridItemList.add(new KsqlDbList.KsqlTablesGridItem(item)));
|
|
||||||
return gridItemList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlDbList.KsqlTablesGridItem getTableByName(String tableName) {
|
|
||||||
return initTablesItems().stream()
|
|
||||||
.filter(e -> e.getTableName().equals(tableName))
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<KsqlDbList.KsqlStreamsGridItem> initStreamsItems() {
|
|
||||||
List<KsqlDbList.KsqlStreamsGridItem> gridItemList = new ArrayList<>();
|
|
||||||
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
|
||||||
.forEach(item -> gridItemList.add(new KsqlDbList.KsqlStreamsGridItem(item)));
|
|
||||||
return gridItemList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlDbList.KsqlStreamsGridItem getStreamByName(String streamName) {
|
|
||||||
return initStreamsItems().stream()
|
|
||||||
.filter(e -> e.getStreamName().equals(streamName))
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class KsqlTablesGridItem extends BasePage {
|
|
||||||
|
|
||||||
private final SelenideElement element;
|
|
||||||
|
|
||||||
public KsqlTablesGridItem(SelenideElement element) {
|
|
||||||
this.element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getTableName() {
|
|
||||||
return element.$x("./td[1]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getTopicName() {
|
|
||||||
return element.$x("./td[2]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getKeyFormat() {
|
|
||||||
return element.$x("./td[3]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getValueFormat() {
|
|
||||||
return element.$x("./td[4]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getIsWindowed() {
|
|
||||||
return element.$x("./td[5]").getText().trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class KsqlStreamsGridItem extends BasePage {
|
|
||||||
|
|
||||||
private final SelenideElement element;
|
|
||||||
|
|
||||||
public KsqlStreamsGridItem(SelenideElement element) {
|
|
||||||
this.element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getStreamName() {
|
|
||||||
return element.$x("./td[1]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getTopicName() {
|
|
||||||
return element.$x("./td[2]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getKeyFormat() {
|
|
||||||
return element.$x("./td[3]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getValueFormat() {
|
|
||||||
return element.$x("./td[4]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getIsWindowed() {
|
|
||||||
return element.$x("./td[5]").getText().trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
package com.provectus.kafka.ui.pages.ksqlDb;
|
|
||||||
|
|
||||||
import com.codeborne.selenide.CollectionCondition;
|
|
||||||
import com.codeborne.selenide.Condition;
|
|
||||||
import com.codeborne.selenide.ElementsCollection;
|
|
||||||
import com.codeborne.selenide.SelenideElement;
|
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
|
||||||
import io.qameta.allure.Step;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.codeborne.selenide.Condition.visible;
|
|
||||||
import static com.codeborne.selenide.Selenide.$$x;
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
public class KsqlQueryForm extends BasePage {
|
|
||||||
protected SelenideElement clearBtn = $x("//div/button[text()='Clear']");
|
|
||||||
protected SelenideElement executeBtn = $x("//div/button[text()='Execute']");
|
|
||||||
protected SelenideElement stopQueryBtn = $x("//div/button[text()='Stop query']");
|
|
||||||
protected SelenideElement clearResultsBtn = $x("//div/button[text()='Clear results']");
|
|
||||||
protected SelenideElement addStreamPropertyBtn = $x("//button[text()='Add Stream Property']");
|
|
||||||
protected SelenideElement queryAreaValue = $x("//div[@class='ace_content']");
|
|
||||||
protected SelenideElement queryArea = $x("//div[@id='ksql']/textarea[@class='ace_text-input']");
|
|
||||||
protected ElementsCollection ksqlGridItems = $$x("//tbody//tr");
|
|
||||||
protected ElementsCollection keyField = $$x("//input[@aria-label='key']");
|
|
||||||
protected ElementsCollection valueField = $$x("//input[@aria-label='value']");
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlQueryForm waitUntilScreenReady() {
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
executeBtn.shouldBe(Condition.visible);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlQueryForm clickClearBtn() {
|
|
||||||
clickByJavaScript(clearBtn);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlQueryForm clickExecuteBtn() {
|
|
||||||
clickByActions(executeBtn);
|
|
||||||
if (queryAreaValue.getText().contains("EMIT CHANGES;")) {
|
|
||||||
loadingSpinner.shouldBe(Condition.visible);
|
|
||||||
} else {
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlQueryForm clickStopQueryBtn() {
|
|
||||||
clickByActions(stopQueryBtn);
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlQueryForm clickClearResultsBtn() {
|
|
||||||
clickByActions(clearResultsBtn);
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlQueryForm clickAddStreamProperty() {
|
|
||||||
clickByJavaScript(addStreamPropertyBtn);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlQueryForm setQuery(String query) {
|
|
||||||
queryAreaValue.shouldBe(Condition.visible).click();
|
|
||||||
queryArea.setValue(query);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public KsqlQueryForm.KsqlResponseGridItem getTableByName(String name) {
|
|
||||||
return initItems().stream()
|
|
||||||
.filter(e -> e.getName().equalsIgnoreCase(name))
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean areResultsVisible() {
|
|
||||||
boolean visible = false;
|
|
||||||
try {
|
|
||||||
visible = initItems().size() > 0;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
return visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<KsqlQueryForm.KsqlResponseGridItem> initItems() {
|
|
||||||
List<KsqlQueryForm.KsqlResponseGridItem> gridItemList = new ArrayList<>();
|
|
||||||
ksqlGridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
|
||||||
.forEach(item -> gridItemList.add(new KsqlQueryForm.KsqlResponseGridItem(item)));
|
|
||||||
return gridItemList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class KsqlResponseGridItem extends BasePage {
|
|
||||||
|
|
||||||
private final SelenideElement element;
|
|
||||||
|
|
||||||
private KsqlResponseGridItem(SelenideElement element) {
|
|
||||||
this.element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getType() {
|
|
||||||
return element.$x("./td[1]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getName() {
|
|
||||||
return element.$x("./td[2]").scrollTo().getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isVisible() {
|
|
||||||
boolean isVisible = false;
|
|
||||||
try {
|
|
||||||
element.$x("./td[2]").shouldBe(visible, Duration.ofMillis(500));
|
|
||||||
isVisible = true;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
return isVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getTopic() {
|
|
||||||
return element.$x("./td[3]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getKeyFormat() {
|
|
||||||
return element.$x("./td[4]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getValueFormat() {
|
|
||||||
return element.$x("./td[5]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getIsWindowed() {
|
|
||||||
return element.$x("./td[6]").getText().trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package com.provectus.kafka.ui.pages.ksqlDb.enums;
|
|
||||||
|
|
||||||
public enum KsqlMenuTabs {
|
|
||||||
|
|
||||||
TABLES("Table"),
|
|
||||||
STREAMS("Streams");
|
|
||||||
|
|
||||||
private final String value;
|
|
||||||
|
|
||||||
KsqlMenuTabs(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package com.provectus.kafka.ui.pages.ksqlDb.enums;
|
|
||||||
|
|
||||||
public enum KsqlQueryConfig {
|
|
||||||
|
|
||||||
SHOW_TABLES("show tables;"),
|
|
||||||
SHOW_STREAMS("show streams;"),
|
|
||||||
SELECT_ALL_FROM("SELECT * FROM %s\n" +
|
|
||||||
"EMIT CHANGES;");
|
|
||||||
|
|
||||||
private final String query;
|
|
||||||
|
|
||||||
KsqlQueryConfig(String query) {
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuery() {
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
package com.provectus.kafka.ui.pages.ksqldb;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.KSQL_DB;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.CollectionCondition;
|
||||||
|
import com.codeborne.selenide.Condition;
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
|
import com.provectus.kafka.ui.pages.ksqldb.enums.KsqlMenuTabs;
|
||||||
|
import io.qameta.allure.Step;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
|
||||||
|
public class KsqlDbList extends BasePage {
|
||||||
|
|
||||||
|
protected SelenideElement executeKsqlBtn = $x("//button[text()='Execute KSQL Request']");
|
||||||
|
protected SelenideElement tablesTab = $x("//nav[@role='navigation']/a[text()='Tables']");
|
||||||
|
protected SelenideElement streamsTab = $x("//nav[@role='navigation']/a[text()='Streams']");
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlDbList waitUntilScreenReady() {
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
getPageTitleFromHeader(KSQL_DB).shouldBe(Condition.visible);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlDbList clickExecuteKsqlRequestBtn() {
|
||||||
|
clickByJavaScript(executeKsqlBtn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlDbList openDetailsTab(KsqlMenuTabs menu) {
|
||||||
|
$(By.linkText(menu.toString())).shouldBe(Condition.visible).click();
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<KsqlDbList.KsqlTablesGridItem> initTablesItems() {
|
||||||
|
List<KsqlDbList.KsqlTablesGridItem> gridItemList = new ArrayList<>();
|
||||||
|
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
||||||
|
.forEach(item -> gridItemList.add(new KsqlDbList.KsqlTablesGridItem(item)));
|
||||||
|
return gridItemList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlDbList.KsqlTablesGridItem getTableByName(String tableName) {
|
||||||
|
return initTablesItems().stream()
|
||||||
|
.filter(e -> e.getTableName().equals(tableName))
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<KsqlDbList.KsqlStreamsGridItem> initStreamsItems() {
|
||||||
|
List<KsqlDbList.KsqlStreamsGridItem> gridItemList = new ArrayList<>();
|
||||||
|
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
||||||
|
.forEach(item -> gridItemList.add(new KsqlDbList.KsqlStreamsGridItem(item)));
|
||||||
|
return gridItemList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlDbList.KsqlStreamsGridItem getStreamByName(String streamName) {
|
||||||
|
return initStreamsItems().stream()
|
||||||
|
.filter(e -> e.getStreamName().equals(streamName))
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KsqlTablesGridItem extends BasePage {
|
||||||
|
|
||||||
|
private final SelenideElement element;
|
||||||
|
|
||||||
|
public KsqlTablesGridItem(SelenideElement element) {
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getTableName() {
|
||||||
|
return element.$x("./td[1]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getTopicName() {
|
||||||
|
return element.$x("./td[2]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getKeyFormat() {
|
||||||
|
return element.$x("./td[3]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getValueFormat() {
|
||||||
|
return element.$x("./td[4]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getIsWindowed() {
|
||||||
|
return element.$x("./td[5]").getText().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KsqlStreamsGridItem extends BasePage {
|
||||||
|
|
||||||
|
private final SelenideElement element;
|
||||||
|
|
||||||
|
public KsqlStreamsGridItem(SelenideElement element) {
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getStreamName() {
|
||||||
|
return element.$x("./td[1]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getTopicName() {
|
||||||
|
return element.$x("./td[2]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getKeyFormat() {
|
||||||
|
return element.$x("./td[3]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getValueFormat() {
|
||||||
|
return element.$x("./td[4]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getIsWindowed() {
|
||||||
|
return element.$x("./td[5]").getText().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package com.provectus.kafka.ui.pages.ksqldb;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Condition.visible;
|
||||||
|
import static com.codeborne.selenide.Selenide.$$x;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.CollectionCondition;
|
||||||
|
import com.codeborne.selenide.Condition;
|
||||||
|
import com.codeborne.selenide.ElementsCollection;
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
|
import io.qameta.allure.Step;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class KsqlQueryForm extends BasePage {
|
||||||
|
protected SelenideElement clearBtn = $x("//div/button[text()='Clear']");
|
||||||
|
protected SelenideElement executeBtn = $x("//div/button[text()='Execute']");
|
||||||
|
protected SelenideElement stopQueryBtn = $x("//div/button[text()='Stop query']");
|
||||||
|
protected SelenideElement clearResultsBtn = $x("//div/button[text()='Clear results']");
|
||||||
|
protected SelenideElement addStreamPropertyBtn = $x("//button[text()='Add Stream Property']");
|
||||||
|
protected SelenideElement queryAreaValue = $x("//div[@class='ace_content']");
|
||||||
|
protected SelenideElement queryArea = $x("//div[@id='ksql']/textarea[@class='ace_text-input']");
|
||||||
|
protected ElementsCollection ksqlGridItems = $$x("//tbody//tr");
|
||||||
|
protected ElementsCollection keyField = $$x("//input[@aria-label='key']");
|
||||||
|
protected ElementsCollection valueField = $$x("//input[@aria-label='value']");
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlQueryForm waitUntilScreenReady() {
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
executeBtn.shouldBe(Condition.visible);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlQueryForm clickClearBtn() {
|
||||||
|
clickByJavaScript(clearBtn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlQueryForm clickExecuteBtn() {
|
||||||
|
clickByActions(executeBtn);
|
||||||
|
if (queryAreaValue.getText().contains("EMIT CHANGES;")) {
|
||||||
|
loadingSpinner.shouldBe(Condition.visible);
|
||||||
|
} else {
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlQueryForm clickStopQueryBtn() {
|
||||||
|
clickByActions(stopQueryBtn);
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlQueryForm clickClearResultsBtn() {
|
||||||
|
clickByActions(clearResultsBtn);
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlQueryForm clickAddStreamProperty() {
|
||||||
|
clickByJavaScript(addStreamPropertyBtn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlQueryForm setQuery(String query) {
|
||||||
|
queryAreaValue.shouldBe(Condition.visible).click();
|
||||||
|
queryArea.setValue(query);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public KsqlQueryForm.KsqlResponseGridItem getTableByName(String name) {
|
||||||
|
return initItems().stream()
|
||||||
|
.filter(e -> e.getName().equalsIgnoreCase(name))
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean areResultsVisible() {
|
||||||
|
boolean visible = false;
|
||||||
|
try {
|
||||||
|
visible = initItems().size() > 0;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<KsqlQueryForm.KsqlResponseGridItem> initItems() {
|
||||||
|
List<KsqlQueryForm.KsqlResponseGridItem> gridItemList = new ArrayList<>();
|
||||||
|
ksqlGridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
||||||
|
.forEach(item -> gridItemList.add(new KsqlQueryForm.KsqlResponseGridItem(item)));
|
||||||
|
return gridItemList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KsqlResponseGridItem extends BasePage {
|
||||||
|
|
||||||
|
private final SelenideElement element;
|
||||||
|
|
||||||
|
private KsqlResponseGridItem(SelenideElement element) {
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getType() {
|
||||||
|
return element.$x("./td[1]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getName() {
|
||||||
|
return element.$x("./td[2]").scrollTo().getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isVisible() {
|
||||||
|
boolean isVisible = false;
|
||||||
|
try {
|
||||||
|
element.$x("./td[2]").shouldBe(visible, Duration.ofMillis(500));
|
||||||
|
isVisible = true;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getTopic() {
|
||||||
|
return element.$x("./td[3]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getKeyFormat() {
|
||||||
|
return element.$x("./td[4]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getValueFormat() {
|
||||||
|
return element.$x("./td[5]").getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getIsWindowed() {
|
||||||
|
return element.$x("./td[6]").getText().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.provectus.kafka.ui.pages.ksqldb.enums;
|
||||||
|
|
||||||
|
public enum KsqlMenuTabs {
|
||||||
|
|
||||||
|
TABLES("Table"),
|
||||||
|
STREAMS("Streams");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
KsqlMenuTabs(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.provectus.kafka.ui.pages.ksqldb.enums;
|
||||||
|
|
||||||
|
public enum KsqlQueryConfig {
|
||||||
|
|
||||||
|
SHOW_TABLES("show tables;"),
|
||||||
|
SHOW_STREAMS("show streams;"),
|
||||||
|
SELECT_ALL_FROM("SELECT * FROM %s\n" + "EMIT CHANGES;");
|
||||||
|
|
||||||
|
private final String query;
|
||||||
|
|
||||||
|
KsqlQueryConfig(String query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.provectus.kafka.ui.pages.ksqlDb.models;
|
package com.provectus.kafka.ui.pages.ksqldb.models;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
@ -7,5 +7,5 @@ import lombok.experimental.Accessors;
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class Stream {
|
public class Stream {
|
||||||
|
|
||||||
private String name, topicName, valueFormat, partitions;
|
private String name, topicName, valueFormat, partitions;
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.provectus.kafka.ui.pages.ksqlDb.models;
|
package com.provectus.kafka.ui.pages.ksqldb.models;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
@ -7,5 +7,5 @@ import lombok.experimental.Accessors;
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class Table {
|
public class Table {
|
||||||
|
|
||||||
private String name, streamName;
|
private String name, streamName;
|
||||||
}
|
}
|
|
@ -1,64 +1,63 @@
|
||||||
package com.provectus.kafka.ui.pages.panels;
|
package com.provectus.kafka.ui.pages.panels;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static com.provectus.kafka.ui.settings.BaseSource.CLUSTER_NAME;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
|
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
|
import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
import static com.provectus.kafka.ui.settings.BaseSource.CLUSTER_NAME;
|
|
||||||
|
|
||||||
public class NaviSideBar extends BasePage {
|
public class NaviSideBar extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement dashboardMenuItem = $x("//a[@title='Dashboard']");
|
protected SelenideElement dashboardMenuItem = $x("//a[@title='Dashboard']");
|
||||||
protected String sideMenuOptionElementLocator = ".//ul/li[contains(.,'%s')]";
|
protected String sideMenuOptionElementLocator = ".//ul/li[contains(.,'%s')]";
|
||||||
protected String clusterElementLocator = "//aside/ul/li[contains(.,'%s')]";
|
protected String clusterElementLocator = "//aside/ul/li[contains(.,'%s')]";
|
||||||
|
|
||||||
private SelenideElement expandCluster(String clusterName) {
|
private SelenideElement expandCluster(String clusterName) {
|
||||||
SelenideElement clusterElement = $x(String.format(clusterElementLocator, clusterName)).shouldBe(Condition.visible);
|
SelenideElement clusterElement = $x(String.format(clusterElementLocator, clusterName)).shouldBe(Condition.visible);
|
||||||
if (clusterElement.parent().$$x(".//ul").size() == 0) {
|
if (clusterElement.parent().$$x(".//ul").size() == 0) {
|
||||||
clickByActions(clusterElement);
|
clickByActions(clusterElement);
|
||||||
}
|
|
||||||
return clusterElement;
|
|
||||||
}
|
}
|
||||||
|
return clusterElement;
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public NaviSideBar waitUntilScreenReady() {
|
public NaviSideBar waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
dashboardMenuItem.shouldBe(Condition.visible, Duration.ofSeconds(30));
|
dashboardMenuItem.shouldBe(Condition.visible, Duration.ofSeconds(30));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public String getPagePath(MenuItem menuItem) {
|
public String getPagePath(MenuItem menuItem) {
|
||||||
return getPagePathFromHeader(menuItem)
|
return getPagePathFromHeader(menuItem)
|
||||||
.shouldBe(Condition.visible)
|
.shouldBe(Condition.visible)
|
||||||
.getText().trim();
|
.getText().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public NaviSideBar openSideMenu(String clusterName, MenuItem menuItem) {
|
public NaviSideBar openSideMenu(String clusterName, MenuItem menuItem) {
|
||||||
clickByActions(expandCluster(clusterName).parent()
|
clickByActions(expandCluster(clusterName).parent()
|
||||||
.$x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())));
|
.$x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public NaviSideBar openSideMenu(MenuItem menuItem) {
|
public NaviSideBar openSideMenu(MenuItem menuItem) {
|
||||||
openSideMenu(CLUSTER_NAME, menuItem);
|
openSideMenu(CLUSTER_NAME, menuItem);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SelenideElement> getAllMenuButtons() {
|
public List<SelenideElement> getAllMenuButtons() {
|
||||||
expandCluster(CLUSTER_NAME);
|
expandCluster(CLUSTER_NAME);
|
||||||
return Stream.of(MenuItem.values())
|
return Stream.of(MenuItem.values())
|
||||||
.map(menuItem -> $x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())))
|
.map(menuItem -> $x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
package com.provectus.kafka.ui.pages.panels;
|
package com.provectus.kafka.ui.pages.panels;
|
||||||
|
|
||||||
import com.codeborne.selenide.SelenideElement;
|
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class TopPanel extends BasePage {
|
public class TopPanel extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement kafkaLogo = $x("//a[contains(text(),'UI for Apache Kafka')]");
|
protected SelenideElement kafkaLogo = $x("//a[contains(text(),'UI for Apache Kafka')]");
|
||||||
protected SelenideElement kafkaVersion = $x("//a[@title='Current commit']");
|
protected SelenideElement kafkaVersion = $x("//a[@title='Current commit']");
|
||||||
protected SelenideElement logOutBtn = $x("//button[contains(text(),'Log out')]");
|
protected SelenideElement logOutBtn = $x("//button[contains(text(),'Log out')]");
|
||||||
protected SelenideElement gitBtn = $x("//a[@href='https://github.com/provectus/kafka-ui']");
|
protected SelenideElement gitBtn = $x("//a[@href='https://github.com/provectus/kafka-ui']");
|
||||||
protected SelenideElement discordBtn = $x("//a[contains(@href,'https://discord.com/invite')]");
|
protected SelenideElement discordBtn = $x("//a[contains(@href,'https://discord.com/invite')]");
|
||||||
|
|
||||||
public List<SelenideElement> getAllVisibleElements() {
|
public List<SelenideElement> getAllVisibleElements() {
|
||||||
return Arrays.asList(kafkaLogo, kafkaVersion, gitBtn, discordBtn);
|
return Arrays.asList(kafkaLogo, kafkaVersion, gitBtn, discordBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SelenideElement> getAllEnabledElements() {
|
public List<SelenideElement> getAllEnabledElements() {
|
||||||
return Arrays.asList(gitBtn, discordBtn, kafkaLogo);
|
return Arrays.asList(gitBtn, discordBtn, kafkaLogo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
package com.provectus.kafka.ui.pages.panels.enums;
|
package com.provectus.kafka.ui.pages.panels.enums;
|
||||||
|
|
||||||
public enum MenuItem {
|
public enum MenuItem {
|
||||||
|
|
||||||
DASHBOARD("Dashboard", "Dashboard"),
|
DASHBOARD("Dashboard", "Dashboard"),
|
||||||
BROKERS("Brokers", "Brokers"),
|
BROKERS("Brokers", "Brokers"),
|
||||||
TOPICS("Topics", "Topics"),
|
TOPICS("Topics", "Topics"),
|
||||||
CONSUMERS("Consumers", "Consumers"),
|
CONSUMERS("Consumers", "Consumers"),
|
||||||
SCHEMA_REGISTRY("Schema Registry", "Schema Registry"),
|
SCHEMA_REGISTRY("Schema Registry", "Schema Registry"),
|
||||||
KAFKA_CONNECT("Kafka Connect", "Connectors"),
|
KAFKA_CONNECT("Kafka Connect", "Connectors"),
|
||||||
KSQL_DB("KSQL DB", "KSQL DB");
|
KSQL_DB("KSQL DB", "KSQL DB");
|
||||||
|
|
||||||
private final String naviTitle;
|
private final String naviTitle;
|
||||||
private final String pageTitle;
|
private final String pageTitle;
|
||||||
|
|
||||||
MenuItem(String naviTitle, String pageTitle) {
|
MenuItem(String naviTitle, String pageTitle) {
|
||||||
this.naviTitle = naviTitle;
|
this.naviTitle = naviTitle;
|
||||||
this.pageTitle = pageTitle;
|
this.pageTitle = pageTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNaviTitle() {
|
public String getNaviTitle() {
|
||||||
return naviTitle;
|
return naviTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPageTitle() {
|
public String getPageTitle() {
|
||||||
return pageTitle;
|
return pageTitle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package com.provectus.kafka.ui.pages.schemas;
|
package com.provectus.kafka.ui.pages.schemas;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$;
|
||||||
|
import static com.codeborne.selenide.Selenide.$$x;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static org.openqa.selenium.By.id;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.codeborne.selenide.WebDriverRunner;
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
@ -7,133 +12,130 @@ import com.provectus.kafka.ui.api.model.CompatibilityLevel;
|
||||||
import com.provectus.kafka.ui.api.model.SchemaType;
|
import com.provectus.kafka.ui.api.model.SchemaType;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
import org.openqa.selenium.Keys;
|
|
||||||
import org.openqa.selenium.interactions.Actions;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import org.openqa.selenium.Keys;
|
||||||
import static com.codeborne.selenide.Selenide.*;
|
import org.openqa.selenium.interactions.Actions;
|
||||||
import static org.openqa.selenium.By.id;
|
|
||||||
|
|
||||||
public class SchemaCreateForm extends BasePage {
|
public class SchemaCreateForm extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement schemaNameField = $x("//input[@name='subject']");
|
protected SelenideElement schemaNameField = $x("//input[@name='subject']");
|
||||||
protected SelenideElement pageTitle = $x("//h1['Edit']");
|
protected SelenideElement pageTitle = $x("//h1['Edit']");
|
||||||
protected SelenideElement schemaTextArea = $x("//textarea[@name='schema']");
|
protected SelenideElement schemaTextArea = $x("//textarea[@name='schema']");
|
||||||
protected SelenideElement newSchemaInput = $("#newSchema [wrap]");
|
protected SelenideElement newSchemaInput = $("#newSchema [wrap]");
|
||||||
protected SelenideElement schemaTypeDdl = $x("//ul[@name='schemaType']");
|
protected SelenideElement schemaTypeDdl = $x("//ul[@name='schemaType']");
|
||||||
protected SelenideElement compatibilityLevelList = $x("//ul[@name='compatibilityLevel']");
|
protected SelenideElement compatibilityLevelList = $x("//ul[@name='compatibilityLevel']");
|
||||||
protected SelenideElement newSchemaTextArea = $x("//div[@id='newSchema']");
|
protected SelenideElement newSchemaTextArea = $x("//div[@id='newSchema']");
|
||||||
protected SelenideElement latestSchemaTextArea = $x("//div[@id='latestSchema']");
|
protected SelenideElement latestSchemaTextArea = $x("//div[@id='latestSchema']");
|
||||||
protected SelenideElement leftVersionDdl = $(id("left-select"));
|
protected SelenideElement leftVersionDdl = $(id("left-select"));
|
||||||
protected SelenideElement rightVersionDdl = $(id("right-select"));
|
protected SelenideElement rightVersionDdl = $(id("right-select"));
|
||||||
protected List<SelenideElement> visibleMarkers = $$x("//div[@class='ace_scroller']//div[contains(@class,'codeMarker')]");
|
protected List<SelenideElement> visibleMarkers =
|
||||||
protected List<SelenideElement> elementsCompareVersionDdl = $$x("//ul[@role='listbox']/ul/li");
|
$$x("//div[@class='ace_scroller']//div[contains(@class,'codeMarker')]");
|
||||||
protected String ddlElementLocator = "//li[@value='%s']";
|
protected List<SelenideElement> elementsCompareVersionDdl = $$x("//ul[@role='listbox']/ul/li");
|
||||||
|
protected String ddlElementLocator = "//li[@value='%s']";
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm waitUntilScreenReady() {
|
public SchemaCreateForm waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
pageTitle.shouldBe(Condition.visible);
|
pageTitle.shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm setSubjectName(String name) {
|
public SchemaCreateForm setSubjectName(String name) {
|
||||||
schemaNameField.setValue(name);
|
schemaNameField.setValue(name);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm setSchemaField(String text) {
|
public SchemaCreateForm setSchemaField(String text) {
|
||||||
schemaTextArea.setValue(text);
|
schemaTextArea.setValue(text);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm selectSchemaTypeFromDropdown(SchemaType schemaType) {
|
public SchemaCreateForm selectSchemaTypeFromDropdown(SchemaType schemaType) {
|
||||||
schemaTypeDdl.shouldBe(Condition.enabled).click();
|
schemaTypeDdl.shouldBe(Condition.enabled).click();
|
||||||
$x(String.format(ddlElementLocator, schemaType.getValue())).shouldBe(Condition.visible).click();
|
$x(String.format(ddlElementLocator, schemaType.getValue())).shouldBe(Condition.visible).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm clickSubmitButton() {
|
public SchemaCreateForm clickSubmitButton() {
|
||||||
clickSubmitBtn();
|
clickSubmitBtn();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm selectCompatibilityLevelFromDropdown(CompatibilityLevel.CompatibilityEnum level) {
|
public SchemaCreateForm selectCompatibilityLevelFromDropdown(CompatibilityLevel.CompatibilityEnum level) {
|
||||||
compatibilityLevelList.shouldBe(Condition.enabled).click();
|
compatibilityLevelList.shouldBe(Condition.enabled).click();
|
||||||
$x(String.format(ddlElementLocator, level.getValue())).shouldBe(Condition.visible).click();
|
$x(String.format(ddlElementLocator, level.getValue())).shouldBe(Condition.visible).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm openLeftVersionDdl() {
|
public SchemaCreateForm openLeftVersionDdl() {
|
||||||
leftVersionDdl.shouldBe(Condition.enabled).click();
|
leftVersionDdl.shouldBe(Condition.enabled).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm openRightVersionDdl() {
|
public SchemaCreateForm openRightVersionDdl() {
|
||||||
rightVersionDdl.shouldBe(Condition.enabled).click();
|
rightVersionDdl.shouldBe(Condition.enabled).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public int getVersionsNumberFromList() {
|
public int getVersionsNumberFromList() {
|
||||||
return elementsCompareVersionDdl.size();
|
return elementsCompareVersionDdl.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm selectVersionFromDropDown(int versionNumberDd) {
|
public SchemaCreateForm selectVersionFromDropDown(int versionNumberDd) {
|
||||||
$x(String.format(ddlElementLocator, versionNumberDd)).shouldBe(Condition.visible).click();
|
$x(String.format(ddlElementLocator, versionNumberDd)).shouldBe(Condition.visible).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public int getMarkedLinesNumber() {
|
public int getMarkedLinesNumber() {
|
||||||
return visibleMarkers.size();
|
return visibleMarkers.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaCreateForm setNewSchemaValue(String configJson) {
|
public SchemaCreateForm setNewSchemaValue(String configJson) {
|
||||||
newSchemaTextArea.shouldBe(Condition.visible).click();
|
newSchemaTextArea.shouldBe(Condition.visible).click();
|
||||||
newSchemaInput.shouldBe(Condition.enabled);
|
newSchemaInput.shouldBe(Condition.enabled);
|
||||||
new Actions(WebDriverRunner.getWebDriver())
|
new Actions(WebDriverRunner.getWebDriver())
|
||||||
.sendKeys(Keys.PAGE_UP)
|
.sendKeys(Keys.PAGE_UP)
|
||||||
.keyDown(Keys.SHIFT)
|
.keyDown(Keys.SHIFT)
|
||||||
.sendKeys(Keys.PAGE_DOWN)
|
.sendKeys(Keys.PAGE_DOWN)
|
||||||
.keyUp(Keys.SHIFT)
|
.keyUp(Keys.SHIFT)
|
||||||
.sendKeys(Keys.DELETE)
|
.sendKeys(Keys.DELETE)
|
||||||
.perform();
|
.perform();
|
||||||
setJsonInputValue(newSchemaInput, configJson);
|
setJsonInputValue(newSchemaInput, configJson);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public List<SelenideElement> getAllDetailsPageElements() {
|
public List<SelenideElement> getAllDetailsPageElements() {
|
||||||
return Stream.of(compatibilityLevelList, newSchemaTextArea, latestSchemaTextArea, submitBtn, schemaTypeDdl)
|
return Stream.of(compatibilityLevelList, newSchemaTextArea, latestSchemaTextArea, submitBtn, schemaTypeDdl)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isSubmitBtnEnabled() {
|
public boolean isSubmitBtnEnabled() {
|
||||||
return isEnabled(submitBtn);
|
return isEnabled(submitBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isSchemaDropDownEnabled() {
|
public boolean isSchemaDropDownEnabled() {
|
||||||
boolean enabled = true;
|
boolean enabled = true;
|
||||||
try {
|
try {
|
||||||
String attribute = schemaTypeDdl.getAttribute("disabled");
|
String attribute = schemaTypeDdl.getAttribute("disabled");
|
||||||
enabled = false;
|
enabled = false;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
|
||||||
return enabled;
|
|
||||||
}
|
}
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +1,69 @@
|
||||||
package com.provectus.kafka.ui.pages.schemas;
|
package com.provectus.kafka.ui.pages.schemas;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
public class SchemaDetails extends BasePage {
|
public class SchemaDetails extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement actualVersionTextArea = $x("//div[@id='schema']");
|
protected SelenideElement actualVersionTextArea = $x("//div[@id='schema']");
|
||||||
protected SelenideElement compatibilityField = $x("//h4[contains(text(),'Compatibility')]/../p");
|
protected SelenideElement compatibilityField = $x("//h4[contains(text(),'Compatibility')]/../p");
|
||||||
protected SelenideElement editSchemaBtn = $x("//button[contains(text(),'Edit Schema')]");
|
protected SelenideElement editSchemaBtn = $x("//button[contains(text(),'Edit Schema')]");
|
||||||
protected SelenideElement removeBtn = $x("//*[contains(text(),'Remove')]");
|
protected SelenideElement removeBtn = $x("//*[contains(text(),'Remove')]");
|
||||||
protected SelenideElement confirmBtn = $x("//div[@role='dialog']//button[contains(text(),'Confirm')]");
|
protected SelenideElement confirmBtn = $x("//div[@role='dialog']//button[contains(text(),'Confirm')]");
|
||||||
protected SelenideElement schemaTypeField = $x("//h4[contains(text(),'Type')]/../p");
|
protected SelenideElement schemaTypeField = $x("//h4[contains(text(),'Type')]/../p");
|
||||||
protected SelenideElement latestVersionField = $x("//h4[contains(text(),'Latest version')]/../p");
|
protected SelenideElement latestVersionField = $x("//h4[contains(text(),'Latest version')]/../p");
|
||||||
protected SelenideElement compareVersionBtn = $x("//button[text()='Compare Versions']");
|
protected SelenideElement compareVersionBtn = $x("//button[text()='Compare Versions']");
|
||||||
protected String schemaHeaderLocator = "//h1[contains(text(),'%s')]";
|
protected String schemaHeaderLocator = "//h1[contains(text(),'%s')]";
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaDetails waitUntilScreenReady() {
|
public SchemaDetails waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
actualVersionTextArea.shouldBe(Condition.visible);
|
actualVersionTextArea.shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public String getCompatibility() {
|
public String getCompatibility() {
|
||||||
return compatibilityField.getText();
|
return compatibilityField.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isSchemaHeaderVisible(String schemaName) {
|
public boolean isSchemaHeaderVisible(String schemaName) {
|
||||||
return isVisible($x(String.format(schemaHeaderLocator, schemaName)));
|
return isVisible($x(String.format(schemaHeaderLocator, schemaName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public int getLatestVersion() {
|
public int getLatestVersion() {
|
||||||
return Integer.parseInt(latestVersionField.getText());
|
return Integer.parseInt(latestVersionField.getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public String getSchemaType() {
|
public String getSchemaType() {
|
||||||
return schemaTypeField.getText();
|
return schemaTypeField.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaDetails openEditSchema() {
|
public SchemaDetails openEditSchema() {
|
||||||
editSchemaBtn.shouldBe(Condition.visible).click();
|
editSchemaBtn.shouldBe(Condition.visible).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaDetails openCompareVersionMenu() {
|
public SchemaDetails openCompareVersionMenu() {
|
||||||
compareVersionBtn.shouldBe(Condition.enabled).click();
|
compareVersionBtn.shouldBe(Condition.enabled).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaDetails removeSchema() {
|
public SchemaDetails removeSchema() {
|
||||||
clickByJavaScript(dotMenuBtn);
|
clickByJavaScript(dotMenuBtn);
|
||||||
removeBtn.shouldBe(Condition.enabled).click();
|
removeBtn.shouldBe(Condition.enabled).click();
|
||||||
confirmBtn.shouldBe(Condition.visible).click();
|
confirmBtn.shouldBe(Condition.visible).click();
|
||||||
confirmBtn.shouldBe(Condition.disappear);
|
confirmBtn.shouldBe(Condition.disappear);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,42 @@
|
||||||
package com.provectus.kafka.ui.pages.schemas;
|
package com.provectus.kafka.ui.pages.schemas;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.SCHEMA_REGISTRY;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.SCHEMA_REGISTRY;
|
|
||||||
|
|
||||||
public class SchemaRegistryList extends BasePage {
|
public class SchemaRegistryList extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement createSchemaBtn = $x("//button[contains(text(),'Create Schema')]");
|
protected SelenideElement createSchemaBtn = $x("//button[contains(text(),'Create Schema')]");
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaRegistryList waitUntilScreenReady() {
|
public SchemaRegistryList waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
getPageTitleFromHeader(SCHEMA_REGISTRY).shouldBe(Condition.visible);
|
getPageTitleFromHeader(SCHEMA_REGISTRY).shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaRegistryList clickCreateSchema() {
|
public SchemaRegistryList clickCreateSchema() {
|
||||||
clickByJavaScript(createSchemaBtn);
|
clickByJavaScript(createSchemaBtn);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public SchemaRegistryList openSchema(String schemaName) {
|
public SchemaRegistryList openSchema(String schemaName) {
|
||||||
getTableElement(schemaName)
|
getTableElement(schemaName)
|
||||||
.shouldBe(Condition.enabled).click();
|
.shouldBe(Condition.enabled).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isSchemaVisible(String schemaName) {
|
public boolean isSchemaVisible(String schemaName) {
|
||||||
tableGrid.shouldBe(Condition.visible);
|
tableGrid.shouldBe(Condition.visible);
|
||||||
return isVisible(getTableElement(schemaName));
|
return isVisible(getTableElement(schemaName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,57 +1,56 @@
|
||||||
package com.provectus.kafka.ui.pages.topics;
|
package com.provectus.kafka.ui.pages.topics;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static com.codeborne.selenide.Selenide.refresh;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
import static com.codeborne.selenide.Selenide.refresh;
|
|
||||||
|
|
||||||
public class ProduceMessagePanel extends BasePage {
|
public class ProduceMessagePanel extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement keyTextArea = $x("//div[@id='key']/textarea");
|
protected SelenideElement keyTextArea = $x("//div[@id='key']/textarea");
|
||||||
protected SelenideElement contentTextArea = $x("//div[@id='content']/textarea");
|
protected SelenideElement valueTextArea = $x("//div[@id='content']/textarea");
|
||||||
protected SelenideElement headersTextArea = $x("//div[@id='headers']/textarea");
|
protected SelenideElement headersTextArea = $x("//div[@id='headers']/textarea");
|
||||||
protected SelenideElement submitBtn = headersTextArea.$x("../../../..//button[@type='submit']");
|
protected SelenideElement submitBtn = headersTextArea.$x("../../../..//button[@type='submit']");
|
||||||
protected SelenideElement partitionDdl = $x("//ul[@name='partition']");
|
protected SelenideElement partitionDdl = $x("//ul[@name='partition']");
|
||||||
protected SelenideElement keySerdeDdl = $x("//ul[@name='keySerde']");
|
protected SelenideElement keySerdeDdl = $x("//ul[@name='keySerde']");
|
||||||
protected SelenideElement contentSerdeDdl = $x("//ul[@name='valueSerde']");
|
protected SelenideElement contentSerdeDdl = $x("//ul[@name='valueSerde']");
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ProduceMessagePanel waitUntilScreenReady() {
|
public ProduceMessagePanel waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
Arrays.asList(partitionDdl, keySerdeDdl, contentSerdeDdl).forEach(element -> element.shouldBe(Condition.visible));
|
Arrays.asList(partitionDdl, keySerdeDdl, contentSerdeDdl).forEach(element -> element.shouldBe(Condition.visible));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ProduceMessagePanel setKeyField(String value) {
|
public ProduceMessagePanel setKeyField(String value) {
|
||||||
clearByKeyboard(keyTextArea);
|
clearByKeyboard(keyTextArea);
|
||||||
keyTextArea.setValue(value);
|
keyTextArea.setValue(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ProduceMessagePanel setContentFiled(String value) {
|
public ProduceMessagePanel setValueFiled(String value) {
|
||||||
clearByKeyboard(contentTextArea);
|
clearByKeyboard(valueTextArea);
|
||||||
contentTextArea.setValue(value);
|
valueTextArea.setValue(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ProduceMessagePanel setHeaderFiled(String value) {
|
public ProduceMessagePanel setHeadersFld(String value) {
|
||||||
headersTextArea.setValue(value);
|
headersTextArea.setValue(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ProduceMessagePanel submitProduceMessage() {
|
public ProduceMessagePanel submitProduceMessage() {
|
||||||
clickByActions(submitBtn);
|
clickByActions(submitBtn);
|
||||||
submitBtn.shouldBe(Condition.disappear);
|
submitBtn.shouldBe(Condition.disappear);
|
||||||
refresh();
|
refresh();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
package com.provectus.kafka.ui.pages.topics;
|
package com.provectus.kafka.ui.pages.topics;
|
||||||
|
|
||||||
import com.codeborne.selenide.*;
|
import static com.codeborne.selenide.Selenide.$;
|
||||||
|
import static com.codeborne.selenide.Selenide.$$;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static org.openqa.selenium.By.id;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.ClickOptions;
|
||||||
|
import com.codeborne.selenide.CollectionCondition;
|
||||||
|
import com.codeborne.selenide.Condition;
|
||||||
|
import com.codeborne.selenide.ElementsCollection;
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import com.provectus.kafka.ui.pages.topics.enums.CleanupPolicyValue;
|
import com.provectus.kafka.ui.pages.topics.enums.CleanupPolicyValue;
|
||||||
import com.provectus.kafka.ui.pages.topics.enums.CustomParameterType;
|
import com.provectus.kafka.ui.pages.topics.enums.CustomParameterType;
|
||||||
|
@ -8,269 +17,264 @@ import com.provectus.kafka.ui.pages.topics.enums.MaxSizeOnDisk;
|
||||||
import com.provectus.kafka.ui.pages.topics.enums.TimeToRetain;
|
import com.provectus.kafka.ui.pages.topics.enums.TimeToRetain;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.*;
|
|
||||||
|
|
||||||
public class TopicCreateEditForm extends BasePage {
|
public class TopicCreateEditForm extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement timeToRetainField = $x("//input[@id='timeToRetain']");
|
protected SelenideElement timeToRetainField = $x("//input[@id='timeToRetain']");
|
||||||
protected SelenideElement partitionsField = $x("//input[@name='partitions']");
|
protected SelenideElement partitionsField = $x("//input[@name='partitions']");
|
||||||
protected SelenideElement nameField = $x("//input[@name='name']");
|
protected SelenideElement nameField = $(id("topicFormName"));
|
||||||
protected SelenideElement maxMessageBytesField = $x("//input[@name='maxMessageBytes']");
|
protected SelenideElement maxMessageBytesField = $x("//input[@name='maxMessageBytes']");
|
||||||
protected SelenideElement minInSyncReplicasField = $x("//input[@name='minInSyncReplicas']");
|
protected SelenideElement minInSyncReplicasField = $x("//input[@name='minInSyncReplicas']");
|
||||||
protected SelenideElement cleanUpPolicyDdl = $x("//ul[@id='topicFormCleanupPolicy']");
|
protected SelenideElement cleanUpPolicyDdl = $x("//ul[@id='topicFormCleanupPolicy']");
|
||||||
protected SelenideElement maxSizeOnDiscDdl = $x("//ul[@id='topicFormRetentionBytes']");
|
protected SelenideElement maxSizeOnDiscDdl = $x("//ul[@id='topicFormRetentionBytes']");
|
||||||
protected SelenideElement customParameterDdl = $x("//ul[contains(@name,'customParams')]");
|
protected SelenideElement customParameterDdl = $x("//ul[contains(@name,'customParams')]");
|
||||||
protected SelenideElement deleteCustomParameterBtn = $x("//span[contains(@title,'Delete customParam')]");
|
protected SelenideElement deleteCustomParameterBtn = $x("//span[contains(@title,'Delete customParam')]");
|
||||||
protected SelenideElement addCustomParameterTypeBtn = $x("//button[contains(text(),'Add Custom Parameter')]");
|
protected SelenideElement addCustomParameterTypeBtn = $x("//button[contains(text(),'Add Custom Parameter')]");
|
||||||
protected SelenideElement customParameterValueField = $x("//input[@placeholder='Value']");
|
protected SelenideElement customParameterValueField = $x("//input[@placeholder='Value']");
|
||||||
protected SelenideElement validationCustomParameterValueMsg = $x("//p[contains(text(),'Value is required')]");
|
protected SelenideElement validationCustomParameterValueMsg = $x("//p[contains(text(),'Value is required')]");
|
||||||
protected String ddlElementLocator = "//li[@value='%s']";
|
protected String ddlElementLocator = "//li[@value='%s']";
|
||||||
protected String btnTimeToRetainLocator = "//button[@class][text()='%s']";
|
protected String btnTimeToRetainLocator = "//button[@class][text()='%s']";
|
||||||
|
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicCreateEditForm waitUntilScreenReady() {
|
public TopicCreateEditForm waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
nameField.shouldBe(Condition.visible);
|
nameField.shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCreateTopicButtonEnabled() {
|
||||||
|
return isEnabled(submitBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleteCustomParameterButtonEnabled() {
|
||||||
|
return isEnabled(deleteCustomParameterBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNameFieldEnabled() {
|
||||||
|
return isEnabled(nameField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setTopicName(String topicName) {
|
||||||
|
sendKeysAfterClear(nameField, topicName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setMinInsyncReplicas(Integer minInsyncReplicas) {
|
||||||
|
minInSyncReplicasField.setValue(minInsyncReplicas.toString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setTimeToRetainDataInMs(Long ms) {
|
||||||
|
timeToRetainField.setValue(ms.toString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setTimeToRetainDataInMs(String ms) {
|
||||||
|
timeToRetainField.setValue(ms);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setMaxSizeOnDiskInGB(MaxSizeOnDisk maxSizeOnDisk) {
|
||||||
|
maxSizeOnDiscDdl.shouldBe(Condition.visible).click();
|
||||||
|
$x(String.format(ddlElementLocator, maxSizeOnDisk.getOptionValue())).shouldBe(Condition.visible).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm clickAddCustomParameterTypeButton() {
|
||||||
|
addCustomParameterTypeBtn.click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm openCustomParameterTypeDdl() {
|
||||||
|
customParameterDdl.shouldBe(Condition.visible).click();
|
||||||
|
ddlOptions.shouldHave(CollectionCondition.sizeGreaterThan(0));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public ElementsCollection getAllDdlOptions() {
|
||||||
|
return getDdlOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setCustomParameterType(CustomParameterType customParameterType) {
|
||||||
|
openCustomParameterTypeDdl();
|
||||||
|
$x(String.format(ddlElementLocator, customParameterType.getOptionValue())).shouldBe(Condition.visible).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm clearCustomParameterValue() {
|
||||||
|
clearByKeyboard(customParameterValueField);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setNumberOfPartitions(int partitions) {
|
||||||
|
partitionsField.shouldBe(Condition.enabled).clear();
|
||||||
|
partitionsField.sendKeys(String.valueOf(partitions));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setTimeToRetainDataByButtons(TimeToRetain timeToRetain) {
|
||||||
|
$x(String.format(btnTimeToRetainLocator, timeToRetain.getButton())).shouldBe(Condition.enabled).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm selectCleanupPolicy(CleanupPolicyValue cleanupPolicyOptionValue) {
|
||||||
|
cleanUpPolicyDdl.shouldBe(Condition.visible).click();
|
||||||
|
$x(String.format(ddlElementLocator, cleanupPolicyOptionValue.getOptionValue())).shouldBe(Condition.visible).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm selectRetentionBytes(String visibleValue) {
|
||||||
|
return selectFromDropDownByVisibleText("retentionBytes", visibleValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm selectRetentionBytes(Long optionValue) {
|
||||||
|
return selectFromDropDownByOptionValue("retentionBytes", optionValue.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm clickSaveTopicBtn() {
|
||||||
|
clickSubmitBtn();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm addCustomParameter(String customParameterName,
|
||||||
|
String customParameterValue) {
|
||||||
|
ElementsCollection customParametersElements =
|
||||||
|
$$("ul[role=listbox][name^=customParams][name$=name]");
|
||||||
|
KafkaUiSelectElement kafkaUiSelectElement = null;
|
||||||
|
if (customParametersElements.size() == 1) {
|
||||||
|
if ("Select".equals(customParametersElements.first().getText())) {
|
||||||
|
kafkaUiSelectElement = new KafkaUiSelectElement(customParametersElements.first());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$$("button")
|
||||||
|
.find(Condition.exactText("Add Custom Parameter"))
|
||||||
|
.click();
|
||||||
|
customParametersElements = $$("ul[role=listbox][name^=customParams][name$=name]");
|
||||||
|
kafkaUiSelectElement = new KafkaUiSelectElement(customParametersElements.last());
|
||||||
|
}
|
||||||
|
if (kafkaUiSelectElement != null) {
|
||||||
|
kafkaUiSelectElement.selectByVisibleText(customParameterName);
|
||||||
|
}
|
||||||
|
$(String.format("input[name=\"customParams.%d.value\"]", customParametersElements.size() - 1))
|
||||||
|
.setValue(customParameterValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm updateCustomParameter(String customParameterName,
|
||||||
|
String customParameterValue) {
|
||||||
|
SelenideElement selenideElement = $$("ul[role=listbox][name^=customParams][name$=name]")
|
||||||
|
.find(Condition.exactText(customParameterName));
|
||||||
|
String name = selenideElement.getAttribute("name");
|
||||||
|
if (name != null) {
|
||||||
|
name = name.substring(0, name.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
$(String.format("input[name^=%s]", name)).setValue(customParameterValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getCleanupPolicy() {
|
||||||
|
return new KafkaUiSelectElement("cleanupPolicy").getCurrentValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getTimeToRetain() {
|
||||||
|
return timeToRetainField.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getMaxSizeOnDisk() {
|
||||||
|
return new KafkaUiSelectElement("retentionBytes").getCurrentValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getMaxMessageBytes() {
|
||||||
|
return maxMessageBytesField.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setMaxMessageBytes(Long bytes) {
|
||||||
|
maxMessageBytesField.setValue(bytes.toString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicCreateEditForm setMaxMessageBytes(String bytes) {
|
||||||
|
return setMaxMessageBytes(Long.parseLong(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isValidationMessageCustomParameterValueVisible() {
|
||||||
|
return isVisible(validationCustomParameterValueMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getCustomParameterValue() {
|
||||||
|
return customParameterValueField.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TopicCreateEditForm selectFromDropDownByOptionValue(String dropDownElementName,
|
||||||
|
String optionValue) {
|
||||||
|
KafkaUiSelectElement select = new KafkaUiSelectElement(dropDownElementName);
|
||||||
|
select.selectByOptionValue(optionValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TopicCreateEditForm selectFromDropDownByVisibleText(String dropDownElementName,
|
||||||
|
String visibleText) {
|
||||||
|
KafkaUiSelectElement select = new KafkaUiSelectElement(dropDownElementName);
|
||||||
|
select.selectByVisibleText(visibleText);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class KafkaUiSelectElement {
|
||||||
|
|
||||||
|
private final SelenideElement selectElement;
|
||||||
|
|
||||||
|
public KafkaUiSelectElement(String selectElementName) {
|
||||||
|
this.selectElement = $("ul[role=listbox][name=" + selectElementName + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCreateTopicButtonEnabled() {
|
public KafkaUiSelectElement(SelenideElement selectElement) {
|
||||||
return isEnabled(submitBtn);
|
this.selectElement = selectElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDeleteCustomParameterButtonEnabled() {
|
public void selectByOptionValue(String optionValue) {
|
||||||
return isEnabled(deleteCustomParameterBtn);
|
selectElement.click();
|
||||||
|
selectElement
|
||||||
|
.$$x(".//ul/li[@role='option']")
|
||||||
|
.find(Condition.attribute("value", optionValue))
|
||||||
|
.click(ClickOptions.usingJavaScript());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNameFieldEnabled() {
|
public void selectByVisibleText(String visibleText) {
|
||||||
return isEnabled(nameField);
|
selectElement.click();
|
||||||
|
selectElement
|
||||||
|
.$$("ul>li[role=option]")
|
||||||
|
.find(Condition.exactText(visibleText))
|
||||||
|
.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
public String getCurrentValue() {
|
||||||
public TopicCreateEditForm setTopicName(String topicName) {
|
return selectElement.$("li").getText();
|
||||||
nameField.shouldBe(Condition.enabled).clear();
|
|
||||||
if (topicName != null) {
|
|
||||||
nameField.sendKeys(topicName);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setMinInsyncReplicas(Integer minInsyncReplicas) {
|
|
||||||
minInSyncReplicasField.setValue(minInsyncReplicas.toString());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setTimeToRetainDataInMs(Long ms) {
|
|
||||||
timeToRetainField.setValue(ms.toString());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setTimeToRetainDataInMs(String ms) {
|
|
||||||
timeToRetainField.setValue(ms);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setMaxSizeOnDiskInGB(MaxSizeOnDisk MaxSizeOnDisk) {
|
|
||||||
maxSizeOnDiscDdl.shouldBe(Condition.visible).click();
|
|
||||||
$x(String.format(ddlElementLocator, MaxSizeOnDisk.getOptionValue())).shouldBe(Condition.visible).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm clickAddCustomParameterTypeButton() {
|
|
||||||
addCustomParameterTypeBtn.click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm openCustomParameterTypeDdl() {
|
|
||||||
customParameterDdl.shouldBe(Condition.visible).click();
|
|
||||||
ddlOptions.shouldHave(CollectionCondition.sizeGreaterThan(0));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public ElementsCollection getAllDdlOptions() {
|
|
||||||
return getDdlOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setCustomParameterType(CustomParameterType customParameterType) {
|
|
||||||
openCustomParameterTypeDdl();
|
|
||||||
$x(String.format(ddlElementLocator, customParameterType.getOptionValue())).shouldBe(Condition.visible).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm clearCustomParameterValue() {
|
|
||||||
clearByKeyboard(customParameterValueField);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setNumberOfPartitions(int partitions) {
|
|
||||||
partitionsField.shouldBe(Condition.enabled).clear();
|
|
||||||
partitionsField.sendKeys(String.valueOf(partitions));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setTimeToRetainDataByButtons(TimeToRetain timeToRetain) {
|
|
||||||
$x(String.format(btnTimeToRetainLocator, timeToRetain.getButton())).shouldBe(Condition.enabled).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm selectCleanupPolicy(CleanupPolicyValue cleanupPolicyOptionValue) {
|
|
||||||
cleanUpPolicyDdl.shouldBe(Condition.visible).click();
|
|
||||||
$x(String.format(ddlElementLocator, cleanupPolicyOptionValue.getOptionValue())).shouldBe(Condition.visible).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm selectRetentionBytes(String visibleValue) {
|
|
||||||
return selectFromDropDownByVisibleText("retentionBytes", visibleValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm selectRetentionBytes(Long optionValue) {
|
|
||||||
return selectFromDropDownByOptionValue("retentionBytes", optionValue.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm clickCreateTopicBtn() {
|
|
||||||
clickSubmitBtn();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm addCustomParameter(String customParameterName,
|
|
||||||
String customParameterValue) {
|
|
||||||
ElementsCollection customParametersElements =
|
|
||||||
$$("ul[role=listbox][name^=customParams][name$=name]");
|
|
||||||
KafkaUISelectElement kafkaUISelectElement = null;
|
|
||||||
if (customParametersElements.size() == 1) {
|
|
||||||
if ("Select".equals(customParametersElements.first().getText())) {
|
|
||||||
kafkaUISelectElement = new KafkaUISelectElement(customParametersElements.first());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$$("button")
|
|
||||||
.find(Condition.exactText("Add Custom Parameter"))
|
|
||||||
.click();
|
|
||||||
customParametersElements = $$("ul[role=listbox][name^=customParams][name$=name]");
|
|
||||||
kafkaUISelectElement = new KafkaUISelectElement(customParametersElements.last());
|
|
||||||
}
|
|
||||||
if (kafkaUISelectElement != null) {
|
|
||||||
kafkaUISelectElement.selectByVisibleText(customParameterName);
|
|
||||||
}
|
|
||||||
$(String.format("input[name=\"customParams.%d.value\"]", customParametersElements.size() - 1))
|
|
||||||
.setValue(customParameterValue);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm updateCustomParameter(String customParameterName,
|
|
||||||
String customParameterValue) {
|
|
||||||
SelenideElement selenideElement = $$("ul[role=listbox][name^=customParams][name$=name]")
|
|
||||||
.find(Condition.exactText(customParameterName));
|
|
||||||
String name = selenideElement.getAttribute("name");
|
|
||||||
if (name != null) {
|
|
||||||
name = name.substring(0, name.lastIndexOf("."));
|
|
||||||
}
|
|
||||||
$(String.format("input[name^=%s]", name)).setValue(customParameterValue);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getCleanupPolicy() {
|
|
||||||
return new KafkaUISelectElement("cleanupPolicy").getCurrentValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getTimeToRetain() {
|
|
||||||
return timeToRetainField.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getMaxSizeOnDisk() {
|
|
||||||
return new KafkaUISelectElement("retentionBytes").getCurrentValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getMaxMessageBytes() {
|
|
||||||
return maxMessageBytesField.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setMaxMessageBytes(Long bytes) {
|
|
||||||
maxMessageBytesField.setValue(bytes.toString());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicCreateEditForm setMaxMessageBytes(String bytes) {
|
|
||||||
return setMaxMessageBytes(Long.parseLong(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isValidationMessageCustomParameterValueVisible() {
|
|
||||||
return isVisible(validationCustomParameterValueMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getCustomParameterValue() {
|
|
||||||
return customParameterValueField.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TopicCreateEditForm selectFromDropDownByOptionValue(String dropDownElementName,
|
|
||||||
String optionValue) {
|
|
||||||
KafkaUISelectElement select = new KafkaUISelectElement(dropDownElementName);
|
|
||||||
select.selectByOptionValue(optionValue);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TopicCreateEditForm selectFromDropDownByVisibleText(String dropDownElementName,
|
|
||||||
String visibleText) {
|
|
||||||
KafkaUISelectElement select = new KafkaUISelectElement(dropDownElementName);
|
|
||||||
select.selectByVisibleText(visibleText);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class KafkaUISelectElement {
|
|
||||||
|
|
||||||
private final SelenideElement selectElement;
|
|
||||||
|
|
||||||
public KafkaUISelectElement(String selectElementName) {
|
|
||||||
this.selectElement = $("ul[role=listbox][name=" + selectElementName + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
public KafkaUISelectElement(SelenideElement selectElement) {
|
|
||||||
this.selectElement = selectElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectByOptionValue(String optionValue) {
|
|
||||||
selectElement.click();
|
|
||||||
selectElement
|
|
||||||
.$$x(".//ul/li[@role='option']")
|
|
||||||
.find(Condition.attribute("value", optionValue))
|
|
||||||
.click(ClickOptions.usingJavaScript());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectByVisibleText(String visibleText) {
|
|
||||||
selectElement.click();
|
|
||||||
selectElement
|
|
||||||
.$$("ul>li[role=option]")
|
|
||||||
.find(Condition.exactText(visibleText))
|
|
||||||
.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrentValue() {
|
|
||||||
return selectElement.$("li").getText();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,463 +1,475 @@
|
||||||
package com.provectus.kafka.ui.pages.topics;
|
package com.provectus.kafka.ui.pages.topics;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$$x;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static com.codeborne.selenide.Selenide.sleep;
|
||||||
|
import static com.provectus.kafka.ui.pages.topics.TopicDetails.TopicMenu.OVERVIEW;
|
||||||
|
import static org.testcontainers.shaded.org.apache.commons.lang3.RandomUtils.nextInt;
|
||||||
|
|
||||||
import com.codeborne.selenide.CollectionCondition;
|
import com.codeborne.selenide.CollectionCondition;
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.ElementsCollection;
|
import com.codeborne.selenide.ElementsCollection;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
import org.openqa.selenium.By;
|
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.time.YearMonth;
|
import java.time.YearMonth;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeFormatterBuilder;
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import static com.codeborne.selenide.Selenide.*;
|
import java.util.Collections;
|
||||||
import static org.testcontainers.shaded.org.apache.commons.lang3.RandomUtils.nextInt;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class TopicDetails extends BasePage {
|
public class TopicDetails extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement clearMessagesBtn = $x(("//div[contains(text(), 'Clear messages')]"));
|
protected SelenideElement clearMessagesBtn = $x(("//div[contains(text(), 'Clear messages')]"));
|
||||||
protected SelenideElement recreateTopicBtn = $x("//div[text()='Recreate Topic']");
|
protected SelenideElement recreateTopicBtn = $x("//div[text()='Recreate Topic']");
|
||||||
protected SelenideElement messageAmountCell = $x("//tbody/tr/td[5]");
|
protected SelenideElement messageAmountCell = $x("//tbody/tr/td[5]");
|
||||||
protected SelenideElement overviewTab = $x("//a[contains(text(),'Overview')]");
|
protected SelenideElement overviewTab = $x("//a[contains(text(),'Overview')]");
|
||||||
protected SelenideElement messagesTab = $x("//a[contains(text(),'Messages')]");
|
protected SelenideElement messagesTab = $x("//a[contains(text(),'Messages')]");
|
||||||
protected SelenideElement seekTypeDdl = $x("//ul[@id='selectSeekType']/li");
|
protected SelenideElement seekTypeDdl = $x("//ul[@id='selectSeekType']//li");
|
||||||
protected SelenideElement seekTypeField = $x("//label[text()='Seek Type']//..//div/input");
|
protected SelenideElement seekTypeField = $x("//label[text()='Seek Type']//..//div/input");
|
||||||
protected SelenideElement addFiltersBtn = $x("//button[text()='Add Filters']");
|
protected SelenideElement addFiltersBtn = $x("//button[text()='Add Filters']");
|
||||||
protected SelenideElement savedFiltersLink = $x("//div[text()='Saved Filters']");
|
protected SelenideElement savedFiltersLink = $x("//div[text()='Saved Filters']");
|
||||||
protected SelenideElement addFilterCodeModalTitle = $x("//label[text()='Filter code']");
|
protected SelenideElement addFilterCodeModalTitle = $x("//label[text()='Filter code']");
|
||||||
protected SelenideElement addFilterCodeInput = $x("//div[@id='ace-editor']//textarea");
|
protected SelenideElement addFilterCodeInput = $x("//div[@id='ace-editor']//textarea");
|
||||||
protected SelenideElement saveThisFilterCheckBoxAddFilterMdl = $x("//input[@name='saveFilter']");
|
protected SelenideElement saveThisFilterCheckBoxAddFilterMdl = $x("//input[@name='saveFilter']");
|
||||||
protected SelenideElement displayNameInputAddFilterMdl = $x("//input[@placeholder='Enter Name']");
|
protected SelenideElement displayNameInputAddFilterMdl = $x("//input[@placeholder='Enter Name']");
|
||||||
protected SelenideElement cancelBtnAddFilterMdl = $x("//button[text()='Cancel']");
|
protected SelenideElement cancelBtnAddFilterMdl = $x("//button[text()='Cancel']");
|
||||||
protected SelenideElement addFilterBtnAddFilterMdl = $x("//button[text()='Add filter']");
|
protected SelenideElement addFilterBtnAddFilterMdl = $x("//button[text()='Add filter']");
|
||||||
protected SelenideElement addFiltersBtnMessages = $x("//button[text()='Add Filters']");
|
protected SelenideElement addFiltersBtnMessages = $x("//button[text()='Add Filters']");
|
||||||
protected SelenideElement selectFilterBtnAddFilterMdl = $x("//button[text()='Select filter']");
|
protected SelenideElement selectFilterBtnAddFilterMdl = $x("//button[text()='Select filter']");
|
||||||
protected SelenideElement editSettingsMenu = $x("//li[@role][contains(text(),'Edit settings')]");
|
protected SelenideElement editSettingsMenu = $x("//li[@role][contains(text(),'Edit settings')]");
|
||||||
protected SelenideElement removeTopicBtn = $x("//ul[@role='menu']//div[contains(text(),'Remove Topic')]");
|
protected SelenideElement removeTopicBtn = $x("//ul[@role='menu']//div[contains(text(),'Remove Topic')]");
|
||||||
protected SelenideElement produceMessageBtn = $x("//div//button[text()='Produce Message']");
|
protected SelenideElement produceMessageBtn = $x("//div//button[text()='Produce Message']");
|
||||||
protected SelenideElement contentMessageTab = $x("//html//div[@id='root']/div/main//table//p");
|
protected SelenideElement contentMessageTab = $x("//html//div[@id='root']/div/main//table//p");
|
||||||
protected SelenideElement cleanUpPolicyField = $x("//div[contains(text(),'Clean Up Policy')]/../span/*");
|
protected SelenideElement cleanUpPolicyField = $x("//div[contains(text(),'Clean Up Policy')]/../span/*");
|
||||||
protected SelenideElement partitionsField = $x("//div[contains(text(),'Partitions')]/../span");
|
protected SelenideElement partitionsField = $x("//div[contains(text(),'Partitions')]/../span");
|
||||||
protected SelenideElement backToCreateFiltersLink = $x("//div[text()='Back To create filters']");
|
protected SelenideElement backToCreateFiltersLink = $x("//div[text()='Back To create filters']");
|
||||||
protected ElementsCollection messageGridItems = $$x("//tbody//tr");
|
protected ElementsCollection messageGridItems = $$x("//tbody//tr");
|
||||||
protected SelenideElement actualCalendarDate = $x("//div[@class='react-datepicker__current-month']");
|
protected SelenideElement actualCalendarDate = $x("//div[@class='react-datepicker__current-month']");
|
||||||
protected SelenideElement previousMonthButton = $x("//button[@aria-label='Previous Month']");
|
protected SelenideElement previousMonthButton = $x("//button[@aria-label='Previous Month']");
|
||||||
protected SelenideElement nextMonthButton = $x("//button[@aria-label='Next Month']");
|
protected SelenideElement nextMonthButton = $x("//button[@aria-label='Next Month']");
|
||||||
protected SelenideElement calendarTimeFld = $x("//input[@placeholder='Time']");
|
protected SelenideElement calendarTimeFld = $x("//input[@placeholder='Time']");
|
||||||
protected String dayCellLtr = "//div[@role='option'][contains(text(),'%d')]";
|
protected String detailsTabLtr = "//nav//a[contains(text(),'%s')]";
|
||||||
protected String seekFilterDdlLocator = "//ul[@id='selectSeekType']/ul/li[text()='%s']";
|
protected String dayCellLtr = "//div[@role='option'][contains(text(),'%d')]";
|
||||||
protected String savedFilterNameLocator = "//div[@role='savedFilter']/div[contains(text(),'%s')]";
|
protected String seekFilterDdlLocator = "//ul[@id='selectSeekType']/ul/li[text()='%s']";
|
||||||
protected String consumerIdLocator = "//a[@title='%s']";
|
protected String savedFilterNameLocator = "//div[@role='savedFilter']/div[contains(text(),'%s')]";
|
||||||
protected String topicHeaderLocator = "//h1[contains(text(),'%s')]";
|
protected String consumerIdLocator = "//a[@title='%s']";
|
||||||
protected String activeFilterNameLocator = "//div[@data-testid='activeSmartFilter'][contains(text(),'%s')]";
|
protected String topicHeaderLocator = "//h1[contains(text(),'%s')]";
|
||||||
protected String settingsGridValueLocator = "//tbody/tr/td/span[text()='%s']//ancestor::tr/td[2]/span";
|
protected String activeFilterNameLocator = "//div[@data-testid='activeSmartFilter'][contains(text(),'%s')]";
|
||||||
|
protected String settingsGridValueLocator = "//tbody/tr/td/span[text()='%s']//ancestor::tr/td[2]/span";
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicDetails waitUntilScreenReady() {
|
public TopicDetails waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
overviewTab.shouldBe(Condition.visible);
|
$x(String.format(detailsTabLtr, OVERVIEW)).shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails openDetailsTab(TopicMenu menu) {
|
||||||
|
$x(String.format(detailsTabLtr, menu.toString())).shouldBe(Condition.enabled).click();
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getSettingsGridValueByKey(String key) {
|
||||||
|
return $x(String.format(settingsGridValueLocator, key)).scrollTo().shouldBe(Condition.visible).getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails openDotMenu() {
|
||||||
|
clickByJavaScript(dotMenuBtn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
|
||||||
|
return isAlertVisible(header, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickEditSettingsMenu() {
|
||||||
|
editSettingsMenu.shouldBe(Condition.visible).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isConfirmationMdlVisible() {
|
||||||
|
return isConfirmationModalVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickClearMessagesMenu() {
|
||||||
|
clearMessagesBtn.shouldBe(Condition.visible).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isClearMessagesMenuEnabled() {
|
||||||
|
return !Objects.requireNonNull(clearMessagesBtn.shouldBe(Condition.visible)
|
||||||
|
.$x("./..").getAttribute("class"))
|
||||||
|
.contains("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickRecreateTopicMenu() {
|
||||||
|
recreateTopicBtn.shouldBe(Condition.visible).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getCleanUpPolicy() {
|
||||||
|
return cleanUpPolicyField.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public int getPartitions() {
|
||||||
|
return Integer.parseInt(partitionsField.getText().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isTopicHeaderVisible(String topicName) {
|
||||||
|
return isVisible($x(String.format(topicHeaderLocator, topicName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickDeleteTopicMenu() {
|
||||||
|
removeTopicBtn.shouldBe(Condition.visible).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickConfirmBtnMdl() {
|
||||||
|
clickConfirmButton();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickProduceMessageBtn() {
|
||||||
|
clickByJavaScript(produceMessageBtn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails selectSeekTypeDdlMessagesTab(String seekTypeName) {
|
||||||
|
seekTypeDdl.shouldBe(Condition.enabled).click();
|
||||||
|
$x(String.format(seekFilterDdlLocator, seekTypeName)).shouldBe(Condition.visible).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails setSeekTypeValueFldMessagesTab(String seekTypeValue) {
|
||||||
|
seekTypeField.shouldBe(Condition.enabled).sendKeys(seekTypeValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickSubmitFiltersBtnMessagesTab() {
|
||||||
|
clickByJavaScript(submitBtn);
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickMessagesAddFiltersBtn() {
|
||||||
|
addFiltersBtn.shouldBe(Condition.enabled).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickNextButton() {
|
||||||
|
nextBtn.shouldBe(Condition.enabled).click();
|
||||||
|
waitUntilSpinnerDisappear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails openSavedFiltersListMdl() {
|
||||||
|
savedFiltersLink.shouldBe(Condition.enabled).click();
|
||||||
|
backToCreateFiltersLink.shouldBe(Condition.visible);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isFilterVisibleAtSavedFiltersMdl(String filterName) {
|
||||||
|
return isVisible($x(String.format(savedFilterNameLocator, filterName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails selectFilterAtSavedFiltersMdl(String filterName) {
|
||||||
|
$x(String.format(savedFilterNameLocator, filterName)).shouldBe(Condition.enabled).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickSelectFilterBtnAtSavedFiltersMdl() {
|
||||||
|
selectFilterBtnAddFilterMdl.shouldBe(Condition.enabled).click();
|
||||||
|
addFilterCodeModalTitle.shouldBe(Condition.disappear);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails waitUntilAddFiltersMdlVisible() {
|
||||||
|
addFilterCodeModalTitle.shouldBe(Condition.visible);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails setFilterCodeFieldAddFilterMdl(String filterCode) {
|
||||||
|
addFilterCodeInput.shouldBe(Condition.enabled).sendKeys(filterCode);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails selectSaveThisFilterCheckboxMdl(boolean select) {
|
||||||
|
selectElement(saveThisFilterCheckBoxAddFilterMdl, select);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isSaveThisFilterCheckBoxSelected() {
|
||||||
|
return isSelected(saveThisFilterCheckBoxAddFilterMdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails setDisplayNameFldAddFilterMdl(String displayName) {
|
||||||
|
displayNameInputAddFilterMdl.shouldBe(Condition.enabled).sendKeys(displayName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails clickAddFilterBtnAndCloseMdl(boolean closeModal) {
|
||||||
|
addFilterBtnAddFilterMdl.shouldBe(Condition.enabled).click();
|
||||||
|
if (closeModal) {
|
||||||
|
addFilterCodeModalTitle.shouldBe(Condition.hidden);
|
||||||
|
} else {
|
||||||
|
addFilterCodeModalTitle.shouldBe(Condition.visible);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isAddFilterBtnAddFilterMdlEnabled() {
|
||||||
|
return isEnabled(addFilterBtnAddFilterMdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isBackButtonEnabled() {
|
||||||
|
return isEnabled(backBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isNextButtonEnabled() {
|
||||||
|
return isEnabled(nextBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isActiveFilterVisible(String activeFilterName) {
|
||||||
|
return isVisible($x(String.format(activeFilterNameLocator, activeFilterName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SelenideElement> getAllAddFilterModalVisibleElements() {
|
||||||
|
return Arrays.asList(savedFiltersLink, displayNameInputAddFilterMdl, addFilterBtnAddFilterMdl,
|
||||||
|
cancelBtnAddFilterMdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SelenideElement> getAllAddFilterModalEnabledElements() {
|
||||||
|
return Arrays.asList(displayNameInputAddFilterMdl, cancelBtnAddFilterMdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SelenideElement> getAllAddFilterModalDisabledElements() {
|
||||||
|
return Collections.singletonList(addFilterBtnAddFilterMdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails openConsumerGroup(String consumerId) {
|
||||||
|
$x(String.format(consumerIdLocator, consumerId)).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectYear(int expectedYear) {
|
||||||
|
while (getActualCalendarDate().getYear() > expectedYear) {
|
||||||
|
clickByJavaScript(previousMonthButton);
|
||||||
|
sleep(1000);
|
||||||
|
if (LocalTime.now().plusMinutes(3).isBefore(LocalTime.now())) {
|
||||||
|
throw new IllegalArgumentException("Unable to select year");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectMonth(int expectedMonth) {
|
||||||
|
while (getActualCalendarDate().getMonthValue() > expectedMonth) {
|
||||||
|
clickByJavaScript(previousMonthButton);
|
||||||
|
sleep(1000);
|
||||||
|
if (LocalTime.now().plusMinutes(3).isBefore(LocalTime.now())) {
|
||||||
|
throw new IllegalArgumentException("Unable to select month");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectDay(int expectedDay) {
|
||||||
|
Objects.requireNonNull($$x(String.format(dayCellLtr, expectedDay)).stream()
|
||||||
|
.filter(day -> !Objects.requireNonNull(day.getAttribute("class")).contains("outside-month"))
|
||||||
|
.findFirst().orElseThrow()).shouldBe(Condition.enabled).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTime(LocalDateTime dateTime) {
|
||||||
|
calendarTimeFld.shouldBe(Condition.enabled)
|
||||||
|
.sendKeys(String.valueOf(dateTime.getHour()), String.valueOf(dateTime.getMinute()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails selectDateAndTimeByCalendar(LocalDateTime dateTime) {
|
||||||
|
setTime(dateTime);
|
||||||
|
selectYear(dateTime.getYear());
|
||||||
|
selectMonth(dateTime.getMonthValue());
|
||||||
|
selectDay(dateTime.getDayOfMonth());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate getActualCalendarDate() {
|
||||||
|
String monthAndYearStr = actualCalendarDate.getText().trim();
|
||||||
|
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
|
||||||
|
.parseCaseInsensitive()
|
||||||
|
.append(DateTimeFormatter.ofPattern("MMMM yyyy"))
|
||||||
|
.toFormatter(Locale.ENGLISH);
|
||||||
|
YearMonth yearMonth = formatter.parse(monthAndYearStr, YearMonth::from);
|
||||||
|
return yearMonth.atDay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails openCalendarSeekType() {
|
||||||
|
seekTypeField.shouldBe(Condition.enabled).click();
|
||||||
|
actualCalendarDate.shouldBe(Condition.visible);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public int getMessageCountAmount() {
|
||||||
|
return Integer.parseInt(messageAmountCell.getText().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TopicDetails.MessageGridItem> initItems() {
|
||||||
|
List<TopicDetails.MessageGridItem> gridItemList = new ArrayList<>();
|
||||||
|
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
||||||
|
.forEach(item -> gridItemList.add(new TopicDetails.MessageGridItem(item)));
|
||||||
|
return gridItemList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails.MessageGridItem getMessageByOffset(int offset) {
|
||||||
|
return initItems().stream()
|
||||||
|
.filter(e -> e.getOffset() == offset)
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails.MessageGridItem getMessageByKey(String key) {
|
||||||
|
return initItems().stream()
|
||||||
|
.filter(e -> e.getKey().equals(key))
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<MessageGridItem> getAllMessages() {
|
||||||
|
return initItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicDetails.MessageGridItem getRandomMessage() {
|
||||||
|
return getMessageByOffset(nextInt(0, initItems().size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TopicMenu {
|
||||||
|
OVERVIEW("Overview"),
|
||||||
|
MESSAGES("Messages"),
|
||||||
|
CONSUMERS("Consumers"),
|
||||||
|
SETTINGS("Settings");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
TopicMenu(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MessageGridItem extends BasePage {
|
||||||
|
|
||||||
|
private final SelenideElement element;
|
||||||
|
|
||||||
|
private MessageGridItem(SelenideElement element) {
|
||||||
|
this.element = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicDetails openDetailsTab(TopicMenu menu) {
|
public MessageGridItem clickExpand() {
|
||||||
$(By.linkText(menu.toString())).shouldBe(Condition.visible).click();
|
clickByJavaScript(element.$x("./td[1]/span"));
|
||||||
waitUntilSpinnerDisappear();
|
return this;
|
||||||
return this;
|
}
|
||||||
|
|
||||||
|
private SelenideElement getOffsetElm() {
|
||||||
|
return element.$x("./td[2]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public String getSettingsGridValueByKey(String key) {
|
public int getOffset() {
|
||||||
return $x(String.format(settingsGridValueLocator, key)).scrollTo().shouldBe(Condition.visible).getText();
|
return Integer.parseInt(getOffsetElm().getText().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicDetails openDotMenu() {
|
public int getPartition() {
|
||||||
clickByJavaScript(dotMenuBtn);
|
return Integer.parseInt(element.$x("./td[3]").getText().trim());
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
|
public LocalDateTime getTimestamp() {
|
||||||
return isAlertVisible(header, message);
|
String timestampValue = element.$x("./td[4]/div").getText().trim();
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy, HH:mm:ss");
|
||||||
|
return LocalDateTime.parse(timestampValue, formatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicDetails clickEditSettingsMenu() {
|
public String getKey() {
|
||||||
editSettingsMenu.shouldBe(Condition.visible).click();
|
return element.$x("./td[5]").getText().trim();
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isConfirmationMdlVisible() {
|
public String getValue() {
|
||||||
return isConfirmationModalVisible();
|
return element.$x("./td[6]").getAttribute("title");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicDetails clickClearMessagesMenu() {
|
public MessageGridItem openDotMenu() {
|
||||||
clearMessagesBtn.shouldBe(Condition.visible).click();
|
getOffsetElm().hover();
|
||||||
return this;
|
element.$x("./td[7]/div/button[@aria-label='Dropdown Toggle']")
|
||||||
}
|
.shouldBe(Condition.visible).click();
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickRecreateTopicMenu() {
|
|
||||||
recreateTopicBtn.shouldBe(Condition.visible).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getCleanUpPolicy() {
|
|
||||||
return cleanUpPolicyField.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getPartitions() {
|
|
||||||
return Integer.parseInt(partitionsField.getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isTopicHeaderVisible(String topicName) {
|
|
||||||
return isVisible($x(String.format(topicHeaderLocator, topicName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickDeleteTopicMenu() {
|
|
||||||
removeTopicBtn.shouldBe(Condition.visible).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickConfirmBtnMdl() {
|
|
||||||
clickConfirmButton();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickProduceMessageBtn() {
|
|
||||||
clickByJavaScript(produceMessageBtn);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails selectSeekTypeDdlMessagesTab(String seekTypeName) {
|
|
||||||
seekTypeDdl.shouldBe(Condition.enabled).click();
|
|
||||||
$x(String.format(seekFilterDdlLocator, seekTypeName)).shouldBe(Condition.visible).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails setSeekTypeValueFldMessagesTab(String seekTypeValue) {
|
|
||||||
seekTypeField.shouldBe(Condition.enabled).sendKeys(seekTypeValue);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickSubmitFiltersBtnMessagesTab() {
|
|
||||||
clickByJavaScript(submitBtn);
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickMessagesAddFiltersBtn() {
|
|
||||||
addFiltersBtn.shouldBe(Condition.enabled).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickNextButton() {
|
|
||||||
nextBtn.shouldBe(Condition.enabled).click();
|
|
||||||
waitUntilSpinnerDisappear();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicDetails openSavedFiltersListMdl() {
|
public MessageGridItem clickCopyToClipBoard() {
|
||||||
savedFiltersLink.shouldBe(Condition.enabled).click();
|
clickByJavaScript(element.$x("./td[7]//li[text() = 'Copy to clipboard']")
|
||||||
backToCreateFiltersLink.shouldBe(Condition.visible);
|
.shouldBe(Condition.visible));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isFilterVisibleAtSavedFiltersMdl(String filterName) {
|
public MessageGridItem clickSaveAsFile() {
|
||||||
return isVisible($x(String.format(savedFilterNameLocator, filterName)));
|
clickByJavaScript(element.$x("./td[7]//li[text() = 'Save as a file']")
|
||||||
}
|
.shouldBe(Condition.visible));
|
||||||
|
return this;
|
||||||
@Step
|
|
||||||
public TopicDetails selectFilterAtSavedFiltersMdl(String filterName) {
|
|
||||||
$x(String.format(savedFilterNameLocator, filterName)).shouldBe(Condition.enabled).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickSelectFilterBtnAtSavedFiltersMdl() {
|
|
||||||
selectFilterBtnAddFilterMdl.shouldBe(Condition.enabled).click();
|
|
||||||
addFilterCodeModalTitle.shouldBe(Condition.disappear);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails waitUntilAddFiltersMdlVisible() {
|
|
||||||
addFilterCodeModalTitle.shouldBe(Condition.visible);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails setFilterCodeFieldAddFilterMdl(String filterCode) {
|
|
||||||
addFilterCodeInput.shouldBe(Condition.enabled).sendKeys(filterCode);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails selectSaveThisFilterCheckboxMdl(boolean select) {
|
|
||||||
selectElement(saveThisFilterCheckBoxAddFilterMdl, select);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isSaveThisFilterCheckBoxSelected() {
|
|
||||||
return isSelected(saveThisFilterCheckBoxAddFilterMdl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails setDisplayNameFldAddFilterMdl(String displayName) {
|
|
||||||
displayNameInputAddFilterMdl.shouldBe(Condition.enabled).sendKeys(displayName);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails clickAddFilterBtnAndCloseMdl(boolean closeModal) {
|
|
||||||
addFilterBtnAddFilterMdl.shouldBe(Condition.enabled).click();
|
|
||||||
if (closeModal) {
|
|
||||||
addFilterCodeModalTitle.shouldBe(Condition.hidden);
|
|
||||||
} else {
|
|
||||||
addFilterCodeModalTitle.shouldBe(Condition.visible);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isAddFilterBtnAddFilterMdlEnabled() {
|
|
||||||
return isEnabled(addFilterBtnAddFilterMdl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isBackButtonEnabled() {
|
|
||||||
return isEnabled(backBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isNextButtonEnabled() {
|
|
||||||
return isEnabled(nextBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isActiveFilterVisible(String activeFilterName) {
|
|
||||||
return isVisible($x(String.format(activeFilterNameLocator, activeFilterName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SelenideElement> getAllAddFilterModalVisibleElements() {
|
|
||||||
return Arrays.asList(savedFiltersLink, displayNameInputAddFilterMdl, addFilterBtnAddFilterMdl, cancelBtnAddFilterMdl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SelenideElement> getAllAddFilterModalEnabledElements() {
|
|
||||||
return Arrays.asList(displayNameInputAddFilterMdl, cancelBtnAddFilterMdl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SelenideElement> getAllAddFilterModalDisabledElements() {
|
|
||||||
return Collections.singletonList(addFilterBtnAddFilterMdl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails openConsumerGroup(String consumerId) {
|
|
||||||
$x(String.format(consumerIdLocator, consumerId)).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isKeyMessageVisible(String keyMessage) {
|
|
||||||
return keyMessage.equals($("td[title]").getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isContentMessageVisible(String contentMessage) {
|
|
||||||
return contentMessage.matches(contentMessageTab.getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectYear(int expectedYear) {
|
|
||||||
while (getActualCalendarDate().getYear() > expectedYear) {
|
|
||||||
clickByJavaScript(previousMonthButton);
|
|
||||||
sleep(1000);
|
|
||||||
if (LocalTime.now().plusMinutes(3).isBefore(LocalTime.now())) {
|
|
||||||
throw new IllegalArgumentException("Unable to select year");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectMonth(int expectedMonth) {
|
|
||||||
while (getActualCalendarDate().getMonthValue() > expectedMonth) {
|
|
||||||
clickByJavaScript(previousMonthButton);
|
|
||||||
sleep(1000);
|
|
||||||
if (LocalTime.now().plusMinutes(3).isBefore(LocalTime.now())) {
|
|
||||||
throw new IllegalArgumentException("Unable to select month");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectDay(int expectedDay) {
|
|
||||||
Objects.requireNonNull($$x(String.format(dayCellLtr, expectedDay)).stream()
|
|
||||||
.filter(day -> !Objects.requireNonNull(day.getAttribute("class")).contains("outside-month"))
|
|
||||||
.findFirst().orElseThrow()).shouldBe(Condition.enabled).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTime(LocalDateTime dateTime) {
|
|
||||||
calendarTimeFld.shouldBe(Condition.enabled)
|
|
||||||
.sendKeys(String.valueOf(dateTime.getHour()), String.valueOf(dateTime.getMinute()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails selectDateAndTimeByCalendar(LocalDateTime dateTime) {
|
|
||||||
setTime(dateTime);
|
|
||||||
selectYear(dateTime.getYear());
|
|
||||||
selectMonth(dateTime.getMonthValue());
|
|
||||||
selectDay(dateTime.getDayOfMonth());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalDate getActualCalendarDate() {
|
|
||||||
String monthAndYearStr = actualCalendarDate.getText().trim();
|
|
||||||
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
|
|
||||||
.parseCaseInsensitive()
|
|
||||||
.append(DateTimeFormatter.ofPattern("MMMM yyyy"))
|
|
||||||
.toFormatter(Locale.ENGLISH);
|
|
||||||
YearMonth yearMonth = formatter.parse(monthAndYearStr, YearMonth::from);
|
|
||||||
return yearMonth.atDay(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails openCalendarSeekType() {
|
|
||||||
seekTypeField.shouldBe(Condition.enabled).click();
|
|
||||||
actualCalendarDate.shouldBe(Condition.visible);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getMessageCountAmount() {
|
|
||||||
return Integer.parseInt(messageAmountCell.getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TopicDetails.MessageGridItem> initItems() {
|
|
||||||
List<TopicDetails.MessageGridItem> gridItemList = new ArrayList<>();
|
|
||||||
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
|
||||||
.forEach(item -> gridItemList.add(new TopicDetails.MessageGridItem(item)));
|
|
||||||
return gridItemList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails.MessageGridItem getMessageByOffset(int offset) {
|
|
||||||
return initItems().stream()
|
|
||||||
.filter(e -> e.getOffset() == offset)
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public List<MessageGridItem> getAllMessages() {
|
|
||||||
return initItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicDetails.MessageGridItem getRandomMessage() {
|
|
||||||
return getMessageByOffset(nextInt(0, initItems().size() - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TopicMenu {
|
|
||||||
OVERVIEW("Overview"),
|
|
||||||
MESSAGES("Messages"),
|
|
||||||
CONSUMERS("Consumers"),
|
|
||||||
SETTINGS("Settings");
|
|
||||||
|
|
||||||
private final String value;
|
|
||||||
|
|
||||||
TopicMenu(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MessageGridItem extends BasePage {
|
|
||||||
|
|
||||||
private final SelenideElement element;
|
|
||||||
|
|
||||||
private MessageGridItem(SelenideElement element) {
|
|
||||||
this.element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public MessageGridItem clickExpand() {
|
|
||||||
clickByJavaScript(element.$x("./td[1]/span"));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SelenideElement getOffsetElm() {
|
|
||||||
return element.$x("./td[2]");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getOffset() {
|
|
||||||
return Integer.parseInt(getOffsetElm().getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getPartition() {
|
|
||||||
return Integer.parseInt(element.$x("./td[3]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public LocalDateTime getTimestamp() {
|
|
||||||
String timestampValue = element.$x("./td[4]/div").getText().trim();
|
|
||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy, HH:mm:ss");
|
|
||||||
return LocalDateTime.parse(timestampValue, formatter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getKey() {
|
|
||||||
return element.$x("./td[5]").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getValue() {
|
|
||||||
return element.$x("./td[6]/span/p").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public MessageGridItem openDotMenu() {
|
|
||||||
getOffsetElm().hover();
|
|
||||||
element.$x("./td[7]/div/button[@aria-label='Dropdown Toggle']")
|
|
||||||
.shouldBe(Condition.visible).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public MessageGridItem clickCopyToClipBoard() {
|
|
||||||
clickByJavaScript(element.$x("./td[7]//li[text() = 'Copy to clipboard']")
|
|
||||||
.shouldBe(Condition.visible));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public MessageGridItem clickSaveAsFile() {
|
|
||||||
clickByJavaScript(element.$x("./td[7]//li[text() = 'Save as a file']")
|
|
||||||
.shouldBe(Condition.visible));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,65 @@
|
||||||
package com.provectus.kafka.ui.pages.topics;
|
package com.provectus.kafka.ui.pages.topics;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
import com.codeborne.selenide.CollectionCondition;
|
import com.codeborne.selenide.CollectionCondition;
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
|
|
||||||
public class TopicSettingsTab extends BasePage {
|
public class TopicSettingsTab extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement defaultValueColumnHeaderLocator = $x("//div[text() = 'Default Value']");
|
protected SelenideElement defaultValueColumnHeaderLocator = $x("//div[text() = 'Default Value']");
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicSettingsTab waitUntilScreenReady() {
|
public TopicSettingsTab waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
defaultValueColumnHeaderLocator.shouldBe(Condition.visible);
|
defaultValueColumnHeaderLocator.shouldBe(Condition.visible);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SettingsGridItem> initGridItems() {
|
private List<SettingsGridItem> initGridItems() {
|
||||||
List<SettingsGridItem> gridItemList = new ArrayList<>();
|
List<SettingsGridItem> gridItemList = new ArrayList<>();
|
||||||
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
||||||
.forEach(item -> gridItemList.add(new SettingsGridItem(item)));
|
.forEach(item -> gridItemList.add(new SettingsGridItem(item)));
|
||||||
return gridItemList;
|
return gridItemList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TopicSettingsTab.SettingsGridItem getItemByKey(String key) {
|
private TopicSettingsTab.SettingsGridItem getItemByKey(String key) {
|
||||||
return initGridItems().stream()
|
return initGridItems().stream()
|
||||||
.filter(e -> e.getKey().equals(key))
|
.filter(e -> e.getKey().equals(key))
|
||||||
.findFirst().orElseThrow();
|
.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getValueByKey(String key) {
|
||||||
|
return getItemByKey(key).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingsGridItem extends BasePage {
|
||||||
|
|
||||||
|
private final SelenideElement element;
|
||||||
|
|
||||||
|
public SettingsGridItem(SelenideElement element) {
|
||||||
|
this.element = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public String getValueByKey(String key) {
|
public String getKey() {
|
||||||
return getItemByKey(key).getValue();
|
return element.$x("./td[1]/span").getText().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SettingsGridItem extends BasePage {
|
@Step
|
||||||
|
public String getValue() {
|
||||||
private final SelenideElement element;
|
return element.$x("./td[2]/span").getText().trim();
|
||||||
|
|
||||||
public SettingsGridItem(SelenideElement element) {
|
|
||||||
this.element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getKey() {
|
|
||||||
return element.$x("./td[1]/span").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getValue() {
|
|
||||||
return element.$x("./td[2]/span").getText().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getDefaultValue() {
|
|
||||||
return element.$x("./td[3]/span").getText().trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public String getDefaultValue() {
|
||||||
|
return element.$x("./td[3]/span").getText().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,265 +1,283 @@
|
||||||
package com.provectus.kafka.ui.pages.topics;
|
package com.provectus.kafka.ui.pages.topics;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Condition.visible;
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.TOPICS;
|
||||||
|
|
||||||
import com.codeborne.selenide.CollectionCondition;
|
import com.codeborne.selenide.CollectionCondition;
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.provectus.kafka.ui.pages.BasePage;
|
import com.provectus.kafka.ui.pages.BasePage;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Condition.visible;
|
|
||||||
import static com.codeborne.selenide.Selenide.$x;
|
|
||||||
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.TOPICS;
|
|
||||||
|
|
||||||
public class TopicsList extends BasePage {
|
public class TopicsList extends BasePage {
|
||||||
|
|
||||||
protected SelenideElement addTopicBtn = $x("//button[normalize-space(text()) ='Add a Topic']");
|
protected SelenideElement addTopicBtn = $x("//button[normalize-space(text()) ='Add a Topic']");
|
||||||
protected SelenideElement searchField = $x("//input[@placeholder='Search by Topic Name']");
|
protected SelenideElement searchField = $x("//input[@placeholder='Search by Topic Name']");
|
||||||
protected SelenideElement showInternalRadioBtn = $x("//input[@name='ShowInternalTopics']");
|
protected SelenideElement showInternalRadioBtn = $x("//input[@name='ShowInternalTopics']");
|
||||||
protected SelenideElement deleteSelectedTopicsBtn = $x("//button[text()='Delete selected topics']");
|
protected SelenideElement deleteSelectedTopicsBtn = $x("//button[text()='Delete selected topics']");
|
||||||
protected SelenideElement copySelectedTopicBtn = $x("//button[text()='Copy selected topic']");
|
protected SelenideElement copySelectedTopicBtn = $x("//button[text()='Copy selected topic']");
|
||||||
protected SelenideElement purgeMessagesOfSelectedTopicsBtn = $x("//button[text()='Purge messages of selected topics']");
|
protected SelenideElement purgeMessagesOfSelectedTopicsBtn =
|
||||||
protected SelenideElement clearMessagesBtn = $x("//ul[contains(@class ,'open')]//div[text()='Clear Messages']");
|
$x("//button[text()='Purge messages of selected topics']");
|
||||||
protected SelenideElement recreateTopicBtn = $x("//ul[contains(@class ,'open')]//div[text()='Recreate Topic']");
|
protected SelenideElement clearMessagesBtn = $x("//ul[contains(@class ,'open')]//div[text()='Clear Messages']");
|
||||||
protected SelenideElement removeTopicBtn = $x("//ul[contains(@class ,'open')]//div[text()='Remove Topic']");
|
protected SelenideElement recreateTopicBtn = $x("//ul[contains(@class ,'open')]//div[text()='Recreate Topic']");
|
||||||
|
protected SelenideElement removeTopicBtn = $x("//ul[contains(@class ,'open')]//div[text()='Remove Topic']");
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicsList waitUntilScreenReady() {
|
public TopicsList waitUntilScreenReady() {
|
||||||
waitUntilSpinnerDisappear();
|
waitUntilSpinnerDisappear();
|
||||||
getPageTitleFromHeader(TOPICS).shouldBe(visible);
|
getPageTitleFromHeader(TOPICS).shouldBe(visible);
|
||||||
return this;
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList clickAddTopicBtn() {
|
||||||
|
clickByJavaScript(addTopicBtn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isTopicVisible(String topicName) {
|
||||||
|
tableGrid.shouldBe(visible);
|
||||||
|
return isVisible(getTableElement(topicName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isShowInternalRadioBtnSelected() {
|
||||||
|
return isSelected(showInternalRadioBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList setShowInternalRadioButton(boolean select) {
|
||||||
|
if (select) {
|
||||||
|
if (!showInternalRadioBtn.isSelected()) {
|
||||||
|
clickByJavaScript(showInternalRadioBtn);
|
||||||
|
waitUntilSpinnerDisappear(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (showInternalRadioBtn.isSelected()) {
|
||||||
|
clickByJavaScript(showInternalRadioBtn);
|
||||||
|
waitUntilSpinnerDisappear(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList openTopic(String topicName) {
|
||||||
|
getTopicItem(topicName).openItem();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList openDotMenuByTopicName(String topicName) {
|
||||||
|
getTopicItem(topicName).openDotMenu();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isCopySelectedTopicBtnEnabled() {
|
||||||
|
return isEnabled(copySelectedTopicBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<SelenideElement> getActionButtons() {
|
||||||
|
return Stream.of(deleteSelectedTopicsBtn, copySelectedTopicBtn, purgeMessagesOfSelectedTopicsBtn)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList clickCopySelectedTopicBtn() {
|
||||||
|
copySelectedTopicBtn.shouldBe(Condition.enabled).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList clickPurgeMessagesOfSelectedTopicsBtn() {
|
||||||
|
purgeMessagesOfSelectedTopicsBtn.shouldBe(Condition.enabled).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList clickClearMessagesBtn() {
|
||||||
|
clickByJavaScript(clearMessagesBtn.shouldBe(visible));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList clickRecreateTopicBtn() {
|
||||||
|
clickByJavaScript(recreateTopicBtn.shouldBe(visible));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList clickRemoveTopicBtn() {
|
||||||
|
clickByJavaScript(removeTopicBtn.shouldBe(visible));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList clickConfirmBtnMdl() {
|
||||||
|
clickConfirmButton();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicsList clickCancelBtnMdl() {
|
||||||
|
clickCancelButton();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isConfirmationMdlVisible() {
|
||||||
|
return isConfirmationModalVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
|
||||||
|
return isAlertVisible(header, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getVisibleColumnHeaders() {
|
||||||
|
return Stream.of("Replication Factor", "Number of messages", "Topic Name", "Partitions", "Out of sync replicas",
|
||||||
|
"Size")
|
||||||
|
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelenideElement> getEnabledColumnHeaders() {
|
||||||
|
return Stream.of("Topic Name", "Partitions", "Out of sync replicas", "Size")
|
||||||
|
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<SelenideElement> getAllVisibleElements() {
|
||||||
|
List<SelenideElement> visibleElements = new ArrayList<>(getVisibleColumnHeaders());
|
||||||
|
visibleElements.addAll(Arrays.asList(searchField, addTopicBtn, tableGrid));
|
||||||
|
visibleElements.addAll(getActionButtons());
|
||||||
|
return visibleElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<SelenideElement> getAllEnabledElements() {
|
||||||
|
List<SelenideElement> enabledElements = new ArrayList<>(getEnabledColumnHeaders());
|
||||||
|
enabledElements.addAll(Arrays.asList(searchField, showInternalRadioBtn, addTopicBtn));
|
||||||
|
return enabledElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TopicGridItem> initGridItems() {
|
||||||
|
List<TopicGridItem> gridItemList = new ArrayList<>();
|
||||||
|
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
||||||
|
.forEach(item -> gridItemList.add(new TopicGridItem(item)));
|
||||||
|
return gridItemList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicGridItem getTopicItem(String name) {
|
||||||
|
TopicGridItem topicGridItem = initGridItems().stream()
|
||||||
|
.filter(e -> e.getName().equals(name))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
if (topicGridItem == null) {
|
||||||
|
searchItem(name);
|
||||||
|
topicGridItem = initGridItems().stream()
|
||||||
|
.filter(e -> e.getName().equals(name))
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
return topicGridItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public TopicGridItem getAnyNonInternalTopic() {
|
||||||
|
return getNonInternalTopics().stream()
|
||||||
|
.findAny().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<TopicGridItem> getNonInternalTopics() {
|
||||||
|
return initGridItems().stream()
|
||||||
|
.filter(e -> !e.isInternal())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public List<TopicGridItem> getInternalTopics() {
|
||||||
|
return initGridItems().stream()
|
||||||
|
.filter(TopicGridItem::isInternal)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TopicGridItem extends BasePage {
|
||||||
|
|
||||||
|
private final SelenideElement element;
|
||||||
|
|
||||||
|
public TopicGridItem(SelenideElement element) {
|
||||||
|
this.element = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicsList clickAddTopicBtn() {
|
public TopicsList selectItem(boolean select) {
|
||||||
clickByJavaScript(addTopicBtn);
|
selectElement(element.$x("./td[1]/input"), select);
|
||||||
return this;
|
return new TopicsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SelenideElement getNameElm() {
|
||||||
|
return element.$x("./td[2]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isTopicVisible(String topicName) {
|
public boolean isInternal() {
|
||||||
tableGrid.shouldBe(visible);
|
boolean internal = false;
|
||||||
return isVisible(getTableElement(topicName));
|
try {
|
||||||
|
internal = getNameElm().$x("./a/span").isDisplayed();
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return internal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isShowInternalRadioBtnSelected() {
|
public String getName() {
|
||||||
return isSelected(showInternalRadioBtn);
|
return getNameElm().$x("./a").getAttribute("title");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicsList setShowInternalRadioButton(boolean select) {
|
public void openItem() {
|
||||||
selectElement(showInternalRadioBtn, select);
|
getNameElm().click();
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicsList openTopic(String topicName) {
|
public int getPartition() {
|
||||||
getTopicItem(topicName).openItem();
|
return Integer.parseInt(element.$x("./td[3]").getText().trim());
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicsList openDotMenuByTopicName(String topicName) {
|
public int getOutOfSyncReplicas() {
|
||||||
getTopicItem(topicName).openDotMenu();
|
return Integer.parseInt(element.$x("./td[4]").getText().trim());
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public boolean isCopySelectedTopicBtnEnabled() {
|
public int getReplicationFactor() {
|
||||||
return isEnabled(copySelectedTopicBtn);
|
return Integer.parseInt(element.$x("./td[5]").getText().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public List<SelenideElement> getActionButtons() {
|
public int getNumberOfMessages() {
|
||||||
return Stream.of(deleteSelectedTopicsBtn, copySelectedTopicBtn, purgeMessagesOfSelectedTopicsBtn)
|
return Integer.parseInt(element.$x("./td[6]").getText().trim());
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicsList clickCopySelectedTopicBtn() {
|
public int getSize() {
|
||||||
copySelectedTopicBtn.shouldBe(Condition.enabled).click();
|
return Integer.parseInt(element.$x("./td[7]").getText().trim());
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public TopicsList clickPurgeMessagesOfSelectedTopicsBtn() {
|
public void openDotMenu() {
|
||||||
purgeMessagesOfSelectedTopicsBtn.shouldBe(Condition.enabled).click();
|
element.$x("./td[8]//button").click();
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicsList clickClearMessagesBtn() {
|
|
||||||
clickByJavaScript(clearMessagesBtn.shouldBe(visible));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicsList clickRecreateTopicBtn() {
|
|
||||||
clickByJavaScript(recreateTopicBtn.shouldBe(visible));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicsList clickRemoveTopicBtn() {
|
|
||||||
clickByJavaScript(removeTopicBtn.shouldBe(visible));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicsList clickConfirmBtnMdl() {
|
|
||||||
clickConfirmButton();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicsList clickCancelBtnMdl() {
|
|
||||||
clickCancelButton();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isConfirmationMdlVisible() {
|
|
||||||
return isConfirmationModalVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
|
|
||||||
return isAlertVisible(header, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getVisibleColumnHeaders() {
|
|
||||||
return Stream.of("Replication Factor", "Number of messages", "Topic Name", "Partitions", "Out of sync replicas", "Size")
|
|
||||||
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelenideElement> getEnabledColumnHeaders() {
|
|
||||||
return Stream.of("Topic Name", "Partitions", "Out of sync replicas", "Size")
|
|
||||||
.map(name -> $x(String.format(columnHeaderLocator, name)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public List<SelenideElement> getAllVisibleElements() {
|
|
||||||
List<SelenideElement> visibleElements = new ArrayList<>(getVisibleColumnHeaders());
|
|
||||||
visibleElements.addAll(Arrays.asList(searchField, addTopicBtn, tableGrid));
|
|
||||||
visibleElements.addAll(getActionButtons());
|
|
||||||
return visibleElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public List<SelenideElement> getAllEnabledElements() {
|
|
||||||
List<SelenideElement> enabledElements = new ArrayList<>(getEnabledColumnHeaders());
|
|
||||||
enabledElements.addAll(Arrays.asList(searchField, showInternalRadioBtn, addTopicBtn));
|
|
||||||
return enabledElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TopicGridItem> initGridItems() {
|
|
||||||
List<TopicGridItem> gridItemList = new ArrayList<>();
|
|
||||||
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
|
|
||||||
.forEach(item -> gridItemList.add(new TopicGridItem(item)));
|
|
||||||
return gridItemList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicGridItem getTopicItem(String name) {
|
|
||||||
return initGridItems().stream()
|
|
||||||
.filter(e -> e.getName().equals(name))
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicGridItem getAnyNonInternalTopic() {
|
|
||||||
return getNonInternalTopics().stream()
|
|
||||||
.findAny().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public List<TopicGridItem> getNonInternalTopics() {
|
|
||||||
return initGridItems().stream()
|
|
||||||
.filter(e -> !e.isInternal())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public List<TopicGridItem> getInternalTopics() {
|
|
||||||
return initGridItems().stream()
|
|
||||||
.filter(TopicGridItem::isInternal)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TopicGridItem extends BasePage {
|
|
||||||
|
|
||||||
private final SelenideElement element;
|
|
||||||
|
|
||||||
public TopicGridItem(SelenideElement element) {
|
|
||||||
this.element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public TopicsList selectItem(boolean select) {
|
|
||||||
selectElement(element.$x("./td[1]/input"), select);
|
|
||||||
return new TopicsList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private SelenideElement getNameElm() {
|
|
||||||
return element.$x("./td[2]");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public boolean isInternal() {
|
|
||||||
boolean internal = false;
|
|
||||||
try {
|
|
||||||
internal = getNameElm().$x("./a/span").isDisplayed();
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
return internal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public String getName() {
|
|
||||||
return getNameElm().$x("./a").getAttribute("title");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public void openItem() {
|
|
||||||
getNameElm().click();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getPartition() {
|
|
||||||
return Integer.parseInt(element.$x("./td[3]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getOutOfSyncReplicas() {
|
|
||||||
return Integer.parseInt(element.$x("./td[4]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getReplicationFactor() {
|
|
||||||
return Integer.parseInt(element.$x("./td[5]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getNumberOfMessages() {
|
|
||||||
return Integer.parseInt(element.$x("./td[6]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public int getSize() {
|
|
||||||
return Integer.parseInt(element.$x("./td[7]").getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public void openDotMenu() {
|
|
||||||
element.$x("./td[8]//button").click();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,24 @@ package com.provectus.kafka.ui.pages.topics.enums;
|
||||||
|
|
||||||
public enum CleanupPolicyValue {
|
public enum CleanupPolicyValue {
|
||||||
|
|
||||||
DELETE("delete", "Delete"),
|
DELETE("delete", "Delete"),
|
||||||
COMPACT("compact", "Compact"),
|
COMPACT("compact", "Compact"),
|
||||||
COMPACT_DELETE("compact,delete", "Compact,Delete");
|
COMPACT_DELETE("compact,delete", "Compact,Delete");
|
||||||
|
|
||||||
private final String optionValue;
|
private final String optionValue;
|
||||||
private final String visibleText;
|
private final String visibleText;
|
||||||
|
|
||||||
CleanupPolicyValue(String optionValue, String visibleText) {
|
CleanupPolicyValue(String optionValue, String visibleText) {
|
||||||
this.optionValue = optionValue;
|
this.optionValue = optionValue;
|
||||||
this.visibleText = visibleText;
|
this.visibleText = visibleText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOptionValue() {
|
public String getOptionValue() {
|
||||||
return optionValue;
|
return optionValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVisibleText() {
|
public String getVisibleText() {
|
||||||
return visibleText;
|
return visibleText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,36 +2,36 @@ package com.provectus.kafka.ui.pages.topics.enums;
|
||||||
|
|
||||||
public enum CustomParameterType {
|
public enum CustomParameterType {
|
||||||
|
|
||||||
COMPRESSION_TYPE("compression.type"),
|
COMPRESSION_TYPE("compression.type"),
|
||||||
DELETE_RETENTION_MS("delete.retention.ms"),
|
DELETE_RETENTION_MS("delete.retention.ms"),
|
||||||
FILE_DELETE_DELAY_MS("file.delete.delay.ms"),
|
FILE_DELETE_DELAY_MS("file.delete.delay.ms"),
|
||||||
FLUSH_MESSAGES("flush.messages"),
|
FLUSH_MESSAGES("flush.messages"),
|
||||||
FLUSH_MS("flush.ms"),
|
FLUSH_MS("flush.ms"),
|
||||||
FOLLOWER_REPLICATION_THROTTLED_REPLICAS("follower.replication.throttled.replicas"),
|
FOLLOWER_REPLICATION_THROTTLED_REPLICAS("follower.replication.throttled.replicas"),
|
||||||
INDEX_INTERVAL_BYTES("index.interval.bytes"),
|
INDEX_INTERVAL_BYTES("index.interval.bytes"),
|
||||||
LEADER_REPLICATION_THROTTLED_REPLICAS("leader.replication.throttled.replicas"),
|
LEADER_REPLICATION_THROTTLED_REPLICAS("leader.replication.throttled.replicas"),
|
||||||
MAX_COMPACTION_LAG_MS("max.compaction.lag.ms"),
|
MAX_COMPACTION_LAG_MS("max.compaction.lag.ms"),
|
||||||
MESSAGE_DOWNCONVERSION_ENABLE("message.downconversion.enable"),
|
MESSAGE_DOWNCONVERSION_ENABLE("message.downconversion.enable"),
|
||||||
MESSAGE_FORMAT_VERSION("message.format.version"),
|
MESSAGE_FORMAT_VERSION("message.format.version"),
|
||||||
MESSAGE_TIMESTAMP_DIFFERENCE_MAX_MS("message.timestamp.difference.max.ms"),
|
MESSAGE_TIMESTAMP_DIFFERENCE_MAX_MS("message.timestamp.difference.max.ms"),
|
||||||
MESSAGE_TIMESTAMP_TYPE("message.timestamp.type"),
|
MESSAGE_TIMESTAMP_TYPE("message.timestamp.type"),
|
||||||
MIN_CLEANABLE_DIRTY_RATIO("min.cleanable.dirty.ratio"),
|
MIN_CLEANABLE_DIRTY_RATIO("min.cleanable.dirty.ratio"),
|
||||||
MIN_COMPACTION_LAG_MS("min.compaction.lag.ms"),
|
MIN_COMPACTION_LAG_MS("min.compaction.lag.ms"),
|
||||||
PREALLOCATE("preallocate"),
|
PREALLOCATE("preallocate"),
|
||||||
RETENTION_BYTES("retention.bytes"),
|
RETENTION_BYTES("retention.bytes"),
|
||||||
SEGMENT_BYTES("segment.bytes"),
|
SEGMENT_BYTES("segment.bytes"),
|
||||||
SEGMENT_INDEX_BYTES("segment.index.bytes"),
|
SEGMENT_INDEX_BYTES("segment.index.bytes"),
|
||||||
SEGMENT_JITTER_MS("segment.jitter.ms"),
|
SEGMENT_JITTER_MS("segment.jitter.ms"),
|
||||||
SEGMENT_MS("segment.ms"),
|
SEGMENT_MS("segment.ms"),
|
||||||
UNCLEAN_LEADER_ELECTION_ENABLE("unclean.leader.election.enable");
|
UNCLEAN_LEADER_ELECTION_ENABLE("unclean.leader.election.enable");
|
||||||
|
|
||||||
private final String optionValue;
|
private final String optionValue;
|
||||||
|
|
||||||
CustomParameterType(String optionValue) {
|
CustomParameterType(String optionValue) {
|
||||||
this.optionValue = optionValue;
|
this.optionValue = optionValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOptionValue() {
|
public String getOptionValue() {
|
||||||
return optionValue;
|
return optionValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,26 @@ package com.provectus.kafka.ui.pages.topics.enums;
|
||||||
|
|
||||||
public enum MaxSizeOnDisk {
|
public enum MaxSizeOnDisk {
|
||||||
|
|
||||||
NOT_SET("-1", "Not Set"),
|
NOT_SET("-1", "Not Set"),
|
||||||
SIZE_1_GB("1073741824", "1 GB"),
|
SIZE_1_GB("1073741824", "1 GB"),
|
||||||
SIZE_10_GB("10737418240", "10 GB"),
|
SIZE_10_GB("10737418240", "10 GB"),
|
||||||
SIZE_20_GB("21474836480", "20 GB"),
|
SIZE_20_GB("21474836480", "20 GB"),
|
||||||
SIZE_50_GB("53687091200", "50 GB");
|
SIZE_50_GB("53687091200", "50 GB");
|
||||||
|
|
||||||
private final String optionValue;
|
private final String optionValue;
|
||||||
private final String visibleText;
|
private final String visibleText;
|
||||||
|
|
||||||
MaxSizeOnDisk(String optionValue, String visibleText) {
|
MaxSizeOnDisk(String optionValue, String visibleText) {
|
||||||
this.optionValue = optionValue;
|
this.optionValue = optionValue;
|
||||||
this.visibleText = visibleText;
|
this.visibleText = visibleText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOptionValue() {
|
public String getOptionValue() {
|
||||||
return optionValue;
|
return optionValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVisibleText() {
|
public String getVisibleText() {
|
||||||
return visibleText;
|
return visibleText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,25 +2,25 @@ package com.provectus.kafka.ui.pages.topics.enums;
|
||||||
|
|
||||||
public enum TimeToRetain {
|
public enum TimeToRetain {
|
||||||
|
|
||||||
BTN_12_HOURS("12 hours", "43200000"),
|
BTN_12_HOURS("12 hours", "43200000"),
|
||||||
BTN_1_DAY("1 day", "86400000"),
|
BTN_1_DAY("1 day", "86400000"),
|
||||||
BTN_2_DAYS("2 days", "172800000"),
|
BTN_2_DAYS("2 days", "172800000"),
|
||||||
BTN_7_DAYS("7 days", "604800000"),
|
BTN_7_DAYS("7 days", "604800000"),
|
||||||
BTN_4_WEEKS("4 weeks", "2419200000");
|
BTN_4_WEEKS("4 weeks", "2419200000");
|
||||||
|
|
||||||
private final String button;
|
private final String button;
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
TimeToRetain(String button, String value) {
|
TimeToRetain(String button, String value) {
|
||||||
this.button = button;
|
this.button = button;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getButton() {
|
public String getButton() {
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,272 +1,282 @@
|
||||||
package com.provectus.kafka.ui.services;
|
package com.provectus.kafka.ui.services;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.sleep;
|
||||||
|
import static com.provectus.kafka.ui.utilities.FileUtils.fileToString;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.provectus.kafka.ui.api.ApiClient;
|
import com.provectus.kafka.ui.api.ApiClient;
|
||||||
import com.provectus.kafka.ui.api.api.*;
|
import com.provectus.kafka.ui.api.api.KafkaConnectApi;
|
||||||
import com.provectus.kafka.ui.api.model.*;
|
import com.provectus.kafka.ui.api.api.KsqlApi;
|
||||||
|
import com.provectus.kafka.ui.api.api.MessagesApi;
|
||||||
|
import com.provectus.kafka.ui.api.api.SchemasApi;
|
||||||
|
import com.provectus.kafka.ui.api.api.TopicsApi;
|
||||||
|
import com.provectus.kafka.ui.api.model.CreateTopicMessage;
|
||||||
|
import com.provectus.kafka.ui.api.model.KsqlCommandV2;
|
||||||
|
import com.provectus.kafka.ui.api.model.KsqlCommandV2Response;
|
||||||
|
import com.provectus.kafka.ui.api.model.KsqlResponse;
|
||||||
|
import com.provectus.kafka.ui.api.model.NewConnector;
|
||||||
|
import com.provectus.kafka.ui.api.model.NewSchemaSubject;
|
||||||
|
import com.provectus.kafka.ui.api.model.TopicCreation;
|
||||||
import com.provectus.kafka.ui.models.Connector;
|
import com.provectus.kafka.ui.models.Connector;
|
||||||
import com.provectus.kafka.ui.models.Schema;
|
import com.provectus.kafka.ui.models.Schema;
|
||||||
import com.provectus.kafka.ui.models.Topic;
|
import com.provectus.kafka.ui.models.Topic;
|
||||||
import com.provectus.kafka.ui.pages.ksqlDb.models.Stream;
|
import com.provectus.kafka.ui.pages.ksqldb.models.Stream;
|
||||||
import com.provectus.kafka.ui.pages.ksqlDb.models.Table;
|
import com.provectus.kafka.ui.pages.ksqldb.models.Table;
|
||||||
import com.provectus.kafka.ui.settings.BaseSource;
|
import com.provectus.kafka.ui.settings.BaseSource;
|
||||||
import io.qameta.allure.Step;
|
import io.qameta.allure.Step;
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import static com.codeborne.selenide.Selenide.sleep;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import static com.provectus.kafka.ui.utilities.FileUtils.fileToString;
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ApiService extends BaseSource {
|
public class ApiService extends BaseSource {
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private TopicsApi topicApi() {
|
private TopicsApi topicApi() {
|
||||||
return new TopicsApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
|
return new TopicsApi(new ApiClient().setBasePath(BASE_API_URL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private SchemasApi schemaApi() {
|
private SchemasApi schemaApi() {
|
||||||
return new SchemasApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
|
return new SchemasApi(new ApiClient().setBasePath(BASE_API_URL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private KafkaConnectApi connectorApi() {
|
private KafkaConnectApi connectorApi() {
|
||||||
return new KafkaConnectApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
|
return new KafkaConnectApi(new ApiClient().setBasePath(BASE_API_URL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private MessagesApi messageApi() {
|
private MessagesApi messageApi() {
|
||||||
return new MessagesApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
|
return new MessagesApi(new ApiClient().setBasePath(BASE_API_URL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private KsqlApi ksqlApi() {
|
private KsqlApi ksqlApi() {
|
||||||
return new KsqlApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
|
return new KsqlApi(new ApiClient().setBasePath(BASE_API_URL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void createTopic(String clusterName, String topicName) {
|
private void createTopic(String clusterName, String topicName) {
|
||||||
TopicCreation topic = new TopicCreation();
|
TopicCreation topic = new TopicCreation();
|
||||||
topic.setName(topicName);
|
topic.setName(topicName);
|
||||||
topic.setPartitions(1);
|
topic.setPartitions(1);
|
||||||
topic.setReplicationFactor(1);
|
topic.setReplicationFactor(1);
|
||||||
try {
|
try {
|
||||||
topicApi().createTopic(clusterName, topic).block();
|
topicApi().createTopic(clusterName, topic).block();
|
||||||
sleep(2000);
|
sleep(2000);
|
||||||
} catch (WebClientResponseException ex) {
|
} catch (WebClientResponseException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService createTopic(Topic topic) {
|
public ApiService createTopic(Topic topic) {
|
||||||
createTopic(CLUSTER_NAME, topic.getName());
|
createTopic(CLUSTER_NAME, topic.getName());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void deleteTopic(String clusterName, String topicName) {
|
private void deleteTopic(String clusterName, String topicName) {
|
||||||
try {
|
try {
|
||||||
topicApi().deleteTopic(clusterName, topicName).block();
|
topicApi().deleteTopic(clusterName, topicName).block();
|
||||||
} catch (WebClientResponseException ignore) {
|
} catch (WebClientResponseException ignored) {
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService deleteTopic(String topicName) {
|
public ApiService deleteTopic(String topicName) {
|
||||||
deleteTopic(CLUSTER_NAME, topicName);
|
deleteTopic(CLUSTER_NAME, topicName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void createSchema(String clusterName, Schema schema) {
|
private void createSchema(String clusterName, Schema schema) {
|
||||||
NewSchemaSubject schemaSubject = new NewSchemaSubject();
|
NewSchemaSubject schemaSubject = new NewSchemaSubject();
|
||||||
schemaSubject.setSubject(schema.getName());
|
schemaSubject.setSubject(schema.getName());
|
||||||
schemaSubject.setSchema(fileToString(schema.getValuePath()));
|
schemaSubject.setSchema(fileToString(schema.getValuePath()));
|
||||||
schemaSubject.setSchemaType(schema.getType());
|
schemaSubject.setSchemaType(schema.getType());
|
||||||
try {
|
try {
|
||||||
schemaApi().createNewSchema(clusterName, schemaSubject).block();
|
schemaApi().createNewSchema(clusterName, schemaSubject).block();
|
||||||
} catch (WebClientResponseException ex) {
|
} catch (WebClientResponseException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService createSchema(Schema schema) {
|
public ApiService createSchema(Schema schema) {
|
||||||
createSchema(CLUSTER_NAME, schema);
|
createSchema(CLUSTER_NAME, schema);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void deleteSchema(String clusterName, String schemaName) {
|
private void deleteSchema(String clusterName, String schemaName) {
|
||||||
try {
|
try {
|
||||||
schemaApi().deleteSchema(clusterName, schemaName).block();
|
schemaApi().deleteSchema(clusterName, schemaName).block();
|
||||||
} catch (WebClientResponseException ignore) {
|
} catch (WebClientResponseException ignored) {
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService deleteSchema(String schemaName) {
|
public ApiService deleteSchema(String schemaName) {
|
||||||
deleteSchema(CLUSTER_NAME, schemaName);
|
deleteSchema(CLUSTER_NAME, schemaName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void deleteConnector(String clusterName, String connectName, String connectorName) {
|
private void deleteConnector(String clusterName, String connectName, String connectorName) {
|
||||||
try {
|
try {
|
||||||
connectorApi().deleteConnector(clusterName, connectName, connectorName).block();
|
connectorApi().deleteConnector(clusterName, connectName, connectorName).block();
|
||||||
} catch (WebClientResponseException ignore) {
|
} catch (WebClientResponseException ignored) {
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService deleteConnector(String connectName, String connectorName) {
|
public ApiService deleteConnector(String connectName, String connectorName) {
|
||||||
deleteConnector(CLUSTER_NAME, connectName, connectorName);
|
deleteConnector(CLUSTER_NAME, connectName, connectorName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService deleteConnector(String connectorName) {
|
public ApiService deleteConnector(String connectorName) {
|
||||||
deleteConnector(CLUSTER_NAME, CONNECT_NAME, connectorName);
|
deleteConnector(CLUSTER_NAME, CONNECT_NAME, connectorName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void createConnector(String clusterName, String connectName, Connector connector) {
|
private void createConnector(String clusterName, String connectName, Connector connector) {
|
||||||
NewConnector connectorProperties = new NewConnector();
|
NewConnector connectorProperties = new NewConnector();
|
||||||
connectorProperties.setName(connector.getName());
|
connectorProperties.setName(connector.getName());
|
||||||
Map<String, Object> configMap = new ObjectMapper().readValue(connector.getConfig(), HashMap.class);
|
Map<String, Object> configMap = new ObjectMapper().readValue(connector.getConfig(), HashMap.class);
|
||||||
connectorProperties.setConfig(configMap);
|
connectorProperties.setConfig(configMap);
|
||||||
try {
|
try {
|
||||||
connectorApi().deleteConnector(clusterName, connectName, connector.getName()).block();
|
connectorApi().deleteConnector(clusterName, connectName, connector.getName()).block();
|
||||||
} catch (WebClientResponseException ignored) {
|
} catch (WebClientResponseException ignored) {
|
||||||
}
|
|
||||||
connectorApi().createConnector(clusterName, connectName, connectorProperties).block();
|
|
||||||
}
|
}
|
||||||
|
connectorApi().createConnector(clusterName, connectName, connectorProperties).block();
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService createConnector(String connectName, Connector connector) {
|
public ApiService createConnector(String connectName, Connector connector) {
|
||||||
createConnector(CLUSTER_NAME, connectName, connector);
|
createConnector(CLUSTER_NAME, connectName, connector);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService createConnector(Connector connector) {
|
public ApiService createConnector(Connector connector) {
|
||||||
createConnector(CLUSTER_NAME, CONNECT_NAME, connector);
|
createConnector(CLUSTER_NAME, CONNECT_NAME, connector);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public String getFirstConnectName(String clusterName) {
|
public String getFirstConnectName(String clusterName) {
|
||||||
return Objects.requireNonNull(connectorApi().getConnects(clusterName).blockFirst()).getName();
|
return Objects.requireNonNull(connectorApi().getConnects(clusterName).blockFirst()).getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void sendMessage(String clusterName, Topic topic) {
|
private void sendMessage(String clusterName, Topic topic) {
|
||||||
CreateTopicMessage createMessage = new CreateTopicMessage();
|
CreateTopicMessage createMessage = new CreateTopicMessage();
|
||||||
createMessage.setPartition(0);
|
createMessage.setPartition(0);
|
||||||
createMessage.setKeySerde("String");
|
createMessage.setKeySerde("String");
|
||||||
createMessage.setValueSerde("String");
|
createMessage.setValueSerde("String");
|
||||||
createMessage.setKey(topic.getMessageKey());
|
createMessage.setKey(topic.getMessageKey());
|
||||||
createMessage.setContent(topic.getMessageContent());
|
createMessage.setContent(topic.getMessageValue());
|
||||||
try {
|
try {
|
||||||
messageApi().sendTopicMessages(clusterName, topic.getName(), createMessage).block();
|
messageApi().sendTopicMessages(clusterName, topic.getName(), createMessage).block();
|
||||||
} catch (WebClientResponseException ex) {
|
} catch (WebClientResponseException ex) {
|
||||||
ex.getRawStatusCode();
|
ex.getRawStatusCode();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService sendMessage(Topic topic) {
|
public ApiService sendMessage(Topic topic) {
|
||||||
sendMessage(CLUSTER_NAME, topic);
|
sendMessage(CLUSTER_NAME, topic);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService createStream(Stream stream) {
|
public ApiService createStream(Stream stream) {
|
||||||
KsqlCommandV2Response pipeIdStream = ksqlApi()
|
KsqlCommandV2Response pipeIdStream = ksqlApi()
|
||||||
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
|
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
|
||||||
.ksql(String.format("CREATE STREAM %s (profileId VARCHAR, latitude DOUBLE, longitude DOUBLE) ",
|
.ksql(String.format("CREATE STREAM %s (profileId VARCHAR, latitude DOUBLE, longitude DOUBLE) ",
|
||||||
stream.getName())
|
stream.getName())
|
||||||
+ String.format("WITH (kafka_topic='%s', value_format='json', partitions=1);",
|
+ String.format("WITH (kafka_topic='%s', value_format='json', partitions=1);",
|
||||||
stream.getTopicName())))
|
stream.getTopicName())))
|
||||||
.block();
|
.block();
|
||||||
assert pipeIdStream != null;
|
assert pipeIdStream != null;
|
||||||
List<KsqlResponse> responseListStream = ksqlApi()
|
List<KsqlResponse> responseListStream = ksqlApi()
|
||||||
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdStream.getPipeId())
|
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdStream.getPipeId())
|
||||||
.collectList()
|
.collectList()
|
||||||
.block();
|
.block();
|
||||||
assert Objects.requireNonNull(responseListStream).size() != 0;
|
assert Objects.requireNonNull(responseListStream).size() != 0;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService createTables(Table firstTable, Table secondTable) {
|
public ApiService createTables(Table firstTable, Table secondTable) {
|
||||||
KsqlCommandV2Response pipeIdTable1 = ksqlApi()
|
KsqlCommandV2Response pipeIdTable1 = ksqlApi()
|
||||||
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
|
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
|
||||||
.ksql(String.format("CREATE TABLE %s AS ", firstTable.getName())
|
.ksql(String.format("CREATE TABLE %s AS ", firstTable.getName())
|
||||||
+ " SELECT profileId, "
|
+ " SELECT profileId, "
|
||||||
+ " LATEST_BY_OFFSET(latitude) AS la, "
|
+ " LATEST_BY_OFFSET(latitude) AS la, "
|
||||||
+ " LATEST_BY_OFFSET(longitude) AS lo "
|
+ " LATEST_BY_OFFSET(longitude) AS lo "
|
||||||
+ String.format(" FROM %s ", firstTable.getStreamName())
|
+ String.format(" FROM %s ", firstTable.getStreamName())
|
||||||
+ " GROUP BY profileId "
|
+ " GROUP BY profileId "
|
||||||
+ " EMIT CHANGES;"))
|
+ " EMIT CHANGES;"))
|
||||||
.block();
|
.block();
|
||||||
assert pipeIdTable1 != null;
|
assert pipeIdTable1 != null;
|
||||||
List<KsqlResponse> responseListTable = ksqlApi()
|
List<KsqlResponse> responseListTable = ksqlApi()
|
||||||
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdTable1.getPipeId())
|
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdTable1.getPipeId())
|
||||||
.collectList()
|
.collectList()
|
||||||
.block();
|
.block();
|
||||||
assert Objects.requireNonNull(responseListTable).size() != 0;
|
assert Objects.requireNonNull(responseListTable).size() != 0;
|
||||||
KsqlCommandV2Response pipeIdTable2 = ksqlApi()
|
KsqlCommandV2Response pipeIdTable2 = ksqlApi()
|
||||||
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
|
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
|
||||||
.ksql(String.format("CREATE TABLE %s AS ", secondTable.getName())
|
.ksql(String.format("CREATE TABLE %s AS ", secondTable.getName())
|
||||||
+ " SELECT ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1) AS distanceInMiles, "
|
+ " SELECT ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1) AS distanceInMiles, "
|
||||||
+ " COLLECT_LIST(profileId) AS riders, "
|
+ " COLLECT_LIST(profileId) AS riders, "
|
||||||
+ " COUNT(*) AS count "
|
+ " COUNT(*) AS count "
|
||||||
+ String.format(" FROM %s ", firstTable.getName())
|
+ String.format(" FROM %s ", firstTable.getName())
|
||||||
+ " GROUP BY ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1);"))
|
+ " GROUP BY ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1);"))
|
||||||
.block();
|
.block();
|
||||||
assert pipeIdTable2 != null;
|
assert pipeIdTable2 != null;
|
||||||
List<KsqlResponse> responseListTable2 = ksqlApi()
|
List<KsqlResponse> responseListTable2 = ksqlApi()
|
||||||
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdTable2.getPipeId())
|
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdTable2.getPipeId())
|
||||||
.collectList()
|
.collectList()
|
||||||
.block();
|
.block();
|
||||||
assert Objects.requireNonNull(responseListTable2).size() != 0;
|
assert Objects.requireNonNull(responseListTable2).size() != 0;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
public ApiService insertInto(Stream stream) {
|
public ApiService insertInto(Stream stream) {
|
||||||
String streamName = stream.getName();
|
String streamName = stream.getName();
|
||||||
KsqlCommandV2Response pipeIdInsert = ksqlApi()
|
KsqlCommandV2Response pipeIdInsert = ksqlApi()
|
||||||
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
|
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
|
||||||
.ksql("INSERT INTO " + streamName + " (profileId, latitude, longitude) VALUES ('c2309eec', 37.7877, -122.4205);"
|
.ksql("INSERT INTO " + streamName
|
||||||
+ "INSERT INTO " + streamName +
|
+ " (profileId, latitude, longitude) VALUES ('c2309eec', 37.7877, -122.4205);"
|
||||||
" (profileId, latitude, longitude) VALUES ('18f4ea86', 37.3903, -122.0643); "
|
+ "INSERT INTO " + streamName
|
||||||
+ "INSERT INTO " + streamName +
|
+ " (profileId, latitude, longitude) VALUES ('18f4ea86', 37.3903, -122.0643); "
|
||||||
" (profileId, latitude, longitude) VALUES ('4ab5cbad', 37.3952, -122.0813); "
|
+ "INSERT INTO " + streamName
|
||||||
+ "INSERT INTO " + streamName +
|
+ " (profileId, latitude, longitude) VALUES ('4ab5cbad', 37.3952, -122.0813); "
|
||||||
" (profileId, latitude, longitude) VALUES ('8b6eae59', 37.3944, -122.0813); "
|
+ "INSERT INTO " + streamName
|
||||||
+ "INSERT INTO " + streamName +
|
+ " (profileId, latitude, longitude) VALUES ('8b6eae59', 37.3944, -122.0813); "
|
||||||
" (profileId, latitude, longitude) VALUES ('4a7c7b41', 37.4049, -122.0822); "
|
+ "INSERT INTO " + streamName
|
||||||
+ "INSERT INTO " + streamName +
|
+ " (profileId, latitude, longitude) VALUES ('4a7c7b41', 37.4049, -122.0822); "
|
||||||
" (profileId, latitude, longitude) VALUES ('4ddad000', 37.7857, -122.4011);"))
|
+ "INSERT INTO " + streamName
|
||||||
.block();
|
+ " (profileId, latitude, longitude) VALUES ('4ddad000', 37.7857, -122.4011);"))
|
||||||
assert pipeIdInsert != null;
|
.block();
|
||||||
List<KsqlResponse> responseListInsert = ksqlApi()
|
assert pipeIdInsert != null;
|
||||||
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdInsert.getPipeId())
|
List<KsqlResponse> responseListInsert = ksqlApi()
|
||||||
.collectList()
|
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdInsert.getPipeId())
|
||||||
.block();
|
.collectList()
|
||||||
assert Objects.requireNonNull(responseListInsert).size() != 0;
|
.block();
|
||||||
return this;
|
assert Objects.requireNonNull(responseListInsert).size() != 0;
|
||||||
}
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,29 @@
|
||||||
package com.provectus.kafka.ui.settings;
|
package com.provectus.kafka.ui.settings;
|
||||||
|
|
||||||
|
import static com.provectus.kafka.ui.variables.Browser.LOCAL;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.settings.configs.Config;
|
import com.provectus.kafka.ui.settings.configs.Config;
|
||||||
import org.aeonbits.owner.ConfigFactory;
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
|
||||||
public abstract class BaseSource {
|
public abstract class BaseSource {
|
||||||
|
|
||||||
public static final String BASE_CONTAINER_URL = "http://host.testcontainers.internal:8080";
|
public static final String CLUSTER_NAME = "local";
|
||||||
public static final String BASE_LOCAL_URL = "http://localhost:8080";
|
public static final String CONNECT_NAME = "first";
|
||||||
public static final String CLUSTER_NAME = "local";
|
private static final String LOCAL_HOST = "localhost";
|
||||||
public static final String CONNECT_NAME = "first";
|
public static final String REMOTE_URL = String.format("http://%s:4444/wd/hub", LOCAL_HOST);
|
||||||
private static Config config;
|
public static final String BASE_API_URL = String.format("http://%s:8080", LOCAL_HOST);
|
||||||
public static final String BROWSER = config().browser();
|
private static Config config;
|
||||||
public static final String SUITE_NAME = config().suite();
|
public static final String BROWSER = config().browser();
|
||||||
|
public static final String BASE_HOST = BROWSER.equals(LOCAL)
|
||||||
|
? LOCAL_HOST
|
||||||
|
: "host.docker.internal";
|
||||||
|
public static final String BASE_UI_URL = String.format("http://%s:8080", BASE_HOST);
|
||||||
|
public static final String SUITE_NAME = config().suite();
|
||||||
|
|
||||||
private static Config config() {
|
private static Config config() {
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
config = ConfigFactory.create(Config.class, System.getProperties());
|
config = ConfigFactory.create(Config.class, System.getProperties());
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
package com.provectus.kafka.ui.settings.configs;
|
package com.provectus.kafka.ui.settings.configs;
|
||||||
|
|
||||||
import org.aeonbits.owner.Config;
|
|
||||||
|
|
||||||
import static com.provectus.kafka.ui.variables.Browser.CONTAINER;
|
import static com.provectus.kafka.ui.variables.Browser.CONTAINER;
|
||||||
import static com.provectus.kafka.ui.variables.Suite.CUSTOM;
|
import static com.provectus.kafka.ui.variables.Suite.CUSTOM;
|
||||||
|
|
||||||
|
import org.aeonbits.owner.Config;
|
||||||
|
|
||||||
public interface Profiles extends Config {
|
public interface Profiles extends Config {
|
||||||
|
|
||||||
@Key("browser")
|
@Key("browser")
|
||||||
@DefaultValue(CONTAINER)
|
@DefaultValue(CONTAINER)
|
||||||
String browser();
|
String browser();
|
||||||
|
|
||||||
@Key("suite")
|
@Key("suite")
|
||||||
@DefaultValue(CUSTOM)
|
@DefaultValue(CUSTOM)
|
||||||
String suite();
|
String suite();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
package com.provectus.kafka.ui.settings.drivers;
|
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.clearBrowserCookies;
|
|
||||||
import static com.codeborne.selenide.Selenide.clearBrowserLocalStorage;
|
|
||||||
import static com.codeborne.selenide.Selenide.open;
|
|
||||||
import static com.codeborne.selenide.Selenide.refresh;
|
|
||||||
|
|
||||||
import com.codeborne.selenide.Configuration;
|
|
||||||
import com.codeborne.selenide.WebDriverRunner;
|
|
||||||
import com.codeborne.selenide.logevents.SelenideLogger;
|
|
||||||
import io.qameta.allure.Step;
|
|
||||||
import io.qameta.allure.selenide.AllureSelenide;
|
|
||||||
import org.openqa.selenium.chrome.ChromeOptions;
|
|
||||||
|
|
||||||
public abstract class LocalWebDriver {
|
|
||||||
|
|
||||||
private static org.openqa.selenium.WebDriver getWebDriver() {
|
|
||||||
try {
|
|
||||||
return WebDriverRunner.getWebDriver();
|
|
||||||
} catch (IllegalStateException ex) {
|
|
||||||
Configuration.headless = false;
|
|
||||||
Configuration.browser = "chrome";
|
|
||||||
Configuration.browserSize = "1920x1080";
|
|
||||||
/**screenshots and savePageSource config is needed for local debug
|
|
||||||
* optionally can be set as 'false' to not duplicate Allure report
|
|
||||||
*/
|
|
||||||
Configuration.screenshots = true;
|
|
||||||
Configuration.savePageSource = true;
|
|
||||||
Configuration.pageLoadTimeout = 120000;
|
|
||||||
Configuration.browserCapabilities = new ChromeOptions()
|
|
||||||
.addArguments("--remote-allow-origins=*")
|
|
||||||
.addArguments("--lang=en_US");
|
|
||||||
open();
|
|
||||||
return WebDriverRunner.getWebDriver();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public static void openUrl(String url) {
|
|
||||||
if (!getWebDriver().getCurrentUrl().equals(url)) {
|
|
||||||
getWebDriver().get(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public static void browserInit() {
|
|
||||||
getWebDriver();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public static void browserClear() {
|
|
||||||
clearBrowserLocalStorage();
|
|
||||||
clearBrowserCookies();
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public static void browserQuit() {
|
|
||||||
getWebDriver().quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
public static void loggerSetup() {
|
|
||||||
SelenideLogger.addListener("AllureSelenide", new AllureSelenide()
|
|
||||||
.screenshots(true)
|
|
||||||
.savePageSource(false));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.provectus.kafka.ui.settings.drivers;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.clearBrowserCookies;
|
||||||
|
import static com.codeborne.selenide.Selenide.clearBrowserLocalStorage;
|
||||||
|
import static com.codeborne.selenide.Selenide.refresh;
|
||||||
|
import static com.provectus.kafka.ui.settings.BaseSource.BROWSER;
|
||||||
|
import static com.provectus.kafka.ui.settings.BaseSource.REMOTE_URL;
|
||||||
|
import static com.provectus.kafka.ui.variables.Browser.CONTAINER;
|
||||||
|
import static com.provectus.kafka.ui.variables.Browser.LOCAL;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Configuration;
|
||||||
|
import com.codeborne.selenide.Selenide;
|
||||||
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
import com.codeborne.selenide.logevents.SelenideLogger;
|
||||||
|
import io.qameta.allure.Step;
|
||||||
|
import io.qameta.allure.selenide.AllureSelenide;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.openqa.selenium.chrome.ChromeOptions;
|
||||||
|
import org.openqa.selenium.remote.DesiredCapabilities;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class WebDriver {
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public static void browserSetup() {
|
||||||
|
Configuration.headless = false;
|
||||||
|
Configuration.browser = "chrome";
|
||||||
|
Configuration.browserSize = "1920x1080";
|
||||||
|
Configuration.screenshots = true;
|
||||||
|
Configuration.savePageSource = false;
|
||||||
|
Configuration.pageLoadTimeout = 120000;
|
||||||
|
ChromeOptions options = new ChromeOptions()
|
||||||
|
.addArguments("--no-sandbox")
|
||||||
|
.addArguments("--verbose")
|
||||||
|
.addArguments("--remote-allow-origins=*")
|
||||||
|
.addArguments("--disable-dev-shm-usage")
|
||||||
|
.addArguments("--disable-gpu")
|
||||||
|
.addArguments("--lang=en_US");
|
||||||
|
switch (BROWSER) {
|
||||||
|
case (LOCAL) -> Configuration.browserCapabilities = options;
|
||||||
|
case (CONTAINER) -> {
|
||||||
|
Configuration.remote = REMOTE_URL;
|
||||||
|
Configuration.remoteConnectionTimeout = 180000;
|
||||||
|
DesiredCapabilities capabilities = new DesiredCapabilities();
|
||||||
|
capabilities.setCapability("enableVNC", true);
|
||||||
|
capabilities.setCapability("enableVideo", false);
|
||||||
|
Configuration.browserCapabilities = capabilities.merge(options);
|
||||||
|
}
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + BROWSER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static org.openqa.selenium.WebDriver getWebDriver() {
|
||||||
|
try {
|
||||||
|
return WebDriverRunner.getWebDriver();
|
||||||
|
} catch (IllegalStateException ex) {
|
||||||
|
browserSetup();
|
||||||
|
Selenide.open();
|
||||||
|
return WebDriverRunner.getWebDriver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public static void openUrl(String url) {
|
||||||
|
org.openqa.selenium.WebDriver driver = getWebDriver();
|
||||||
|
if (!driver.getCurrentUrl().equals(url)) {
|
||||||
|
driver.get(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public static void browserInit() {
|
||||||
|
getWebDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public static void browserClear() {
|
||||||
|
clearBrowserLocalStorage();
|
||||||
|
clearBrowserCookies();
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public static void browserQuit() {
|
||||||
|
org.openqa.selenium.WebDriver driver = null;
|
||||||
|
try {
|
||||||
|
driver = WebDriverRunner.getWebDriver();
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
if (driver != null) {
|
||||||
|
driver.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
public static void loggerSetup() {
|
||||||
|
SelenideLogger.addListener("AllureSelenide", new AllureSelenide()
|
||||||
|
.screenshots(true)
|
||||||
|
.savePageSource(false));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +1,39 @@
|
||||||
package com.provectus.kafka.ui.settings.listeners;
|
package com.provectus.kafka.ui.settings.listeners;
|
||||||
|
|
||||||
|
import static java.nio.file.Files.newInputStream;
|
||||||
|
|
||||||
import com.codeborne.selenide.Screenshots;
|
import com.codeborne.selenide.Screenshots;
|
||||||
import io.qameta.allure.Allure;
|
import io.qameta.allure.Allure;
|
||||||
import io.qameta.allure.testng.AllureTestNg;
|
import io.qameta.allure.testng.AllureTestNg;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.testng.ITestListener;
|
import org.testng.ITestListener;
|
||||||
import org.testng.ITestResult;
|
import org.testng.ITestResult;
|
||||||
|
|
||||||
import java.io.File;
|
@Slf4j
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static java.nio.file.Files.newInputStream;
|
|
||||||
|
|
||||||
public class AllureListener extends AllureTestNg implements ITestListener {
|
public class AllureListener extends AllureTestNg implements ITestListener {
|
||||||
|
|
||||||
private void takeScreenshot() {
|
private void takeScreenshot() {
|
||||||
File screenshot = Screenshots.takeScreenShotAsFile();
|
File screenshot = Screenshots.takeScreenShotAsFile();
|
||||||
try {
|
try {
|
||||||
Allure.addAttachment(Objects.requireNonNull(screenshot).getName(), newInputStream(screenshot.toPath()));
|
if (screenshot != null) {
|
||||||
} catch (IOException e) {
|
Allure.addAttachment(screenshot.getName(), newInputStream(screenshot.toPath()));
|
||||||
throw new RuntimeException(e);
|
} else {
|
||||||
}
|
log.warn("Unable to take screenshot");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestFailure(ITestResult result) {
|
public void onTestFailure(ITestResult result) {
|
||||||
takeScreenshot();
|
takeScreenshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestSkipped(ITestResult result) {
|
public void onTestSkipped(ITestResult result) {
|
||||||
takeScreenshot();
|
takeScreenshot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,31 +7,31 @@ import org.testng.TestListenerAdapter;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LoggerListener extends TestListenerAdapter {
|
public class LoggerListener extends TestListenerAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestStart(final ITestResult testResult) {
|
public void onTestStart(final ITestResult testResult) {
|
||||||
log.info(String.format("\n------------------------------------------------------------------------ " +
|
log.info(String.format("\n------------------------------------------------------------------------ "
|
||||||
"\nTEST STARTED: %s.%s \n------------------------------------------------------------------------ \n",
|
+ "\nTEST STARTED: %s.%s \n------------------------------------------------------------------------ \n",
|
||||||
testResult.getInstanceName(), testResult.getName()));
|
testResult.getInstanceName(), testResult.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestSuccess(final ITestResult testResult) {
|
public void onTestSuccess(final ITestResult testResult) {
|
||||||
log.info(String.format("\n------------------------------------------------------------------------ " +
|
log.info(String.format("\n------------------------------------------------------------------------ "
|
||||||
"\nTEST PASSED: %s.%s \n------------------------------------------------------------------------ \n",
|
+ "\nTEST PASSED: %s.%s \n------------------------------------------------------------------------ \n",
|
||||||
testResult.getInstanceName(), testResult.getName()));
|
testResult.getInstanceName(), testResult.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestFailure(final ITestResult testResult) {
|
public void onTestFailure(final ITestResult testResult) {
|
||||||
log.info(String.format("\n------------------------------------------------------------------------ " +
|
log.info(String.format("\n------------------------------------------------------------------------ "
|
||||||
"\nTEST FAILED: %s.%s \n------------------------------------------------------------------------ \n",
|
+ "\nTEST FAILED: %s.%s \n------------------------------------------------------------------------ \n",
|
||||||
testResult.getInstanceName(), testResult.getName()));
|
testResult.getInstanceName(), testResult.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestSkipped(final ITestResult testResult) {
|
public void onTestSkipped(final ITestResult testResult) {
|
||||||
log.info(String.format("\n------------------------------------------------------------------------ " +
|
log.info(String.format("\n------------------------------------------------------------------------ "
|
||||||
"\nTEST SKIPPED: %s.%s \n------------------------------------------------------------------------ \n",
|
+ "\nTEST SKIPPED: %s.%s \n------------------------------------------------------------------------ \n",
|
||||||
testResult.getInstanceName(), testResult.getName()));
|
testResult.getInstanceName(), testResult.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,28 @@
|
||||||
package com.provectus.kafka.ui.settings.listeners;
|
package com.provectus.kafka.ui.settings.listeners;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Automation;
|
import static io.qase.api.utils.IntegrationUtils.getCaseTitle;
|
||||||
import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Status;
|
|
||||||
import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Suite;
|
import com.provectus.kafka.ui.utilities.qase.annotations.Automation;
|
||||||
|
import com.provectus.kafka.ui.utilities.qase.annotations.Status;
|
||||||
|
import com.provectus.kafka.ui.utilities.qase.annotations.Suite;
|
||||||
import io.qase.api.QaseClient;
|
import io.qase.api.QaseClient;
|
||||||
import io.qase.api.StepStorage;
|
import io.qase.api.StepStorage;
|
||||||
import io.qase.api.annotation.QaseId;
|
import io.qase.api.annotation.QaseId;
|
||||||
import io.qase.client.ApiClient;
|
import io.qase.client.ApiClient;
|
||||||
import io.qase.client.api.CasesApi;
|
import io.qase.client.api.CasesApi;
|
||||||
import io.qase.client.model.*;
|
import io.qase.client.model.GetCasesFiltersParameter;
|
||||||
|
import io.qase.client.model.ResultCreateStepsInner;
|
||||||
|
import io.qase.client.model.TestCase;
|
||||||
|
import io.qase.client.model.TestCaseCreate;
|
||||||
|
import io.qase.client.model.TestCaseCreateStepsInner;
|
||||||
|
import io.qase.client.model.TestCaseListResponse;
|
||||||
|
import io.qase.client.model.TestCaseListResponseAllOfResult;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.testng.Assert;
|
import org.testng.Assert;
|
||||||
|
@ -16,108 +30,107 @@ import org.testng.ITestListener;
|
||||||
import org.testng.ITestResult;
|
import org.testng.ITestResult;
|
||||||
import org.testng.TestListenerAdapter;
|
import org.testng.TestListenerAdapter;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static io.qase.api.utils.IntegrationUtils.getCaseTitle;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class QaseCreateListener extends TestListenerAdapter implements ITestListener {
|
public class QaseCreateListener extends TestListenerAdapter implements ITestListener {
|
||||||
|
|
||||||
private static final CasesApi QASE_API = getQaseApi();
|
private static final CasesApi QASE_API = getQaseApi();
|
||||||
|
|
||||||
private static CasesApi getQaseApi() {
|
private static CasesApi getQaseApi() {
|
||||||
ApiClient apiClient = QaseClient.getApiClient();
|
ApiClient apiClient = QaseClient.getApiClient();
|
||||||
apiClient.setApiKey(System.getProperty("QASEIO_API_TOKEN"));
|
apiClient.setApiKey(System.getProperty("QASEIO_API_TOKEN"));
|
||||||
return new CasesApi(apiClient);
|
return new CasesApi(apiClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getStatus(Method method) {
|
||||||
|
if (method.isAnnotationPresent(Status.class)) {
|
||||||
|
return method.getDeclaredAnnotation(Status.class).status().getValue();
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
private static int getStatus(Method method) {
|
private static int getAutomation(Method method) {
|
||||||
if (method.isAnnotationPresent(Status.class))
|
if (method.isAnnotationPresent(Automation.class)) {
|
||||||
return method.getDeclaredAnnotation(Status.class).status().getValue();
|
return method.getDeclaredAnnotation(Automation.class).state().getValue();
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static int getAutomation(Method method) {
|
@SneakyThrows
|
||||||
if (method.isAnnotationPresent(Automation.class))
|
private static HashMap<Long, String> getCaseTitlesAndIdsFromQase() {
|
||||||
return method.getDeclaredAnnotation(Automation.class).state().getValue();
|
HashMap<Long, String> cases = new HashMap<>();
|
||||||
return 0;
|
boolean getCases = true;
|
||||||
}
|
int offSet = 0;
|
||||||
|
while (getCases) {
|
||||||
@SneakyThrows
|
getCases = false;
|
||||||
private static HashMap<Long, String> getCaseTitlesAndIdsFromQase() {
|
TestCaseListResponse response = QASE_API.getCases(System.getProperty("QASE_PROJECT_CODE"),
|
||||||
HashMap<Long, String> cases = new HashMap<>();
|
new GetCasesFiltersParameter().status(GetCasesFiltersParameter.SERIALIZED_NAME_STATUS), 100, offSet);
|
||||||
boolean getCases = true;
|
TestCaseListResponseAllOfResult result = response.getResult();
|
||||||
int offSet = 0;
|
Assert.assertNotNull(result);
|
||||||
while (getCases) {
|
List<TestCase> entities = result.getEntities();
|
||||||
getCases = false;
|
Assert.assertNotNull(entities);
|
||||||
TestCaseListResponse response = QASE_API.getCases(System.getProperty("QASE_PROJECT_CODE"),
|
if (entities.size() > 0) {
|
||||||
new GetCasesFiltersParameter().status(GetCasesFiltersParameter.SERIALIZED_NAME_STATUS), 100, offSet);
|
for (TestCase testCase : entities) {
|
||||||
TestCaseListResponseAllOfResult result = response.getResult();
|
cases.put(testCase.getId(), testCase.getTitle());
|
||||||
Assert.assertNotNull(result);
|
|
||||||
List<TestCase> entities = result.getEntities();
|
|
||||||
Assert.assertNotNull(entities);
|
|
||||||
if (entities.size() > 0) {
|
|
||||||
for (TestCase testCase : entities) {
|
|
||||||
cases.put(testCase.getId(), testCase.getTitle());
|
|
||||||
}
|
|
||||||
offSet = offSet + 100;
|
|
||||||
getCases = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return cases;
|
offSet = offSet + 100;
|
||||||
|
getCases = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return cases;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isCaseWithTitleExistInQase(Method method) {
|
private static boolean isCaseWithTitleExistInQase(Method method) {
|
||||||
HashMap<Long, String> cases = getCaseTitlesAndIdsFromQase();
|
HashMap<Long, String> cases = getCaseTitlesAndIdsFromQase();
|
||||||
String title = getCaseTitle(method);
|
String title = getCaseTitle(method);
|
||||||
if (cases.containsValue(title)) {
|
if (cases.containsValue(title)) {
|
||||||
for (Map.Entry<Long, String> map : cases.entrySet()) {
|
for (Map.Entry<Long, String> map : cases.entrySet()) {
|
||||||
if (map.getValue().matches(title)) {
|
if (map.getValue().matches(title)) {
|
||||||
long id = map.getKey();
|
long id = map.getKey();
|
||||||
log.warn(String.format("Test case with @QaseTitle='%s' already exists with @QaseId=%d. " +
|
log.warn(String.format("Test case with @QaseTitle='%s' already exists with @QaseId=%d. "
|
||||||
"Please verify @QaseTitle annotation", title, id));
|
+ "Please verify @QaseTitle annotation", title, id));
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void onTestSuccess(final ITestResult testResult) {
|
public void onTestSuccess(final ITestResult testResult) {
|
||||||
Method method = testResult.getMethod()
|
Method method = testResult.getMethod()
|
||||||
.getConstructorOrMethod()
|
.getConstructorOrMethod()
|
||||||
.getMethod();
|
.getMethod();
|
||||||
String title = getCaseTitle(method);
|
String title = getCaseTitle(method);
|
||||||
if (!method.isAnnotationPresent(QaseId.class)) {
|
if (!method.isAnnotationPresent(QaseId.class)) {
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
if (!isCaseWithTitleExistInQase(method)) {
|
if (!isCaseWithTitleExistInQase(method)) {
|
||||||
LinkedList<ResultCreateStepsInner> resultSteps = StepStorage.stopSteps();
|
LinkedList<ResultCreateStepsInner> resultSteps = StepStorage.stopSteps();
|
||||||
LinkedList<TestCaseCreateStepsInner> createSteps = new LinkedList<>();
|
LinkedList<TestCaseCreateStepsInner> createSteps = new LinkedList<>();
|
||||||
resultSteps.forEach(step -> {
|
resultSteps.forEach(step -> {
|
||||||
TestCaseCreateStepsInner caseStep = new TestCaseCreateStepsInner();
|
TestCaseCreateStepsInner caseStep = new TestCaseCreateStepsInner();
|
||||||
caseStep.setAction(step.getAction());
|
caseStep.setAction(step.getAction());
|
||||||
caseStep.setExpectedResult(step.getExpectedResult());
|
caseStep.setExpectedResult(step.getExpectedResult());
|
||||||
createSteps.add(caseStep);
|
createSteps.add(caseStep);
|
||||||
});
|
});
|
||||||
TestCaseCreate newCase = new TestCaseCreate();
|
TestCaseCreate newCase = new TestCaseCreate();
|
||||||
newCase.setTitle(title);
|
newCase.setTitle(title);
|
||||||
newCase.setStatus(getStatus(method));
|
newCase.setStatus(getStatus(method));
|
||||||
newCase.setAutomation(getAutomation(method));
|
newCase.setAutomation(getAutomation(method));
|
||||||
newCase.setSteps(createSteps);
|
newCase.setSteps(createSteps);
|
||||||
if (method.isAnnotationPresent(Suite.class)) {
|
if (method.isAnnotationPresent(Suite.class)) {
|
||||||
long suiteId = method.getDeclaredAnnotation(Suite.class).id();
|
long suiteId = method.getDeclaredAnnotation(Suite.class).id();
|
||||||
newCase.suiteId(suiteId);
|
newCase.suiteId(suiteId);
|
||||||
}
|
}
|
||||||
Long id = Objects.requireNonNull(QASE_API.createCase(System.getProperty("QASE_PROJECT_CODE"),
|
Long id = Objects.requireNonNull(QASE_API.createCase(System.getProperty("QASE_PROJECT_CODE"),
|
||||||
newCase).getResult()).getId();
|
newCase).getResult()).getId();
|
||||||
log.info(String.format("New test case '%s' was created with @QaseId=%d", title, id));
|
log.info(String.format("New test case '%s' was created with @QaseId=%d", title, id));
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
log.warn("To create new test case in Qase.io please add @QaseTitle annotation");
|
log.warn("To create new test case in Qase.io please add @QaseTitle annotation");
|
||||||
} else
|
}
|
||||||
log.warn("To create new test case in Qase.io please remove @QaseId annotation");
|
} else {
|
||||||
|
log.warn("To create new test case in Qase.io please remove @QaseId annotation");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
package com.provectus.kafka.ui.settings.listeners;
|
package com.provectus.kafka.ui.settings.listeners;
|
||||||
|
|
||||||
|
import static io.qase.api.utils.IntegrationUtils.getCaseId;
|
||||||
|
import static io.qase.api.utils.IntegrationUtils.getCaseTitle;
|
||||||
|
import static io.qase.api.utils.IntegrationUtils.getStacktrace;
|
||||||
|
import static io.qase.client.model.ResultCreate.StatusEnum.FAILED;
|
||||||
|
import static io.qase.client.model.ResultCreate.StatusEnum.PASSED;
|
||||||
|
import static io.qase.client.model.ResultCreate.StatusEnum.SKIPPED;
|
||||||
|
|
||||||
import io.qase.api.StepStorage;
|
import io.qase.api.StepStorage;
|
||||||
import io.qase.api.config.QaseConfig;
|
import io.qase.api.config.QaseConfig;
|
||||||
import io.qase.api.services.QaseTestCaseListener;
|
import io.qase.api.services.QaseTestCaseListener;
|
||||||
|
@ -7,6 +14,9 @@ import io.qase.client.model.ResultCreate;
|
||||||
import io.qase.client.model.ResultCreateCase;
|
import io.qase.client.model.ResultCreateCase;
|
||||||
import io.qase.client.model.ResultCreateStepsInner;
|
import io.qase.client.model.ResultCreateStepsInner;
|
||||||
import io.qase.testng.guice.module.TestNgModule;
|
import io.qase.testng.guice.module.TestNgModule;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Optional;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -15,88 +25,81 @@ import org.testng.ITestListener;
|
||||||
import org.testng.ITestResult;
|
import org.testng.ITestResult;
|
||||||
import org.testng.TestListenerAdapter;
|
import org.testng.TestListenerAdapter;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static io.qase.api.utils.IntegrationUtils.*;
|
|
||||||
import static io.qase.client.model.ResultCreate.StatusEnum.*;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class QaseResultListener extends TestListenerAdapter implements ITestListener {
|
public class QaseResultListener extends TestListenerAdapter implements ITestListener {
|
||||||
|
|
||||||
private static final String REPORTER_NAME = "TestNG";
|
private static final String REPORTER_NAME = "TestNG";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
System.setProperty(QaseConfig.QASE_CLIENT_REPORTER_NAME_KEY, REPORTER_NAME);
|
System.setProperty(QaseConfig.QASE_CLIENT_REPORTER_NAME_KEY, REPORTER_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter(lazy = true, value = AccessLevel.PRIVATE)
|
@Getter(lazy = true, value = AccessLevel.PRIVATE)
|
||||||
private final QaseTestCaseListener qaseTestCaseListener = createQaseListener();
|
private final QaseTestCaseListener qaseTestCaseListener = createQaseListener();
|
||||||
|
|
||||||
private static QaseTestCaseListener createQaseListener() {
|
private static QaseTestCaseListener createQaseListener() {
|
||||||
return TestNgModule.getInjector().getInstance(QaseTestCaseListener.class);
|
return TestNgModule.getInjector().getInstance(QaseTestCaseListener.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestStart(ITestResult tr) {
|
public void onTestStart(ITestResult tr) {
|
||||||
getQaseTestCaseListener().onTestCaseStarted();
|
getQaseTestCaseListener().onTestCaseStarted();
|
||||||
super.onTestStart(tr);
|
super.onTestStart(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestSuccess(ITestResult tr) {
|
public void onTestSuccess(ITestResult tr) {
|
||||||
getQaseTestCaseListener()
|
getQaseTestCaseListener()
|
||||||
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, PASSED));
|
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, PASSED));
|
||||||
super.onTestSuccess(tr);
|
super.onTestSuccess(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestSkipped(ITestResult tr) {
|
public void onTestSkipped(ITestResult tr) {
|
||||||
getQaseTestCaseListener()
|
getQaseTestCaseListener()
|
||||||
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, SKIPPED));
|
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, SKIPPED));
|
||||||
super.onTestSuccess(tr);
|
super.onTestSuccess(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestFailure(ITestResult tr) {
|
public void onTestFailure(ITestResult tr) {
|
||||||
getQaseTestCaseListener()
|
getQaseTestCaseListener()
|
||||||
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, FAILED));
|
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, FAILED));
|
||||||
super.onTestFailure(tr);
|
super.onTestFailure(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinish(ITestContext testContext) {
|
public void onFinish(ITestContext testContext) {
|
||||||
getQaseTestCaseListener().onTestCasesSetFinished();
|
getQaseTestCaseListener().onTestCasesSetFinished();
|
||||||
super.onFinish(testContext);
|
super.onFinish(testContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupResultItem(ResultCreate resultCreate, ITestResult result, ResultCreate.StatusEnum status) {
|
private void setupResultItem(ResultCreate resultCreate, ITestResult result, ResultCreate.StatusEnum status) {
|
||||||
Optional<Throwable> resultThrowable = Optional.ofNullable(result.getThrowable());
|
Optional<Throwable> resultThrowable = Optional.ofNullable(result.getThrowable());
|
||||||
String comment = resultThrowable
|
String comment = resultThrowable
|
||||||
.flatMap(throwable -> Optional.of(throwable.toString())).orElse(null);
|
.flatMap(throwable -> Optional.of(throwable.toString())).orElse(null);
|
||||||
Boolean isDefect = resultThrowable
|
Boolean isDefect = resultThrowable
|
||||||
.flatMap(throwable -> Optional.of(throwable instanceof AssertionError))
|
.flatMap(throwable -> Optional.of(throwable instanceof AssertionError))
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
String stacktrace = resultThrowable
|
String stacktrace = resultThrowable
|
||||||
.flatMap(throwable -> Optional.of(getStacktrace(throwable)))
|
.flatMap(throwable -> Optional.of(getStacktrace(throwable)))
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
Method method = result.getMethod()
|
Method method = result.getMethod()
|
||||||
.getConstructorOrMethod()
|
.getConstructorOrMethod()
|
||||||
.getMethod();
|
.getMethod();
|
||||||
Long caseId = getCaseId(method);
|
Long caseId = getCaseId(method);
|
||||||
String caseTitle = null;
|
String caseTitle = null;
|
||||||
if (caseId == null) {
|
if (caseId == null) {
|
||||||
caseTitle = getCaseTitle(method);
|
caseTitle = getCaseTitle(method);
|
||||||
}
|
|
||||||
LinkedList<ResultCreateStepsInner> steps = StepStorage.stopSteps();
|
|
||||||
resultCreate
|
|
||||||
._case(caseTitle == null ? null : new ResultCreateCase().title(caseTitle))
|
|
||||||
.caseId(caseId)
|
|
||||||
.status(status)
|
|
||||||
.comment(comment)
|
|
||||||
.stacktrace(stacktrace)
|
|
||||||
.steps(steps.isEmpty() ? null : steps)
|
|
||||||
.defect(isDefect);
|
|
||||||
}
|
}
|
||||||
|
LinkedList<ResultCreateStepsInner> steps = StepStorage.stopSteps();
|
||||||
|
resultCreate
|
||||||
|
._case(caseTitle == null ? null : new ResultCreateCase().title(caseTitle))
|
||||||
|
.caseId(caseId)
|
||||||
|
.status(status)
|
||||||
|
.comment(comment)
|
||||||
|
.stacktrace(stacktrace)
|
||||||
|
.steps(steps.isEmpty() ? null : steps)
|
||||||
|
.defect(isDefect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
package com.provectus.kafka.ui.utilities;
|
package com.provectus.kafka.ui.utilities;
|
||||||
|
|
||||||
import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
import static org.apache.kafka.common.utils.Utils.readFileAsString;
|
import static org.apache.kafka.common.utils.Utils.readFileAsString;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
|
|
||||||
public static String getResourceAsString(String resourceFileName) {
|
public static String getResourceAsString(String resourceFileName) {
|
||||||
try {
|
try {
|
||||||
return IOUtils.resourceToString("/" + resourceFileName, StandardCharsets.UTF_8);
|
return IOUtils.resourceToString("/" + resourceFileName, StandardCharsets.UTF_8);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String fileToString(String path) {
|
public static String fileToString(String path) {
|
||||||
try {
|
try {
|
||||||
return readFileAsString(path);
|
return readFileAsString(path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
package com.provectus.kafka.ui.utilities;
|
package com.provectus.kafka.ui.utilities;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import static com.codeborne.selenide.Selenide.sleep;
|
||||||
|
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import static com.codeborne.selenide.Selenide.sleep;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class TimeUtils {
|
public class TimeUtils {
|
||||||
|
|
||||||
public static void waitUntilNewMinuteStarted() {
|
public static void waitUntilNewMinuteStarted() {
|
||||||
int secondsLeft = 60 - LocalTime.now().getSecond();
|
int secondsLeft = 60 - LocalTime.now().getSecond();
|
||||||
log.debug("\nwaitUntilNewMinuteStarted: {}s", secondsLeft);
|
log.debug("\nwaitUntilNewMinuteStarted: {}s", secondsLeft);
|
||||||
sleep(secondsLeft * 1000);
|
sleep(secondsLeft * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,90 +1,110 @@
|
||||||
package com.provectus.kafka.ui.utilities;
|
package com.provectus.kafka.ui.utilities;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.executeJavaScript;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.codeborne.selenide.WebDriverRunner;
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
import java.time.Duration;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.openqa.selenium.Keys;
|
import org.openqa.selenium.Keys;
|
||||||
import org.openqa.selenium.interactions.Actions;
|
import org.openqa.selenium.interactions.Actions;
|
||||||
|
|
||||||
import static com.codeborne.selenide.Selenide.executeJavaScript;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WebUtils {
|
public class WebUtils {
|
||||||
|
|
||||||
public static void clickByActions(SelenideElement element) {
|
public static int getTimeout(int... timeoutInSeconds) {
|
||||||
log.debug("\nclickByActions: {}", element.getSearchCriteria());
|
return (timeoutInSeconds != null && timeoutInSeconds.length > 0) ? timeoutInSeconds[0] : 4;
|
||||||
element.shouldBe(Condition.enabled);
|
}
|
||||||
new Actions(WebDriverRunner.getWebDriver())
|
|
||||||
.moveToElement(element)
|
|
||||||
.click(element)
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendKeysByActions(SelenideElement element, String keys) {
|
public static void sendKeysAfterClear(SelenideElement element, String keys) {
|
||||||
log.debug("\nsendKeysByActions: {} \nsend keys '{}'", element.getSearchCriteria(), keys);
|
log.debug("\nsendKeysAfterClear: {} \nsend keys '{}'", element.getSearchCriteria(), keys);
|
||||||
element.shouldBe(Condition.enabled);
|
element.shouldBe(Condition.enabled).clear();
|
||||||
new Actions(WebDriverRunner.getWebDriver())
|
if (keys != null) {
|
||||||
.moveToElement(element)
|
element.sendKeys(keys);
|
||||||
.sendKeys(element, keys)
|
|
||||||
.perform();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void clickByJavaScript(SelenideElement element) {
|
public static void clickByActions(SelenideElement element) {
|
||||||
log.debug("\nclickByJavaScript: {}", element.getSearchCriteria());
|
log.debug("\nclickByActions: {}", element.getSearchCriteria());
|
||||||
element.shouldBe(Condition.enabled);
|
element.shouldBe(Condition.enabled);
|
||||||
String script = "arguments[0].click();";
|
new Actions(WebDriverRunner.getWebDriver())
|
||||||
executeJavaScript(script, element);
|
.moveToElement(element)
|
||||||
}
|
.click(element)
|
||||||
|
.perform();
|
||||||
|
}
|
||||||
|
|
||||||
public static void clearByKeyboard(SelenideElement field) {
|
public static void sendKeysByActions(SelenideElement element, String keys) {
|
||||||
log.debug("\nclearByKeyboard: {}", field.getSearchCriteria());
|
log.debug("\nsendKeysByActions: {} \nsend keys '{}'", element.getSearchCriteria(), keys);
|
||||||
field.shouldBe(Condition.enabled).sendKeys(Keys.END);
|
element.shouldBe(Condition.enabled);
|
||||||
field.sendKeys(Keys.chord(Keys.CONTROL + "a"), Keys.DELETE);
|
new Actions(WebDriverRunner.getWebDriver())
|
||||||
}
|
.moveToElement(element)
|
||||||
|
.sendKeys(element, keys)
|
||||||
|
.perform();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isVisible(SelenideElement element) {
|
public static void clickByJavaScript(SelenideElement element) {
|
||||||
log.debug("\nisVisible: {}", element.getSearchCriteria());
|
log.debug("\nclickByJavaScript: {}", element.getSearchCriteria());
|
||||||
boolean isVisible = false;
|
element.shouldBe(Condition.enabled);
|
||||||
try {
|
String script = "arguments[0].click();";
|
||||||
element.shouldBe(Condition.visible);
|
executeJavaScript(script, element);
|
||||||
isVisible = true;
|
}
|
||||||
} catch (Throwable e) {
|
|
||||||
log.debug("{} is not visible", element.getSearchCriteria());
|
|
||||||
}
|
|
||||||
return isVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isEnabled(SelenideElement element) {
|
public static void clearByKeyboard(SelenideElement field) {
|
||||||
log.debug("\nisEnabled: {}", element.getSearchCriteria());
|
log.debug("\nclearByKeyboard: {}", field.getSearchCriteria());
|
||||||
boolean isEnabled = false;
|
field.shouldBe(Condition.enabled).sendKeys(Keys.END);
|
||||||
try {
|
field.sendKeys(Keys.chord(Keys.CONTROL + "a"), Keys.DELETE);
|
||||||
element.shouldBe(Condition.enabled);
|
}
|
||||||
isEnabled = true;
|
|
||||||
} catch (Throwable e) {
|
|
||||||
log.debug("{} is not enabled", element.getSearchCriteria());
|
|
||||||
}
|
|
||||||
return isEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSelected(SelenideElement element) {
|
public static boolean isVisible(SelenideElement element, int... timeoutInSeconds) {
|
||||||
log.debug("\nisSelected: {}", element.getSearchCriteria());
|
log.debug("\nisVisible: {}", element.getSearchCriteria());
|
||||||
boolean isSelected = false;
|
boolean isVisible = false;
|
||||||
try {
|
try {
|
||||||
element.shouldBe(Condition.selected);
|
element.shouldBe(Condition.visible,
|
||||||
isSelected = true;
|
Duration.ofSeconds(getTimeout(timeoutInSeconds)));
|
||||||
} catch (Throwable e) {
|
isVisible = true;
|
||||||
log.debug("{} is not selected", element.getSearchCriteria());
|
} catch (Throwable e) {
|
||||||
}
|
log.debug("{} is not visible", element.getSearchCriteria());
|
||||||
return isSelected;
|
|
||||||
}
|
}
|
||||||
|
return isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean selectElement(SelenideElement element, boolean select) {
|
public static boolean isEnabled(SelenideElement element, int... timeoutInSeconds) {
|
||||||
if (select) {
|
log.debug("\nisEnabled: {}", element.getSearchCriteria());
|
||||||
if (!element.isSelected()) clickByJavaScript(element);
|
boolean isEnabled = false;
|
||||||
} else {
|
try {
|
||||||
if (element.isSelected()) clickByJavaScript(element);
|
element.shouldBe(Condition.enabled,
|
||||||
}
|
Duration.ofSeconds(getTimeout(timeoutInSeconds)));
|
||||||
return true;
|
isEnabled = true;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.debug("{} is not enabled", element.getSearchCriteria());
|
||||||
}
|
}
|
||||||
|
return isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSelected(SelenideElement element, int... timeoutInSeconds) {
|
||||||
|
log.debug("\nisSelected: {}", element.getSearchCriteria());
|
||||||
|
boolean isSelected = false;
|
||||||
|
try {
|
||||||
|
element.shouldBe(Condition.selected,
|
||||||
|
Duration.ofSeconds(getTimeout(timeoutInSeconds)));
|
||||||
|
isSelected = true;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.debug("{} is not selected", element.getSearchCriteria());
|
||||||
|
}
|
||||||
|
return isSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean selectElement(SelenideElement element, boolean select) {
|
||||||
|
if (select) {
|
||||||
|
if (!element.isSelected()) {
|
||||||
|
clickByJavaScript(element);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (element.isSelected()) {
|
||||||
|
clickByJavaScript(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.provectus.kafka.ui.utilities.qase;
|
||||||
|
|
||||||
|
import static com.provectus.kafka.ui.settings.BaseSource.SUITE_NAME;
|
||||||
|
import static com.provectus.kafka.ui.variables.Suite.MANUAL;
|
||||||
|
import static org.apache.commons.lang3.BooleanUtils.FALSE;
|
||||||
|
import static org.apache.commons.lang3.BooleanUtils.TRUE;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isEmpty;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class QaseSetup {
|
||||||
|
|
||||||
|
public static void qaseIntegrationSetup() {
|
||||||
|
String qaseApiToken = System.getProperty("QASEIO_API_TOKEN");
|
||||||
|
if (isEmpty(qaseApiToken)) {
|
||||||
|
log.warn("Integration with Qase is disabled due to run config or token wasn't defined.");
|
||||||
|
System.setProperty("QASE_ENABLE", FALSE);
|
||||||
|
} else {
|
||||||
|
log.warn("Integration with Qase is enabled. Find this run at https://app.qase.io/run/KAFKAUI.");
|
||||||
|
String automation = SUITE_NAME.equalsIgnoreCase(MANUAL) ? "" : "Automation ";
|
||||||
|
System.setProperty("QASE_ENABLE", TRUE);
|
||||||
|
System.setProperty("QASE_PROJECT_CODE", "KAFKAUI");
|
||||||
|
System.setProperty("QASE_API_TOKEN", qaseApiToken);
|
||||||
|
System.setProperty("QASE_USE_BULK", TRUE);
|
||||||
|
System.setProperty("QASE_RUN_NAME", DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
|
||||||
|
.format(OffsetDateTime.now(ZoneOffset.UTC)) + ": " + automation + SUITE_NAME.toUpperCase() + " suite");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package com.provectus.kafka.ui.utilities.qaseUtils.annotations;
|
package com.provectus.kafka.ui.utilities.qase.annotations;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.utilities.qaseUtils.enums.State;
|
|
||||||
|
|
||||||
|
import com.provectus.kafka.ui.utilities.qase.enums.State;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
@ -11,5 +10,5 @@ import java.lang.annotation.Target;
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface Automation {
|
public @interface Automation {
|
||||||
|
|
||||||
State state();
|
State state();
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.provectus.kafka.ui.utilities.qaseUtils.annotations;
|
package com.provectus.kafka.ui.utilities.qase.annotations;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
|
@ -9,5 +9,5 @@ import java.lang.annotation.Target;
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface Status {
|
public @interface Status {
|
||||||
|
|
||||||
com.provectus.kafka.ui.utilities.qaseUtils.enums.Status status();
|
com.provectus.kafka.ui.utilities.qase.enums.Status status();
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue