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:
iliax 2023-04-11 12:09:47 +04:00
commit a58c2055f5
182 changed files with 6467 additions and 5149 deletions

View 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"
]
}
}
}

View file

@ -36,7 +36,7 @@ jobs:
- name: Pull with Docker
id: pull_chrome
run: |
docker pull selenium/standalone-chrome:103.0
docker pull selenoid/vnc_chrome:103.0
- name: Set up JDK
uses: actions/setup-java@v3
with:
@ -52,6 +52,7 @@ jobs:
id: compose_app
# use the following command until #819 will be fixed
run: |
docker-compose -f kafka-ui-e2e-checks/docker/selenoid-git.yaml up -d
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
- name: Run test suite
run: |
@ -78,7 +79,7 @@ jobs:
uses: Sibz/github-status-action@v1.1.6
with:
authToken: ${{secrets.GITHUB_TOKEN}}
context: "Test report"
context: "Click Details button to open Allure report"
state: "success"
sha: ${{ github.sha }}
target_url: http://kafkaui-allure-reports.s3-website.eu-central-1.amazonaws.com/${{ github.run_number }}

View file

@ -15,20 +15,20 @@ jobs:
- uses: actions/checkout@v3
with:
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
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-central-1
- name: Set the values
- name: Set up environment
id: set_env_values
run: |
cat "./kafka-ui-e2e-checks/.env.ci" >> "./kafka-ui-e2e-checks/.env"
- name: pull docker
- name: Pull with Docker
id: pull_chrome
run: |
docker pull selenium/standalone-chrome:103.0
docker pull selenoid/vnc_chrome:103.0
- name: Set up JDK
uses: actions/setup-java@v3
with:
@ -40,12 +40,13 @@ jobs:
run: |
./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 }}
- name: compose app
- name: Compose with Docker
id: compose_app
# use the following command until #819 will be fixed
run: |
docker-compose -f kafka-ui-e2e-checks/docker/selenoid-git.yaml up -d
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
- name: e2e run
- name: Run test suite
run: |
./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
@ -65,7 +66,7 @@ jobs:
AWS_S3_BUCKET: 'kafkaui-allure-reports'
AWS_REGION: 'eu-central-1'
SOURCE_DIR: 'allure-history/allure-results'
- name: Post the link to allure report
- name: Deploy report to Amazon S3
if: always()
uses: Sibz/github-status-action@v1.1.6
with:

View file

@ -23,7 +23,7 @@ jobs:
- name: Pull with Docker
id: pull_chrome
run: |
docker pull selenium/standalone-chrome:103.0
docker pull selenoid/vnc_chrome:103.0
- name: Set up JDK
uses: actions/setup-java@v3
with:
@ -39,6 +39,7 @@ jobs:
id: compose_app
# use the following command until #819 will be fixed
run: |
docker-compose -f kafka-ui-e2e-checks/docker/selenoid-git.yaml up -d
docker-compose -f ./documentation/compose/e2e-tests.yaml up -d
- name: Run test suite
run: |
@ -65,7 +66,7 @@ jobs:
uses: Sibz/github-status-action@v1.1.6
with:
authToken: ${{secrets.GITHUB_TOKEN}}
context: "Test report"
context: "Click Details button to open Allure report"
state: "success"
sha: ${{ github.sha }}
target_url: http://kafkaui-allure-reports.s3-website.eu-central-1.amazonaws.com/${{ github.run_number }}

View file

@ -11,14 +11,14 @@ services:
test: wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health
interval: 30s
timeout: 10s
retries: 10
retries: 10
depends_on:
kafka0:
condition: service_healthy
schemaregistry0:
condition: service_healthy
kafka-connect0:
condition: service_healthy
kafka0:
condition: service_healthy
schemaregistry0:
condition: service_healthy
kafka-connect0:
condition: service_healthy
environment:
KAFKA_CLUSTERS_0_NAME: local
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092
@ -33,10 +33,10 @@ services:
hostname: kafka0
container_name: kafka0
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
interval: 30s
timeout: 10s
retries: 10
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
timeout: 10s
retries: 10
ports:
- "9092:9092"
- "9997:9997"
@ -68,12 +68,12 @@ services:
- 8085:8085
depends_on:
kafka0:
condition: service_healthy
condition: service_healthy
healthcheck:
test: ["CMD", "timeout", "1", "curl", "--silent", "--fail", "http://schemaregistry0:8085/subjects"]
interval: 30s
timeout: 10s
retries: 10
test: [ "CMD", "timeout", "1", "curl", "--silent", "--fail", "http://schemaregistry0:8085/subjects" ]
interval: 30s
timeout: 10s
retries: 10
environment:
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092
SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT
@ -93,11 +93,11 @@ services:
- 8083:8083
depends_on:
kafka0:
condition: service_healthy
condition: service_healthy
schemaregistry0:
condition: service_healthy
condition: service_healthy
healthcheck:
test: ["CMD", "nc", "127.0.0.1", "8083"]
test: [ "CMD", "nc", "127.0.0.1", "8083" ]
interval: 30s
timeout: 10s
retries: 10
@ -118,8 +118,8 @@ services:
CONNECT_INTERNAL_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect0
CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components"
# AWS_ACCESS_KEY_ID: ""
# AWS_SECRET_ACCESS_KEY: ""
# AWS_ACCESS_KEY_ID: ""
# AWS_SECRET_ACCESS_KEY: ""
kafka-init-topics:
image: confluentinc/cp-kafka:7.2.1
@ -127,7 +127,7 @@ services:
- ./message.json:/data/message.json
depends_on:
kafka0:
condition: service_healthy
condition: service_healthy
command: "bash -c 'echo Waiting for Kafka to be ready... && \
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 && \
@ -142,10 +142,10 @@ services:
ports:
- 5432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev_user"]
test: [ "CMD-SHELL", "pg_isready -U dev_user" ]
interval: 10s
timeout: 5s
retries: 5
retries: 5
environment:
POSTGRES_USER: 'dev_user'
POSTGRES_PASSWORD: '12345'
@ -154,7 +154,7 @@ services:
image: ellerbrock/alpine-bash-curl-ssl
depends_on:
postgres-db:
condition: service_healthy
condition: service_healthy
kafka-connect0:
condition: service_healthy
volumes:
@ -164,7 +164,7 @@ services:
ksqldb:
image: confluentinc/ksqldb-server:0.18.0
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
timeout: 10s
retries: 10
@ -174,7 +174,7 @@ services:
kafka-connect0:
condition: service_healthy
schemaregistry0:
condition: service_healthy
condition: service_healthy
ports:
- 8088:8088
environment:

View 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>

View file

@ -318,7 +318,7 @@
<property name="ignoreInlineTags" value="false"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
<property name="exceptionVariableName" value="ignored"/>
</module>
<module name="CommentsIndentation">
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
@ -330,4 +330,4 @@
<property name="optional" value="true"/>
</module>
</module>
</module>
</module>

View file

@ -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 addgroup -S kafkaui && adduser -S kafkaui -G kafkaui

View file

@ -6,7 +6,13 @@ import com.provectus.kafka.ui.config.ClustersProperties;
import com.provectus.kafka.ui.connect.ApiClient;
import com.provectus.kafka.ui.connect.api.KafkaConnectClientApi;
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.TaskStatus;
import com.provectus.kafka.ui.exception.KafkaConnectConflictReponseException;
import com.provectus.kafka.ui.exception.ValidationException;
import com.provectus.kafka.ui.util.WebClientConfigurator;
@ -15,11 +21,7 @@ import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.http.ResponseEntity;
import org.springframework.util.unit.DataSize;
import org.springframework.web.client.RestClientException;
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 {
public RetryingApiClient(ConnectCluster config,
@ -108,35 +280,5 @@ public class RetryingKafkaConnectClient extends KafkaConnectClientApi {
.configureBufferSize(maxBuffSize)
.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)
);
}
}
}

View file

@ -1,6 +1,7 @@
package com.provectus.kafka.ui.config;
import com.provectus.kafka.ui.model.MetricsConfig;
import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -8,7 +9,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

View file

@ -1,9 +1,9 @@
package com.provectus.kafka.ui.config.auth;
import jakarta.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;

View file

@ -13,12 +13,12 @@ import com.provectus.kafka.ui.model.ClusterConfigValidationDTO;
import com.provectus.kafka.ui.model.RestartRequestDTO;
import com.provectus.kafka.ui.model.UploadedFileInfoDTO;
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.rbac.AccessControlService;
import com.provectus.kafka.ui.util.ApplicationRestarter;
import com.provectus.kafka.ui.util.DynamicConfigOperations;
import com.provectus.kafka.ui.util.DynamicConfigOperations.PropertiesStructure;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.RequiredArgsConstructor;
@ -53,18 +53,11 @@ public class ApplicationConfigController implements ApplicationConfigApi {
private final DynamicConfigOperations dynamicConfigOperations;
private final ApplicationRestarter restarter;
private final KafkaClusterFactory kafkaClusterFactory;
private final ApplicationInfoService applicationInfoService;
@Override
public Mono<ResponseEntity<ApplicationInfoDTO>> getApplicationInfo(ServerWebExchange exchange) {
return Mono.just(
new ApplicationInfoDTO()
.enabledFeatures(
dynamicConfigOperations.dynamicConfigEnabled()
? List.of(ApplicationInfoDTO.EnabledFeaturesEnum.DYNAMIC_CONFIG)
: List.of()
)
).map(ResponseEntity::ok);
return Mono.just(applicationInfoService.getApplicationInfo()).map(ResponseEntity::ok);
}
@Override

View file

@ -149,10 +149,9 @@ public class KafkaConnectController extends AbstractController implements KafkaC
}
@Override
public Mono<ResponseEntity<ConnectorDTO>> setConnectorConfig(String clusterName,
String connectName,
public Mono<ResponseEntity<ConnectorDTO>> setConnectorConfig(String clusterName, String connectName,
String connectorName,
@Valid Mono<Object> requestBody,
Mono<Map<String, Object>> requestBody,
ServerWebExchange exchange) {
Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
@ -164,8 +163,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
return validateAccess.then(
kafkaConnectService
.setConnectorConfig(getCluster(clusterName), connectName, connectorName, requestBody)
.map(ResponseEntity::ok)
);
.map(ResponseEntity::ok));
}
@Override
@ -242,7 +240,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
@Override
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) {
return kafkaConnectService
.validateConnectorPluginConfig(

View file

@ -1,9 +1,6 @@
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.time.Duration;
import java.time.Instant;
import org.apache.kafka.clients.consumer.Consumer;
@ -14,13 +11,12 @@ import reactor.core.publisher.FluxSink;
public abstract class AbstractEmitter {
private final ConsumerRecordDeserializer recordDeserializer;
private final ConsumingStats consumingStats = new ConsumingStats();
private final MessagesProcessing messagesProcessing;
private final PollingThrottler throttler;
protected final PollingSettings pollingSettings;
protected AbstractEmitter(ConsumerRecordDeserializer recordDeserializer, PollingSettings pollingSettings) {
this.recordDeserializer = recordDeserializer;
protected AbstractEmitter(MessagesProcessing messagesProcessing, PollingSettings pollingSettings) {
this.messagesProcessing = messagesProcessing;
this.pollingSettings = pollingSettings;
this.throttler = pollingSettings.getPollingThrottler();
}
@ -40,39 +36,27 @@ public abstract class AbstractEmitter {
return records;
}
protected boolean sendLimitReached() {
return messagesProcessing.limitReached();
}
protected void sendMessage(FluxSink<TopicMessageEventDTO> sink,
ConsumerRecord<Bytes, Bytes> msg) {
final TopicMessageDTO topicMessage = recordDeserializer.deserialize(msg);
sink.next(
new TopicMessageEventDTO()
.type(TopicMessageEventDTO.TypeEnum.MESSAGE)
.message(topicMessage)
);
ConsumerRecord<Bytes, Bytes> msg) {
messagesProcessing.sendMsg(sink, msg);
}
protected void sendPhase(FluxSink<TopicMessageEventDTO> sink, String name) {
sink.next(
new TopicMessageEventDTO()
.type(TopicMessageEventDTO.TypeEnum.PHASE)
.phase(new TopicMessagePhaseDTO().name(name))
);
messagesProcessing.sendPhase(sink, name);
}
protected int sendConsuming(FluxSink<TopicMessageEventDTO> sink,
ConsumerRecords<Bytes, Bytes> records,
long elapsed) {
return consumingStats.sendConsumingEvt(sink, records, elapsed, getFilterApplyErrors(sink));
ConsumerRecords<Bytes, Bytes> records,
long elapsed) {
return messagesProcessing.sentConsumingInfo(sink, records, elapsed);
}
protected void sendFinishStatsAndCompleteSink(FluxSink<TopicMessageEventDTO> sink) {
consumingStats.sendFinishEvent(sink, getFilterApplyErrors(sink));
messagesProcessing.sendFinishEvent(sink);
sink.complete();
}
protected Number getFilterApplyErrors(FluxSink<?> sink) {
return sink.contextView()
.<MessageFilterStats>getOrEmpty(MessageFilterStats.class)
.<Number>map(MessageFilterStats::getFilterApplyErrors)
.orElse(0);
}
}

View file

@ -2,7 +2,6 @@ package com.provectus.kafka.ui.emitter;
import com.provectus.kafka.ui.model.ConsumerPosition;
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -31,9 +30,9 @@ public class BackwardRecordEmitter
Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
ConsumerPosition consumerPosition,
int messagesPerPage,
ConsumerRecordDeserializer recordDeserializer,
MessagesProcessing messagesProcessing,
PollingSettings pollingSettings) {
super(recordDeserializer, pollingSettings);
super(messagesProcessing, pollingSettings);
this.consumerPosition = consumerPosition;
this.messagesPerPage = messagesPerPage;
this.consumerSupplier = consumerSupplier;
@ -52,7 +51,7 @@ public class BackwardRecordEmitter
int msgsToPollPerPartition = (int) Math.ceil((double) messagesPerPage / readUntilOffsets.size());
log.debug("'Until' offsets for polling: {}", readUntilOffsets);
while (!sink.isCancelled() && !readUntilOffsets.isEmpty()) {
while (!sink.isCancelled() && !readUntilOffsets.isEmpty() && !sendLimitReached()) {
new TreeMap<>(readUntilOffsets).forEach((tp, readToOffset) -> {
if (sink.isCancelled()) {
return; //fast return in case of sink cancellation
@ -61,8 +60,6 @@ public class BackwardRecordEmitter
long readFromOffset = Math.max(beginOffset, readToOffset - msgsToPollPerPartition);
partitionPollIteration(tp, readFromOffset, readToOffset, consumer, sink)
.stream()
.filter(r -> !sink.isCancelled())
.forEach(r -> sendMessage(sink, r));
if (beginOffset == readFromOffset) {
@ -106,6 +103,7 @@ public class BackwardRecordEmitter
EmptyPollsCounter emptyPolls = pollingSettings.createEmptyPollsCounter();
while (!sink.isCancelled()
&& !sendLimitReached()
&& recordsToSend.size() < desiredMsgsToPoll
&& !emptyPolls.noDataEmptyPollsReached()) {
var polledRecords = poll(sink, consumer, pollingSettings.getPartitionPollTimeout());

View file

@ -19,7 +19,7 @@ class ConsumingStats {
int sendConsumingEvt(FluxSink<TopicMessageEventDTO> sink,
ConsumerRecords<Bytes, Bytes> polledRecords,
long elapsed,
Number filterApplyErrors) {
int filterApplyErrors) {
int polledBytes = ConsumerRecordsUtil.calculatePolledSize(polledRecords);
bytes += polledBytes;
this.records += polledRecords.count();
@ -32,7 +32,7 @@ class ConsumingStats {
return polledBytes;
}
void sendFinishEvent(FluxSink<TopicMessageEventDTO> sink, Number filterApplyErrors) {
void sendFinishEvent(FluxSink<TopicMessageEventDTO> sink, int filterApplyErrors) {
sink.next(
new TopicMessageEventDTO()
.type(TopicMessageEventDTO.TypeEnum.DONE)
@ -41,12 +41,12 @@ class ConsumingStats {
}
private TopicMessageConsumingDTO createConsumingStats(FluxSink<TopicMessageEventDTO> sink,
Number filterApplyErrors) {
int filterApplyErrors) {
return new TopicMessageConsumingDTO()
.bytesConsumed(this.bytes)
.elapsedMs(this.elapsed)
.isCancelled(sink.isCancelled())
.filterApplyErrors(filterApplyErrors.intValue())
.filterApplyErrors(filterApplyErrors)
.messagesConsumed(this.records);
}
}

View file

@ -2,7 +2,6 @@ package com.provectus.kafka.ui.emitter;
import com.provectus.kafka.ui.model.ConsumerPosition;
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
@ -23,9 +22,9 @@ public class ForwardRecordEmitter
public ForwardRecordEmitter(
Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
ConsumerPosition position,
ConsumerRecordDeserializer recordDeserializer,
MessagesProcessing messagesProcessing,
PollingSettings pollingSettings) {
super(recordDeserializer, pollingSettings);
super(messagesProcessing, pollingSettings);
this.position = position;
this.consumerSupplier = consumerSupplier;
}
@ -40,6 +39,7 @@ public class ForwardRecordEmitter
EmptyPollsCounter emptyPolls = pollingSettings.createEmptyPollsCounter();
while (!sink.isCancelled()
&& !sendLimitReached()
&& !seekOperations.assignedPartitionsFullyPolled()
&& !emptyPolls.noDataEmptyPollsReached()) {
@ -50,11 +50,7 @@ public class ForwardRecordEmitter
log.debug("{} records polled", records.count());
for (ConsumerRecord<Bytes, Bytes> msg : records) {
if (!sink.isCancelled()) {
sendMessage(sink, msg);
} else {
break;
}
sendMessage(sink, msg);
}
}
sendFinishStatsAndCompleteSink(sink);

View file

@ -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();
}
}

View file

@ -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))
);
}
}
}

View file

@ -2,7 +2,6 @@ package com.provectus.kafka.ui.emitter;
import com.provectus.kafka.ui.model.ConsumerPosition;
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
import java.util.HashMap;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
@ -20,9 +19,9 @@ public class TailingEmitter extends AbstractEmitter
public TailingEmitter(Supplier<KafkaConsumer<Bytes, Bytes>> consumerSupplier,
ConsumerPosition consumerPosition,
ConsumerRecordDeserializer recordDeserializer,
MessagesProcessing messagesProcessing,
PollingSettings pollingSettings) {
super(recordDeserializer, pollingSettings);
super(messagesProcessing, pollingSettings);
this.consumerSupplier = consumerSupplier;
this.consumerPosition = consumerPosition;
}

View file

@ -134,7 +134,7 @@ public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHan
.timestamp(currentTimestamp())
.stackTrace(Throwables.getStackTraceAsString(exception));
return ServerResponse
.status(exception.getStatus())
.status(exception.getStatusCode())
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(response);
}

View file

@ -1,5 +1,6 @@
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.KSQL;
@ -26,6 +27,8 @@ import org.springframework.util.Assert;
@EqualsAndHashCode
public class Permission {
private static final List<Resource> RBAC_ACTION_EXEMPT_LIST = List.of(KSQL, CLUSTERCONFIG, APPLICATIONCONFIG);
Resource resource;
List<String> actions;
@ -51,7 +54,7 @@ public class Permission {
public void validate() {
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);
}
}

View file

@ -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();
}
}

View file

@ -1,38 +1,58 @@
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.HashSet;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.kafka.common.config.ConfigDef;
import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.config.SslConfigs;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.stereotype.Component;
@Component
class KafkaConfigSanitizer extends Sanitizer {
private static final List<String> DEFAULT_PATTERNS_TO_SANITIZE = Arrays.asList(
"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 */
);
class KafkaConfigSanitizer {
private static final String SANITIZED_VALUE = "******";
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(
@Value("${kafka.config.sanitizer.enabled:true}") boolean enabled,
@Value("${kafka.config.sanitizer.patterns:}") List<String> patternsToSanitize
) {
if (!enabled) {
setKeysToSanitize();
} else {
var keysToSanitize = new HashSet<>(
patternsToSanitize.isEmpty() ? DEFAULT_PATTERNS_TO_SANITIZE : patternsToSanitize);
keysToSanitize.addAll(kafkaConfigKeysToSanitize());
setKeysToSanitize(keysToSanitize.toArray(new String[] {}));
}
this.sanitizeKeysPatterns = enabled
? compile(patternsToSanitize.isEmpty() ? DEFAULT_PATTERNS_TO_SANITIZE : patternsToSanitize)
: List.of();
}
private static List<Pattern> compile(Collection<String> patternStrings) {
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() {
@ -45,4 +65,17 @@ class KafkaConfigSanitizer extends Sanitizer {
.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;
}
}

View file

@ -225,11 +225,11 @@ public class KafkaConnectService {
}
public Mono<ConnectorDTO> setConnectorConfig(KafkaCluster cluster, String connectName,
String connectorName, Mono<Object> requestBody) {
String connectorName, Mono<Map<String, Object>> requestBody) {
return api(cluster, connectName)
.mono(c ->
requestBody
.flatMap(body -> c.setConnectorConfig(connectorName, (Map<String, Object>) body))
.flatMap(body -> c.setConnectorConfig(connectorName, body))
.map(kafkaConnectMapper::fromClient));
}
@ -298,12 +298,12 @@ public class KafkaConnectService {
}
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)
.mono(client ->
requestBody
.flatMap(body ->
client.validateConnectorPluginConfig(pluginName, (Map<String, Object>) body))
client.validateConnectorPluginConfig(pluginName, body))
.map(kafkaConnectMapper::fromClient)
);
}

View file

@ -3,9 +3,8 @@ package com.provectus.kafka.ui.service;
import com.google.common.util.concurrent.RateLimiter;
import com.provectus.kafka.ui.emitter.BackwardRecordEmitter;
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.ResultSizeLimiter;
import com.provectus.kafka.ui.emitter.MessagesProcessing;
import com.provectus.kafka.ui.emitter.TailingEmitter;
import com.provectus.kafka.ui.exception.TopicNotFoundException;
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.MessageFilterTypeDTO;
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.serde.api.Serde;
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
import com.provectus.kafka.ui.serdes.ProducerRecordCreator;
import com.provectus.kafka.ui.util.SslPropertiesUtil;
import java.util.List;
@ -162,13 +161,18 @@ public class MessagesService {
@Nullable String valueSerde) {
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)) {
emitter = new ForwardRecordEmitter(
() -> consumerGroupService.createConsumer(cluster),
consumerPosition,
recordDeserializer,
processing,
cluster.getPollingSettings()
);
} else if (seekDirection.equals(SeekDirectionDTO.BACKWARD)) {
@ -176,33 +180,22 @@ public class MessagesService {
() -> consumerGroupService.createConsumer(cluster),
consumerPosition,
limit,
recordDeserializer,
processing,
cluster.getPollingSettings()
);
} else {
emitter = new TailingEmitter(
() -> consumerGroupService.createConsumer(cluster),
consumerPosition,
recordDeserializer,
processing,
cluster.getPollingSettings()
);
}
MessageFilterStats filterStats = new MessageFilterStats();
return Flux.create(emitter)
.contextWrite(ctx -> ctx.put(MessageFilterStats.class, filterStats))
.filter(getMsgFilter(query, filterQueryType, filterStats))
.map(getDataMasker(cluster, topic))
.takeWhile(createTakeWhilePredicate(seekDirection, limit))
.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) {
var keyMasker = cluster.getMasking().getMaskingFunction(topicName, Serde.Target.KEY);
var valMasker = cluster.getMasking().getMaskingFunction(topicName, Serde.Target.VALUE);
@ -211,32 +204,18 @@ public class MessagesService {
return evt;
}
return evt.message(
evt.getMessage()
.key(keyMasker.apply(evt.getMessage().getKey()))
.content(valMasker.apply(evt.getMessage().getContent())));
evt.getMessage()
.key(keyMasker.apply(evt.getMessage().getKey()))
.content(valMasker.apply(evt.getMessage().getContent())));
};
}
private Predicate<TopicMessageEventDTO> getMsgFilter(String query,
MessageFilterTypeDTO filterQueryType,
MessageFilterStats filterStats) {
private Predicate<TopicMessageDTO> getMsgFilter(String query,
MessageFilterTypeDTO filterQueryType) {
if (StringUtils.isEmpty(query)) {
return evt -> true;
}
var messageFilter = 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;
};
return MessageFilters.createMsgFilter(query, filterQueryType);
}
private <T> UnaryOperator<T> throttleUiPublish(SeekDirectionDTO seekDirection) {

View file

@ -4,6 +4,7 @@ import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
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.collect.ImmutableTable;
import com.google.common.collect.Iterables;
@ -514,6 +515,14 @@ public class ReactiveAdminClient implements Closeable {
.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,
boolean failOnUnknownLeader) {
var targetTopics = partitions.stream().map(TopicPartition::topic).collect(Collectors.toSet());
@ -523,34 +532,44 @@ public class ReactiveAdminClient implements Closeable {
descriptions.values(), partitions::contains, failOnUnknownLeader));
}
private Set<TopicPartition> filterPartitionsWithLeaderCheck(Collection<TopicDescription> topicDescriptions,
@VisibleForTesting
static Set<TopicPartition> filterPartitionsWithLeaderCheck(Collection<TopicDescription> topicDescriptions,
Predicate<TopicPartition> partitionPredicate,
boolean failOnUnknownLeader) {
var goodPartitions = new HashSet<TopicPartition>();
for (TopicDescription description : topicDescriptions) {
var goodTopicPartitions = new ArrayList<TopicPartition>();
for (TopicPartitionInfo partitionInfo : description.partitions()) {
TopicPartition topicPartition = new TopicPartition(description.name(), partitionInfo.partition());
if (!partitionPredicate.test(topicPartition)) {
continue;
if (partitionInfo.leader() == null) {
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) {
goodPartitions.add(topicPartition);
} else if (failOnUnknownLeader) {
throw new ValidationException(String.format("Topic partition %s has no leader", topicPartition));
if (partitionPredicate.test(topicPartition)) {
goodTopicPartitions.add(topicPartition);
}
}
goodPartitions.addAll(goodTopicPartitions);
}
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)
// 2. NOTE(!): Skips partitions that were not initialized yet
// (UnknownTopicOrPartitionException thrown, ex. after topic creation)
// 3. TODO: check if it is a bug that AdminClient never throws LeaderNotAvailableException and just retrying instead
@KafkaClientInternalsDependant
public Mono<Map<TopicPartition, Long>> listOffsetsUnsafe(Collection<TopicPartition> partitions,
OffsetSpec offsetSpec) {
@VisibleForTesting
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 =
parts -> {

View file

@ -3,6 +3,7 @@ package com.provectus.kafka.ui.service;
import static java.util.stream.Collectors.toList;
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.exception.TopicMetadataException;
import com.provectus.kafka.ui.exception.TopicNotFoundException;
@ -136,22 +137,14 @@ public class TopicsService {
}
private Mono<InternalPartitionsOffsets> getPartitionOffsets(Map<String, TopicDescription>
descriptions,
descriptionsMap,
ReactiveAdminClient ac) {
var topicPartitions = descriptions.values().stream()
.flatMap(desc ->
desc.partitions().stream()
// 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()),
var descriptions = descriptionsMap.values();
return ac.listOffsets(descriptions, OffsetSpec.earliest())
.zipWith(ac.listOffsets(descriptions, OffsetSpec.latest()),
(earliest, latest) ->
topicPartitions.stream()
.filter(tp -> earliest.containsKey(tp) && latest.containsKey(tp))
Sets.intersection(earliest.keySet(), latest.keySet())
.stream()
.map(tp ->
Map.entry(tp,
new InternalPartitionsOffsets.Offsets(

View file

@ -2,7 +2,7 @@ package com.provectus.kafka.ui.service.analyze;
import com.provectus.kafka.ui.model.TopicAnalysisSizeStatsDTO;
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.Instant;
import java.util.Comparator;
@ -78,10 +78,10 @@ class TopicAnalysisStats {
}
}
List<TopicAnalysisStatsHourlyMsgCountsDTO> toDto() {
List<TopicAnalysisStatsHourlyMsgCountsInnerDTO> toDto() {
return hourlyStats.entrySet().stream()
.sorted(Comparator.comparingLong(Map.Entry::getKey))
.map(e -> new TopicAnalysisStatsHourlyMsgCountsDTO()
.map(e -> new TopicAnalysisStatsHourlyMsgCountsInnerDTO()
.hourStart(e.getKey())
.count(e.getValue()))
.collect(Collectors.toList());

View file

@ -52,7 +52,10 @@ public class KsqlApiClient {
boolean error;
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();
}
}

View file

@ -89,7 +89,14 @@ public class KsqlServiceV2 {
.name(resp.getColumnValue(row, "name").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))
.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()));
});
}

View file

@ -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.LdapAuthorityExtractor;
import com.provectus.kafka.ui.service.rbac.extractor.ProviderAuthorityExtractor;
import jakarta.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@ -28,7 +29,6 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;

View file

@ -1,9 +1,9 @@
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.provider.Provider;
import com.provectus.kafka.ui.service.rbac.AccessControlService;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -44,7 +44,7 @@ public class CognitoAuthorityExtractor implements ProviderAuthorityExtractor {
.map(Role::getName)
.collect(Collectors.toSet());
JSONArray groups = principal.getAttribute(COGNITO_GROUPS_ATTRIBUTE_NAME);
List<String> groups = principal.getAttribute(COGNITO_GROUPS_ATTRIBUTE_NAME);
if (groups == null) {
log.debug("Cognito groups param is not present");
return Mono.just(groupsByUsername);
@ -56,9 +56,8 @@ public class CognitoAuthorityExtractor implements ProviderAuthorityExtractor {
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_COGNITO))
.filter(s -> s.getType().equals("group"))
.anyMatch(subject -> Stream.of(groups.toArray())
.anyMatch(subject -> Stream.of(groups)
.map(Object::toString)
.distinct()
.anyMatch(cognitoGroup -> cognitoGroup.equals(subject.getValue()))
))
.map(Role::getName)

View file

@ -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;
}
}

View file

@ -16,7 +16,7 @@ import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ActiveProfiles;
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.Network;
import org.testcontainers.utility.DockerImageName;
@ -61,7 +61,7 @@ public abstract class AbstractIntegrationTest {
System.setProperty("kafka.clusters.0.bootstrapServers", kafka.getBootstrapServers());
// List unavailable hosts to verify failover
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.userName", "kafka-connect");
System.setProperty("kafka.clusters.0.kafkaConnect.0.password", "kafka-connect");

View file

@ -5,13 +5,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.Sanitizer;
class KafkaConfigSanitizerTest {
@Test
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("sasl.jaas.config", "secret")).isEqualTo("secret");
assertThat(sanitizer.sanitize("database.password", "secret")).isEqualTo("secret");
@ -19,7 +18,7 @@ class KafkaConfigSanitizerTest {
@Test
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("consumer.sasl.jaas.config", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("producer.sasl.jaas.config", "secret")).isEqualTo("******");
@ -37,7 +36,7 @@ class KafkaConfigSanitizerTest {
@Test
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");
final String[] bootstrapServer = new String[] {"test1:9092", "test2:9092"};
assertThat(sanitizer.sanitize("bootstrap.servers", bootstrapServer)).isEqualTo(bootstrapServer);
@ -45,7 +44,7 @@ class KafkaConfigSanitizerTest {
@Test
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("this.is.test.credentials", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("this.is.not.credential", "not.credential"))

View file

@ -4,8 +4,11 @@ import static com.provectus.kafka.ui.service.ReactiveAdminClient.toMonoWithExcep
import static java.util.Objects.requireNonNull;
import static org.apache.kafka.clients.admin.ListOffsetsResult.ListOffsetsResultInfo;
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.exception.ValidationException;
import com.provectus.kafka.ui.producer.KafkaTestProducer;
import java.time.Duration;
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.NewTopic;
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.KafkaConsumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.KafkaFuture;
import org.apache.kafka.common.Node;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.TopicPartitionInfo;
import org.apache.kafka.common.config.ConfigResource;
import org.apache.kafka.common.errors.UnknownTopicOrPartitionException;
import org.apache.kafka.common.internals.KafkaFutureImpl;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.assertj.core.api.ThrowableAssert;
import org.junit.function.ThrowingRunnable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -133,6 +140,56 @@ class ReactiveAdminClientTest extends AbstractIntegrationTest {
.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
void testListOffsetsUnsafe() {
String topic = UUID.randomUUID().toString();

View file

@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import com.provectus.kafka.ui.AbstractIntegrationTest;
import com.provectus.kafka.ui.emitter.BackwardRecordEmitter;
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.model.ConsumerPosition;
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
void pollNothingOnEmptyTopic() {
var forwardEmitter = new ForwardRecordEmitter(
this::createConsumer,
new ConsumerPosition(BEGINNING, EMPTY_TOPIC, null),
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -119,7 +124,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
this::createConsumer,
new ConsumerPosition(BEGINNING, EMPTY_TOPIC, null),
100,
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -141,7 +146,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
var forwardEmitter = new ForwardRecordEmitter(
this::createConsumer,
new ConsumerPosition(BEGINNING, TOPIC, null),
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -149,7 +154,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
this::createConsumer,
new ConsumerPosition(LATEST, TOPIC, null),
PARTITIONS * MSGS_PER_PARTITION,
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -170,7 +175,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
var forwardEmitter = new ForwardRecordEmitter(
this::createConsumer,
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -178,7 +183,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
this::createConsumer,
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
PARTITIONS * MSGS_PER_PARTITION,
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -215,7 +220,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
var forwardEmitter = new ForwardRecordEmitter(
this::createConsumer,
new ConsumerPosition(TIMESTAMP, TOPIC, targetTimestamps),
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -223,7 +228,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
this::createConsumer,
new ConsumerPosition(TIMESTAMP, TOPIC, targetTimestamps),
PARTITIONS * MSGS_PER_PARTITION,
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -254,7 +259,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
this::createConsumer,
new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
numMessages,
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);
@ -280,7 +285,7 @@ class RecordEmitterTest extends AbstractIntegrationTest {
this::createConsumer,
new ConsumerPosition(OFFSET, TOPIC, offsets),
100,
RECORD_DESERIALIZER,
createMessagesProcessing(),
PollingSettings.createDefault()
);

View file

@ -15,7 +15,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.util.unit.DataSize;
import org.testcontainers.utility.DockerImageName;
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> TABLES_TO_DELETE = new CopyOnWriteArraySet<>();
private static final DataSize maxBuffSize = DataSize.ofMegabytes(20);
@BeforeAll
static void init() {
KSQL_DB.start();

View file

@ -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");
}
}

View file

@ -27,20 +27,24 @@
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations.version}</version>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-integration-jakarta</artifactId>
<version>2.2.8</version>
</dependency>
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>${jackson-databind-nullable.version}</version>
<version>0.2.4</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<scope>provided</scope>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
@ -71,6 +75,7 @@
<library>webclient</library>
<useBeanValidation>true</useBeanValidation>
<dateLibrary>java8</dateLibrary>
<useJakartaEe>true</useJakartaEe>
</configOptions>
</configuration>
</execution>
@ -80,8 +85,7 @@
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/swagger/kafka-ui-api.yaml
</inputSpec>
<inputSpec>${project.basedir}/src/main/resources/swagger/kafka-ui-api.yaml</inputSpec>
<output>${project.build.directory}/generated-sources/api</output>
<generatorName>spring</generatorName>
<modelNameSuffix>DTO</modelNameSuffix>
@ -89,14 +93,12 @@
<modelPackage>com.provectus.kafka.ui.model</modelPackage>
<apiPackage>com.provectus.kafka.ui.api</apiPackage>
<sourceFolder>kafka-ui-contract</sourceFolder>
<reactive>true</reactive>
<interfaceOnly>true</interfaceOnly>
<skipDefaultInterface>true</skipDefaultInterface>
<useBeanValidation>true</useBeanValidation>
<useTags>true</useTags>
<useSpringBoot3>true</useSpringBoot3>
<dateLibrary>java8</dateLibrary>
</configOptions>
<typeMappings>
@ -116,15 +118,13 @@
<generatorName>java</generatorName>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<configOptions>
<modelPackage>com.provectus.kafka.ui.connect.model</modelPackage>
<apiPackage>com.provectus.kafka.ui.connect.api</apiPackage>
<sourceFolder>kafka-connect-client</sourceFolder>
<asyncNative>true</asyncNative>
<library>webclient</library>
<useJakartaEe>true</useJakartaEe>
<useBeanValidation>true</useBeanValidation>
<dateLibrary>java8</dateLibrary>
</configOptions>
@ -142,15 +142,13 @@
<generatorName>java</generatorName>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<configOptions>
<modelPackage>com.provectus.kafka.ui.sr.model</modelPackage>
<apiPackage>com.provectus.kafka.ui.sr.api</apiPackage>
<sourceFolder>kafka-sr-client</sourceFolder>
<asyncNative>true</asyncNative>
<library>webclient</library>
<useJakartaEe>true</useJakartaEe>
<useBeanValidation>true</useBeanValidation>
<dateLibrary>java8</dateLibrary>
</configOptions>

View file

@ -2021,6 +2021,26 @@ components:
type: string
enum:
- 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:
type: object
@ -2493,6 +2513,10 @@ components:
- UNKNOWN
ConsumerGroup:
discriminator:
propertyName: inherit
mapping:
details: "#/components/schemas/ConsumerGroupDetails"
type: object
properties:
groupId:

View file

@ -27,7 +27,7 @@ This repository is for E2E UI automation.
```
git clone https://github.com/provectus/kafka-ui.git
cd kafka-ui-e2e-checks
docker pull selenoid/vnc:chrome_86.0
docker pull selenoid/vnc_chrome:103.0
```
### How to run checks
@ -36,6 +36,7 @@ docker pull selenoid/vnc:chrome_86.0
```
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
```
@ -51,6 +52,14 @@ docker-compose -f documentation/compose/e2e-tests.yaml up -d
-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
Found instruction for Qase.io integration (for internal use only) at `kafka-ui-e2e-checks/QASE.md`

View file

@ -1,17 +1,19 @@
---
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:/video"
- "../selenoid/video:/opt/selenoid/video"
- "../selenoid/logs:/opt/selenoid/logs"
environment:
- OVERRIDE_VIDEO_OUTPUT_DIR=video
command: [ "-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs" ]
- OVERRIDE_VIDEO_OUTPUT_DIR=../selenoid/video
command: [ "-conf", "/etc/selenoid/browsersGit.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs" ]
ports:
- "4444:4444"
@ -22,10 +24,10 @@ services:
- selenoid
ports:
- "8081:8080"
command: [ "--selenoid-uri", "http://localhost:4444" ]
command: [ "--selenoid-uri", "http://selenoid:4444" ]
selenoid-chrome:
network_mode: bridge
image: selenoid/vnc:chrome_96.0
image: selenoid/vnc_chrome:103.0
extra_hosts:
- "host.docker.internal:host-gateway"

View 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"

View file

@ -17,15 +17,14 @@
<testcontainers.version>1.17.6</testcontainers.version>
<httpcomponents.version>5.2.1</httpcomponents.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>
<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>
<assertj.version>3.24.2</assertj.version>
<hamcrest.version>2.2</hamcrest.version>
<slf4j.version>1.7.36</slf4j.version>
<dotenv.version>2.3.1</dotenv.version>
<slf4j.version>2.0.5</slf4j.version>
<kafka.version>3.3.1</kafka.version>
</properties>
@ -122,6 +121,11 @@
<artifactId>selenium</artifactId>
<version>${testcontainers.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
@ -132,6 +136,11 @@
<artifactId>httpclient5</artifactId>
<version>${httpcomponents.version}</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-http-jdk-client</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-http</artifactId>
@ -187,16 +196,6 @@
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>${dotenv.version}</version>
</dependency>
<dependency>
<groupId>com.provectus</groupId>
<artifactId>kafka-ui-contract</artifactId>
@ -265,6 +264,37 @@
<artifactId>allure-maven</artifactId>
<version>2.10.0</version>
</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>
</build>
</profile>

View 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": "/"
}
}
}
}

View file

@ -1,9 +1,9 @@
{
"chrome": {
"default": "96.0",
"default": "103.0",
"versions": {
"96.0": {
"image": "selenoid/vnc_chrome:96.0",
"103.0": {
"image": "selenoid/vnc_chrome:103.0",
"port": "4444",
"path": "/"
}

View file

@ -7,5 +7,5 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
public class Connector {
private String name, config;
private String name, config;
}

View file

@ -1,33 +1,34 @@
package com.provectus.kafka.ui.models;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
import com.provectus.kafka.ui.api.model.SchemaType;
import lombok.Data;
import lombok.experimental.Accessors;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
@Data
@Accessors(chain = true)
public class Schema {
private String name, valuePath;
private SchemaType type;
private String name, valuePath;
private SchemaType type;
public static Schema createSchemaAvro() {
return new Schema().setName("schema_avro-" + randomAlphabetic(5))
.setType(SchemaType.AVRO)
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_avro_value.json");
}
public static Schema createSchemaAvro() {
return new Schema().setName("schema_avro-" + randomAlphabetic(5))
.setType(SchemaType.AVRO)
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_avro_value.json");
}
public static Schema createSchemaJson() {
return new Schema().setName("schema_json-" + randomAlphabetic(5))
.setType(SchemaType.JSON)
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_json_Value.json");
}
public static Schema createSchemaJson() {
return new Schema().setName("schema_json-" + randomAlphabetic(5))
.setType(SchemaType.JSON)
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_json_Value.json");
}
public static Schema createSchemaProtobuf() {
return new Schema().setName("schema_protobuf-" + randomAlphabetic(5))
.setType(SchemaType.PROTOBUF)
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_protobuf_value.txt");
}
public static Schema createSchemaProtobuf() {
return new Schema().setName("schema_protobuf-" + randomAlphabetic(5))
.setType(SchemaType.PROTOBUF)
.setValuePath(
System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_protobuf_value.txt");
}
}

View file

@ -11,10 +11,10 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
public class Topic {
private String name, timeToRetainData, maxMessageBytes, messageKey, messageContent, customParameterValue;
private int numberOfPartitions;
private CustomParameterType customParameterType;
private CleanupPolicyValue cleanupPolicyValue;
private MaxSizeOnDisk maxSizeOnDisk;
private TimeToRetain timeToRetain;
private String name, timeToRetainData, maxMessageBytes, messageKey, messageValue, customParameterValue;
private int numberOfPartitions;
private CustomParameterType customParameterType;
private CleanupPolicyValue cleanupPolicyValue;
private MaxSizeOnDisk maxSizeOnDisk;
private TimeToRetain timeToRetain;
}

View file

@ -1,135 +1,142 @@
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.ElementsCollection;
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebDriverRunner;
import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
import com.provectus.kafka.ui.utilities.WebUtils;
import java.time.Duration;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.Keys;
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
public abstract class BasePage extends WebUtils {
protected SelenideElement loadingSpinner = $x("//div[@role='progressbar']");
protected SelenideElement submitBtn = $x("//button[@type='submit']");
protected SelenideElement tableGrid = $x("//table");
protected SelenideElement dotMenuBtn = $x("//button[@aria-label='Dropdown Toggle']");
protected SelenideElement alertHeader = $x("//div[@role='alert']//div[@role='heading']");
protected SelenideElement alertMessage = $x("//div[@role='alert']//div[@role='contentinfo']");
protected SelenideElement confirmationMdl = $x("//div[text()= 'Confirm the action']/..");
protected SelenideElement confirmBtn = $x("//button[contains(text(),'Confirm')]");
protected SelenideElement cancelBtn = $x("//button[contains(text(),'Cancel')]");
protected SelenideElement backBtn = $x("//button[contains(text(),'Back')]");
protected SelenideElement nextBtn = $x("//button[contains(text(),'Next')]");
protected ElementsCollection ddlOptions = $$x("//li[@value]");
protected ElementsCollection gridItems = $$x("//tr[@class]");
protected String summaryCellLocator = "//div[contains(text(),'%s')]";
protected String tableElementNameLocator = "//tbody//a[contains(text(),'%s')]";
protected String columnHeaderLocator = "//table//tr/th//div[text()='%s']";
protected String pageTitleFromHeader = "//h1[text()='%s']";
protected String pagePathFromHeader = "//a[text()='%s']/../h1";
protected SelenideElement loadingSpinner = $x("//div[@role='progressbar']");
protected SelenideElement submitBtn = $x("//button[@type='submit']");
protected SelenideElement tableGrid = $x("//table");
protected SelenideElement searchFld = $x("//input[@type='text'][contains(@id, ':r')]");
protected SelenideElement dotMenuBtn = $x("//button[@aria-label='Dropdown Toggle']");
protected SelenideElement alertHeader = $x("//div[@role='alert']//div[@role='heading']");
protected SelenideElement alertMessage = $x("//div[@role='alert']//div[@role='contentinfo']");
protected SelenideElement confirmationMdl = $x("//div[text()= 'Confirm the action']/..");
protected SelenideElement confirmBtn = $x("//button[contains(text(),'Confirm')]");
protected SelenideElement cancelBtn = $x("//button[contains(text(),'Cancel')]");
protected SelenideElement backBtn = $x("//button[contains(text(),'Back')]");
protected SelenideElement nextBtn = $x("//button[contains(text(),'Next')]");
protected ElementsCollection ddlOptions = $$x("//li[@value]");
protected ElementsCollection gridItems = $$x("//tr[@class]");
protected String summaryCellLocator = "//div[contains(text(),'%s')]";
protected String tableElementNameLocator = "//tbody//a[contains(text(),'%s')]";
protected String columnHeaderLocator = "//table//tr/th//div[text()='%s']";
protected String pageTitleFromHeader = "//h1[text()='%s']";
protected String pagePathFromHeader = "//a[text()='%s']/../h1";
protected void waitUntilSpinnerDisappear() {
log.debug("\nwaitUntilSpinnerDisappear");
if (isVisible(loadingSpinner)) {
loadingSpinner.shouldBe(Condition.disappear, Duration.ofSeconds(60));
}
protected void waitUntilSpinnerDisappear(int... timeoutInSeconds) {
log.debug("\nwaitUntilSpinnerDisappear");
if (isVisible(loadingSpinner, timeoutInSeconds)) {
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) {
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;
}
public String toString() {
return value;
}
public String toString() {
return value;
}
}
}

View file

@ -1,41 +1,40 @@
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.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.codeborne.selenide.Selenide.$$x;
import static com.codeborne.selenide.Selenide.$x;
public class BrokersConfigTab extends BasePage {
protected List<SelenideElement> editBtn = $$x("//button[@aria-label='editAction']");
protected SelenideElement searchByKeyField = $x("//input[@placeholder='Search by Key']");
protected List<SelenideElement> editBtn = $$x("//button[@aria-label='editAction']");
protected SelenideElement searchByKeyField = $x("//input[@placeholder='Search by Key']");
@Step
public BrokersConfigTab waitUntilScreenReady() {
waitUntilSpinnerDisappear();
searchByKeyField.shouldBe(Condition.visible);
return this;
}
@Step
public BrokersConfigTab waitUntilScreenReady() {
waitUntilSpinnerDisappear();
searchByKeyField.shouldBe(Condition.visible);
return this;
}
@Step
public boolean isSearchByKeyVisible() {
return isVisible(searchByKeyField);
}
@Step
public boolean isSearchByKeyVisible() {
return isVisible(searchByKeyField);
}
public List<SelenideElement> getColumnHeaders() {
return Stream.of("Key", "Value", "Source")
.map(name -> $x(String.format(columnHeaderLocator, name)))
.collect(Collectors.toList());
}
public List<SelenideElement> getColumnHeaders() {
return Stream.of("Key", "Value", "Source")
.map(name -> $x(String.format(columnHeaderLocator, name)))
.collect(Collectors.toList());
}
public List<SelenideElement> getEditButtons() {
return editBtn;
}
public List<SelenideElement> getEditButtons() {
return editBtn;
}
}

View file

@ -1,92 +1,91 @@
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.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import org.openqa.selenium.By;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.codeborne.selenide.Selenide.$;
import static com.codeborne.selenide.Selenide.$x;
import org.openqa.selenium.By;
public class BrokersDetails extends BasePage {
protected SelenideElement logDirectoriesTab = $x("//a[text()='Log directories']");
protected SelenideElement metricsTab = $x("//a[text()='Metrics']");
protected String brokersTabLocator = "//a[text()='%s']";
protected SelenideElement logDirectoriesTab = $x("//a[text()='Log directories']");
protected SelenideElement metricsTab = $x("//a[text()='Metrics']");
protected String brokersTabLocator = "//a[text()='%s']";
@Step
public BrokersDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
Arrays.asList(logDirectoriesTab, metricsTab).forEach(element -> element.shouldBe(Condition.visible));
return this;
@Step
public BrokersDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
Arrays.asList(logDirectoriesTab, metricsTab).forEach(element -> element.shouldBe(Condition.visible));
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 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;
}
public String toString() {
return value;
}
public String toString() {
return value;
}
}
}

View file

@ -1,123 +1,122 @@
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.Condition;
import com.codeborne.selenide.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
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 {
@Step
public BrokersList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(BROKERS).shouldBe(Condition.visible);
return this;
@Step
public BrokersList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(BROKERS).shouldBe(Condition.visible);
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
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());
public int getId() {
return Integer.parseInt(getIdElm().getText().trim());
}
@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());
public void openItem() {
getIdElm().click();
}
@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;
public int getSegmentSize() {
return Integer.parseInt(element.$x("./td[2]").getText().trim());
}
@Step
public BrokerGridItem getBrokerItem(int id) {
return initGridItems().stream()
.filter(e -> e.getId() == id)
.findFirst().orElseThrow();
public int getSegmentCount() {
return Integer.parseInt(element.$x("./td[3]").getText().trim());
}
@Step
public List<BrokerGridItem> getAllBrokers() {
return initGridItems();
public int getPort() {
return Integer.parseInt(element.$x("./td[4]").getText().trim());
}
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
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();
}
@Step
public String getHost() {
return element.$x("./td[5]").getText().trim();
}
}
}

View file

@ -1,49 +1,49 @@
package com.provectus.kafka.ui.pages.connectors;
import static com.codeborne.selenide.Selenide.$x;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import static com.codeborne.selenide.Selenide.$x;
public class ConnectorCreateForm extends BasePage {
protected SelenideElement nameField = $x("//input[@name='name']");
protected SelenideElement contentTextArea = $x("//textarea[@class='ace_text-input']");
protected SelenideElement configField = $x("//div[@id='config']");
protected SelenideElement nameField = $x("//input[@name='name']");
protected SelenideElement contentTextArea = $x("//textarea[@class='ace_text-input']");
protected SelenideElement configField = $x("//div[@id='config']");
@Step
public ConnectorCreateForm waitUntilScreenReady() {
waitUntilSpinnerDisappear();
nameField.shouldBe(Condition.visible);
return this;
}
@Step
public ConnectorCreateForm waitUntilScreenReady() {
waitUntilSpinnerDisappear();
nameField.shouldBe(Condition.visible);
return this;
}
@Step
public ConnectorCreateForm setName(String connectName) {
nameField.shouldBe(Condition.enabled).setValue(connectName);
return this;
}
@Step
public ConnectorCreateForm setName(String connectName) {
nameField.shouldBe(Condition.enabled).setValue(connectName);
return this;
}
@Step
public ConnectorCreateForm setConfig(String configJson) {
configField.shouldBe(Condition.enabled).click();
setJsonInputValue(contentTextArea, configJson);
return this;
}
@Step
public ConnectorCreateForm setConfig(String configJson) {
configField.shouldBe(Condition.enabled).click();
setJsonInputValue(contentTextArea, configJson);
return this;
}
@Step
public ConnectorCreateForm setConnectorDetails(String connectName, String configJson) {
setName(connectName);
setConfig(configJson);
return this;
}
@Step
public ConnectorCreateForm setConnectorDetails(String connectName, String configJson) {
setName(connectName);
setConfig(configJson);
return this;
}
@Step
public ConnectorCreateForm clickSubmitButton() {
clickSubmitBtn();
waitUntilSpinnerDisappear();
return this;
}
@Step
public ConnectorCreateForm clickSubmitButton() {
clickSubmitBtn();
waitUntilSpinnerDisappear();
return this;
}
}

View file

@ -1,84 +1,84 @@
package com.provectus.kafka.ui.pages.connectors;
import static com.codeborne.selenide.Selenide.$x;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import static com.codeborne.selenide.Selenide.$x;
public class ConnectorDetails extends BasePage {
protected SelenideElement deleteBtn = $x("//li/div[contains(text(),'Delete')]");
protected SelenideElement confirmBtnMdl = $x("//div[@role='dialog']//button[contains(text(),'Confirm')]");
protected SelenideElement contentTextArea = $x("//textarea[@class='ace_text-input']");
protected SelenideElement taskTab = $x("//a[contains(text(),'Tasks')]");
protected SelenideElement configTab = $x("//a[contains(text(),'Config')]");
protected SelenideElement configField = $x("//div[@id='config']");
protected String connectorHeaderLocator = "//h1[contains(text(),'%s')]";
protected SelenideElement deleteBtn = $x("//li/div[contains(text(),'Delete')]");
protected SelenideElement confirmBtnMdl = $x("//div[@role='dialog']//button[contains(text(),'Confirm')]");
protected SelenideElement contentTextArea = $x("//textarea[@class='ace_text-input']");
protected SelenideElement taskTab = $x("//a[contains(text(),'Tasks')]");
protected SelenideElement configTab = $x("//a[contains(text(),'Config')]");
protected SelenideElement configField = $x("//div[@id='config']");
protected String connectorHeaderLocator = "//h1[contains(text(),'%s')]";
@Step
public ConnectorDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
dotMenuBtn.shouldBe(Condition.visible);
return this;
}
@Step
public ConnectorDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
dotMenuBtn.shouldBe(Condition.visible);
return this;
}
@Step
public ConnectorDetails openConfigTab() {
clickByJavaScript(configTab);
return this;
}
@Step
public ConnectorDetails openConfigTab() {
clickByJavaScript(configTab);
return this;
}
@Step
public ConnectorDetails setConfig(String configJson) {
configField.shouldBe(Condition.enabled).click();
clearByKeyboard(contentTextArea);
contentTextArea.setValue(configJson);
configField.shouldBe(Condition.enabled).click();
return this;
}
@Step
public ConnectorDetails setConfig(String configJson) {
configField.shouldBe(Condition.enabled).click();
clearByKeyboard(contentTextArea);
contentTextArea.setValue(configJson);
configField.shouldBe(Condition.enabled).click();
return this;
}
@Step
public ConnectorDetails clickSubmitButton() {
clickSubmitBtn();
return this;
}
@Step
public ConnectorDetails clickSubmitButton() {
clickSubmitBtn();
return this;
}
@Step
public ConnectorDetails openDotMenu() {
clickByJavaScript(dotMenuBtn);
return this;
}
@Step
public ConnectorDetails openDotMenu() {
clickByJavaScript(dotMenuBtn);
return this;
}
@Step
public ConnectorDetails clickDeleteBtn() {
clickByJavaScript(deleteBtn);
return this;
}
@Step
public ConnectorDetails clickDeleteBtn() {
clickByJavaScript(deleteBtn);
return this;
}
@Step
public ConnectorDetails clickConfirmBtn() {
confirmBtnMdl.shouldBe(Condition.enabled).click();
confirmBtnMdl.shouldBe(Condition.disappear);
return this;
}
@Step
public ConnectorDetails clickConfirmBtn() {
confirmBtnMdl.shouldBe(Condition.enabled).click();
confirmBtnMdl.shouldBe(Condition.disappear);
return this;
}
@Step
public ConnectorDetails deleteConnector() {
openDotMenu();
clickDeleteBtn();
clickConfirmBtn();
return this;
}
@Step
public ConnectorDetails deleteConnector() {
openDotMenu();
clickDeleteBtn();
clickConfirmBtn();
return this;
}
@Step
public boolean isConnectorHeaderVisible(String connectorName) {
return isVisible($x(String.format(connectorHeaderLocator, connectorName)));
}
@Step
public boolean isConnectorHeaderVisible(String connectorName) {
return isVisible($x(String.format(connectorHeaderLocator, connectorName)));
}
@Step
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
return isAlertVisible(header, message);
}
@Step
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
return isAlertVisible(header, message);
}
}

View file

@ -1,44 +1,44 @@
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.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
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 {
protected SelenideElement createConnectorBtn = $x("//button[contains(text(),'Create Connector')]");
protected SelenideElement createConnectorBtn = $x("//button[contains(text(),'Create Connector')]");
public KafkaConnectList() {
tableElementNameLocator = "//tbody//td[contains(text(),'%s')]";
}
public KafkaConnectList() {
tableElementNameLocator = "//tbody//td[contains(text(),'%s')]";
}
@Step
public KafkaConnectList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(KAFKA_CONNECT).shouldBe(Condition.visible);
return this;
}
@Step
public KafkaConnectList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(KAFKA_CONNECT).shouldBe(Condition.visible);
return this;
}
@Step
public KafkaConnectList clickCreateConnectorBtn() {
clickByJavaScript(createConnectorBtn);
return this;
}
@Step
public KafkaConnectList clickCreateConnectorBtn() {
clickByJavaScript(createConnectorBtn);
return this;
}
@Step
public KafkaConnectList openConnector(String connectorName) {
getTableElement(connectorName).shouldBe(Condition.enabled).click();
return this;
}
@Step
public KafkaConnectList openConnector(String connectorName) {
getTableElement(connectorName).shouldBe(Condition.enabled).click();
return this;
}
@Step
public boolean isConnectorVisible(String connectorName) {
tableGrid.shouldBe(Condition.visible);
return isVisible(getTableElement(connectorName));
}
@Step
public boolean isConnectorVisible(String connectorName) {
tableGrid.shouldBe(Condition.visible);
return isVisible(getTableElement(connectorName));
}
}

View file

@ -1,31 +1,31 @@
package com.provectus.kafka.ui.pages.consumers;
import static com.codeborne.selenide.Selenide.$x;
import com.codeborne.selenide.Condition;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import static com.codeborne.selenide.Selenide.$x;
public class ConsumersDetails extends BasePage {
protected String consumerIdHeaderLocator = "//h1[contains(text(),'%s')]";
protected String topicElementLocator = "//tbody//td//a[text()='%s']";
protected String consumerIdHeaderLocator = "//h1[contains(text(),'%s')]";
protected String topicElementLocator = "//tbody//td//a[text()='%s']";
@Step
public ConsumersDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
tableGrid.shouldBe(Condition.visible);
return this;
}
@Step
public ConsumersDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
tableGrid.shouldBe(Condition.visible);
return this;
}
@Step
public boolean isRedirectedConsumerTitleVisible(String consumerGroupId) {
return isVisible($x(String.format(consumerIdHeaderLocator, consumerGroupId)));
}
@Step
public boolean isRedirectedConsumerTitleVisible(String consumerGroupId) {
return isVisible($x(String.format(consumerIdHeaderLocator, consumerGroupId)));
}
@Step
public boolean isTopicInConsumersDetailsVisible(String topicName) {
tableGrid.shouldBe(Condition.visible);
return isVisible($x(String.format(topicElementLocator, topicName)));
}
@Step
public boolean isTopicInConsumersDetailsVisible(String topicName) {
tableGrid.shouldBe(Condition.visible);
return isVisible($x(String.format(topicElementLocator, topicName)));
}
}

View file

@ -1,17 +1,17 @@
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.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import static com.provectus.kafka.ui.pages.panels.enums.MenuItem.CONSUMERS;
public class ConsumersList extends BasePage {
@Step
public ConsumersList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(CONSUMERS).shouldBe(Condition.visible);
return this;
}
@Step
public ConsumersList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(CONSUMERS).shouldBe(Condition.visible);
return this;
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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.experimental.Accessors;
@ -7,5 +7,5 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
public class Stream {
private String name, topicName, valueFormat, partitions;
private String name, topicName, valueFormat, partitions;
}

View file

@ -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.experimental.Accessors;
@ -7,5 +7,5 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
public class Table {
private String name, streamName;
private String name, streamName;
}

View file

@ -1,64 +1,63 @@
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.SelenideElement;
import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
import com.provectus.kafka.ui.pages.BasePage;
import com.provectus.kafka.ui.pages.panels.enums.MenuItem;
import io.qameta.allure.Step;
import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
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 {
protected SelenideElement dashboardMenuItem = $x("//a[@title='Dashboard']");
protected String sideMenuOptionElementLocator = ".//ul/li[contains(.,'%s')]";
protected String clusterElementLocator = "//aside/ul/li[contains(.,'%s')]";
protected SelenideElement dashboardMenuItem = $x("//a[@title='Dashboard']");
protected String sideMenuOptionElementLocator = ".//ul/li[contains(.,'%s')]";
protected String clusterElementLocator = "//aside/ul/li[contains(.,'%s')]";
private SelenideElement expandCluster(String clusterName) {
SelenideElement clusterElement = $x(String.format(clusterElementLocator, clusterName)).shouldBe(Condition.visible);
if (clusterElement.parent().$$x(".//ul").size() == 0) {
clickByActions(clusterElement);
}
return clusterElement;
private SelenideElement expandCluster(String clusterName) {
SelenideElement clusterElement = $x(String.format(clusterElementLocator, clusterName)).shouldBe(Condition.visible);
if (clusterElement.parent().$$x(".//ul").size() == 0) {
clickByActions(clusterElement);
}
return clusterElement;
}
@Step
public NaviSideBar waitUntilScreenReady() {
waitUntilSpinnerDisappear();
dashboardMenuItem.shouldBe(Condition.visible, Duration.ofSeconds(30));
return this;
}
@Step
public NaviSideBar waitUntilScreenReady() {
waitUntilSpinnerDisappear();
dashboardMenuItem.shouldBe(Condition.visible, Duration.ofSeconds(30));
return this;
}
@Step
public String getPagePath(MenuItem menuItem) {
return getPagePathFromHeader(menuItem)
.shouldBe(Condition.visible)
.getText().trim();
}
@Step
public String getPagePath(MenuItem menuItem) {
return getPagePathFromHeader(menuItem)
.shouldBe(Condition.visible)
.getText().trim();
}
@Step
public NaviSideBar openSideMenu(String clusterName, MenuItem menuItem) {
clickByActions(expandCluster(clusterName).parent()
.$x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())));
return this;
}
@Step
public NaviSideBar openSideMenu(String clusterName, MenuItem menuItem) {
clickByActions(expandCluster(clusterName).parent()
.$x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())));
return this;
}
@Step
public NaviSideBar openSideMenu(MenuItem menuItem) {
openSideMenu(CLUSTER_NAME, menuItem);
return this;
}
@Step
public NaviSideBar openSideMenu(MenuItem menuItem) {
openSideMenu(CLUSTER_NAME, menuItem);
return this;
}
public List<SelenideElement> getAllMenuButtons() {
expandCluster(CLUSTER_NAME);
return Stream.of(MenuItem.values())
.map(menuItem -> $x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())))
.collect(Collectors.toList());
}
public List<SelenideElement> getAllMenuButtons() {
expandCluster(CLUSTER_NAME);
return Stream.of(MenuItem.values())
.map(menuItem -> $x(String.format(sideMenuOptionElementLocator, menuItem.getNaviTitle())))
.collect(Collectors.toList());
}
}

View file

@ -1,26 +1,25 @@
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 com.codeborne.selenide.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import java.util.Arrays;
import java.util.List;
public class TopPanel extends BasePage {
protected SelenideElement kafkaLogo = $x("//a[contains(text(),'UI for Apache Kafka')]");
protected SelenideElement kafkaVersion = $x("//a[@title='Current commit']");
protected SelenideElement logOutBtn = $x("//button[contains(text(),'Log out')]");
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 kafkaLogo = $x("//a[contains(text(),'UI for Apache Kafka')]");
protected SelenideElement kafkaVersion = $x("//a[@title='Current commit']");
protected SelenideElement logOutBtn = $x("//button[contains(text(),'Log out')]");
protected SelenideElement gitBtn = $x("//a[@href='https://github.com/provectus/kafka-ui']");
protected SelenideElement discordBtn = $x("//a[contains(@href,'https://discord.com/invite')]");
public List<SelenideElement> getAllVisibleElements() {
return Arrays.asList(kafkaLogo, kafkaVersion, gitBtn, discordBtn);
}
public List<SelenideElement> getAllVisibleElements() {
return Arrays.asList(kafkaLogo, kafkaVersion, gitBtn, discordBtn);
}
public List<SelenideElement> getAllEnabledElements() {
return Arrays.asList(gitBtn, discordBtn, kafkaLogo);
}
public List<SelenideElement> getAllEnabledElements() {
return Arrays.asList(gitBtn, discordBtn, kafkaLogo);
}
}

View file

@ -1,28 +1,28 @@
package com.provectus.kafka.ui.pages.panels.enums;
public enum MenuItem {
DASHBOARD("Dashboard", "Dashboard"),
BROKERS("Brokers", "Brokers"),
TOPICS("Topics", "Topics"),
CONSUMERS("Consumers", "Consumers"),
SCHEMA_REGISTRY("Schema Registry", "Schema Registry"),
KAFKA_CONNECT("Kafka Connect", "Connectors"),
KSQL_DB("KSQL DB", "KSQL DB");
private final String naviTitle;
private final String pageTitle;
MenuItem(String naviTitle, String pageTitle) {
this.naviTitle = naviTitle;
this.pageTitle = pageTitle;
}
public String getNaviTitle() {
return naviTitle;
}
public String getPageTitle() {
return pageTitle;
}
DASHBOARD("Dashboard", "Dashboard"),
BROKERS("Brokers", "Brokers"),
TOPICS("Topics", "Topics"),
CONSUMERS("Consumers", "Consumers"),
SCHEMA_REGISTRY("Schema Registry", "Schema Registry"),
KAFKA_CONNECT("Kafka Connect", "Connectors"),
KSQL_DB("KSQL DB", "KSQL DB");
private final String naviTitle;
private final String pageTitle;
MenuItem(String naviTitle, String pageTitle) {
this.naviTitle = naviTitle;
this.pageTitle = pageTitle;
}
public String getNaviTitle() {
return naviTitle;
}
public String getPageTitle() {
return pageTitle;
}
}

View file

@ -1,5 +1,10 @@
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.SelenideElement;
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.pages.BasePage;
import io.qameta.allure.Step;
import org.openqa.selenium.Keys;
import org.openqa.selenium.interactions.Actions;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.codeborne.selenide.Selenide.*;
import static org.openqa.selenium.By.id;
import org.openqa.selenium.Keys;
import org.openqa.selenium.interactions.Actions;
public class SchemaCreateForm extends BasePage {
protected SelenideElement schemaNameField = $x("//input[@name='subject']");
protected SelenideElement pageTitle = $x("//h1['Edit']");
protected SelenideElement schemaTextArea = $x("//textarea[@name='schema']");
protected SelenideElement newSchemaInput = $("#newSchema [wrap]");
protected SelenideElement schemaTypeDdl = $x("//ul[@name='schemaType']");
protected SelenideElement compatibilityLevelList = $x("//ul[@name='compatibilityLevel']");
protected SelenideElement newSchemaTextArea = $x("//div[@id='newSchema']");
protected SelenideElement latestSchemaTextArea = $x("//div[@id='latestSchema']");
protected SelenideElement leftVersionDdl = $(id("left-select"));
protected SelenideElement rightVersionDdl = $(id("right-select"));
protected List<SelenideElement> visibleMarkers = $$x("//div[@class='ace_scroller']//div[contains(@class,'codeMarker')]");
protected List<SelenideElement> elementsCompareVersionDdl = $$x("//ul[@role='listbox']/ul/li");
protected String ddlElementLocator = "//li[@value='%s']";
protected SelenideElement schemaNameField = $x("//input[@name='subject']");
protected SelenideElement pageTitle = $x("//h1['Edit']");
protected SelenideElement schemaTextArea = $x("//textarea[@name='schema']");
protected SelenideElement newSchemaInput = $("#newSchema [wrap]");
protected SelenideElement schemaTypeDdl = $x("//ul[@name='schemaType']");
protected SelenideElement compatibilityLevelList = $x("//ul[@name='compatibilityLevel']");
protected SelenideElement newSchemaTextArea = $x("//div[@id='newSchema']");
protected SelenideElement latestSchemaTextArea = $x("//div[@id='latestSchema']");
protected SelenideElement leftVersionDdl = $(id("left-select"));
protected SelenideElement rightVersionDdl = $(id("right-select"));
protected List<SelenideElement> visibleMarkers =
$$x("//div[@class='ace_scroller']//div[contains(@class,'codeMarker')]");
protected List<SelenideElement> elementsCompareVersionDdl = $$x("//ul[@role='listbox']/ul/li");
protected String ddlElementLocator = "//li[@value='%s']";
@Step
public SchemaCreateForm waitUntilScreenReady() {
waitUntilSpinnerDisappear();
pageTitle.shouldBe(Condition.visible);
return this;
}
@Step
public SchemaCreateForm waitUntilScreenReady() {
waitUntilSpinnerDisappear();
pageTitle.shouldBe(Condition.visible);
return this;
}
@Step
public SchemaCreateForm setSubjectName(String name) {
schemaNameField.setValue(name);
return this;
}
@Step
public SchemaCreateForm setSubjectName(String name) {
schemaNameField.setValue(name);
return this;
}
@Step
public SchemaCreateForm setSchemaField(String text) {
schemaTextArea.setValue(text);
return this;
}
@Step
public SchemaCreateForm setSchemaField(String text) {
schemaTextArea.setValue(text);
return this;
}
@Step
public SchemaCreateForm selectSchemaTypeFromDropdown(SchemaType schemaType) {
schemaTypeDdl.shouldBe(Condition.enabled).click();
$x(String.format(ddlElementLocator, schemaType.getValue())).shouldBe(Condition.visible).click();
return this;
}
@Step
public SchemaCreateForm selectSchemaTypeFromDropdown(SchemaType schemaType) {
schemaTypeDdl.shouldBe(Condition.enabled).click();
$x(String.format(ddlElementLocator, schemaType.getValue())).shouldBe(Condition.visible).click();
return this;
}
@Step
public SchemaCreateForm clickSubmitButton() {
clickSubmitBtn();
return this;
}
@Step
public SchemaCreateForm clickSubmitButton() {
clickSubmitBtn();
return this;
}
@Step
public SchemaCreateForm selectCompatibilityLevelFromDropdown(CompatibilityLevel.CompatibilityEnum level) {
compatibilityLevelList.shouldBe(Condition.enabled).click();
$x(String.format(ddlElementLocator, level.getValue())).shouldBe(Condition.visible).click();
return this;
}
@Step
public SchemaCreateForm selectCompatibilityLevelFromDropdown(CompatibilityLevel.CompatibilityEnum level) {
compatibilityLevelList.shouldBe(Condition.enabled).click();
$x(String.format(ddlElementLocator, level.getValue())).shouldBe(Condition.visible).click();
return this;
}
@Step
public SchemaCreateForm openLeftVersionDdl() {
leftVersionDdl.shouldBe(Condition.enabled).click();
return this;
}
@Step
public SchemaCreateForm openLeftVersionDdl() {
leftVersionDdl.shouldBe(Condition.enabled).click();
return this;
}
@Step
public SchemaCreateForm openRightVersionDdl() {
rightVersionDdl.shouldBe(Condition.enabled).click();
return this;
}
@Step
public SchemaCreateForm openRightVersionDdl() {
rightVersionDdl.shouldBe(Condition.enabled).click();
return this;
}
@Step
public int getVersionsNumberFromList() {
return elementsCompareVersionDdl.size();
}
@Step
public int getVersionsNumberFromList() {
return elementsCompareVersionDdl.size();
}
@Step
public SchemaCreateForm selectVersionFromDropDown(int versionNumberDd) {
$x(String.format(ddlElementLocator, versionNumberDd)).shouldBe(Condition.visible).click();
return this;
}
@Step
public SchemaCreateForm selectVersionFromDropDown(int versionNumberDd) {
$x(String.format(ddlElementLocator, versionNumberDd)).shouldBe(Condition.visible).click();
return this;
}
@Step
public int getMarkedLinesNumber() {
return visibleMarkers.size();
}
@Step
public int getMarkedLinesNumber() {
return visibleMarkers.size();
}
@Step
public SchemaCreateForm setNewSchemaValue(String configJson) {
newSchemaTextArea.shouldBe(Condition.visible).click();
newSchemaInput.shouldBe(Condition.enabled);
new Actions(WebDriverRunner.getWebDriver())
.sendKeys(Keys.PAGE_UP)
.keyDown(Keys.SHIFT)
.sendKeys(Keys.PAGE_DOWN)
.keyUp(Keys.SHIFT)
.sendKeys(Keys.DELETE)
.perform();
setJsonInputValue(newSchemaInput, configJson);
return this;
}
@Step
public SchemaCreateForm setNewSchemaValue(String configJson) {
newSchemaTextArea.shouldBe(Condition.visible).click();
newSchemaInput.shouldBe(Condition.enabled);
new Actions(WebDriverRunner.getWebDriver())
.sendKeys(Keys.PAGE_UP)
.keyDown(Keys.SHIFT)
.sendKeys(Keys.PAGE_DOWN)
.keyUp(Keys.SHIFT)
.sendKeys(Keys.DELETE)
.perform();
setJsonInputValue(newSchemaInput, configJson);
return this;
}
@Step
public List<SelenideElement> getAllDetailsPageElements() {
return Stream.of(compatibilityLevelList, newSchemaTextArea, latestSchemaTextArea, submitBtn, schemaTypeDdl)
.collect(Collectors.toList());
}
@Step
public List<SelenideElement> getAllDetailsPageElements() {
return Stream.of(compatibilityLevelList, newSchemaTextArea, latestSchemaTextArea, submitBtn, schemaTypeDdl)
.collect(Collectors.toList());
}
@Step
public boolean isSubmitBtnEnabled() {
return isEnabled(submitBtn);
}
@Step
public boolean isSubmitBtnEnabled() {
return isEnabled(submitBtn);
}
@Step
public boolean isSchemaDropDownEnabled() {
boolean enabled = true;
try {
String attribute = schemaTypeDdl.getAttribute("disabled");
enabled = false;
} catch (Throwable ignored) {
}
return enabled;
@Step
public boolean isSchemaDropDownEnabled() {
boolean enabled = true;
try {
String attribute = schemaTypeDdl.getAttribute("disabled");
enabled = false;
} catch (Throwable ignored) {
}
return enabled;
}
}

View file

@ -1,69 +1,69 @@
package com.provectus.kafka.ui.pages.schemas;
import static com.codeborne.selenide.Selenide.$x;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import static com.codeborne.selenide.Selenide.$x;
public class SchemaDetails extends BasePage {
protected SelenideElement actualVersionTextArea = $x("//div[@id='schema']");
protected SelenideElement compatibilityField = $x("//h4[contains(text(),'Compatibility')]/../p");
protected SelenideElement editSchemaBtn = $x("//button[contains(text(),'Edit Schema')]");
protected SelenideElement removeBtn = $x("//*[contains(text(),'Remove')]");
protected SelenideElement confirmBtn = $x("//div[@role='dialog']//button[contains(text(),'Confirm')]");
protected SelenideElement schemaTypeField = $x("//h4[contains(text(),'Type')]/../p");
protected SelenideElement latestVersionField = $x("//h4[contains(text(),'Latest version')]/../p");
protected SelenideElement compareVersionBtn = $x("//button[text()='Compare Versions']");
protected String schemaHeaderLocator = "//h1[contains(text(),'%s')]";
protected SelenideElement actualVersionTextArea = $x("//div[@id='schema']");
protected SelenideElement compatibilityField = $x("//h4[contains(text(),'Compatibility')]/../p");
protected SelenideElement editSchemaBtn = $x("//button[contains(text(),'Edit Schema')]");
protected SelenideElement removeBtn = $x("//*[contains(text(),'Remove')]");
protected SelenideElement confirmBtn = $x("//div[@role='dialog']//button[contains(text(),'Confirm')]");
protected SelenideElement schemaTypeField = $x("//h4[contains(text(),'Type')]/../p");
protected SelenideElement latestVersionField = $x("//h4[contains(text(),'Latest version')]/../p");
protected SelenideElement compareVersionBtn = $x("//button[text()='Compare Versions']");
protected String schemaHeaderLocator = "//h1[contains(text(),'%s')]";
@Step
public SchemaDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
actualVersionTextArea.shouldBe(Condition.visible);
return this;
}
@Step
public SchemaDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
actualVersionTextArea.shouldBe(Condition.visible);
return this;
}
@Step
public String getCompatibility() {
return compatibilityField.getText();
}
@Step
public String getCompatibility() {
return compatibilityField.getText();
}
@Step
public boolean isSchemaHeaderVisible(String schemaName) {
return isVisible($x(String.format(schemaHeaderLocator, schemaName)));
}
@Step
public boolean isSchemaHeaderVisible(String schemaName) {
return isVisible($x(String.format(schemaHeaderLocator, schemaName)));
}
@Step
public int getLatestVersion() {
return Integer.parseInt(latestVersionField.getText());
}
@Step
public int getLatestVersion() {
return Integer.parseInt(latestVersionField.getText());
}
@Step
public String getSchemaType() {
return schemaTypeField.getText();
}
@Step
public String getSchemaType() {
return schemaTypeField.getText();
}
@Step
public SchemaDetails openEditSchema() {
editSchemaBtn.shouldBe(Condition.visible).click();
return this;
}
@Step
public SchemaDetails openEditSchema() {
editSchemaBtn.shouldBe(Condition.visible).click();
return this;
}
@Step
public SchemaDetails openCompareVersionMenu() {
compareVersionBtn.shouldBe(Condition.enabled).click();
return this;
}
@Step
public SchemaDetails openCompareVersionMenu() {
compareVersionBtn.shouldBe(Condition.enabled).click();
return this;
}
@Step
public SchemaDetails removeSchema() {
clickByJavaScript(dotMenuBtn);
removeBtn.shouldBe(Condition.enabled).click();
confirmBtn.shouldBe(Condition.visible).click();
confirmBtn.shouldBe(Condition.disappear);
return this;
}
@Step
public SchemaDetails removeSchema() {
clickByJavaScript(dotMenuBtn);
removeBtn.shouldBe(Condition.enabled).click();
confirmBtn.shouldBe(Condition.visible).click();
confirmBtn.shouldBe(Condition.disappear);
return this;
}
}

View file

@ -1,42 +1,42 @@
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.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
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 {
protected SelenideElement createSchemaBtn = $x("//button[contains(text(),'Create Schema')]");
protected SelenideElement createSchemaBtn = $x("//button[contains(text(),'Create Schema')]");
@Step
public SchemaRegistryList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(SCHEMA_REGISTRY).shouldBe(Condition.visible);
return this;
}
@Step
public SchemaRegistryList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(SCHEMA_REGISTRY).shouldBe(Condition.visible);
return this;
}
@Step
public SchemaRegistryList clickCreateSchema() {
clickByJavaScript(createSchemaBtn);
return this;
}
@Step
public SchemaRegistryList clickCreateSchema() {
clickByJavaScript(createSchemaBtn);
return this;
}
@Step
public SchemaRegistryList openSchema(String schemaName) {
getTableElement(schemaName)
.shouldBe(Condition.enabled).click();
return this;
}
@Step
public SchemaRegistryList openSchema(String schemaName) {
getTableElement(schemaName)
.shouldBe(Condition.enabled).click();
return this;
}
@Step
public boolean isSchemaVisible(String schemaName) {
tableGrid.shouldBe(Condition.visible);
return isVisible(getTableElement(schemaName));
}
@Step
public boolean isSchemaVisible(String schemaName) {
tableGrid.shouldBe(Condition.visible);
return isVisible(getTableElement(schemaName));
}
}

View file

@ -1,57 +1,56 @@
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.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import java.util.Arrays;
import static com.codeborne.selenide.Selenide.$x;
import static com.codeborne.selenide.Selenide.refresh;
public class ProduceMessagePanel extends BasePage {
protected SelenideElement keyTextArea = $x("//div[@id='key']/textarea");
protected SelenideElement contentTextArea = $x("//div[@id='content']/textarea");
protected SelenideElement headersTextArea = $x("//div[@id='headers']/textarea");
protected SelenideElement submitBtn = headersTextArea.$x("../../../..//button[@type='submit']");
protected SelenideElement partitionDdl = $x("//ul[@name='partition']");
protected SelenideElement keySerdeDdl = $x("//ul[@name='keySerde']");
protected SelenideElement contentSerdeDdl = $x("//ul[@name='valueSerde']");
protected SelenideElement keyTextArea = $x("//div[@id='key']/textarea");
protected SelenideElement valueTextArea = $x("//div[@id='content']/textarea");
protected SelenideElement headersTextArea = $x("//div[@id='headers']/textarea");
protected SelenideElement submitBtn = headersTextArea.$x("../../../..//button[@type='submit']");
protected SelenideElement partitionDdl = $x("//ul[@name='partition']");
protected SelenideElement keySerdeDdl = $x("//ul[@name='keySerde']");
protected SelenideElement contentSerdeDdl = $x("//ul[@name='valueSerde']");
@Step
public ProduceMessagePanel waitUntilScreenReady() {
waitUntilSpinnerDisappear();
Arrays.asList(partitionDdl, keySerdeDdl, contentSerdeDdl).forEach(element -> element.shouldBe(Condition.visible));
return this;
}
@Step
public ProduceMessagePanel waitUntilScreenReady() {
waitUntilSpinnerDisappear();
Arrays.asList(partitionDdl, keySerdeDdl, contentSerdeDdl).forEach(element -> element.shouldBe(Condition.visible));
return this;
}
@Step
public ProduceMessagePanel setKeyField(String value) {
clearByKeyboard(keyTextArea);
keyTextArea.setValue(value);
return this;
}
@Step
public ProduceMessagePanel setKeyField(String value) {
clearByKeyboard(keyTextArea);
keyTextArea.setValue(value);
return this;
}
@Step
public ProduceMessagePanel setContentFiled(String value) {
clearByKeyboard(contentTextArea);
contentTextArea.setValue(value);
return this;
}
@Step
public ProduceMessagePanel setValueFiled(String value) {
clearByKeyboard(valueTextArea);
valueTextArea.setValue(value);
return this;
}
@Step
public ProduceMessagePanel setHeaderFiled(String value) {
headersTextArea.setValue(value);
return this;
}
@Step
public ProduceMessagePanel setHeadersFld(String value) {
headersTextArea.setValue(value);
return this;
}
@Step
public ProduceMessagePanel submitProduceMessage() {
clickByActions(submitBtn);
submitBtn.shouldBe(Condition.disappear);
refresh();
return this;
}
@Step
public ProduceMessagePanel submitProduceMessage() {
clickByActions(submitBtn);
submitBtn.shouldBe(Condition.disappear);
refresh();
return this;
}
}

View file

@ -1,6 +1,15 @@
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.topics.enums.CleanupPolicyValue;
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 io.qameta.allure.Step;
import static com.codeborne.selenide.Selenide.*;
public class TopicCreateEditForm extends BasePage {
protected SelenideElement timeToRetainField = $x("//input[@id='timeToRetain']");
protected SelenideElement partitionsField = $x("//input[@name='partitions']");
protected SelenideElement nameField = $x("//input[@name='name']");
protected SelenideElement maxMessageBytesField = $x("//input[@name='maxMessageBytes']");
protected SelenideElement minInSyncReplicasField = $x("//input[@name='minInSyncReplicas']");
protected SelenideElement cleanUpPolicyDdl = $x("//ul[@id='topicFormCleanupPolicy']");
protected SelenideElement maxSizeOnDiscDdl = $x("//ul[@id='topicFormRetentionBytes']");
protected SelenideElement customParameterDdl = $x("//ul[contains(@name,'customParams')]");
protected SelenideElement deleteCustomParameterBtn = $x("//span[contains(@title,'Delete customParam')]");
protected SelenideElement addCustomParameterTypeBtn = $x("//button[contains(text(),'Add Custom Parameter')]");
protected SelenideElement customParameterValueField = $x("//input[@placeholder='Value']");
protected SelenideElement validationCustomParameterValueMsg = $x("//p[contains(text(),'Value is required')]");
protected String ddlElementLocator = "//li[@value='%s']";
protected String btnTimeToRetainLocator = "//button[@class][text()='%s']";
protected SelenideElement timeToRetainField = $x("//input[@id='timeToRetain']");
protected SelenideElement partitionsField = $x("//input[@name='partitions']");
protected SelenideElement nameField = $(id("topicFormName"));
protected SelenideElement maxMessageBytesField = $x("//input[@name='maxMessageBytes']");
protected SelenideElement minInSyncReplicasField = $x("//input[@name='minInSyncReplicas']");
protected SelenideElement cleanUpPolicyDdl = $x("//ul[@id='topicFormCleanupPolicy']");
protected SelenideElement maxSizeOnDiscDdl = $x("//ul[@id='topicFormRetentionBytes']");
protected SelenideElement customParameterDdl = $x("//ul[contains(@name,'customParams')]");
protected SelenideElement deleteCustomParameterBtn = $x("//span[contains(@title,'Delete customParam')]");
protected SelenideElement addCustomParameterTypeBtn = $x("//button[contains(text(),'Add Custom Parameter')]");
protected SelenideElement customParameterValueField = $x("//input[@placeholder='Value']");
protected SelenideElement validationCustomParameterValueMsg = $x("//p[contains(text(),'Value is required')]");
protected String ddlElementLocator = "//li[@value='%s']";
protected String btnTimeToRetainLocator = "//button[@class][text()='%s']";
@Step
public TopicCreateEditForm waitUntilScreenReady() {
waitUntilSpinnerDisappear();
nameField.shouldBe(Condition.visible);
return this;
@Step
public TopicCreateEditForm waitUntilScreenReady() {
waitUntilSpinnerDisappear();
nameField.shouldBe(Condition.visible);
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() {
return isEnabled(submitBtn);
public KafkaUiSelectElement(SelenideElement selectElement) {
this.selectElement = selectElement;
}
public boolean isDeleteCustomParameterButtonEnabled() {
return isEnabled(deleteCustomParameterBtn);
public void selectByOptionValue(String optionValue) {
selectElement.click();
selectElement
.$$x(".//ul/li[@role='option']")
.find(Condition.attribute("value", optionValue))
.click(ClickOptions.usingJavaScript());
}
public boolean isNameFieldEnabled() {
return isEnabled(nameField);
public void selectByVisibleText(String visibleText) {
selectElement.click();
selectElement
.$$("ul>li[role=option]")
.find(Condition.exactText(visibleText))
.click();
}
@Step
public TopicCreateEditForm setTopicName(String topicName) {
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();
}
public String getCurrentValue() {
return selectElement.$("li").getText();
}
}
}

View file

@ -1,463 +1,475 @@
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.Condition;
import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import org.openqa.selenium.By;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.*;
import static com.codeborne.selenide.Selenide.*;
import static org.testcontainers.shaded.org.apache.commons.lang3.RandomUtils.nextInt;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
public class TopicDetails extends BasePage {
protected SelenideElement clearMessagesBtn = $x(("//div[contains(text(), 'Clear messages')]"));
protected SelenideElement recreateTopicBtn = $x("//div[text()='Recreate Topic']");
protected SelenideElement messageAmountCell = $x("//tbody/tr/td[5]");
protected SelenideElement overviewTab = $x("//a[contains(text(),'Overview')]");
protected SelenideElement messagesTab = $x("//a[contains(text(),'Messages')]");
protected SelenideElement seekTypeDdl = $x("//ul[@id='selectSeekType']/li");
protected SelenideElement seekTypeField = $x("//label[text()='Seek Type']//..//div/input");
protected SelenideElement addFiltersBtn = $x("//button[text()='Add Filters']");
protected SelenideElement savedFiltersLink = $x("//div[text()='Saved Filters']");
protected SelenideElement addFilterCodeModalTitle = $x("//label[text()='Filter code']");
protected SelenideElement addFilterCodeInput = $x("//div[@id='ace-editor']//textarea");
protected SelenideElement saveThisFilterCheckBoxAddFilterMdl = $x("//input[@name='saveFilter']");
protected SelenideElement displayNameInputAddFilterMdl = $x("//input[@placeholder='Enter Name']");
protected SelenideElement cancelBtnAddFilterMdl = $x("//button[text()='Cancel']");
protected SelenideElement addFilterBtnAddFilterMdl = $x("//button[text()='Add filter']");
protected SelenideElement addFiltersBtnMessages = $x("//button[text()='Add Filters']");
protected SelenideElement selectFilterBtnAddFilterMdl = $x("//button[text()='Select filter']");
protected SelenideElement editSettingsMenu = $x("//li[@role][contains(text(),'Edit settings')]");
protected SelenideElement removeTopicBtn = $x("//ul[@role='menu']//div[contains(text(),'Remove Topic')]");
protected SelenideElement produceMessageBtn = $x("//div//button[text()='Produce Message']");
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 partitionsField = $x("//div[contains(text(),'Partitions')]/../span");
protected SelenideElement backToCreateFiltersLink = $x("//div[text()='Back To create filters']");
protected ElementsCollection messageGridItems = $$x("//tbody//tr");
protected SelenideElement actualCalendarDate = $x("//div[@class='react-datepicker__current-month']");
protected SelenideElement previousMonthButton = $x("//button[@aria-label='Previous Month']");
protected SelenideElement nextMonthButton = $x("//button[@aria-label='Next Month']");
protected SelenideElement calendarTimeFld = $x("//input[@placeholder='Time']");
protected String dayCellLtr = "//div[@role='option'][contains(text(),'%d')]";
protected String seekFilterDdlLocator = "//ul[@id='selectSeekType']/ul/li[text()='%s']";
protected String savedFilterNameLocator = "//div[@role='savedFilter']/div[contains(text(),'%s')]";
protected String consumerIdLocator = "//a[@title='%s']";
protected String topicHeaderLocator = "//h1[contains(text(),'%s')]";
protected String activeFilterNameLocator = "//div[@data-testid='activeSmartFilter'][contains(text(),'%s')]";
protected String settingsGridValueLocator = "//tbody/tr/td/span[text()='%s']//ancestor::tr/td[2]/span";
protected SelenideElement clearMessagesBtn = $x(("//div[contains(text(), 'Clear messages')]"));
protected SelenideElement recreateTopicBtn = $x("//div[text()='Recreate Topic']");
protected SelenideElement messageAmountCell = $x("//tbody/tr/td[5]");
protected SelenideElement overviewTab = $x("//a[contains(text(),'Overview')]");
protected SelenideElement messagesTab = $x("//a[contains(text(),'Messages')]");
protected SelenideElement seekTypeDdl = $x("//ul[@id='selectSeekType']//li");
protected SelenideElement seekTypeField = $x("//label[text()='Seek Type']//..//div/input");
protected SelenideElement addFiltersBtn = $x("//button[text()='Add Filters']");
protected SelenideElement savedFiltersLink = $x("//div[text()='Saved Filters']");
protected SelenideElement addFilterCodeModalTitle = $x("//label[text()='Filter code']");
protected SelenideElement addFilterCodeInput = $x("//div[@id='ace-editor']//textarea");
protected SelenideElement saveThisFilterCheckBoxAddFilterMdl = $x("//input[@name='saveFilter']");
protected SelenideElement displayNameInputAddFilterMdl = $x("//input[@placeholder='Enter Name']");
protected SelenideElement cancelBtnAddFilterMdl = $x("//button[text()='Cancel']");
protected SelenideElement addFilterBtnAddFilterMdl = $x("//button[text()='Add filter']");
protected SelenideElement addFiltersBtnMessages = $x("//button[text()='Add Filters']");
protected SelenideElement selectFilterBtnAddFilterMdl = $x("//button[text()='Select filter']");
protected SelenideElement editSettingsMenu = $x("//li[@role][contains(text(),'Edit settings')]");
protected SelenideElement removeTopicBtn = $x("//ul[@role='menu']//div[contains(text(),'Remove Topic')]");
protected SelenideElement produceMessageBtn = $x("//div//button[text()='Produce Message']");
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 partitionsField = $x("//div[contains(text(),'Partitions')]/../span");
protected SelenideElement backToCreateFiltersLink = $x("//div[text()='Back To create filters']");
protected ElementsCollection messageGridItems = $$x("//tbody//tr");
protected SelenideElement actualCalendarDate = $x("//div[@class='react-datepicker__current-month']");
protected SelenideElement previousMonthButton = $x("//button[@aria-label='Previous Month']");
protected SelenideElement nextMonthButton = $x("//button[@aria-label='Next Month']");
protected SelenideElement calendarTimeFld = $x("//input[@placeholder='Time']");
protected String detailsTabLtr = "//nav//a[contains(text(),'%s')]";
protected String dayCellLtr = "//div[@role='option'][contains(text(),'%d')]";
protected String seekFilterDdlLocator = "//ul[@id='selectSeekType']/ul/li[text()='%s']";
protected String savedFilterNameLocator = "//div[@role='savedFilter']/div[contains(text(),'%s')]";
protected String consumerIdLocator = "//a[@title='%s']";
protected String topicHeaderLocator = "//h1[contains(text(),'%s')]";
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
public TopicDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
overviewTab.shouldBe(Condition.visible);
return this;
@Step
public TopicDetails waitUntilScreenReady() {
waitUntilSpinnerDisappear();
$x(String.format(detailsTabLtr, OVERVIEW)).shouldBe(Condition.visible);
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
public TopicDetails openDetailsTab(TopicMenu menu) {
$(By.linkText(menu.toString())).shouldBe(Condition.visible).click();
waitUntilSpinnerDisappear();
return this;
public MessageGridItem clickExpand() {
clickByJavaScript(element.$x("./td[1]/span"));
return this;
}
private SelenideElement getOffsetElm() {
return element.$x("./td[2]");
}
@Step
public String getSettingsGridValueByKey(String key) {
return $x(String.format(settingsGridValueLocator, key)).scrollTo().shouldBe(Condition.visible).getText();
public int getOffset() {
return Integer.parseInt(getOffsetElm().getText().trim());
}
@Step
public TopicDetails openDotMenu() {
clickByJavaScript(dotMenuBtn);
return this;
public int getPartition() {
return Integer.parseInt(element.$x("./td[3]").getText().trim());
}
@Step
public boolean isAlertWithMessageVisible(AlertHeader header, String message) {
return isAlertVisible(header, message);
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 TopicDetails clickEditSettingsMenu() {
editSettingsMenu.shouldBe(Condition.visible).click();
return this;
public String getKey() {
return element.$x("./td[5]").getText().trim();
}
@Step
public boolean isConfirmationMdlVisible() {
return isConfirmationModalVisible();
public String getValue() {
return element.$x("./td[6]").getAttribute("title");
}
@Step
public TopicDetails clickClearMessagesMenu() {
clearMessagesBtn.shouldBe(Condition.visible).click();
return this;
}
@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();
public MessageGridItem openDotMenu() {
getOffsetElm().hover();
element.$x("./td[7]/div/button[@aria-label='Dropdown Toggle']")
.shouldBe(Condition.visible).click();
return this;
}
@Step
public TopicDetails openSavedFiltersListMdl() {
savedFiltersLink.shouldBe(Condition.enabled).click();
backToCreateFiltersLink.shouldBe(Condition.visible);
return this;
public MessageGridItem clickCopyToClipBoard() {
clickByJavaScript(element.$x("./td[7]//li[text() = 'Copy to clipboard']")
.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;
}
@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;
}
public MessageGridItem clickSaveAsFile() {
clickByJavaScript(element.$x("./td[7]//li[text() = 'Save as a file']")
.shouldBe(Condition.visible));
return this;
}
}
}

View file

@ -1,66 +1,65 @@
package com.provectus.kafka.ui.pages.topics;
import static com.codeborne.selenide.Selenide.$x;
import com.codeborne.selenide.CollectionCondition;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import java.util.ArrayList;
import java.util.List;
import static com.codeborne.selenide.Selenide.$x;
public class TopicSettingsTab extends BasePage {
protected SelenideElement defaultValueColumnHeaderLocator = $x("//div[text() = 'Default Value']");
protected SelenideElement defaultValueColumnHeaderLocator = $x("//div[text() = 'Default Value']");
@Step
public TopicSettingsTab waitUntilScreenReady() {
waitUntilSpinnerDisappear();
defaultValueColumnHeaderLocator.shouldBe(Condition.visible);
return this;
}
@Step
public TopicSettingsTab waitUntilScreenReady() {
waitUntilSpinnerDisappear();
defaultValueColumnHeaderLocator.shouldBe(Condition.visible);
return this;
}
private List<SettingsGridItem> initGridItems() {
List<SettingsGridItem> gridItemList = new ArrayList<>();
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
.forEach(item -> gridItemList.add(new SettingsGridItem(item)));
return gridItemList;
}
private List<SettingsGridItem> initGridItems() {
List<SettingsGridItem> gridItemList = new ArrayList<>();
gridItems.shouldHave(CollectionCondition.sizeGreaterThan(0))
.forEach(item -> gridItemList.add(new SettingsGridItem(item)));
return gridItemList;
}
private TopicSettingsTab.SettingsGridItem getItemByKey(String key) {
return initGridItems().stream()
.filter(e -> e.getKey().equals(key))
.findFirst().orElseThrow();
private TopicSettingsTab.SettingsGridItem getItemByKey(String key) {
return initGridItems().stream()
.filter(e -> e.getKey().equals(key))
.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
public String getValueByKey(String key) {
return getItemByKey(key).getValue();
public String getKey() {
return element.$x("./td[1]/span").getText().trim();
}
public static class SettingsGridItem extends BasePage {
private final SelenideElement element;
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 getValue() {
return element.$x("./td[2]/span").getText().trim();
}
@Step
public String getDefaultValue() {
return element.$x("./td[3]/span").getText().trim();
}
}
}

View file

@ -1,265 +1,283 @@
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.Condition;
import com.codeborne.selenide.SelenideElement;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
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 {
protected SelenideElement addTopicBtn = $x("//button[normalize-space(text()) ='Add a Topic']");
protected SelenideElement searchField = $x("//input[@placeholder='Search by Topic Name']");
protected SelenideElement showInternalRadioBtn = $x("//input[@name='ShowInternalTopics']");
protected SelenideElement deleteSelectedTopicsBtn = $x("//button[text()='Delete selected topics']");
protected SelenideElement copySelectedTopicBtn = $x("//button[text()='Copy selected topic']");
protected SelenideElement purgeMessagesOfSelectedTopicsBtn = $x("//button[text()='Purge messages of selected topics']");
protected SelenideElement clearMessagesBtn = $x("//ul[contains(@class ,'open')]//div[text()='Clear Messages']");
protected SelenideElement recreateTopicBtn = $x("//ul[contains(@class ,'open')]//div[text()='Recreate Topic']");
protected SelenideElement removeTopicBtn = $x("//ul[contains(@class ,'open')]//div[text()='Remove Topic']");
protected SelenideElement addTopicBtn = $x("//button[normalize-space(text()) ='Add a Topic']");
protected SelenideElement searchField = $x("//input[@placeholder='Search by Topic Name']");
protected SelenideElement showInternalRadioBtn = $x("//input[@name='ShowInternalTopics']");
protected SelenideElement deleteSelectedTopicsBtn = $x("//button[text()='Delete selected topics']");
protected SelenideElement copySelectedTopicBtn = $x("//button[text()='Copy selected topic']");
protected SelenideElement purgeMessagesOfSelectedTopicsBtn =
$x("//button[text()='Purge messages of selected topics']");
protected SelenideElement clearMessagesBtn = $x("//ul[contains(@class ,'open')]//div[text()='Clear Messages']");
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
public TopicsList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(TOPICS).shouldBe(visible);
return this;
@Step
public TopicsList waitUntilScreenReady() {
waitUntilSpinnerDisappear();
getPageTitleFromHeader(TOPICS).shouldBe(visible);
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
public TopicsList clickAddTopicBtn() {
clickByJavaScript(addTopicBtn);
return this;
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 isTopicVisible(String topicName) {
tableGrid.shouldBe(visible);
return isVisible(getTableElement(topicName));
public boolean isInternal() {
boolean internal = false;
try {
internal = getNameElm().$x("./a/span").isDisplayed();
} catch (Throwable ignored) {
}
return internal;
}
@Step
public boolean isShowInternalRadioBtnSelected() {
return isSelected(showInternalRadioBtn);
public String getName() {
return getNameElm().$x("./a").getAttribute("title");
}
@Step
public TopicsList setShowInternalRadioButton(boolean select) {
selectElement(showInternalRadioBtn, select);
return this;
public void openItem() {
getNameElm().click();
}
@Step
public TopicsList openTopic(String topicName) {
getTopicItem(topicName).openItem();
return this;
public int getPartition() {
return Integer.parseInt(element.$x("./td[3]").getText().trim());
}
@Step
public TopicsList openDotMenuByTopicName(String topicName) {
getTopicItem(topicName).openDotMenu();
return this;
public int getOutOfSyncReplicas() {
return Integer.parseInt(element.$x("./td[4]").getText().trim());
}
@Step
public boolean isCopySelectedTopicBtnEnabled() {
return isEnabled(copySelectedTopicBtn);
public int getReplicationFactor() {
return Integer.parseInt(element.$x("./td[5]").getText().trim());
}
@Step
public List<SelenideElement> getActionButtons() {
return Stream.of(deleteSelectedTopicsBtn, copySelectedTopicBtn, purgeMessagesOfSelectedTopicsBtn)
.collect(Collectors.toList());
public int getNumberOfMessages() {
return Integer.parseInt(element.$x("./td[6]").getText().trim());
}
@Step
public TopicsList clickCopySelectedTopicBtn() {
copySelectedTopicBtn.shouldBe(Condition.enabled).click();
return this;
public int getSize() {
return Integer.parseInt(element.$x("./td[7]").getText().trim());
}
@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) {
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();
}
public void openDotMenu() {
element.$x("./td[8]//button").click();
}
}
}

View file

@ -2,24 +2,24 @@ package com.provectus.kafka.ui.pages.topics.enums;
public enum CleanupPolicyValue {
DELETE("delete", "Delete"),
COMPACT("compact", "Compact"),
COMPACT_DELETE("compact,delete", "Compact,Delete");
DELETE("delete", "Delete"),
COMPACT("compact", "Compact"),
COMPACT_DELETE("compact,delete", "Compact,Delete");
private final String optionValue;
private final String visibleText;
private final String optionValue;
private final String visibleText;
CleanupPolicyValue(String optionValue, String visibleText) {
this.optionValue = optionValue;
this.visibleText = visibleText;
}
CleanupPolicyValue(String optionValue, String visibleText) {
this.optionValue = optionValue;
this.visibleText = visibleText;
}
public String getOptionValue() {
return optionValue;
}
public String getOptionValue() {
return optionValue;
}
public String getVisibleText() {
return visibleText;
}
public String getVisibleText() {
return visibleText;
}
}

View file

@ -2,36 +2,36 @@ package com.provectus.kafka.ui.pages.topics.enums;
public enum CustomParameterType {
COMPRESSION_TYPE("compression.type"),
DELETE_RETENTION_MS("delete.retention.ms"),
FILE_DELETE_DELAY_MS("file.delete.delay.ms"),
FLUSH_MESSAGES("flush.messages"),
FLUSH_MS("flush.ms"),
FOLLOWER_REPLICATION_THROTTLED_REPLICAS("follower.replication.throttled.replicas"),
INDEX_INTERVAL_BYTES("index.interval.bytes"),
LEADER_REPLICATION_THROTTLED_REPLICAS("leader.replication.throttled.replicas"),
MAX_COMPACTION_LAG_MS("max.compaction.lag.ms"),
MESSAGE_DOWNCONVERSION_ENABLE("message.downconversion.enable"),
MESSAGE_FORMAT_VERSION("message.format.version"),
MESSAGE_TIMESTAMP_DIFFERENCE_MAX_MS("message.timestamp.difference.max.ms"),
MESSAGE_TIMESTAMP_TYPE("message.timestamp.type"),
MIN_CLEANABLE_DIRTY_RATIO("min.cleanable.dirty.ratio"),
MIN_COMPACTION_LAG_MS("min.compaction.lag.ms"),
PREALLOCATE("preallocate"),
RETENTION_BYTES("retention.bytes"),
SEGMENT_BYTES("segment.bytes"),
SEGMENT_INDEX_BYTES("segment.index.bytes"),
SEGMENT_JITTER_MS("segment.jitter.ms"),
SEGMENT_MS("segment.ms"),
UNCLEAN_LEADER_ELECTION_ENABLE("unclean.leader.election.enable");
COMPRESSION_TYPE("compression.type"),
DELETE_RETENTION_MS("delete.retention.ms"),
FILE_DELETE_DELAY_MS("file.delete.delay.ms"),
FLUSH_MESSAGES("flush.messages"),
FLUSH_MS("flush.ms"),
FOLLOWER_REPLICATION_THROTTLED_REPLICAS("follower.replication.throttled.replicas"),
INDEX_INTERVAL_BYTES("index.interval.bytes"),
LEADER_REPLICATION_THROTTLED_REPLICAS("leader.replication.throttled.replicas"),
MAX_COMPACTION_LAG_MS("max.compaction.lag.ms"),
MESSAGE_DOWNCONVERSION_ENABLE("message.downconversion.enable"),
MESSAGE_FORMAT_VERSION("message.format.version"),
MESSAGE_TIMESTAMP_DIFFERENCE_MAX_MS("message.timestamp.difference.max.ms"),
MESSAGE_TIMESTAMP_TYPE("message.timestamp.type"),
MIN_CLEANABLE_DIRTY_RATIO("min.cleanable.dirty.ratio"),
MIN_COMPACTION_LAG_MS("min.compaction.lag.ms"),
PREALLOCATE("preallocate"),
RETENTION_BYTES("retention.bytes"),
SEGMENT_BYTES("segment.bytes"),
SEGMENT_INDEX_BYTES("segment.index.bytes"),
SEGMENT_JITTER_MS("segment.jitter.ms"),
SEGMENT_MS("segment.ms"),
UNCLEAN_LEADER_ELECTION_ENABLE("unclean.leader.election.enable");
private final String optionValue;
private final String optionValue;
CustomParameterType(String optionValue) {
this.optionValue = optionValue;
}
CustomParameterType(String optionValue) {
this.optionValue = optionValue;
}
public String getOptionValue() {
return optionValue;
}
public String getOptionValue() {
return optionValue;
}
}

View file

@ -2,26 +2,26 @@ package com.provectus.kafka.ui.pages.topics.enums;
public enum MaxSizeOnDisk {
NOT_SET("-1", "Not Set"),
SIZE_1_GB("1073741824", "1 GB"),
SIZE_10_GB("10737418240", "10 GB"),
SIZE_20_GB("21474836480", "20 GB"),
SIZE_50_GB("53687091200", "50 GB");
NOT_SET("-1", "Not Set"),
SIZE_1_GB("1073741824", "1 GB"),
SIZE_10_GB("10737418240", "10 GB"),
SIZE_20_GB("21474836480", "20 GB"),
SIZE_50_GB("53687091200", "50 GB");
private final String optionValue;
private final String visibleText;
private final String optionValue;
private final String visibleText;
MaxSizeOnDisk(String optionValue, String visibleText) {
this.optionValue = optionValue;
this.visibleText = visibleText;
}
MaxSizeOnDisk(String optionValue, String visibleText) {
this.optionValue = optionValue;
this.visibleText = visibleText;
}
public String getOptionValue() {
return optionValue;
}
public String getOptionValue() {
return optionValue;
}
public String getVisibleText() {
return visibleText;
}
public String getVisibleText() {
return visibleText;
}
}

View file

@ -2,25 +2,25 @@ package com.provectus.kafka.ui.pages.topics.enums;
public enum TimeToRetain {
BTN_12_HOURS("12 hours", "43200000"),
BTN_1_DAY("1 day", "86400000"),
BTN_2_DAYS("2 days", "172800000"),
BTN_7_DAYS("7 days", "604800000"),
BTN_4_WEEKS("4 weeks", "2419200000");
BTN_12_HOURS("12 hours", "43200000"),
BTN_1_DAY("1 day", "86400000"),
BTN_2_DAYS("2 days", "172800000"),
BTN_7_DAYS("7 days", "604800000"),
BTN_4_WEEKS("4 weeks", "2419200000");
private final String button;
private final String value;
private final String button;
private final String value;
TimeToRetain(String button, String value) {
this.button = button;
this.value = value;
}
TimeToRetain(String button, String value) {
this.button = button;
this.value = value;
}
public String getButton() {
return button;
}
public String getButton() {
return button;
}
public String getValue() {
return value;
}
public String getValue() {
return value;
}
}

View file

@ -1,272 +1,282 @@
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.provectus.kafka.ui.api.ApiClient;
import com.provectus.kafka.ui.api.api.*;
import com.provectus.kafka.ui.api.model.*;
import com.provectus.kafka.ui.api.api.KafkaConnectApi;
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.Schema;
import com.provectus.kafka.ui.models.Topic;
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.Stream;
import com.provectus.kafka.ui.pages.ksqldb.models.Table;
import com.provectus.kafka.ui.settings.BaseSource;
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.List;
import java.util.Map;
import java.util.Objects;
import static com.codeborne.selenide.Selenide.sleep;
import static com.provectus.kafka.ui.utilities.FileUtils.fileToString;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.reactive.function.client.WebClientResponseException;
@Slf4j
public class ApiService extends BaseSource {
@SneakyThrows
private TopicsApi topicApi() {
return new TopicsApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
}
@SneakyThrows
private TopicsApi topicApi() {
return new TopicsApi(new ApiClient().setBasePath(BASE_API_URL));
}
@SneakyThrows
private SchemasApi schemaApi() {
return new SchemasApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
}
@SneakyThrows
private SchemasApi schemaApi() {
return new SchemasApi(new ApiClient().setBasePath(BASE_API_URL));
}
@SneakyThrows
private KafkaConnectApi connectorApi() {
return new KafkaConnectApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
}
@SneakyThrows
private KafkaConnectApi connectorApi() {
return new KafkaConnectApi(new ApiClient().setBasePath(BASE_API_URL));
}
@SneakyThrows
private MessagesApi messageApi() {
return new MessagesApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
}
@SneakyThrows
private MessagesApi messageApi() {
return new MessagesApi(new ApiClient().setBasePath(BASE_API_URL));
}
@SneakyThrows
private KsqlApi ksqlApi() {
return new KsqlApi(new ApiClient().setBasePath(BASE_LOCAL_URL));
}
@SneakyThrows
private KsqlApi ksqlApi() {
return new KsqlApi(new ApiClient().setBasePath(BASE_API_URL));
}
@SneakyThrows
private void createTopic(String clusterName, String topicName) {
TopicCreation topic = new TopicCreation();
topic.setName(topicName);
topic.setPartitions(1);
topic.setReplicationFactor(1);
try {
topicApi().createTopic(clusterName, topic).block();
sleep(2000);
} catch (WebClientResponseException ex) {
ex.printStackTrace();
}
@SneakyThrows
private void createTopic(String clusterName, String topicName) {
TopicCreation topic = new TopicCreation();
topic.setName(topicName);
topic.setPartitions(1);
topic.setReplicationFactor(1);
try {
topicApi().createTopic(clusterName, topic).block();
sleep(2000);
} catch (WebClientResponseException ex) {
ex.printStackTrace();
}
}
@Step
public ApiService createTopic(Topic topic) {
createTopic(CLUSTER_NAME, topic.getName());
return this;
}
@Step
public ApiService createTopic(Topic topic) {
createTopic(CLUSTER_NAME, topic.getName());
return this;
}
@SneakyThrows
private void deleteTopic(String clusterName, String topicName) {
try {
topicApi().deleteTopic(clusterName, topicName).block();
} catch (WebClientResponseException ignore) {
}
@SneakyThrows
private void deleteTopic(String clusterName, String topicName) {
try {
topicApi().deleteTopic(clusterName, topicName).block();
} catch (WebClientResponseException ignored) {
}
}
@Step
public ApiService deleteTopic(String topicName) {
deleteTopic(CLUSTER_NAME, topicName);
return this;
}
@Step
public ApiService deleteTopic(String topicName) {
deleteTopic(CLUSTER_NAME, topicName);
return this;
}
@SneakyThrows
private void createSchema(String clusterName, Schema schema) {
NewSchemaSubject schemaSubject = new NewSchemaSubject();
schemaSubject.setSubject(schema.getName());
schemaSubject.setSchema(fileToString(schema.getValuePath()));
schemaSubject.setSchemaType(schema.getType());
try {
schemaApi().createNewSchema(clusterName, schemaSubject).block();
} catch (WebClientResponseException ex) {
ex.printStackTrace();
}
@SneakyThrows
private void createSchema(String clusterName, Schema schema) {
NewSchemaSubject schemaSubject = new NewSchemaSubject();
schemaSubject.setSubject(schema.getName());
schemaSubject.setSchema(fileToString(schema.getValuePath()));
schemaSubject.setSchemaType(schema.getType());
try {
schemaApi().createNewSchema(clusterName, schemaSubject).block();
} catch (WebClientResponseException ex) {
ex.printStackTrace();
}
}
@Step
public ApiService createSchema(Schema schema) {
createSchema(CLUSTER_NAME, schema);
return this;
}
@Step
public ApiService createSchema(Schema schema) {
createSchema(CLUSTER_NAME, schema);
return this;
}
@SneakyThrows
private void deleteSchema(String clusterName, String schemaName) {
try {
schemaApi().deleteSchema(clusterName, schemaName).block();
} catch (WebClientResponseException ignore) {
}
@SneakyThrows
private void deleteSchema(String clusterName, String schemaName) {
try {
schemaApi().deleteSchema(clusterName, schemaName).block();
} catch (WebClientResponseException ignored) {
}
}
@Step
public ApiService deleteSchema(String schemaName) {
deleteSchema(CLUSTER_NAME, schemaName);
return this;
}
@Step
public ApiService deleteSchema(String schemaName) {
deleteSchema(CLUSTER_NAME, schemaName);
return this;
}
@SneakyThrows
private void deleteConnector(String clusterName, String connectName, String connectorName) {
try {
connectorApi().deleteConnector(clusterName, connectName, connectorName).block();
} catch (WebClientResponseException ignore) {
}
@SneakyThrows
private void deleteConnector(String clusterName, String connectName, String connectorName) {
try {
connectorApi().deleteConnector(clusterName, connectName, connectorName).block();
} catch (WebClientResponseException ignored) {
}
}
@Step
public ApiService deleteConnector(String connectName, String connectorName) {
deleteConnector(CLUSTER_NAME, connectName, connectorName);
return this;
}
@Step
public ApiService deleteConnector(String connectName, String connectorName) {
deleteConnector(CLUSTER_NAME, connectName, connectorName);
return this;
}
@Step
public ApiService deleteConnector(String connectorName) {
deleteConnector(CLUSTER_NAME, CONNECT_NAME, connectorName);
return this;
}
@Step
public ApiService deleteConnector(String connectorName) {
deleteConnector(CLUSTER_NAME, CONNECT_NAME, connectorName);
return this;
}
@SneakyThrows
private void createConnector(String clusterName, String connectName, Connector connector) {
NewConnector connectorProperties = new NewConnector();
connectorProperties.setName(connector.getName());
Map<String, Object> configMap = new ObjectMapper().readValue(connector.getConfig(), HashMap.class);
connectorProperties.setConfig(configMap);
try {
connectorApi().deleteConnector(clusterName, connectName, connector.getName()).block();
} catch (WebClientResponseException ignored) {
}
connectorApi().createConnector(clusterName, connectName, connectorProperties).block();
@SneakyThrows
private void createConnector(String clusterName, String connectName, Connector connector) {
NewConnector connectorProperties = new NewConnector();
connectorProperties.setName(connector.getName());
Map<String, Object> configMap = new ObjectMapper().readValue(connector.getConfig(), HashMap.class);
connectorProperties.setConfig(configMap);
try {
connectorApi().deleteConnector(clusterName, connectName, connector.getName()).block();
} catch (WebClientResponseException ignored) {
}
connectorApi().createConnector(clusterName, connectName, connectorProperties).block();
}
@Step
public ApiService createConnector(String connectName, Connector connector) {
createConnector(CLUSTER_NAME, connectName, connector);
return this;
}
@Step
public ApiService createConnector(String connectName, Connector connector) {
createConnector(CLUSTER_NAME, connectName, connector);
return this;
}
@Step
public ApiService createConnector(Connector connector) {
createConnector(CLUSTER_NAME, CONNECT_NAME, connector);
return this;
}
@Step
public ApiService createConnector(Connector connector) {
createConnector(CLUSTER_NAME, CONNECT_NAME, connector);
return this;
}
@Step
public String getFirstConnectName(String clusterName) {
return Objects.requireNonNull(connectorApi().getConnects(clusterName).blockFirst()).getName();
}
@Step
public String getFirstConnectName(String clusterName) {
return Objects.requireNonNull(connectorApi().getConnects(clusterName).blockFirst()).getName();
}
@SneakyThrows
private void sendMessage(String clusterName, Topic topic) {
CreateTopicMessage createMessage = new CreateTopicMessage();
createMessage.setPartition(0);
createMessage.setKeySerde("String");
createMessage.setValueSerde("String");
createMessage.setKey(topic.getMessageKey());
createMessage.setContent(topic.getMessageContent());
try {
messageApi().sendTopicMessages(clusterName, topic.getName(), createMessage).block();
} catch (WebClientResponseException ex) {
ex.getRawStatusCode();
}
@SneakyThrows
private void sendMessage(String clusterName, Topic topic) {
CreateTopicMessage createMessage = new CreateTopicMessage();
createMessage.setPartition(0);
createMessage.setKeySerde("String");
createMessage.setValueSerde("String");
createMessage.setKey(topic.getMessageKey());
createMessage.setContent(topic.getMessageValue());
try {
messageApi().sendTopicMessages(clusterName, topic.getName(), createMessage).block();
} catch (WebClientResponseException ex) {
ex.getRawStatusCode();
}
}
@Step
public ApiService sendMessage(Topic topic) {
sendMessage(CLUSTER_NAME, topic);
return this;
}
@Step
public ApiService sendMessage(Topic topic) {
sendMessage(CLUSTER_NAME, topic);
return this;
}
@Step
public ApiService createStream(Stream stream) {
KsqlCommandV2Response pipeIdStream = ksqlApi()
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
.ksql(String.format("CREATE STREAM %s (profileId VARCHAR, latitude DOUBLE, longitude DOUBLE) ",
stream.getName())
+ String.format("WITH (kafka_topic='%s', value_format='json', partitions=1);",
stream.getTopicName())))
.block();
assert pipeIdStream != null;
List<KsqlResponse> responseListStream = ksqlApi()
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdStream.getPipeId())
.collectList()
.block();
assert Objects.requireNonNull(responseListStream).size() != 0;
return this;
}
@Step
public ApiService createStream(Stream stream) {
KsqlCommandV2Response pipeIdStream = ksqlApi()
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
.ksql(String.format("CREATE STREAM %s (profileId VARCHAR, latitude DOUBLE, longitude DOUBLE) ",
stream.getName())
+ String.format("WITH (kafka_topic='%s', value_format='json', partitions=1);",
stream.getTopicName())))
.block();
assert pipeIdStream != null;
List<KsqlResponse> responseListStream = ksqlApi()
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdStream.getPipeId())
.collectList()
.block();
assert Objects.requireNonNull(responseListStream).size() != 0;
return this;
}
@Step
public ApiService createTables(Table firstTable, Table secondTable) {
KsqlCommandV2Response pipeIdTable1 = ksqlApi()
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
.ksql(String.format("CREATE TABLE %s AS ", firstTable.getName())
+ " SELECT profileId, "
+ " LATEST_BY_OFFSET(latitude) AS la, "
+ " LATEST_BY_OFFSET(longitude) AS lo "
+ String.format(" FROM %s ", firstTable.getStreamName())
+ " GROUP BY profileId "
+ " EMIT CHANGES;"))
.block();
assert pipeIdTable1 != null;
List<KsqlResponse> responseListTable = ksqlApi()
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdTable1.getPipeId())
.collectList()
.block();
assert Objects.requireNonNull(responseListTable).size() != 0;
KsqlCommandV2Response pipeIdTable2 = ksqlApi()
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
.ksql(String.format("CREATE TABLE %s AS ", secondTable.getName())
+ " SELECT ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1) AS distanceInMiles, "
+ " COLLECT_LIST(profileId) AS riders, "
+ " COUNT(*) AS count "
+ String.format(" FROM %s ", firstTable.getName())
+ " GROUP BY ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1);"))
.block();
assert pipeIdTable2 != null;
List<KsqlResponse> responseListTable2 = ksqlApi()
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdTable2.getPipeId())
.collectList()
.block();
assert Objects.requireNonNull(responseListTable2).size() != 0;
return this;
}
@Step
public ApiService createTables(Table firstTable, Table secondTable) {
KsqlCommandV2Response pipeIdTable1 = ksqlApi()
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
.ksql(String.format("CREATE TABLE %s AS ", firstTable.getName())
+ " SELECT profileId, "
+ " LATEST_BY_OFFSET(latitude) AS la, "
+ " LATEST_BY_OFFSET(longitude) AS lo "
+ String.format(" FROM %s ", firstTable.getStreamName())
+ " GROUP BY profileId "
+ " EMIT CHANGES;"))
.block();
assert pipeIdTable1 != null;
List<KsqlResponse> responseListTable = ksqlApi()
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdTable1.getPipeId())
.collectList()
.block();
assert Objects.requireNonNull(responseListTable).size() != 0;
KsqlCommandV2Response pipeIdTable2 = ksqlApi()
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
.ksql(String.format("CREATE TABLE %s AS ", secondTable.getName())
+ " SELECT ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1) AS distanceInMiles, "
+ " COLLECT_LIST(profileId) AS riders, "
+ " COUNT(*) AS count "
+ String.format(" FROM %s ", firstTable.getName())
+ " GROUP BY ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1);"))
.block();
assert pipeIdTable2 != null;
List<KsqlResponse> responseListTable2 = ksqlApi()
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdTable2.getPipeId())
.collectList()
.block();
assert Objects.requireNonNull(responseListTable2).size() != 0;
return this;
}
@Step
public ApiService insertInto(Stream stream) {
String streamName = stream.getName();
KsqlCommandV2Response pipeIdInsert = ksqlApi()
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
.ksql("INSERT INTO " + streamName + " (profileId, latitude, longitude) VALUES ('c2309eec', 37.7877, -122.4205);"
+ "INSERT INTO " + streamName +
" (profileId, latitude, longitude) VALUES ('18f4ea86', 37.3903, -122.0643); "
+ "INSERT INTO " + streamName +
" (profileId, latitude, longitude) VALUES ('4ab5cbad', 37.3952, -122.0813); "
+ "INSERT INTO " + streamName +
" (profileId, latitude, longitude) VALUES ('8b6eae59', 37.3944, -122.0813); "
+ "INSERT INTO " + streamName +
" (profileId, latitude, longitude) VALUES ('4a7c7b41', 37.4049, -122.0822); "
+ "INSERT INTO " + streamName +
" (profileId, latitude, longitude) VALUES ('4ddad000', 37.7857, -122.4011);"))
.block();
assert pipeIdInsert != null;
List<KsqlResponse> responseListInsert = ksqlApi()
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdInsert.getPipeId())
.collectList()
.block();
assert Objects.requireNonNull(responseListInsert).size() != 0;
return this;
}
@Step
public ApiService insertInto(Stream stream) {
String streamName = stream.getName();
KsqlCommandV2Response pipeIdInsert = ksqlApi()
.executeKsql(CLUSTER_NAME, new KsqlCommandV2()
.ksql("INSERT INTO " + streamName
+ " (profileId, latitude, longitude) VALUES ('c2309eec', 37.7877, -122.4205);"
+ "INSERT INTO " + streamName
+ " (profileId, latitude, longitude) VALUES ('18f4ea86', 37.3903, -122.0643); "
+ "INSERT INTO " + streamName
+ " (profileId, latitude, longitude) VALUES ('4ab5cbad', 37.3952, -122.0813); "
+ "INSERT INTO " + streamName
+ " (profileId, latitude, longitude) VALUES ('8b6eae59', 37.3944, -122.0813); "
+ "INSERT INTO " + streamName
+ " (profileId, latitude, longitude) VALUES ('4a7c7b41', 37.4049, -122.0822); "
+ "INSERT INTO " + streamName
+ " (profileId, latitude, longitude) VALUES ('4ddad000', 37.7857, -122.4011);"))
.block();
assert pipeIdInsert != null;
List<KsqlResponse> responseListInsert = ksqlApi()
.openKsqlResponsePipe(CLUSTER_NAME, pipeIdInsert.getPipeId())
.collectList()
.block();
assert Objects.requireNonNull(responseListInsert).size() != 0;
return this;
}
}

View file

@ -1,22 +1,29 @@
package com.provectus.kafka.ui.settings;
import static com.provectus.kafka.ui.variables.Browser.LOCAL;
import com.provectus.kafka.ui.settings.configs.Config;
import org.aeonbits.owner.ConfigFactory;
public abstract class BaseSource {
public static final String BASE_CONTAINER_URL = "http://host.testcontainers.internal:8080";
public static final String BASE_LOCAL_URL = "http://localhost:8080";
public static final String CLUSTER_NAME = "local";
public static final String CONNECT_NAME = "first";
private static Config config;
public static final String BROWSER = config().browser();
public static final String SUITE_NAME = config().suite();
public static final String CLUSTER_NAME = "local";
public static final String CONNECT_NAME = "first";
private static final String LOCAL_HOST = "localhost";
public static final String REMOTE_URL = String.format("http://%s:4444/wd/hub", LOCAL_HOST);
public static final String BASE_API_URL = String.format("http://%s:8080", LOCAL_HOST);
private static Config config;
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() {
if (config == null) {
config = ConfigFactory.create(Config.class, System.getProperties());
}
return config;
private static Config config() {
if (config == null) {
config = ConfigFactory.create(Config.class, System.getProperties());
}
return config;
}
}

View file

@ -1,17 +1,17 @@
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.Suite.CUSTOM;
import org.aeonbits.owner.Config;
public interface Profiles extends Config {
@Key("browser")
@DefaultValue(CONTAINER)
String browser();
@Key("browser")
@DefaultValue(CONTAINER)
String browser();
@Key("suite")
@DefaultValue(CUSTOM)
String suite();
@Key("suite")
@DefaultValue(CUSTOM)
String suite();
}

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -1,35 +1,39 @@
package com.provectus.kafka.ui.settings.listeners;
import static java.nio.file.Files.newInputStream;
import com.codeborne.selenide.Screenshots;
import io.qameta.allure.Allure;
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.ITestResult;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import static java.nio.file.Files.newInputStream;
@Slf4j
public class AllureListener extends AllureTestNg implements ITestListener {
private void takeScreenshot() {
File screenshot = Screenshots.takeScreenShotAsFile();
try {
Allure.addAttachment(Objects.requireNonNull(screenshot).getName(), newInputStream(screenshot.toPath()));
} catch (IOException e) {
throw new RuntimeException(e);
}
private void takeScreenshot() {
File screenshot = Screenshots.takeScreenShotAsFile();
try {
if (screenshot != null) {
Allure.addAttachment(screenshot.getName(), newInputStream(screenshot.toPath()));
} else {
log.warn("Unable to take screenshot");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void onTestFailure(ITestResult result) {
takeScreenshot();
}
@Override
public void onTestFailure(ITestResult result) {
takeScreenshot();
}
@Override
public void onTestSkipped(ITestResult result) {
takeScreenshot();
}
@Override
public void onTestSkipped(ITestResult result) {
takeScreenshot();
}
}

View file

@ -7,31 +7,31 @@ import org.testng.TestListenerAdapter;
@Slf4j
public class LoggerListener extends TestListenerAdapter {
@Override
public void onTestStart(final ITestResult testResult) {
log.info(String.format("\n------------------------------------------------------------------------ " +
"\nTEST STARTED: %s.%s \n------------------------------------------------------------------------ \n",
testResult.getInstanceName(), testResult.getName()));
}
@Override
public void onTestStart(final ITestResult testResult) {
log.info(String.format("\n------------------------------------------------------------------------ "
+ "\nTEST STARTED: %s.%s \n------------------------------------------------------------------------ \n",
testResult.getInstanceName(), testResult.getName()));
}
@Override
public void onTestSuccess(final ITestResult testResult) {
log.info(String.format("\n------------------------------------------------------------------------ " +
"\nTEST PASSED: %s.%s \n------------------------------------------------------------------------ \n",
testResult.getInstanceName(), testResult.getName()));
}
@Override
public void onTestSuccess(final ITestResult testResult) {
log.info(String.format("\n------------------------------------------------------------------------ "
+ "\nTEST PASSED: %s.%s \n------------------------------------------------------------------------ \n",
testResult.getInstanceName(), testResult.getName()));
}
@Override
public void onTestFailure(final ITestResult testResult) {
log.info(String.format("\n------------------------------------------------------------------------ " +
"\nTEST FAILED: %s.%s \n------------------------------------------------------------------------ \n",
testResult.getInstanceName(), testResult.getName()));
}
@Override
public void onTestFailure(final ITestResult testResult) {
log.info(String.format("\n------------------------------------------------------------------------ "
+ "\nTEST FAILED: %s.%s \n------------------------------------------------------------------------ \n",
testResult.getInstanceName(), testResult.getName()));
}
@Override
public void onTestSkipped(final ITestResult testResult) {
log.info(String.format("\n------------------------------------------------------------------------ " +
"\nTEST SKIPPED: %s.%s \n------------------------------------------------------------------------ \n",
testResult.getInstanceName(), testResult.getName()));
}
@Override
public void onTestSkipped(final ITestResult testResult) {
log.info(String.format("\n------------------------------------------------------------------------ "
+ "\nTEST SKIPPED: %s.%s \n------------------------------------------------------------------------ \n",
testResult.getInstanceName(), testResult.getName()));
}
}

View file

@ -1,14 +1,28 @@
package com.provectus.kafka.ui.settings.listeners;
import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Automation;
import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Status;
import com.provectus.kafka.ui.utilities.qaseUtils.annotations.Suite;
import static io.qase.api.utils.IntegrationUtils.getCaseTitle;
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.StepStorage;
import io.qase.api.annotation.QaseId;
import io.qase.client.ApiClient;
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.extern.slf4j.Slf4j;
import org.testng.Assert;
@ -16,108 +30,107 @@ import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import java.lang.reflect.Method;
import java.util.*;
import static io.qase.api.utils.IntegrationUtils.getCaseTitle;
@Slf4j
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() {
ApiClient apiClient = QaseClient.getApiClient();
apiClient.setApiKey(System.getProperty("QASEIO_API_TOKEN"));
return new CasesApi(apiClient);
private static CasesApi getQaseApi() {
ApiClient apiClient = QaseClient.getApiClient();
apiClient.setApiKey(System.getProperty("QASEIO_API_TOKEN"));
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) {
if (method.isAnnotationPresent(Status.class))
return method.getDeclaredAnnotation(Status.class).status().getValue();
return 1;
private static int getAutomation(Method method) {
if (method.isAnnotationPresent(Automation.class)) {
return method.getDeclaredAnnotation(Automation.class).state().getValue();
}
return 0;
}
private static int getAutomation(Method method) {
if (method.isAnnotationPresent(Automation.class))
return method.getDeclaredAnnotation(Automation.class).state().getValue();
return 0;
}
@SneakyThrows
private static HashMap<Long, String> getCaseTitlesAndIdsFromQase() {
HashMap<Long, String> cases = new HashMap<>();
boolean getCases = true;
int offSet = 0;
while (getCases) {
getCases = false;
TestCaseListResponse response = QASE_API.getCases(System.getProperty("QASE_PROJECT_CODE"),
new GetCasesFiltersParameter().status(GetCasesFiltersParameter.SERIALIZED_NAME_STATUS), 100, offSet);
TestCaseListResponseAllOfResult result = response.getResult();
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;
}
@SneakyThrows
private static HashMap<Long, String> getCaseTitlesAndIdsFromQase() {
HashMap<Long, String> cases = new HashMap<>();
boolean getCases = true;
int offSet = 0;
while (getCases) {
getCases = false;
TestCaseListResponse response = QASE_API.getCases(System.getProperty("QASE_PROJECT_CODE"),
new GetCasesFiltersParameter().status(GetCasesFiltersParameter.SERIALIZED_NAME_STATUS), 100, offSet);
TestCaseListResponseAllOfResult result = response.getResult();
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());
}
return cases;
offSet = offSet + 100;
getCases = true;
}
}
return cases;
}
private static boolean isCaseWithTitleExistInQase(Method method) {
HashMap<Long, String> cases = getCaseTitlesAndIdsFromQase();
String title = getCaseTitle(method);
if (cases.containsValue(title)) {
for (Map.Entry<Long, String> map : cases.entrySet()) {
if (map.getValue().matches(title)) {
long id = map.getKey();
log.warn(String.format("Test case with @QaseTitle='%s' already exists with @QaseId=%d. " +
"Please verify @QaseTitle annotation", title, id));
return true;
}
}
private static boolean isCaseWithTitleExistInQase(Method method) {
HashMap<Long, String> cases = getCaseTitlesAndIdsFromQase();
String title = getCaseTitle(method);
if (cases.containsValue(title)) {
for (Map.Entry<Long, String> map : cases.entrySet()) {
if (map.getValue().matches(title)) {
long id = map.getKey();
log.warn(String.format("Test case with @QaseTitle='%s' already exists with @QaseId=%d. "
+ "Please verify @QaseTitle annotation", title, id));
return true;
}
return false;
}
}
return false;
}
@Override
@SneakyThrows
public void onTestSuccess(final ITestResult testResult) {
Method method = testResult.getMethod()
.getConstructorOrMethod()
.getMethod();
String title = getCaseTitle(method);
if (!method.isAnnotationPresent(QaseId.class)) {
if (title != null) {
if (!isCaseWithTitleExistInQase(method)) {
LinkedList<ResultCreateStepsInner> resultSteps = StepStorage.stopSteps();
LinkedList<TestCaseCreateStepsInner> createSteps = new LinkedList<>();
resultSteps.forEach(step -> {
TestCaseCreateStepsInner caseStep = new TestCaseCreateStepsInner();
caseStep.setAction(step.getAction());
caseStep.setExpectedResult(step.getExpectedResult());
createSteps.add(caseStep);
});
TestCaseCreate newCase = new TestCaseCreate();
newCase.setTitle(title);
newCase.setStatus(getStatus(method));
newCase.setAutomation(getAutomation(method));
newCase.setSteps(createSteps);
if (method.isAnnotationPresent(Suite.class)) {
long suiteId = method.getDeclaredAnnotation(Suite.class).id();
newCase.suiteId(suiteId);
}
Long id = Objects.requireNonNull(QASE_API.createCase(System.getProperty("QASE_PROJECT_CODE"),
newCase).getResult()).getId();
log.info(String.format("New test case '%s' was created with @QaseId=%d", title, id));
}
} else
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");
@Override
@SneakyThrows
public void onTestSuccess(final ITestResult testResult) {
Method method = testResult.getMethod()
.getConstructorOrMethod()
.getMethod();
String title = getCaseTitle(method);
if (!method.isAnnotationPresent(QaseId.class)) {
if (title != null) {
if (!isCaseWithTitleExistInQase(method)) {
LinkedList<ResultCreateStepsInner> resultSteps = StepStorage.stopSteps();
LinkedList<TestCaseCreateStepsInner> createSteps = new LinkedList<>();
resultSteps.forEach(step -> {
TestCaseCreateStepsInner caseStep = new TestCaseCreateStepsInner();
caseStep.setAction(step.getAction());
caseStep.setExpectedResult(step.getExpectedResult());
createSteps.add(caseStep);
});
TestCaseCreate newCase = new TestCaseCreate();
newCase.setTitle(title);
newCase.setStatus(getStatus(method));
newCase.setAutomation(getAutomation(method));
newCase.setSteps(createSteps);
if (method.isAnnotationPresent(Suite.class)) {
long suiteId = method.getDeclaredAnnotation(Suite.class).id();
newCase.suiteId(suiteId);
}
Long id = Objects.requireNonNull(QASE_API.createCase(System.getProperty("QASE_PROJECT_CODE"),
newCase).getResult()).getId();
log.info(String.format("New test case '%s' was created with @QaseId=%d", title, id));
}
} else {
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");
}
}
}

View file

@ -1,5 +1,12 @@
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.config.QaseConfig;
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.ResultCreateStepsInner;
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.Getter;
import lombok.extern.slf4j.Slf4j;
@ -15,88 +25,81 @@ import org.testng.ITestListener;
import org.testng.ITestResult;
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
public class QaseResultListener extends TestListenerAdapter implements ITestListener {
private static final String REPORTER_NAME = "TestNG";
private static final String REPORTER_NAME = "TestNG";
static {
System.setProperty(QaseConfig.QASE_CLIENT_REPORTER_NAME_KEY, REPORTER_NAME);
}
@Getter(lazy = true, value = AccessLevel.PRIVATE)
private final QaseTestCaseListener qaseTestCaseListener = createQaseListener();
private static QaseTestCaseListener createQaseListener() {
return TestNgModule.getInjector().getInstance(QaseTestCaseListener.class);
}
@Override
public void onTestStart(ITestResult tr) {
getQaseTestCaseListener().onTestCaseStarted();
super.onTestStart(tr);
}
@Override
public void onTestSuccess(ITestResult tr) {
getQaseTestCaseListener()
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, PASSED));
super.onTestSuccess(tr);
}
@Override
public void onTestSkipped(ITestResult tr) {
getQaseTestCaseListener()
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, SKIPPED));
super.onTestSuccess(tr);
}
@Override
public void onTestFailure(ITestResult tr) {
getQaseTestCaseListener()
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, FAILED));
super.onTestFailure(tr);
}
@Override
public void onFinish(ITestContext testContext) {
getQaseTestCaseListener().onTestCasesSetFinished();
super.onFinish(testContext);
}
private void setupResultItem(ResultCreate resultCreate, ITestResult result, ResultCreate.StatusEnum status) {
Optional<Throwable> resultThrowable = Optional.ofNullable(result.getThrowable());
String comment = resultThrowable
.flatMap(throwable -> Optional.of(throwable.toString())).orElse(null);
Boolean isDefect = resultThrowable
.flatMap(throwable -> Optional.of(throwable instanceof AssertionError))
.orElse(false);
String stacktrace = resultThrowable
.flatMap(throwable -> Optional.of(getStacktrace(throwable)))
.orElse(null);
Method method = result.getMethod()
.getConstructorOrMethod()
.getMethod();
Long caseId = getCaseId(method);
String caseTitle = null;
if (caseId == null) {
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);
static {
System.setProperty(QaseConfig.QASE_CLIENT_REPORTER_NAME_KEY, REPORTER_NAME);
}
@Getter(lazy = true, value = AccessLevel.PRIVATE)
private final QaseTestCaseListener qaseTestCaseListener = createQaseListener();
private static QaseTestCaseListener createQaseListener() {
return TestNgModule.getInjector().getInstance(QaseTestCaseListener.class);
}
@Override
public void onTestStart(ITestResult tr) {
getQaseTestCaseListener().onTestCaseStarted();
super.onTestStart(tr);
}
@Override
public void onTestSuccess(ITestResult tr) {
getQaseTestCaseListener()
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, PASSED));
super.onTestSuccess(tr);
}
@Override
public void onTestSkipped(ITestResult tr) {
getQaseTestCaseListener()
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, SKIPPED));
super.onTestSuccess(tr);
}
@Override
public void onTestFailure(ITestResult tr) {
getQaseTestCaseListener()
.onTestCaseFinished(resultCreate -> setupResultItem(resultCreate, tr, FAILED));
super.onTestFailure(tr);
}
@Override
public void onFinish(ITestContext testContext) {
getQaseTestCaseListener().onTestCasesSetFinished();
super.onFinish(testContext);
}
private void setupResultItem(ResultCreate resultCreate, ITestResult result, ResultCreate.StatusEnum status) {
Optional<Throwable> resultThrowable = Optional.ofNullable(result.getThrowable());
String comment = resultThrowable
.flatMap(throwable -> Optional.of(throwable.toString())).orElse(null);
Boolean isDefect = resultThrowable
.flatMap(throwable -> Optional.of(throwable instanceof AssertionError))
.orElse(false);
String stacktrace = resultThrowable
.flatMap(throwable -> Optional.of(getStacktrace(throwable)))
.orElse(null);
Method method = result.getMethod()
.getConstructorOrMethod()
.getMethod();
Long caseId = getCaseId(method);
String caseTitle = null;
if (caseId == null) {
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);
}
}

View file

@ -1,27 +1,26 @@
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 java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
public class FileUtils {
public static String getResourceAsString(String resourceFileName) {
try {
return IOUtils.resourceToString("/" + resourceFileName, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
public static String getResourceAsString(String resourceFileName) {
try {
return IOUtils.resourceToString("/" + resourceFileName, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String fileToString(String path) {
try {
return readFileAsString(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
public static String fileToString(String path) {
try {
return readFileAsString(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,17 +1,16 @@
package com.provectus.kafka.ui.utilities;
import lombok.extern.slf4j.Slf4j;
import static com.codeborne.selenide.Selenide.sleep;
import java.time.LocalTime;
import static com.codeborne.selenide.Selenide.sleep;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TimeUtils {
public static void waitUntilNewMinuteStarted() {
int secondsLeft = 60 - LocalTime.now().getSecond();
log.debug("\nwaitUntilNewMinuteStarted: {}s", secondsLeft);
sleep(secondsLeft * 1000);
}
public static void waitUntilNewMinuteStarted() {
int secondsLeft = 60 - LocalTime.now().getSecond();
log.debug("\nwaitUntilNewMinuteStarted: {}s", secondsLeft);
sleep(secondsLeft * 1000);
}
}

View file

@ -1,90 +1,110 @@
package com.provectus.kafka.ui.utilities;
import static com.codeborne.selenide.Selenide.executeJavaScript;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebDriverRunner;
import java.time.Duration;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.Keys;
import org.openqa.selenium.interactions.Actions;
import static com.codeborne.selenide.Selenide.executeJavaScript;
@Slf4j
public class WebUtils {
public static void clickByActions(SelenideElement element) {
log.debug("\nclickByActions: {}", element.getSearchCriteria());
element.shouldBe(Condition.enabled);
new Actions(WebDriverRunner.getWebDriver())
.moveToElement(element)
.click(element)
.perform();
}
public static int getTimeout(int... timeoutInSeconds) {
return (timeoutInSeconds != null && timeoutInSeconds.length > 0) ? timeoutInSeconds[0] : 4;
}
public static void sendKeysByActions(SelenideElement element, String keys) {
log.debug("\nsendKeysByActions: {} \nsend keys '{}'", element.getSearchCriteria(), keys);
element.shouldBe(Condition.enabled);
new Actions(WebDriverRunner.getWebDriver())
.moveToElement(element)
.sendKeys(element, keys)
.perform();
public static void sendKeysAfterClear(SelenideElement element, String keys) {
log.debug("\nsendKeysAfterClear: {} \nsend keys '{}'", element.getSearchCriteria(), keys);
element.shouldBe(Condition.enabled).clear();
if (keys != null) {
element.sendKeys(keys);
}
}
public static void clickByJavaScript(SelenideElement element) {
log.debug("\nclickByJavaScript: {}", element.getSearchCriteria());
element.shouldBe(Condition.enabled);
String script = "arguments[0].click();";
executeJavaScript(script, element);
}
public static void clickByActions(SelenideElement element) {
log.debug("\nclickByActions: {}", element.getSearchCriteria());
element.shouldBe(Condition.enabled);
new Actions(WebDriverRunner.getWebDriver())
.moveToElement(element)
.click(element)
.perform();
}
public static void clearByKeyboard(SelenideElement field) {
log.debug("\nclearByKeyboard: {}", field.getSearchCriteria());
field.shouldBe(Condition.enabled).sendKeys(Keys.END);
field.sendKeys(Keys.chord(Keys.CONTROL + "a"), Keys.DELETE);
}
public static void sendKeysByActions(SelenideElement element, String keys) {
log.debug("\nsendKeysByActions: {} \nsend keys '{}'", element.getSearchCriteria(), keys);
element.shouldBe(Condition.enabled);
new Actions(WebDriverRunner.getWebDriver())
.moveToElement(element)
.sendKeys(element, keys)
.perform();
}
public static boolean isVisible(SelenideElement element) {
log.debug("\nisVisible: {}", element.getSearchCriteria());
boolean isVisible = false;
try {
element.shouldBe(Condition.visible);
isVisible = true;
} catch (Throwable e) {
log.debug("{} is not visible", element.getSearchCriteria());
}
return isVisible;
}
public static void clickByJavaScript(SelenideElement element) {
log.debug("\nclickByJavaScript: {}", element.getSearchCriteria());
element.shouldBe(Condition.enabled);
String script = "arguments[0].click();";
executeJavaScript(script, element);
}
public static boolean isEnabled(SelenideElement element) {
log.debug("\nisEnabled: {}", element.getSearchCriteria());
boolean isEnabled = false;
try {
element.shouldBe(Condition.enabled);
isEnabled = true;
} catch (Throwable e) {
log.debug("{} is not enabled", element.getSearchCriteria());
}
return isEnabled;
}
public static void clearByKeyboard(SelenideElement field) {
log.debug("\nclearByKeyboard: {}", field.getSearchCriteria());
field.shouldBe(Condition.enabled).sendKeys(Keys.END);
field.sendKeys(Keys.chord(Keys.CONTROL + "a"), Keys.DELETE);
}
public static boolean isSelected(SelenideElement element) {
log.debug("\nisSelected: {}", element.getSearchCriteria());
boolean isSelected = false;
try {
element.shouldBe(Condition.selected);
isSelected = true;
} catch (Throwable e) {
log.debug("{} is not selected", element.getSearchCriteria());
}
return isSelected;
public static boolean isVisible(SelenideElement element, int... timeoutInSeconds) {
log.debug("\nisVisible: {}", element.getSearchCriteria());
boolean isVisible = false;
try {
element.shouldBe(Condition.visible,
Duration.ofSeconds(getTimeout(timeoutInSeconds)));
isVisible = true;
} catch (Throwable e) {
log.debug("{} is not visible", element.getSearchCriteria());
}
return isVisible;
}
public static boolean selectElement(SelenideElement element, boolean select) {
if (select) {
if (!element.isSelected()) clickByJavaScript(element);
} else {
if (element.isSelected()) clickByJavaScript(element);
}
return true;
public static boolean isEnabled(SelenideElement element, int... timeoutInSeconds) {
log.debug("\nisEnabled: {}", element.getSearchCriteria());
boolean isEnabled = false;
try {
element.shouldBe(Condition.enabled,
Duration.ofSeconds(getTimeout(timeoutInSeconds)));
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;
}
}

View file

@ -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");
}
}
}

View file

@ -1,7 +1,6 @@
package com.provectus.kafka.ui.utilities.qaseUtils.annotations;
import com.provectus.kafka.ui.utilities.qaseUtils.enums.State;
package com.provectus.kafka.ui.utilities.qase.annotations;
import com.provectus.kafka.ui.utilities.qase.enums.State;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -11,5 +10,5 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
public @interface Automation {
State state();
State state();
}

View file

@ -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.Retention;
@ -9,5 +9,5 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
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