From 9a86e66be763309b353f344cc6ed9d69814756e8 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 11:47:22 +0300 Subject: [PATCH 01/54] add Jenkinsfile --- ui_ci.groovy | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 ui_ci.groovy diff --git a/ui_ci.groovy b/ui_ci.groovy new file mode 100644 index 000000000..203fd54c3 --- /dev/null +++ b/ui_ci.groovy @@ -0,0 +1,124 @@ +@Library('pipelinex@development') _ +import com.iguazio.pipelinex.DockerRepo + +pipeline { + agent { label '192.168.201.57-ubuntu-ui-runner' } // Specify the runner here + + environment { + REACT_APP_FUNCTION_CATALOG_URL = 'https://raw.githubusercontent.com/mlrun/functions/master' + REACT_APP_MLRUN_API_URL = 'http://localhost:30000/mlrun-api-ingress.default-tenant.app.vmdev36.lab.iguazeng.com' + REACT_APP_NUCLIO_API_URL = 'http://localhost:30000/nuclio-ingress.default-tenant.app.vmdev36.lab.iguazeng.com' + REACT_APP_IGUAZIO_API_URL = 'http://localhost:30000/platform-api.default-tenant.app.vmdev36.lab.iguazeng.com' + ARTIFACTORY_URL = 'http://artifactory.example.com/path/to/artifacts/cucumber_report.json' + ARTIFACTORY_USER = credentials('artifactory-username') // Assume you have these credentials stored in Jenkins + ARTIFACTORY_PASSWORD = credentials('artifactory-password') + SLACK_TOKEN = credentials('slack-bot-token') + SLACK_CHANNEL = '#your-slack-channel' + } + + + + + // triggers { + // cron('H 0 * * *') // Run the job nightly at midnight + // } + + stages { + stage('Pull Latest Changes') { + steps { + script { + dir('/root/ui') { + sh 'git pull' + } + } + } + } + + stage('Set up Environment') { + steps { + script { + dir('/root/ui') { + // You can uncomment the following line if npm install is needed + // sh 'npm install' + + // Ensure environment variables are set for the session + sh ''' + export REACT_APP_FUNCTION_CATALOG_URL=${REACT_APP_FUNCTION_CATALOG_URL} + export REACT_APP_MLRUN_API_URL=${REACT_APP_MLRUN_API_URL} + export REACT_APP_NUCLIO_API_URL=${REACT_APP_NUCLIO_API_URL} + export REACT_APP_IGUAZIO_API_URL=${REACT_APP_IGUAZIO_API_URL} + ''' + } + } + } + } + + stage('Start Services') { + steps { + script { + dir('/root/ui') { + // Start mock-server and application in the background + sh 'npm run mock-server &' + sh 'npm start &' + } + } + } + } + + stage('Run Regression Tests') { + steps { + script { + dir('/root/ui') { + // Run regression tests + sh 'npm run test:regression' + } + } + } + } + + stage('Post-Test Cleanup') { + steps { + script { + // Ensure background processes are killed after tests + sh 'kill %1 || true' + sh 'kill %2 || true' + } + } + } + + stage('Upload Artifacts') { + steps { + script { + dir('/root/ui') { + // Upload the test report to Artifactory + sh ''' + curl -u ${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD} -T tests/reports/cucumber_report.json ${ARTIFACTORY_URL} + ''' + } + } + } + } + + stage('Send Report to Slack') { + steps { + script { + dir('/root/ui') { + // Send the test report to Slack + sh ''' + curl -F file=@tests/reports/cucumber_report.json -F "initial_comment=Here is the latest regression test report" -F channels=${SLACK_CHANNEL} -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/files.upload + ''' + } + } + } + } + } + + post { + always { + script { + // Ensure any remaining background processes are terminated + sh 'pkill -f npm || true' + } + } + } +} \ No newline at end of file From 5dd40eab4817d97727db80a3b95183a0cfd5d58f Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 13:54:50 +0300 Subject: [PATCH 02/54] add Jenkinsfile --- ui_ci.groovy | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 203fd54c3..c329b8e1e 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -1,8 +1,7 @@ @Library('pipelinex@development') _ -import com.iguazio.pipelinex.DockerRepo pipeline { - agent { label '192.168.201.57-ubuntu-ui-runner' } // Specify the runner here + agent { label 'ubuntu_ui_runner' } // Specify the runner here environment { REACT_APP_FUNCTION_CATALOG_URL = 'https://raw.githubusercontent.com/mlrun/functions/master' @@ -16,12 +15,9 @@ pipeline { SLACK_CHANNEL = '#your-slack-channel' } - - - - // triggers { - // cron('H 0 * * *') // Run the job nightly at midnight - // } + // triggers { + // cron('H 0 * * *') // Run the job nightly at midnight + // } stages { stage('Pull Latest Changes') { From f1781d1f3cdbae5af49375b134f29cc2d2833850 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 14:45:12 +0300 Subject: [PATCH 03/54] add Jenkinsfile --- ui_ci.groovy | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index c329b8e1e..76e93491b 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -1,4 +1,5 @@ @Library('pipelinex@development') _ +import com.iguazio.pipelinex.DockerRepo pipeline { agent { label 'ubuntu_ui_runner' } // Specify the runner here @@ -8,16 +9,17 @@ pipeline { REACT_APP_MLRUN_API_URL = 'http://localhost:30000/mlrun-api-ingress.default-tenant.app.vmdev36.lab.iguazeng.com' REACT_APP_NUCLIO_API_URL = 'http://localhost:30000/nuclio-ingress.default-tenant.app.vmdev36.lab.iguazeng.com' REACT_APP_IGUAZIO_API_URL = 'http://localhost:30000/platform-api.default-tenant.app.vmdev36.lab.iguazeng.com' - ARTIFACTORY_URL = 'http://artifactory.example.com/path/to/artifacts/cucumber_report.json' + ARTIFACTORY_URL = 'http://artifactory.example.com/path/to/artifacts/cucumber_report.html' ARTIFACTORY_USER = credentials('artifactory-username') // Assume you have these credentials stored in Jenkins ARTIFACTORY_PASSWORD = credentials('artifactory-password') SLACK_TOKEN = credentials('slack-bot-token') SLACK_CHANNEL = '#your-slack-channel' + ARTIFACTORY_BASE_URL = 'http://artifactory.example.com/path/to/artifacts/' // Base URL for Artifactory } - // triggers { - // cron('H 0 * * *') // Run the job nightly at midnight - // } + triggers { + cron('H 0 * * *') // Run the job nightly at midnight + } stages { stage('Pull Latest Changes') { @@ -88,22 +90,24 @@ pipeline { dir('/root/ui') { // Upload the test report to Artifactory sh ''' - curl -u ${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD} -T tests/reports/cucumber_report.json ${ARTIFACTORY_URL} + curl -u ${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD} -T tests/reports/cucumber_report.html ${ARTIFACTORY_URL} ''' } } } } - stage('Send Report to Slack') { + stage('Send Report Link to Slack') { steps { script { - dir('/root/ui') { - // Send the test report to Slack - sh ''' - curl -F file=@tests/reports/cucumber_report.json -F "initial_comment=Here is the latest regression test report" -F channels=${SLACK_CHANNEL} -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/files.upload - ''' - } + // Construct the URL to the uploaded report + def report_url = "${ARTIFACTORY_BASE_URL}cucumber_report.html" + + // Send the link to Slack + sh """ + curl -X POST -H 'Content-type: application/json' --data '{"channel": "${SLACK_CHANNEL}", "text": "Here is the latest regression test report: ${report_url}"}' \ + -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/chat.postMessage + """ } } } From a78e95da0d70f9af2e981b61022e6f577fe1a469 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:05:28 +0300 Subject: [PATCH 04/54] add Jenkinsfile --- ui_ci.groovy | 138 +++++++++++++++++++++------------------------------ 1 file changed, 57 insertions(+), 81 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 76e93491b..2e8caae9f 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -1,45 +1,31 @@ -@Library('pipelinex@development') _ -import com.iguazio.pipelinex.DockerRepo - -pipeline { - agent { label 'ubuntu_ui_runner' } // Specify the runner here - - environment { - REACT_APP_FUNCTION_CATALOG_URL = 'https://raw.githubusercontent.com/mlrun/functions/master' - REACT_APP_MLRUN_API_URL = 'http://localhost:30000/mlrun-api-ingress.default-tenant.app.vmdev36.lab.iguazeng.com' - REACT_APP_NUCLIO_API_URL = 'http://localhost:30000/nuclio-ingress.default-tenant.app.vmdev36.lab.iguazeng.com' - REACT_APP_IGUAZIO_API_URL = 'http://localhost:30000/platform-api.default-tenant.app.vmdev36.lab.iguazeng.com' - ARTIFACTORY_URL = 'http://artifactory.example.com/path/to/artifacts/cucumber_report.html' - ARTIFACTORY_USER = credentials('artifactory-username') // Assume you have these credentials stored in Jenkins - ARTIFACTORY_PASSWORD = credentials('artifactory-password') - SLACK_TOKEN = credentials('slack-bot-token') - SLACK_CHANNEL = '#your-slack-channel' - ARTIFACTORY_BASE_URL = 'http://artifactory.example.com/path/to/artifacts/' // Base URL for Artifactory - } +@Library('pipelinex@development') +import com.iguazio.pipelinex.JobException - triggers { - cron('H 0 * * *') // Run the job nightly at midnight - } - stages { - stage('Pull Latest Changes') { - steps { - script { + +def node_label = 'ubuntu_ui_runner' + +common.set_current_display_name("UI_Tests") + +common.main { + timestamps { + nodes.runner(node_label) { + withCredentials([ + usernamePassword(credentialsId: 'iguazio-credentials', usernameVariable: 'RUNNER_USER', passwordVariable: 'RUNNER_PASSWORD') + ]) { + + common.conditional_stage('Pull Latest Changes', true) { dir('/root/ui') { + deleteDir() + checkout scm sh 'git pull' } } - } - } - stage('Set up Environment') { - steps { - script { + common.conditional_stage('Set up Environment', true) { dir('/root/ui') { - // You can uncomment the following line if npm install is needed + // Uncomment the following line if npm install is needed // sh 'npm install' - - // Ensure environment variables are set for the session sh ''' export REACT_APP_FUNCTION_CATALOG_URL=${REACT_APP_FUNCTION_CATALOG_URL} export REACT_APP_MLRUN_API_URL=${REACT_APP_MLRUN_API_URL} @@ -48,77 +34,67 @@ pipeline { ''' } } - } - } - stage('Start Services') { - steps { - script { + common.conditional_stage('Start Services', true) { dir('/root/ui') { // Start mock-server and application in the background sh 'npm run mock-server &' sh 'npm start &' } } - } - } - stage('Run Regression Tests') { - steps { - script { - dir('/root/ui') { - // Run regression tests - sh 'npm run test:regression' - } - } - } - } + // common.conditional_stage('Run Regression Tests', true) { + // dir('/root/ui') { + // // Run cucumber-js tests + // sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' + // } + // } - stage('Post-Test Cleanup') { - steps { - script { - // Ensure background processes are killed after tests + common.conditional_stage('Post-Test Cleanup', true) { sh 'kill %1 || true' sh 'kill %2 || true' } - } - } - stage('Upload Artifacts') { - steps { - script { + common.conditional_stage('Upload Artifacts', true) { dir('/root/ui') { - // Upload the test report to Artifactory sh ''' - curl -u ${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD} -T tests/reports/cucumber_report.html ${ARTIFACTORY_URL} + # Environment variables + ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" + AUTH="${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD}" + LOCAL_FILE="tests/reports/cucumber_report_default.html" + + # Generate the Artifactory path with the build number + ARTIFACTORY_PATH="cucumber_report_defualt_${env.BUILD_NUMBER}.html" + + # Construct the full URL + URL="${ART_URL}/${ARTIFACTORY_PATH}" + + # Upload the file to Artifactory + curl -X PUT -u ${AUTH} "${URL}" --data-binary @"${LOCAL_FILE}" ''' } } - } - } - stage('Send Report Link to Slack') { - steps { - script { - // Construct the URL to the uploaded report - def report_url = "${ARTIFACTORY_BASE_URL}cucumber_report.html" - - // Send the link to Slack - sh """ - curl -X POST -H 'Content-type: application/json' --data '{"channel": "${SLACK_CHANNEL}", "text": "Here is the latest regression test report: ${report_url}"}' \ - -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/chat.postMessage - """ - } + // common.conditional_stage('Send Report Link to Slack', true) { + // script { + // def report_url = "http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports/cucumber_report_defualt_${env.BUILD_NUMBER}.html" + // // Send the link to Slack + // sh """ + // curl -X POST -H 'Content-type: application/json' --data '{"channel": "${SLACK_CHANNEL}", "text": "Here is the latest regression test report: ${report_url}"}' \ + // -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/chat.postMessage + // """ + // } + // } } } } +} - post { - always { - script { - // Ensure any remaining background processes are terminated - sh 'pkill -f npm || true' - } +post { + always { + script { + // Ensure any remaining background processes are terminated + sh 'pkill -f npm || true' } } } \ No newline at end of file From f604f45dfbff1384850ea29d8c9578afa56634c3 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:08:21 +0300 Subject: [PATCH 05/54] add Jenkinsfile --- ui_ci.groovy | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 2e8caae9f..4a21ef7a9 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -5,14 +5,12 @@ import com.iguazio.pipelinex.JobException def node_label = 'ubuntu_ui_runner' -common.set_current_display_name("UI_Tests") +common.set_current_display_name("UI_CI_Test") common.main { timestamps { nodes.runner(node_label) { - withCredentials([ - usernamePassword(credentialsId: 'iguazio-credentials', usernameVariable: 'RUNNER_USER', passwordVariable: 'RUNNER_PASSWORD') - ]) { + { common.conditional_stage('Pull Latest Changes', true) { dir('/root/ui') { From 1f20b299ae20db8e57d18e625a0c41556d1b1d74 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:08:44 +0300 Subject: [PATCH 06/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 4a21ef7a9..cdc15ff92 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -10,7 +10,7 @@ common.set_current_display_name("UI_CI_Test") common.main { timestamps { nodes.runner(node_label) { - { + common.conditional_stage('Pull Latest Changes', true) { dir('/root/ui') { From b82bd9d63b0aef848e722d835969295ecff1eae5 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:10:26 +0300 Subject: [PATCH 07/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index cdc15ff92..4a21ef7a9 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -10,7 +10,7 @@ common.set_current_display_name("UI_CI_Test") common.main { timestamps { nodes.runner(node_label) { - + { common.conditional_stage('Pull Latest Changes', true) { dir('/root/ui') { From be628ceb183de8a470c66afe326e1d7512cde8d4 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:12:54 +0300 Subject: [PATCH 08/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 4a21ef7a9..d05eddd56 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -10,7 +10,7 @@ common.set_current_display_name("UI_CI_Test") common.main { timestamps { nodes.runner(node_label) { - { + stage('UI CI Test') { common.conditional_stage('Pull Latest Changes', true) { dir('/root/ui') { From 383c6592685f146a93af58028a83b267ce0ed194 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:15:37 +0300 Subject: [PATCH 09/54] add Jenkinsfile --- ui_ci.groovy | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index d05eddd56..2cbe03e84 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -1,8 +1,6 @@ @Library('pipelinex@development') import com.iguazio.pipelinex.JobException - - def node_label = 'ubuntu_ui_runner' common.set_current_display_name("UI_CI_Test") @@ -10,10 +8,10 @@ common.set_current_display_name("UI_CI_Test") common.main { timestamps { nodes.runner(node_label) { - stage('UI CI Test') { + stage('UI CI Test') { common.conditional_stage('Pull Latest Changes', true) { - dir('/root/ui') { + dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui deleteDir() checkout scm sh 'git pull' @@ -21,7 +19,7 @@ common.main { } common.conditional_stage('Set up Environment', true) { - dir('/root/ui') { + dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui // Uncomment the following line if npm install is needed // sh 'npm install' sh ''' @@ -34,19 +32,20 @@ common.main { } common.conditional_stage('Start Services', true) { - dir('/root/ui') { + dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui // Start mock-server and application in the background sh 'npm run mock-server &' sh 'npm start &' } } - // common.conditional_stage('Run Regression Tests', true) { - // dir('/root/ui') { - // // Run cucumber-js tests - // sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' - // } - // } + // Uncomment this stage if needed + // common.conditional_stage('Run Regression Tests', true) { + // dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui + // // Run cucumber-js tests + // sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' + // } + // } common.conditional_stage('Post-Test Cleanup', true) { sh 'kill %1 || true' @@ -54,7 +53,7 @@ common.main { } common.conditional_stage('Upload Artifacts', true) { - dir('/root/ui') { + dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui sh ''' # Environment variables ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" @@ -62,7 +61,7 @@ common.main { LOCAL_FILE="tests/reports/cucumber_report_default.html" # Generate the Artifactory path with the build number - ARTIFACTORY_PATH="cucumber_report_defualt_${env.BUILD_NUMBER}.html" + ARTIFACTORY_PATH="cucumber_report_default_${env.BUILD_NUMBER}.html" # Construct the full URL URL="${ART_URL}/${ARTIFACTORY_PATH}" @@ -73,16 +72,17 @@ common.main { } } - // common.conditional_stage('Send Report Link to Slack', true) { - // script { - // def report_url = "http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports/cucumber_report_defualt_${env.BUILD_NUMBER}.html" - // // Send the link to Slack - // sh """ - // curl -X POST -H 'Content-type: application/json' --data '{"channel": "${SLACK_CHANNEL}", "text": "Here is the latest regression test report: ${report_url}"}' \ - // -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/chat.postMessage - // """ - // } - // } + // Uncomment this stage if needed + // common.conditional_stage('Send Report Link to Slack', true) { + // script { + // def report_url = "http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports/cucumber_report_default_${env.BUILD_NUMBER}.html" + // // Send the link to Slack + // sh """ + // curl -X POST -H 'Content-type: application/json' --data '{"channel": "${SLACK_CHANNEL}", "text": "Here is the latest regression test report: ${report_url}"}' \ + // -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/chat.postMessage + // """ + // } + // } } } } From ea9f9c6fac57b95a607bdd995a26d7f652bab821 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:22:04 +0300 Subject: [PATCH 10/54] add Jenkinsfile --- ui_ci.groovy | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 2cbe03e84..b6e3c3ca1 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -11,16 +11,14 @@ common.main { stage('UI CI Test') { common.conditional_stage('Pull Latest Changes', true) { - dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui - deleteDir() + { // Changed directory to /home/jenkins/ui checkout scm sh 'git pull' } } common.conditional_stage('Set up Environment', true) { - dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui - // Uncomment the following line if npm install is needed + { // sh 'npm install' sh ''' export REACT_APP_FUNCTION_CATALOG_URL=${REACT_APP_FUNCTION_CATALOG_URL} @@ -32,7 +30,7 @@ common.main { } common.conditional_stage('Start Services', true) { - dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui + { // Changed directory to /home/jenkins/ui // Start mock-server and application in the background sh 'npm run mock-server &' sh 'npm start &' From 26301b051d05cda0fb9d64a5e88c45c22ea720a0 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:22:38 +0300 Subject: [PATCH 11/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index b6e3c3ca1..9c84d882d 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -11,7 +11,7 @@ common.main { stage('UI CI Test') { common.conditional_stage('Pull Latest Changes', true) { - { // Changed directory to /home/jenkins/ui + { checkout scm sh 'git pull' } From 66b42da77ea222562c9b9985528cb065bffd80eb Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Tue, 2 Jul 2024 16:24:19 +0300 Subject: [PATCH 12/54] add Jenkinsfile --- ui_ci.groovy | 62 ++++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 9c84d882d..1a721e689 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -11,38 +11,30 @@ common.main { stage('UI CI Test') { common.conditional_stage('Pull Latest Changes', true) { - { - checkout scm - sh 'git pull' - } + checkout scm + sh 'git pull' } common.conditional_stage('Set up Environment', true) { - { - // sh 'npm install' - sh ''' - export REACT_APP_FUNCTION_CATALOG_URL=${REACT_APP_FUNCTION_CATALOG_URL} - export REACT_APP_MLRUN_API_URL=${REACT_APP_MLRUN_API_URL} - export REACT_APP_NUCLIO_API_URL=${REACT_APP_NUCLIO_API_URL} - export REACT_APP_IGUAZIO_API_URL=${REACT_APP_IGUAZIO_API_URL} - ''' - } + // sh 'npm install' + sh ''' + export REACT_APP_FUNCTION_CATALOG_URL=${REACT_APP_FUNCTION_CATALOG_URL} + export REACT_APP_MLRUN_API_URL=${REACT_APP_MLRUN_API_URL} + export REACT_APP_NUCLIO_API_URL=${REACT_APP_NUCLIO_API_URL} + export REACT_APP_IGUAZIO_API_URL=${REACT_APP_IGUAZIO_API_URL} + ''' } common.conditional_stage('Start Services', true) { - { // Changed directory to /home/jenkins/ui - // Start mock-server and application in the background - sh 'npm run mock-server &' - sh 'npm start &' - } + // Start mock-server and application in the background + sh 'npm run mock-server &' + sh 'npm start &' } // Uncomment this stage if needed // common.conditional_stage('Run Regression Tests', true) { - // dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui - // // Run cucumber-js tests - // sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' - // } + // // Run cucumber-js tests + // sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' // } common.conditional_stage('Post-Test Cleanup', true) { @@ -51,23 +43,21 @@ common.main { } common.conditional_stage('Upload Artifacts', true) { - dir('/home/jenkins/ui') { // Changed directory to /home/jenkins/ui - sh ''' - # Environment variables - ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" - AUTH="${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD}" - LOCAL_FILE="tests/reports/cucumber_report_default.html" + sh ''' + # Environment variables + ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" + AUTH="${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD}" + LOCAL_FILE="tests/reports/cucumber_report_default.html" - # Generate the Artifactory path with the build number - ARTIFACTORY_PATH="cucumber_report_default_${env.BUILD_NUMBER}.html" + # Generate the Artifactory path with the build number + ARTIFACTORY_PATH="cucumber_report_default_${env.BUILD_NUMBER}.html" - # Construct the full URL - URL="${ART_URL}/${ARTIFACTORY_PATH}" + # Construct the full URL + URL="${ART_URL}/${ARTIFACTORY_PATH}" - # Upload the file to Artifactory - curl -X PUT -u ${AUTH} "${URL}" --data-binary @"${LOCAL_FILE}" - ''' - } + # Upload the file to Artifactory + curl -X PUT -u ${AUTH} "${URL}" --data-binary @"${LOCAL_FILE}" + ''' } // Uncomment this stage if needed From 5b56669465aba07c5b0f1e21b02640bd7d1b5ded Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 09:55:39 +0300 Subject: [PATCH 13/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 1a721e689..2f3e44aa2 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -1,7 +1,7 @@ @Library('pipelinex@development') import com.iguazio.pipelinex.JobException -def node_label = 'ubuntu_ui_runner' +def node_label = 'ubuntu-ui-runner' common.set_current_display_name("UI_CI_Test") From 39bcaf8380800011b9d26ca0826de125355f1b85 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 10:00:57 +0300 Subject: [PATCH 14/54] add Jenkinsfile --- ui_ci.groovy | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 2f3e44aa2..d56665b0e 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -16,12 +16,12 @@ common.main { } common.conditional_stage('Set up Environment', true) { - // sh 'npm install' + sh 'npm install' sh ''' - export REACT_APP_FUNCTION_CATALOG_URL=${REACT_APP_FUNCTION_CATALOG_URL} - export REACT_APP_MLRUN_API_URL=${REACT_APP_MLRUN_API_URL} - export REACT_APP_NUCLIO_API_URL=${REACT_APP_NUCLIO_API_URL} - export REACT_APP_IGUAZIO_API_URL=${REACT_APP_IGUAZIO_API_URL} + export REACT_APP_FUNCTION_CATALOG_URL=https://raw.githubusercontent.com/mlrun/functions/master + export REACT_APP_MLRUN_API_URL=http://localhost:30000/mlrun-api-ingress.default-tenant.app.vmdev36.lab.iguazeng.com + export REACT_APP_NUCLIO_API_URL=http://localhost:30000/nuclio-ingress.default-tenant.app.vmdev36.lab.iguazeng.com + export REACT_APP_IGUAZIO_API_URL=http://localhost:30000/platform-api.default-tenant.app.vmdev36.lab.iguazeng.com ''' } From baa7ceebfb25afa56b7686606616c421fbe397c1 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 11:59:13 +0300 Subject: [PATCH 15/54] add Jenkinsfile --- ui_ci.groovy | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index d56665b0e..2ab9bdb97 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -12,34 +12,45 @@ common.main { common.conditional_stage('Pull Latest Changes', true) { checkout scm - sh 'git pull' } common.conditional_stage('Set up Environment', true) { - sh 'npm install' + sh ''' export REACT_APP_FUNCTION_CATALOG_URL=https://raw.githubusercontent.com/mlrun/functions/master export REACT_APP_MLRUN_API_URL=http://localhost:30000/mlrun-api-ingress.default-tenant.app.vmdev36.lab.iguazeng.com export REACT_APP_NUCLIO_API_URL=http://localhost:30000/nuclio-ingress.default-tenant.app.vmdev36.lab.iguazeng.com export REACT_APP_IGUAZIO_API_URL=http://localhost:30000/platform-api.default-tenant.app.vmdev36.lab.iguazeng.com ''' + + sh 'npm install' } common.conditional_stage('Start Services', true) { - // Start mock-server and application in the background - sh 'npm run mock-server &' - sh 'npm start &' + sh ''' + # Check if the mock server is already running + if lsof -i:30000 -t >/dev/null; then + echo "Mock server already running on port 30000" + else + # Start mock-server and application in the background + npm run mock-server & + npm start & + fi + ''' } - // Uncomment this stage if needed - // common.conditional_stage('Run Regression Tests', true) { - // // Run cucumber-js tests - // sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' - // } + common.conditional_stage('Run Regression Tests', true) { + // Run cucumber-js tests + sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' + } common.conditional_stage('Post-Test Cleanup', true) { - sh 'kill %1 || true' - sh 'kill %2 || true' + sh ''' + kill %1 || true + kill %2 || true + # Ensure any remaining background processes are terminated + pkill -f npm || true + ''' } common.conditional_stage('Upload Artifacts', true) { From 6b686e4645521bcf58c7c2c9f222780c3cdd704a Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 12:02:50 +0300 Subject: [PATCH 16/54] add Jenkinsfile --- ui_ci.groovy | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 2ab9bdb97..7c2c5dfd6 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -15,7 +15,6 @@ common.main { } common.conditional_stage('Set up Environment', true) { - sh ''' export REACT_APP_FUNCTION_CATALOG_URL=https://raw.githubusercontent.com/mlrun/functions/master export REACT_APP_MLRUN_API_URL=http://localhost:30000/mlrun-api-ingress.default-tenant.app.vmdev36.lab.iguazeng.com @@ -28,29 +27,28 @@ common.main { common.conditional_stage('Start Services', true) { sh ''' - # Check if the mock server is already running - if lsof -i:30000 -t >/dev/null; then - echo "Mock server already running on port 30000" - else - # Start mock-server and application in the background - npm run mock-server & - npm start & + # Check if the port 30000 is in use and kill the process if it is + PID=$(lsof -t -i:30000) + if [ -n "$PID" ]; then + echo "Port 30000 is in use by PID $PID. Terminating process." + kill -9 $PID fi + + # Start mock-server and application in the background + npm run mock-server & + npm start & ''' } - common.conditional_stage('Run Regression Tests', true) { - // Run cucumber-js tests - sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' - } + // Uncomment this stage if needed + // common.conditional_stage('Run Regression Tests', true) { + // // Run cucumber-js tests + // sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' + // } common.conditional_stage('Post-Test Cleanup', true) { - sh ''' - kill %1 || true - kill %2 || true - # Ensure any remaining background processes are terminated - pkill -f npm || true - ''' + sh 'kill %1 || true' + sh 'kill %2 || true' } common.conditional_stage('Upload Artifacts', true) { From ab155e1af705d99a5048a1474b88d063b0d9077d Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 12:05:26 +0300 Subject: [PATCH 17/54] add Jenkinsfile --- ui_ci.groovy | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 7c2c5dfd6..8467a13e2 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -15,6 +15,7 @@ common.main { } common.conditional_stage('Set up Environment', true) { + sh ''' export REACT_APP_FUNCTION_CATALOG_URL=https://raw.githubusercontent.com/mlrun/functions/master export REACT_APP_MLRUN_API_URL=http://localhost:30000/mlrun-api-ingress.default-tenant.app.vmdev36.lab.iguazeng.com @@ -30,8 +31,8 @@ common.main { # Check if the port 30000 is in use and kill the process if it is PID=$(lsof -t -i:30000) if [ -n "$PID" ]; then - echo "Port 30000 is in use by PID $PID. Terminating process." - kill -9 $PID + echo "Port 30000 is in use by PID $PID. Terminating process." + kill -9 $PID fi # Start mock-server and application in the background @@ -47,8 +48,12 @@ common.main { // } common.conditional_stage('Post-Test Cleanup', true) { - sh 'kill %1 || true' - sh 'kill %2 || true' + sh ''' + kill %1 || true + kill %2 || true + # Ensure any remaining background processes are terminated + pkill -f npm || true + ''' } common.conditional_stage('Upload Artifacts', true) { From 9e0138588967ab0c1733deece6cddea6f980cc24 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 12:32:34 +0300 Subject: [PATCH 18/54] add Jenkinsfile --- ui_ci.groovy | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 8467a13e2..ec7bb5794 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -28,24 +28,16 @@ common.main { common.conditional_stage('Start Services', true) { sh ''' - # Check if the port 30000 is in use and kill the process if it is - PID=$(lsof -t -i:30000) - if [ -n "$PID" ]; then - echo "Port 30000 is in use by PID $PID. Terminating process." - kill -9 $PID - fi - - # Start mock-server and application in the background npm run mock-server & npm start & ''' } - // Uncomment this stage if needed - // common.conditional_stage('Run Regression Tests', true) { - // // Run cucumber-js tests - // sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' - // } + Uncomment this stage if needed + common.conditional_stage('Run Regression Tests', true) { + // Run cucumber-js tests + sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' + } common.conditional_stage('Post-Test Cleanup', true) { sh ''' From 4982f406890eb20b67fa126539595eb9f2bb381e Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 12:32:55 +0300 Subject: [PATCH 19/54] add Jenkinsfile --- ui_ci.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index ec7bb5794..ce1779c4c 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -33,7 +33,6 @@ common.main { ''' } - Uncomment this stage if needed common.conditional_stage('Run Regression Tests', true) { // Run cucumber-js tests sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' From cdd612c7923486149ff45373bee983f2961456e8 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 15:05:29 +0300 Subject: [PATCH 20/54] add Jenkinsfile --- ui_ci.groovy | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index ce1779c4c..51e718f31 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -16,13 +16,6 @@ common.main { common.conditional_stage('Set up Environment', true) { - sh ''' - export REACT_APP_FUNCTION_CATALOG_URL=https://raw.githubusercontent.com/mlrun/functions/master - export REACT_APP_MLRUN_API_URL=http://localhost:30000/mlrun-api-ingress.default-tenant.app.vmdev36.lab.iguazeng.com - export REACT_APP_NUCLIO_API_URL=http://localhost:30000/nuclio-ingress.default-tenant.app.vmdev36.lab.iguazeng.com - export REACT_APP_IGUAZIO_API_URL=http://localhost:30000/platform-api.default-tenant.app.vmdev36.lab.iguazeng.com - ''' - sh 'npm install' } @@ -49,9 +42,11 @@ common.main { common.conditional_stage('Upload Artifacts', true) { sh ''' + + touch tests/reports/cucumber_report_default.html # Environment variables ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" - AUTH="${ARTIFACTORY_USER}:${ARTIFACTORY_PASSWORD}" + AUTH="${ARTIFACTORY_CRED}" LOCAL_FILE="tests/reports/cucumber_report_default.html" # Generate the Artifactory path with the build number From 49d3b1e10b3780d9b1f5c18f9f4eef7383457c28 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 15:07:31 +0300 Subject: [PATCH 21/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 51e718f31..7608c4c23 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -50,7 +50,7 @@ common.main { LOCAL_FILE="tests/reports/cucumber_report_default.html" # Generate the Artifactory path with the build number - ARTIFACTORY_PATH="cucumber_report_default_${env.BUILD_NUMBER}.html" + ARTIFACTORY_PATH="cucumber_report_default_${BUILD_NUMBER}.html" # Construct the full URL URL="${ART_URL}/${ARTIFACTORY_PATH}" From 14da6503dc5f8c141b3b6965d85db8ef4709e690 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 15:14:43 +0300 Subject: [PATCH 22/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 7608c4c23..9f58205ad 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -43,7 +43,7 @@ common.main { common.conditional_stage('Upload Artifacts', true) { sh ''' - touch tests/reports/cucumber_report_default.html + # touch tests/reports/cucumber_report_default.html # Environment variables ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" AUTH="${ARTIFACTORY_CRED}" From 70ebcc00f182cd3efbd5f552d511a1e741d67f16 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 15:18:51 +0300 Subject: [PATCH 23/54] add Jenkinsfile --- ui_ci.groovy | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 9f58205ad..3e16463c7 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -26,7 +26,7 @@ common.main { ''' } - common.conditional_stage('Run Regression Tests', true) { + common.conditional_stage('Run Regression Tests', false) { // Run cucumber-js tests sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' } @@ -83,4 +83,19 @@ post { sh 'pkill -f npm || true' } } + success { + script { + echo 'Build was successful!' + } + } + failure { + script { + echo 'Build failed!' + } + } + cleanup { + script { + echo 'Cleaning up...' + } + } } \ No newline at end of file From 29193c070734d593aa2b384fb4a3592ae1a26abe Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 15:29:23 +0300 Subject: [PATCH 24/54] add Jenkinsfile --- ui_ci.groovy | 53 ++++++++++++++++------------------------------------ 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 3e16463c7..45643a7aa 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -26,7 +26,7 @@ common.main { ''' } - common.conditional_stage('Run Regression Tests', false) { + common.conditional_stage('Run Regression Tests', true) { // Run cucumber-js tests sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' } @@ -42,7 +42,6 @@ common.main { common.conditional_stage('Upload Artifacts', true) { sh ''' - # touch tests/reports/cucumber_report_default.html # Environment variables ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" @@ -60,42 +59,22 @@ common.main { ''' } - // Uncomment this stage if needed - // common.conditional_stage('Send Report Link to Slack', true) { - // script { - // def report_url = "http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports/cucumber_report_default_${env.BUILD_NUMBER}.html" - // // Send the link to Slack - // sh """ - // curl -X POST -H 'Content-type: application/json' --data '{"channel": "${SLACK_CHANNEL}", "text": "Here is the latest regression test report: ${report_url}"}' \ - // -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/chat.postMessage - // """ - // } - // } - } - } - } -} + common.conditional_stage('Cleaning up', true) { + sh ''' + pkill -f npm || true + ''' + } -post { - always { - script { - // Ensure any remaining background processes are terminated - sh 'pkill -f npm || true' - } - } - success { - script { - echo 'Build was successful!' - } - } - failure { - script { - echo 'Build failed!' - } - } - cleanup { - script { - echo 'Cleaning up...' + common.conditional_stage('Build Status', true) { + script { + if (currentBuild.currentResult == 'SUCCESS') { + echo 'Build was successful!' + } else { + echo 'Build failed!' + } + } + } + } } } } \ No newline at end of file From 7b6b4276518d2e82a2c8e3acd1513d05115cdc1d Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 15:29:33 +0300 Subject: [PATCH 25/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 45643a7aa..15d6f50d0 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -26,7 +26,7 @@ common.main { ''' } - common.conditional_stage('Run Regression Tests', true) { + common.conditional_stage('Run Regression Tests', false) { // Run cucumber-js tests sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' } From 96d1a7bba68ee25a3bfe30f9d5477c468a074df4 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Wed, 3 Jul 2024 15:31:32 +0300 Subject: [PATCH 26/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 15d6f50d0..45643a7aa 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -26,7 +26,7 @@ common.main { ''' } - common.conditional_stage('Run Regression Tests', false) { + common.conditional_stage('Run Regression Tests', true) { // Run cucumber-js tests sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' } From 245e50893722cb2dea2ed0f9e96fb5e1e61b7694 Mon Sep 17 00:00:00 2001 From: pinis-gini-apps Date: Thu, 4 Jul 2024 12:41:35 +0300 Subject: [PATCH 27/54] add scripts test for ci-cd flow --- package.json | 6 +- .../appendCommentToHttpClient.js | 34 + scripts/ci-cd-scripts/commentedHttpClient.js | 195 ++++ scripts/ci-cd-scripts/tempSmoke1Test.js | 28 + scripts/testui.js | 51 +- tests/config.js | 8 +- tests/features/artifacts.feature | 21 +- tests/features/step-definitions/steps.js | 980 +++++++----------- tests/features/support/world.js | 23 +- 9 files changed, 686 insertions(+), 660 deletions(-) create mode 100644 scripts/ci-cd-scripts/appendCommentToHttpClient.js create mode 100644 scripts/ci-cd-scripts/commentedHttpClient.js create mode 100644 scripts/ci-cd-scripts/tempSmoke1Test.js diff --git a/package.json b/package.json index d0a50ff4f..68e4cc646 100644 --- a/package.json +++ b/package.json @@ -70,9 +70,11 @@ "build-storybook": "build-storybook", "mock-server": "node scripts/mockServer.js", "mock-server:dev": "nodemon --watch tests/mockServer scripts/mockServer.js", + "add-comment-to-http-client": "node scripts/ci-cd-scripts/appendCommentToHttpClient.js", + "test:ci-cd-smoke-1": "DRIVER_SLEEP=5000 HEADLESS=true node scripts/ci-cd-scripts/tempSmoke1Test.js", "test:ui": "node scripts/testui.js", "report": "node tests/report.js", - "test:regression": "npm run test:ui && npm run report", + "test:regression": "DRIVER_SLEEP=5000 HEADLESS=true npm run test:ui && npm run report", "start:regression": "concurrently \"npm:mock-server\" \"npm:start\" \"npm:test:regression\"", "ui-steps": "export BABEL_ENV=test; export NODE_ENV=test; npx -p @babel/core -p @babel/node babel-node --presets @babel/preset-env scripts/collectUITestsSteps.js", "nli": "npm link iguazio.dashboard-react-controls", @@ -128,7 +130,7 @@ "body-parser": "^1.19.0", "case-sensitive-paths-webpack-plugin": "^2.4.0", "chai": "^4.3.4", - "chromedriver": "^124.0.0", + "chromedriver": "^126.0.0", "css-loader": "^6.5.1", "cucumber-html-reporter": "^5.3.0", "eslint": "^8.57.0", diff --git a/scripts/ci-cd-scripts/appendCommentToHttpClient.js b/scripts/ci-cd-scripts/appendCommentToHttpClient.js new file mode 100644 index 000000000..405b872fc --- /dev/null +++ b/scripts/ci-cd-scripts/appendCommentToHttpClient.js @@ -0,0 +1,34 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +const fs = require('fs') +const path = require('path') + +const sourceFilePath = path.join(__dirname, 'commentedHttpClient.js') +const targetFilePath = path.join(__dirname, '../../src/httpClient.js') + +try { + const fileContent = fs.readFileSync(sourceFilePath, 'utf-8') + fs.writeFileSync(targetFilePath, fileContent) + + console.log(`Successfully overwritten ${targetFilePath} with content from ${sourceFilePath}`) +} catch (err) { + console.error(`Error occurred: ${err.message}`) + process.exit(1) +} diff --git a/scripts/ci-cd-scripts/commentedHttpClient.js b/scripts/ci-cd-scripts/commentedHttpClient.js new file mode 100644 index 000000000..4e51b7039 --- /dev/null +++ b/scripts/ci-cd-scripts/commentedHttpClient.js @@ -0,0 +1,195 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import axios from 'axios' +import qs from 'qs' + +// import { ConfirmDialog } from 'igz-controls/components' +import { + CANCEL_REQUEST_TIMEOUT, + LARGE_REQUEST_CANCELED + // PROJECTS_PAGE_PATH +} from './constants' +// import { openPopUp } from 'igz-controls/utils/common.util' +// import { mlrunUnhealthyErrors } from './components/ProjectsPage/projects.util' + +const headers = { + 'Cache-Control': 'no-cache' +} + +console.log('----------- comments ----------') +// serialize a param with an array value as a repeated param, for example: +// { label: ['host', 'owner=admin'] } => 'label=host&label=owner%3Dadmin' +const paramsSerializer = params => qs.stringify(params, { arrayFormat: 'repeat' }) + +// const MAX_CONSECUTIVE_ERRORS_COUNT = 2 +// let consecutiveErrorsCount = 0 + +export const mainBaseUrl = `${process.env.PUBLIC_URL}/api/v1` +export const mainBaseUrlV2 = `${process.env.PUBLIC_URL}/api/v2` + +export const mainHttpClient = axios.create({ + baseURL: mainBaseUrl, + headers, + paramsSerializer +}) + +export const mainHttpClientV2 = axios.create({ + baseURL: mainBaseUrlV2, + headers, + paramsSerializer +}) + +export const functionTemplatesHttpClient = axios.create({ + baseURL: `${process.env.PUBLIC_URL}/function-catalog`, + headers +}) + +export const nuclioHttpClient = axios.create({ + baseURL: `${process.env.PUBLIC_URL}/nuclio/api`, + headers +}) + +export const iguazioHttpClient = axios.create({ + baseURL: process.env.NODE_ENV === 'production' ? '/api' : '/iguazio/api', + headers +}) + +const getAbortSignal = (controller, abortCallback, timeoutMs) => { + let timeoutId = null + const newController = new AbortController() + const abortController = controller || newController + + if (timeoutMs) { + timeoutId = setTimeout(() => abortController.abort(LARGE_REQUEST_CANCELED), timeoutMs) + } + + abortController.signal.onabort = event => { + if (timeoutId) { + clearTimeout(timeoutId) + } + + if (abortCallback) { + abortCallback(event) + } + } + + return [abortController.signal, timeoutId] +} + +let requestId = 1 +let requestTimeouts = {} +let largeResponsePopUpIsOpen = false + +const requestLargeDataOnFulfill = config => { + if (config?.ui?.setLargeRequestErrorMessage) { + const [signal, timeoutId] = getAbortSignal( + config.ui?.controller, + abortEvent => { + if (abortEvent.target.reason === LARGE_REQUEST_CANCELED) { + showLargeResponsePopUp(config.ui.setLargeRequestErrorMessage) + } + }, + CANCEL_REQUEST_TIMEOUT + ) + + config.signal = signal + + requestTimeouts[requestId] = timeoutId + config.ui.requestId = requestId + requestId++ + } + + return config +} +const requestLargeDataOnReject = error => { + return Promise.reject(error) +} +const responseFulfillInterceptor = response => { + // consecutiveErrorsCount = 0 + + if (response.config?.ui?.requestId) { + const isLargeResponse = + response.data?.total_size >= 0 + ? response.data.total_size > 10000 + : Object.values(response.data)?.[0]?.length > 10000 + + clearTimeout(requestTimeouts[response.config.ui.requestId]) + delete requestTimeouts[response.config.ui.requestId] + + if (isLargeResponse) { + showLargeResponsePopUp(response.config.ui.setLargeRequestErrorMessage) + + throw new Error(LARGE_REQUEST_CANCELED) + } else { + response.config.ui.setLargeRequestErrorMessage('') + } + } + + return response +} +const responseRejectInterceptor = error => { + if (error.config?.ui?.requestId) { + clearTimeout(requestTimeouts[error.config.ui.requestId]) + delete requestTimeouts[error.config.ui.requestId] + } + + // if (error.config?.method === 'get') { + // if ( + // mlrunUnhealthyErrors.includes(error.response?.status) && + // consecutiveErrorsCount < MAX_CONSECUTIVE_ERRORS_COUNT + // ) { + // consecutiveErrorsCount++ + // + // if ( + // consecutiveErrorsCount === MAX_CONSECUTIVE_ERRORS_COUNT && + // window.location.pathname !== `/${PROJECTS_PAGE_PATH}` + // ) { + // window.location.href = '/projects' + // } + // } + // } + + return Promise.reject(error) +} + +// Request interceptors +mainHttpClient.interceptors.request.use(requestLargeDataOnFulfill, requestLargeDataOnReject) +mainHttpClientV2.interceptors.request.use(requestLargeDataOnFulfill, requestLargeDataOnReject) + +// Response interceptors +mainHttpClient.interceptors.response.use(responseFulfillInterceptor, responseRejectInterceptor) +mainHttpClientV2.interceptors.response.use(responseFulfillInterceptor, responseRejectInterceptor) + +export const showLargeResponsePopUp = setLargeRequestErrorMessage => { + if (!largeResponsePopUpIsOpen) { + const errorMessage = + 'The query result is too large to display. Add a filter (or narrow it) to retrieve fewer results.' + + setLargeRequestErrorMessage(errorMessage) + largeResponsePopUpIsOpen = true + + // openPopUp(ConfirmDialog, { + // message: errorMessage, + // closePopUp: () => { + // largeResponsePopUpIsOpen = false + // } + // }) + } +} diff --git a/scripts/ci-cd-scripts/tempSmoke1Test.js b/scripts/ci-cd-scripts/tempSmoke1Test.js new file mode 100644 index 000000000..d8aa27c17 --- /dev/null +++ b/scripts/ci-cd-scripts/tempSmoke1Test.js @@ -0,0 +1,28 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +const { execSync } = require('child_process') + +const cucumberCommand = + "./node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t '@smoke1'" +try { + execSync(cucumberCommand, { stdio: 'inherit' }) +} catch (err) { + console.log(err.message) +} diff --git a/scripts/testui.js b/scripts/testui.js index 7d931679b..b7f17bbc7 100644 --- a/scripts/testui.js +++ b/scripts/testui.js @@ -17,48 +17,53 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -'use strict'; -const { report } = require('../tests/config'); -const fs = require('fs'); +const { report } = require('../tests/config') +const fs = require('fs') // Do this as the first thing so that any code reading it knows the right env. -process.env.BABEL_ENV = 'test'; -process.env.NODE_ENV = 'test'; +process.env.BABEL_ENV = 'test' +process.env.NODE_ENV = 'test' // Makes the script crash on unhandled rejections instead of silently // ignoring them. In the future, promise rejections that are not handled will // terminate the Node.js process with a non-zero exit code. process.on('unhandledRejection', err => { - throw err; -}); + throw err +}) // Ensure environment variables are read. -require('../config/env'); +require('../config/env') -const execSync = require('child_process').execSync; -const argv = process.argv.slice(2); +const execSync = require('child_process').execSync +const argv = process.argv.slice(2) // build cucumber executive command -const cucumberCommand = 'cucumber-js --require-module @babel/register --require-module @babel/polyfill ' + - '-f json:' + report + '.json -f html:' + report +'_default.html tests ' + - argv.join(' '); +const cucumberCommand = + 'cucumber-js --require-module @babel/register --require-module @babel/polyfill ' + + '-f json:' + + report + + '.json -f html:' + + report + + '_default.html tests ' + + argv.join(' ') + + '-t @smoke' // check and create report folder -const reportDir = report.split('/').slice(0, -1).join('/'); -console.log(reportDir); +const reportDir = report.split('/').slice(0, -1).join('/') +console.log(reportDir) if (!fs.existsSync(reportDir)) { - fs.mkdirSync(reportDir); + fs.mkdirSync(reportDir) } function runCrossPlatform() { - try { - execSync(cucumberCommand); - return true; - } catch (e) { - return false; - } + try { + execSync(cucumberCommand) + return true + } catch (e) { + return false + } } // cucumber -runCrossPlatform(); +runCrossPlatform() diff --git a/tests/config.js b/tests/config.js index 1d98a1409..987df470b 100644 --- a/tests/config.js +++ b/tests/config.js @@ -17,10 +17,16 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ + +const HEADLESS = process.env.HEADLESS === 'true' || false + +/* eslint-disable-next-line no-console */ +console.log(`DRIVER_SLEEP: ${HEADLESS}`) + module.exports = { timeout: 60000, browser: 'chrome', - headless: false, + headless: HEADLESS, screen_size: { width: 1600, height: 900 }, report: 'tests/reports/cucumber_report', test_url: 'localhost', diff --git a/tests/features/artifacts.feature b/tests/features/artifacts.feature index 5447c29d8..1e0e7ff91 100644 --- a/tests/features/artifacts.feature +++ b/tests/features/artifacts.feature @@ -5,6 +5,7 @@ Feature: Files Page @MLA @passive @smoke + @smoke1 Scenario: MLA001 - Check all mandatory components on Artifacts tab Given open url And wait load page @@ -354,7 +355,7 @@ Feature: Files Page When click on cell with value "survival-curves_km-survival" in "name" column in "Files_Table" table on "Files" wizard Then select "Preview" tab in "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard And wait load page - Then verify "Pop_Out_Button" element visibility on "Files_Info_Pane" wizard + Then verify "Pop_Out_Button" element visibility on "Files_Info_Pane" wizard Then click on "Pop_Out_Button" element on "Files_Info_Pane" wizard And wait load page Then verify "Preview_Row" element visibility on "Artifact_Preview_Popup" wizard @@ -377,7 +378,7 @@ Feature: Files Page @MLA @smoke - Scenario: MLA022 - Verify the Delete option state in Artifacts table and Overview details action menu + Scenario: MLA022 - Verify the Delete option state in Artifacts table and Overview details action menu Given open url And wait load page And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard @@ -514,7 +515,7 @@ Feature: Files Page Then "Header_Download_Pop_Up" element on "Downloads_Popup" should contains "Downloads" value Then click on "Download_Pop_Up_Cross_Cancel_Button" element on "Downloads_Popup" wizard Then click on "Cross_Cancel_Button" element on "Preview_Popup" wizard - + @MLA @passive @smoke @@ -534,7 +535,7 @@ Feature: Files Page Then verify "Cross_Cancel_Button" element visibility on "View_YAML" wizard Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard - @MLA + @MLA @smoke Scenario: MLA020 - Check broken link redirection Given open url @@ -578,8 +579,8 @@ Feature: Files Page Then compare "Header" element value on "Files_Info_Pane" wizard with test "name" context value Then verify that row index 1 is active in "Files_Table" table on "Files" wizard Then verify that row index 2 is NOT active in "Files_Table" table on "Files" wizard - Then click on cell with row index 2 in "name" column in "Files_Table" table on "Files" wizard - Then verify that row index 2 is active in "Files_Table" table on "Files" wizard + Then click on cell with row index 2 in "name" column in "Files_Table" table on "Files" wizard + Then verify that row index 2 is active in "Files_Table" table on "Files" wizard Then verify that row index 1 is NOT active in "Files_Table" table on "Files" wizard Then verify "Info_Pane_Tab_Selector" element visibility on "Files_Info_Pane" wizard Then verify "Overview" tab is active in "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard @@ -603,12 +604,12 @@ Feature: Files Page Then verify "Overview" tab is active in "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard Then verify "Overview_General_Headers" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Overview_General_Headers" Then check "latest" value in "tag" column in "Overview_Table" table on "Files_Info_Pane" wizard - Then click on "Edit_btn_table_view" element on "Files_Info_Pane" wizard + Then click on "Edit_btn_table_view" element on "Files_Info_Pane" wizard Then verify "Version_tag_Input_table_view" on "Files_Info_Pane" wizard should contains "latest" value Then click on "Full_View_Button" element on "Files_Info_Pane" wizard Then verify "Cross_Close_Button" element not exists on "Files_Info_Pane" wizard Then click on "Edit_btn_full_view" element on "Files_Info_Pane" wizard - Then verify "Version_tag_Input_full_view" on "Files_Info_Pane" wizard should contains "latest" value + Then verify "Version_tag_Input_full_view" on "Files_Info_Pane" wizard should contains "latest" value Then click on "Tabel_View_Button" element on "Files_Info_Pane" wizard Then verify "Cross_Close_Button" element visibility on "Files_Info_Pane" wizard @@ -653,7 +654,7 @@ Feature: Files Page When click on cell with row index 1 in "name" column in "Files_Table" table on "Files" wizard Then verify "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Tab_List" Then verify "Overview" tab is active in "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard - Then verify "Overview_General_Headers" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Overview_General_Headers" + Then verify "Overview_General_Headers" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Overview_General_Headers" Then check "latest" value in "tag" column in "Overview_Table" table on "Files_Info_Pane" wizard Then click on "Edit_btn_table_view" element on "Files_Info_Pane" wizard Then type value "" to "Version_tag_Input" field on "Files_Info_Pane" wizard @@ -696,4 +697,4 @@ Feature: Files Page Then verify "Overview_General_Headers" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Overview_General_Headers" Then check "latest123456" value in "tag" column in "Overview_Table" table on "Files_Info_Pane" wizard Then save to context "name" column on 3 row from "Files_Table" table on "Files" wizard - Then compare "Header" element value on "Files_Info_Pane" wizard with test "name" context value \ No newline at end of file + Then compare "Header" element value on "Files_Info_Pane" wizard with test "name" context value diff --git a/tests/features/step-definitions/steps.js b/tests/features/step-definitions/steps.js index eafc0e148..16fa6c759 100644 --- a/tests/features/step-definitions/steps.js +++ b/tests/features/step-definitions/steps.js @@ -105,15 +105,23 @@ import { isRadioButtonUnselected, selectRadiobutton } from '../common/actions/radio-button.action' -import { - openActionMenu, +import { + openActionMenu, selectOptionInActionMenu, verifyOptionInActionMenuEnabled, - verifyOptionInActionMenuDisabled + verifyOptionInActionMenuDisabled } from '../common/actions/action-menu.action' import { expect } from 'chai' +import { toInteger } from 'lodash' + +require('dotenv').config() + +const DRIVER_SLEEP = toInteger(process.env.DRIVER_SLEEP) -Given('open url', async function() { +/* eslint-disable-next-line no-console */ +console.log(`DRIVER_SLEEP: ${DRIVER_SLEEP}`) + +Given('open url', async function () { await navigateToPage(this.driver, `http://${test_url}:${test_port}`) }) @@ -132,29 +140,26 @@ Given('open url', async function() { */ -When('turn on demo mode', async function() { +When('turn on demo mode', async function () { const url = await this.driver.getCurrentUrl() await navigateToPage(this.driver, `${url}?mode=demo`) }) -When('turn on staging mode', async function() { +When('turn on staging mode', async function () { const url = await this.driver.getCurrentUrl() await navigateToPage(this.driver, `${url}?mode=staging`) }) -Then('turn Off MLRun CE mode', async function() { - await this.driver.executeScript(function() { +Then('turn Off MLRun CE mode', async function () { + await this.driver.executeScript(function () { localStorage.setItem('igzFullVersion', '3.5.5') }) -}) +}) -Then('additionally redirect by INVALID-TAB', async function() { +Then('additionally redirect by INVALID-TAB', async function () { const beforeURL = await this.driver.getCurrentUrl() const urlNodesArr = beforeURL.split('/') - const invalidTab = beforeURL.replace( - urlNodesArr[urlNodesArr.length - 1], - 'INVALID-TAB' - ) + const invalidTab = beforeURL.replace(urlNodesArr[urlNodesArr.length - 1], 'INVALID-TAB') await navigateToPage(this.driver, `${invalidTab}`) const afterURL = await this.driver.getCurrentUrl() expect(beforeURL).equal( @@ -163,114 +168,101 @@ Then('additionally redirect by INVALID-TAB', async function() { ) }) -Then( - 'verify redirection from {string} to {string}', - async function (invalidPath, expectedPath) { - const invalidUrl = `http://${test_url}:${test_port}/${invalidPath}` - const expectedUrl = `http://${test_url}:${test_port}/${expectedPath}` +Then('verify redirection from {string} to {string}', async function (invalidPath, expectedPath) { + const invalidUrl = `http://${test_url}:${test_port}/${invalidPath}` + const expectedUrl = `http://${test_url}:${test_port}/${expectedPath}` - await navigateToPage(this.driver, invalidUrl) - await this.driver.sleep(250) - const afterURL = await this.driver.getCurrentUrl() + await navigateToPage(this.driver, invalidUrl) + await this.driver.sleep(DRIVER_SLEEP || 250) + const afterURL = await this.driver.getCurrentUrl() - expect(expectedUrl).equal( - afterURL, - `Redirection from "${invalidUrl}"\nshould be "${expectedUrl}"\nbut is "${afterURL}"` - ) - } -) + expect(expectedUrl).equal( + afterURL, + `Redirection from "${invalidUrl}"\nshould be "${expectedUrl}"\nbut is "${afterURL}"` + ) +}) -Then( - 'verify redirection to {string}', - async function (expectedPath) { - const expectedUrl = `http://${test_url}:${test_port}/${expectedPath}` - const afterURL = await this.driver.getCurrentUrl() +Then('verify redirection to {string}', async function (expectedPath) { + const expectedUrl = `http://${test_url}:${test_port}/${expectedPath}` + const afterURL = await this.driver.getCurrentUrl() - expect(expectedUrl).equal( - afterURL, - `Redirection should be "${expectedUrl}"\nbut is "${afterURL}"` - ) - } -) + expect(expectedUrl).equal( + afterURL, + `Redirection should be "${expectedUrl}"\nbut is "${afterURL}"` + ) +}) -Then('wait load page', async function() { +Then('wait load page', async function () { + console.log(DRIVER_SLEEP) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) }) -Then('navigate forward', async function() { +Then('navigate forward', async function () { await navigateForward(this.driver) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) }) -Then('navigate back', async function() { +Then('navigate back', async function () { await navigateBack(this.driver) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) }) -Then('refresh a page', async function() { +Then('refresh a page', async function () { await refreshPage(this.driver) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) - await this.driver.sleep(250) + await this.driver.sleep(DRIVER_SLEEP || 250) }) -Then('click on {string} element on {string} wizard', async function( - component, - wizard -) { +Then('click on {string} element on {string} wizard', async function (component, wizard) { await waiteUntilComponent(this.driver, pageObjects[wizard][component]) await clickOnComponent(this.driver, pageObjects[wizard][component]) - await this.driver.sleep(250) + await this.driver.sleep(DRIVER_SLEEP || 250) }) -Then('click on breadcrumbs {string} label on {string} wizard', async function( - labelType, - wizard -) { +Then('click on breadcrumbs {string} label on {string} wizard', async function (labelType, wizard) { await waiteUntilComponent(this.driver, pageObjects[wizard]['Breadcrumbs'][`${labelType}Label`]) await clickOnComponent(this.driver, pageObjects[wizard]['Breadcrumbs'][`${labelType}Label`]) - await this.driver.sleep(250) + await this.driver.sleep(DRIVER_SLEEP || 250) }) -Then('verify if {string} popup dialog appears', async function(popup) { +Then('verify if {string} popup dialog appears', async function (popup) { await waiteUntilComponent(this.driver, pageObjects[popup]['Title']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) await componentIsPresent(this.driver, pageObjects[popup]['Title']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) await componentIsVisible(this.driver, pageObjects[popup]['Title']) }) Then( 'type into {string} on {string} popup dialog {string} value', - async function(component, wizard, value) { + async function (component, wizard, value) { await typeValue(this.driver, pageObjects[wizard][component], value) await verifyTypedValue(this.driver, pageObjects[wizard][component], value) } ) -Then('type value {string} to {string} field on {string} wizard', async function( - value, - inputField, - wizard -) { - await typeValue(this.driver, pageObjects[wizard][inputField], value) - await this.driver.sleep(250) - await verifyTypedValue(this.driver, pageObjects[wizard][inputField], value) - await this.driver.sleep(250) -}) +Then( + 'type value {string} to {string} field on {string} wizard', + async function (value, inputField, wizard) { + await typeValue(this.driver, pageObjects[wizard][inputField], value) + await this.driver.sleep(DRIVER_SLEEP || 250) + await verifyTypedValue(this.driver, pageObjects[wizard][inputField], value) + await this.driver.sleep(DRIVER_SLEEP || 250) + } +) -Then('type value {string} to {string} field on {string} wizard without inputgroup', async function( - value, - inputField, - wizard -) { - await typeValueWithoutInputgroup(this.driver, pageObjects[wizard][inputField], value) - await this.driver.sleep(250) - await verifyTypedValueWithoutInputgroup(this.driver, pageObjects[wizard][inputField], value) - await this.driver.sleep(250) -}) +Then( + 'type value {string} to {string} field on {string} wizard without inputgroup', + async function (value, inputField, wizard) { + await typeValueWithoutInputgroup(this.driver, pageObjects[wizard][inputField], value) + await this.driver.sleep(DRIVER_SLEEP || 250) + await verifyTypedValueWithoutInputgroup(this.driver, pageObjects[wizard][inputField], value) + await this.driver.sleep(DRIVER_SLEEP || 250) + } +) Then( 'verify {string} element on {string} wizard is enabled', @@ -345,74 +337,58 @@ Then( Then( 'verify checkbox {string} element in {string} on {string} wizard is disabled', async function (elementName, accordionName, wizardName) { - await verifyCheckboxDisabled(this.driver, pageObjects[wizardName][accordionName][elementName].root) + await verifyCheckboxDisabled( + this.driver, + pageObjects[wizardName][accordionName][elementName].root + ) } ) Then( 'verify {string} element in {string} on {string} wizard is enabled', - async function(inputField, accordionName, wizardName) { - await verifyInputEnabled( - this.driver, - pageObjects[wizardName][accordionName][inputField] - ) + async function (inputField, accordionName, wizardName) { + await verifyInputEnabled(this.driver, pageObjects[wizardName][accordionName][inputField]) } ) Then( 'verify {string} element in {string} on {string} wizard is enabled by class name', - async function(inputField, accordionName, wizardName) { - await verifyInputClassEnabled( - this.driver, - pageObjects[wizardName][accordionName][inputField] - ) + async function (inputField, accordionName, wizardName) { + await verifyInputClassEnabled(this.driver, pageObjects[wizardName][accordionName][inputField]) } ) Then( 'verify {string} element in {string} on {string} wizard is disabled', - async function(inputField, accordionName, wizardName) { - await verifyInputDisabled( - this.driver, - pageObjects[wizardName][accordionName][inputField] - ) + async function (inputField, accordionName, wizardName) { + await verifyInputDisabled(this.driver, pageObjects[wizardName][accordionName][inputField]) } ) Then( 'verify {string} element in {string} on {string} wizard is disabled by class name', - async function(inputField, accordionName, wizardName) { - await verifyInputClassDisabled( - this.driver, - pageObjects[wizardName][accordionName][inputField] - ) + async function (inputField, accordionName, wizardName) { + await verifyInputClassDisabled(this.driver, pageObjects[wizardName][accordionName][inputField]) } ) Then( 'verify {string} element on {string} wizard is disabled by class name', - async function(inputField, wizardName) { - await verifyClassDisabled( - this.driver, - pageObjects[wizardName][inputField] - ) + async function (inputField, wizardName) { + await verifyClassDisabled(this.driver, pageObjects[wizardName][inputField]) } ) When( 'type searchable fragment {string} into {string} on {string} wizard', - async function(subName, inputGroup, wizard) { - await typeSearchableValue( - this.driver, - pageObjects[wizard][inputGroup], - subName - ) + async function (subName, inputGroup, wizard) { + await typeSearchableValue(this.driver, pageObjects[wizard][inputGroup], subName) } ) When( 'type searchable fragment {string} into {string} combobox input in {string} on {string} wizard', - async function(subName, combobox, accordion, wizard) { + async function (subName, combobox, accordion, wizard) { await typeSearchableValue( this.driver, pageObjects[wizard][accordion][combobox]['comboDropdown'], @@ -423,8 +399,8 @@ When( Then( 'searchable case {string} fragment {string} should be in every suggested option into {string} on {string} wizard', - async function(textCase, subName, inputGroup, wizard) { - await this.driver.sleep(1000) + async function (textCase, subName, inputGroup, wizard) { + await this.driver.sleep(DRIVER_SLEEP || 1000) await isContainsSubstringInSuggestedOptions( this.driver, pageObjects[wizard][inputGroup], @@ -436,8 +412,8 @@ Then( Then( 'searchable fragment {string} should be in every suggested option into {string} combobox input in {string} on {string} wizard', - async function(subName, combobox, accordion, wizard) { - await this.driver.sleep(200) + async function (subName, combobox, accordion, wizard) { + await this.driver.sleep(DRIVER_SLEEP || 200) await isContainsSubstringInSuggestedOptions( this.driver, pageObjects[wizard][accordion][combobox]['comboDropdown'], @@ -448,17 +424,10 @@ Then( Then( 'increase value on {int} points in {string} field on {string} on {string} wizard', - async function(value, inputField, accordion, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][accordion][inputField] - ) + async function (value, inputField, accordion, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][accordion][inputField]) const result = Number.parseInt(txt) + value - await incrementValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) + await incrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) await verifyTypedValue( this.driver, pageObjects[wizard][accordion][inputField], @@ -469,63 +438,38 @@ Then( Then( 'increase value on {int} points in {string} field on {string} wizard', - async function(value, inputField, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][inputField] - ) + async function (value, inputField, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][inputField]) const result = Number.parseInt(txt) + value - await incrementValue( - this.driver, - pageObjects[wizard][inputField], - value - ) - await verifyTypedValue( - this.driver, - pageObjects[wizard][inputField], - result.toString() - ) + await incrementValue(this.driver, pageObjects[wizard][inputField], value) + await verifyTypedValue(this.driver, pageObjects[wizard][inputField], result.toString()) } ) Then( - 'increase value on {int} points in {string} field with {string} on {string} on {string} wizard', - async function(value, inputField, unit, accordion, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][accordion][inputField] - ) - const unitValue = unit === 'cpu' ? value / 1000 : unit === 'millicpu' ? value * 100 : value - let result = Number.parseFloat(txt || '0') + unitValue - if (unit === 'cpu') { - return result.toFixed(3) - } - await incrementValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) - await verifyTypedValue( - this.driver, - pageObjects[wizard][accordion][inputField], - result.toString() - ) + 'increase value on {int} points in {string} field with {string} on {string} on {string} wizard', + async function (value, inputField, unit, accordion, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][accordion][inputField]) + const unitValue = unit === 'cpu' ? value / 1000 : unit === 'millicpu' ? value * 100 : value + let result = Number.parseFloat(txt || '0') + unitValue + if (unit === 'cpu') { + return result.toFixed(3) } + await incrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) + await verifyTypedValue( + this.driver, + pageObjects[wizard][accordion][inputField], + result.toString() + ) + } ) Then( 'decrease value on {int} points in {string} field on {string} on {string} wizard', - async function(value, inputField, accordion, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][accordion][inputField] - ) + async function (value, inputField, accordion, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][accordion][inputField]) const result = Number.parseInt(txt) - value - await decrementValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) + await decrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) await verifyTypedValue( this.driver, pageObjects[wizard][accordion][inputField], @@ -536,22 +480,11 @@ Then( Then( 'decrease value on {int} points in {string} field on {string} wizard', - async function(value, inputField, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][inputField] - ) + async function (value, inputField, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][inputField]) const result = Number.parseInt(txt) - value - await decrementValue( - this.driver, - pageObjects[wizard][inputField], - value - ) - await verifyTypedValue( - this.driver, - pageObjects[wizard][inputField], - result.toString() - ) + await decrementValue(this.driver, pageObjects[wizard][inputField], value) + await verifyTypedValue(this.driver, pageObjects[wizard][inputField], result.toString()) } ) @@ -563,8 +496,7 @@ Then( let result = Number.parseFloat(txt) - unitValue if (unit === 'cpu') { return result.toFixed(3) - } - else if (unit !== 'cpu' && result < 1) { + } else if (unit !== 'cpu' && result < 1) { result = 1 } await decrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) @@ -578,23 +510,15 @@ Then( Then( 'type value {string} to {string} field on {string} on {string} wizard', - async function(value, inputField, accordion, wizard) { - await typeValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) - await verifyTypedValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) + async function (value, inputField, accordion, wizard) { + await typeValue(this.driver, pageObjects[wizard][accordion][inputField], value) + await verifyTypedValue(this.driver, pageObjects[wizard][accordion][inputField], value) } ) Then( '{string} component on {string} should contains {string}.{string}', - async function(component, wizard, constStorage, constValue) { + async function (component, wizard, constStorage, constValue) { await waiteUntilComponent(this.driver, pageObjects[wizard][component]) await verifyText( this.driver, @@ -606,19 +530,15 @@ Then( Then( '{string} element on {string} should contains {string} value', - async function(component, wizard, value) { + async function (component, wizard, value) { await verifyText(this.driver, pageObjects[wizard][component], value) } ) Then( '{string} element in {string} on {string} should contains {string} value', - async function(component, accordion, wizard, value) { - await verifyText( - this.driver, - pageObjects[wizard][accordion][component], - value - ) + async function (component, accordion, wizard, value) { + await verifyText(this.driver, pageObjects[wizard][accordion][component], value) } ) @@ -636,7 +556,7 @@ Then( Then( '{string} component on {string} should be equal {string}.{string}', - async function(component, wizard, constStorage, constValue) { + async function (component, wizard, constStorage, constValue) { await verifyTextRegExp( this.driver, pageObjects[wizard][component], @@ -647,11 +567,8 @@ Then( Then( '{string} component in {string} on {string} should contains {string}.{string}', - async function(component, accordion, wizard, constStorage, constValue) { - await waiteUntilComponent( - this.driver, - pageObjects[wizard][accordion][component] - ) + async function (component, accordion, wizard, constStorage, constValue) { + await waiteUntilComponent(this.driver, pageObjects[wizard][accordion][component]) await verifyText( this.driver, pageObjects[wizard][accordion][component], @@ -662,30 +579,23 @@ Then( When( 'select {string} option in {string} dropdown on {string} wizard', - async function(optionValue, dropdownName, wizardName) { + async function (optionValue, dropdownName, wizardName) { await openDropdown(this.driver, pageObjects[wizardName][dropdownName]) - await selectOptionInDropdown( - this.driver, - pageObjects[wizardName][dropdownName], - optionValue - ) - await this.driver.sleep(500) + await selectOptionInDropdown(this.driver, pageObjects[wizardName][dropdownName], optionValue) + await this.driver.sleep(DRIVER_SLEEP || 500) } ) When( 'select {string} option in {string} dropdown on {string} on {string} wizard', - async function(optionValue, dropdownName, accordionName, wizardName) { - await openDropdown( - this.driver, - pageObjects[wizardName][accordionName][dropdownName] - ) + async function (optionValue, dropdownName, accordionName, wizardName) { + await openDropdown(this.driver, pageObjects[wizardName][accordionName][dropdownName]) await selectOptionInDropdown( this.driver, pageObjects[wizardName][accordionName][dropdownName], optionValue ) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) await checkDropdownSelectedOption( this.driver, pageObjects[wizardName][accordionName][dropdownName], @@ -696,7 +606,7 @@ When( Then( 'verify {string} dropdown on {string} wizard selected option value {string}', - async function(dropdownName, wizardName, optionValue) { + async function (dropdownName, wizardName, optionValue) { await checkDropdownSelectedOption( this.driver, pageObjects[wizardName][dropdownName], @@ -707,7 +617,7 @@ Then( Then( 'verify {string} dropdown in {string} on {string} wizard selected option value {string}', - async function(dropdownName, accordionName, wizardName, optionValue) { + async function (dropdownName, accordionName, wizardName, optionValue) { await checkDropdownSelectedOption( this.driver, pageObjects[wizardName][accordionName][dropdownName], @@ -718,7 +628,7 @@ Then( When( 'select {string} option in {string} filter dropdown on {string} wizard', - async function(optionValue, dropdownName, wizardName) { + async function (optionValue, dropdownName, wizardName) { await openDropdown(this.driver, pageObjects[wizardName][dropdownName]) await selectOptionInDropdownWithoutCheck( this.driver, @@ -730,7 +640,7 @@ When( Then( 'verify {string} filter band in {string} filter dropdown on {string} wizard', - async function(optionValue, dropdownName, wizardName) { + async function (optionValue, dropdownName, wizardName) { await verifyTimeFilterBand( this.driver, pageObjects[wizardName][dropdownName], @@ -741,37 +651,27 @@ Then( When( 'pick up {string} from {string} to {string} in {string} via {string} on {string} wizard', - async function( - optionValue, - fromDatetime, - toDatetime, - datetimePicker, - dropdownName, - wizardName - ) { + async function (optionValue, fromDatetime, toDatetime, datetimePicker, dropdownName, wizardName) { await openDropdown(this.driver, pageObjects[wizardName][dropdownName]) await selectOptionInDropdownWithoutCheck( this.driver, pageObjects[wizardName][dropdownName], optionValue ) - await this.driver.sleep(100) + await this.driver.sleep(DRIVER_SLEEP || 100) await pickUpCustomDatetimeRange( this.driver, pageObjects[wizardName][datetimePicker], fromDatetime, toDatetime ) - await applyDatetimePickerRange( - this.driver, - pageObjects[wizardName][datetimePicker] - ) + await applyDatetimePickerRange(this.driver, pageObjects[wizardName][datetimePicker]) } ) Then( 'verify from {string} to {string} filter band in {string} filter dropdown on {string} wizard', - async function(fromDatetime, toDatetime, dropdownName, wizardName) { + async function (fromDatetime, toDatetime, dropdownName, wizardName) { await verifyTimeFilterBand( this.driver, pageObjects[wizardName][dropdownName], @@ -782,7 +682,7 @@ Then( Then( 'verify error message in {string} on {string} wizard with value {string}.{string}', - async function(datetimePicker, wizard, constStorage, constValue) { + async function (datetimePicker, wizard, constStorage, constValue) { await verifyText( this.driver, pageObjects[wizard][datetimePicker].errorMessage, @@ -793,7 +693,7 @@ Then( Then( 'verify {string} element in {string} on {string} wizard should contains {string}.{string}', - async function(dropdown, accordion, wizard, constStorage, constValue) { + async function (dropdown, accordion, wizard, constStorage, constValue) { await openDropdown(this.driver, pageObjects[wizard][accordion][dropdown]) await checkDropdownOptions( this.driver, @@ -801,95 +701,64 @@ Then( pageObjectsConsts[constStorage][constValue] ) // close dropdown options - await clickNearComponent( - this.driver, - pageObjects[wizard][accordion][dropdown]['open_button'] - ) + await clickNearComponent(this.driver, pageObjects[wizard][accordion][dropdown]['open_button']) } ) Then( 'verify {string} dropdown element on {string} wizard should contains {string}.{string}', - async function(dropdownName, wizardName, constStorage, constValue) { + async function (dropdownName, wizardName, constStorage, constValue) { await openDropdown(this.driver, pageObjects[wizardName][dropdownName]) await checkDropdownContainsOptions( this.driver, pageObjects[wizardName][dropdownName], pageObjectsConsts[constStorage][constValue] ) - await clickNearComponent( - this.driver, - pageObjects[wizardName][dropdownName]['open_button'] - ) + await clickNearComponent(this.driver, pageObjects[wizardName][dropdownName]['open_button']) } ) -Then('verify {string} element visibility on {string} wizard', async function( - component, - wizard -) { +Then('verify {string} element visibility on {string} wizard', async function (component, wizard) { await componentIsVisible(this.driver, pageObjects[wizard][component]) }) -Then('verify {string} element invisibility on {string} wizard', async function( - component, - wizard -) { +Then('verify {string} element invisibility on {string} wizard', async function (component, wizard) { await componentIsNotVisible(this.driver, pageObjects[wizard][component]) }) Then( 'verify {string} element visibility in {string} on {string} wizard', - async function(component, accordion, wizard) { - await componentIsVisible( - this.driver, - pageObjects[wizard][accordion][component] - ) + async function (component, accordion, wizard) { + await componentIsVisible(this.driver, pageObjects[wizard][accordion][component]) } ) -Then('verify {string} element not exists on {string} wizard', async function( - component, - wizard -) { +Then('verify {string} element not exists on {string} wizard', async function (component, wizard) { await componentIsNotPresent(this.driver, pageObjects[wizard][component]) }) -Then('verify {string} element not exists in {string} on {string} wizard', async function( - component, - accordion, - wizard -) { - await componentIsNotPresent(this.driver, pageObjects[wizard][accordion][component]) -}) +Then( + 'verify {string} element not exists in {string} on {string} wizard', + async function (component, accordion, wizard) { + await componentIsNotPresent(this.driver, pageObjects[wizard][accordion][component]) + } +) -When('collapse {string} on {string} wizard', async function(accordion, wizard) { - await collapseAccordionSection( - this.driver, - pageObjects[wizard][accordion]['Collapse_Button'] - ) - await this.driver.sleep(100) +When('collapse {string} on {string} wizard', async function (accordion, wizard) { + await collapseAccordionSection(this.driver, pageObjects[wizard][accordion]['Collapse_Button']) + await this.driver.sleep(DRIVER_SLEEP || 100) }) -When('expand {string} on {string} wizard', async function(accordion, wizard) { - await expandAccordionSection( - this.driver, - pageObjects[wizard][accordion]['Collapse_Button'] - ) - await this.driver.sleep(100) +When('expand {string} on {string} wizard', async function (accordion, wizard) { + await expandAccordionSection(this.driver, pageObjects[wizard][accordion]['Collapse_Button']) + await this.driver.sleep(DRIVER_SLEEP || 100) }) -Then('verify {string} is collapsed on {string} wizard', async function( - accordion, - wizard -) { - await isAccordionSectionCollapsed( - this.driver, - pageObjects[wizard][accordion]['Collapse_Button'] - ) +Then('verify {string} is collapsed on {string} wizard', async function (accordion, wizard) { + await isAccordionSectionCollapsed(this.driver, pageObjects[wizard][accordion]['Collapse_Button']) }) -Then('sort projects in ascending order', async function() { +Then('sort projects in ascending order', async function () { const upSorted = await isComponentContainsAttributeValue( this.driver, pageObjects['Projects']['Projects_Sorter'], @@ -897,21 +766,14 @@ Then('sort projects in ascending order', async function() { 'sort_up' ) if (!upSorted) { - await clickOnComponent( - this.driver, - pageObjects['Projects']['Projects_Sorter'] - ) + await clickOnComponent(this.driver, pageObjects['Projects']['Projects_Sorter']) } if (upSorted) { - await isTableColumnSorted( - this.driver, - pageObjects['Projects']['Projects_Table'], - 'name' - ) + await isTableColumnSorted(this.driver, pageObjects['Projects']['Projects_Table'], 'name') } }) -Then('sort projects in descending order', async function() { +Then('sort projects in descending order', async function () { const downSorted = await isComponentContainsAttributeValue( this.driver, pageObjects['Projects']['Projects_Sorter'], @@ -919,22 +781,14 @@ Then('sort projects in descending order', async function() { 'sort_down' ) if (!downSorted) { - await clickOnComponent( - this.driver, - pageObjects['Projects']['Projects_Sorter'] - ) + await clickOnComponent(this.driver, pageObjects['Projects']['Projects_Sorter']) } - await isTableColumnSorted( - this.driver, - pageObjects['Projects']['Projects_Table'], - 'name', - 'desc' - ) + await isTableColumnSorted(this.driver, pageObjects['Projects']['Projects_Table'], 'name', 'desc') }) Then( 'verify {string} tab is active in {string} on {string} wizard', - async function(tabName, tabSelector, wizard) { + async function (tabName, tabSelector, wizard) { const arr = await findRowIndexesByColumnValue( this.driver, pageObjects[wizard][tabSelector], @@ -942,14 +796,14 @@ Then( tabName ) const indx = arr[0] - + await isTabActive(this.driver, pageObjects[wizard][tabSelector], indx) } ) Then( 'verify {string} on {string} wizard should contains {string}.{string}', - async function(tabSelector, wizard, constWizard, constValue) { + async function (tabSelector, wizard, constWizard, constValue) { await checkTableColumnValues( this.driver, pageObjects[wizard][tabSelector], @@ -959,27 +813,26 @@ Then( } ) -When('select {string} tab in {string} on {string} wizard', async function( - tabName, - tabSelector, - wizard -) { - const arr = await findRowIndexesByColumnValue( - this.driver, - pageObjects[wizard][tabSelector], - 'key', - tabName - ) - const indx = arr[0] - await clickOnComponent( - this.driver, - pageObjects[wizard][tabSelector]['tableFields']['key'](indx) - ) -}) +When( + 'select {string} tab in {string} on {string} wizard', + async function (tabName, tabSelector, wizard) { + const arr = await findRowIndexesByColumnValue( + this.driver, + pageObjects[wizard][tabSelector], + 'key', + tabName + ) + const indx = arr[0] + await clickOnComponent( + this.driver, + pageObjects[wizard][tabSelector]['tableFields']['key'](indx) + ) + } +) Then( 'verify {string} on {string} wizard should display {string}.{string}', - async function(inputField, wizard, constStorage, constValue) { + async function (inputField, wizard, constStorage, constValue) { await checkHintText( this.driver, pageObjects[wizard][inputField], @@ -991,7 +844,7 @@ Then( Then( 'verify {string} on {string} wizard should display {string}.{string} in {string}', - async function(inputField, wizard, constStorage, constValue, commonTipType) { + async function (inputField, wizard, constStorage, constValue, commonTipType) { await checkHintText( this.driver, pageObjects[wizard][inputField], @@ -1003,7 +856,7 @@ Then( Then( 'verify {string} on {string} wizard should display options {string}.{string}', - async function(inputField, wizard, constStorage, constValue) { + async function (inputField, wizard, constStorage, constValue) { await checkWarningHintText( this.driver, pageObjects[wizard][inputField], @@ -1015,7 +868,7 @@ Then( Then( 'verify labels warning should display options {string}.{string}', - async function(constStorage, constValue) { + async function (constStorage, constValue) { await checkWarningText( this.driver, pageObjects['commonPagesHeader']['Common_Options'], @@ -1025,20 +878,20 @@ Then( ) Then( - 'verify {string} in {string} on {string} wizard should display options {string}.{string}', - async function(inputField, accordion, wizard, constStorage, constValue) { - await checkWarningHintText( - this.driver, - pageObjects[wizard][accordion][inputField], - pageObjects['commonPagesHeader']['Common_Options'], - pageObjectsConsts[constStorage][constValue] - ) - } + 'verify {string} in {string} on {string} wizard should display options {string}.{string}', + async function (inputField, accordion, wizard, constStorage, constValue) { + await checkWarningHintText( + this.driver, + pageObjects[wizard][accordion][inputField], + pageObjects['commonPagesHeader']['Common_Options'], + pageObjectsConsts[constStorage][constValue] + ) + } ) Then( 'verify {string} element in {string} on {string} wizard should display hint {string}.{string}', - async function(inputField, accordion, wizard, constStorage, constValue) { + async function (inputField, accordion, wizard, constStorage, constValue) { await checkHintText( this.driver, pageObjects[wizard][accordion][inputField], @@ -1050,17 +903,10 @@ Then( Then( 'verify {string} on {string} wizard should display warning {string}.{string}', - async function(input, wizard, constStorage, constValue) { - await clickOnComponent( - this.driver, - pageObjects[wizard][input]['inputField'] - ) - await this.driver.sleep(100) - await hoverComponent( - this.driver, - pageObjects[wizard][input]['inputField'], - false - ) + async function (input, wizard, constStorage, constValue) { + await clickOnComponent(this.driver, pageObjects[wizard][input]['inputField']) + await this.driver.sleep(DRIVER_SLEEP || 100) + await hoverComponent(this.driver, pageObjects[wizard][input]['inputField'], false) await checkWarningHintText( this.driver, pageObjects[wizard][input], @@ -1072,16 +918,10 @@ Then( Then( 'verify {string} element in {string} on {string} wizard should display warning {string}.{string}', - async function(input, accordion, wizard, constStorage, constValue) { - await clickOnComponent( - this.driver, - pageObjects[wizard][accordion][input]['inputField'] - ) - await this.driver.sleep(100) - await clickNearComponent( - this.driver, - pageObjects[wizard][accordion][input]['inputField'] - ) + async function (input, accordion, wizard, constStorage, constValue) { + await clickOnComponent(this.driver, pageObjects[wizard][accordion][input]['inputField']) + await this.driver.sleep(DRIVER_SLEEP || 100) + await clickNearComponent(this.driver, pageObjects[wizard][accordion][input]['inputField']) await checkWarningHintText( this.driver, pageObjects[wizard][accordion][input], @@ -1091,67 +931,47 @@ Then( } ) -When('check {string} element on {string} wizard', async function( - checkbox, - wizard -) { +When('check {string} element on {string} wizard', async function (checkbox, wizard) { await checkCheckbox(this.driver, pageObjects[wizard][checkbox]) }) -When('check {string} element in {string} on {string} wizard', async function( - checkbox, - accordion, - wizard -) { - await checkCheckbox(this.driver, pageObjects[wizard][accordion][checkbox]) -}) +When( + 'check {string} element in {string} on {string} wizard', + async function (checkbox, accordion, wizard) { + await checkCheckbox(this.driver, pageObjects[wizard][accordion][checkbox]) + } +) -When('uncheck {string} element in {string} on {string} wizard', async function( - checkbox, - accordion, - wizard -) { - await uncheckCheckbox(this.driver, pageObjects[wizard][accordion][checkbox]) -}) +When( + 'uncheck {string} element in {string} on {string} wizard', + async function (checkbox, accordion, wizard) { + await uncheckCheckbox(this.driver, pageObjects[wizard][accordion][checkbox]) + } +) -When('uncheck {string} element on {string} wizard', async function( - checkbox, - wizard -) { +When('uncheck {string} element on {string} wizard', async function (checkbox, wizard) { await uncheckCheckbox(this.driver, pageObjects[wizard][checkbox]) }) -Then('{string} element should be unchecked on {string} wizard', async function( - checkbox, - wizard -) { +Then('{string} element should be unchecked on {string} wizard', async function (checkbox, wizard) { await isCheckboxUnchecked(this.driver, pageObjects[wizard][checkbox]) }) Then( '{string} element should be unchecked in {string} on {string} wizard', - async function(checkbox, accordion, wizard) { - await isCheckboxUnchecked( - this.driver, - pageObjects[wizard][accordion][checkbox] - ) + async function (checkbox, accordion, wizard) { + await isCheckboxUnchecked(this.driver, pageObjects[wizard][accordion][checkbox]) } ) -Then('{string} element should be checked on {string} wizard', async function( - checkbox, - wizard -) { +Then('{string} element should be checked on {string} wizard', async function (checkbox, wizard) { await isCheckboxChecked(this.driver, pageObjects[wizard][checkbox]) }) Then( '{string} element should be checked in {string} on {string} wizard', - async function(checkbox, accordion, wizard) { - await isCheckboxChecked( - this.driver, - pageObjects[wizard][accordion][checkbox] - ) + async function (checkbox, accordion, wizard) { + await isCheckboxChecked(this.driver, pageObjects[wizard][accordion][checkbox]) } ) @@ -1160,54 +980,36 @@ When( async function (component, accordion, wizardName) { await waiteUntilComponent(this.driver, pageObjects[wizardName][accordion][component]) await clickOnComponent(this.driver, pageObjects[wizardName][accordion][component]) - await this.driver.sleep(250) + await this.driver.sleep(DRIVER_SLEEP || 250) } ) -Then('is {string} on {string} selected', async function(radiobutton, wizard) { +Then('is {string} on {string} selected', async function (radiobutton, wizard) { await isRadioButtonSelected(this.driver, pageObjects[wizard][radiobutton]) }) -Then('is {string} in {string} on {string} selected', async function( - radiobutton, - accordion, - wizard -) { - await isRadioButtonSelected( - this.driver, - pageObjects[wizard][accordion][radiobutton] - ) -}) +Then( + 'is {string} in {string} on {string} selected', + async function (radiobutton, accordion, wizard) { + await isRadioButtonSelected(this.driver, pageObjects[wizard][accordion][radiobutton]) + } +) -Then('is not {string} in {string} on {string} selected', async function( - radiobutton, - accordion, - wizard -) { - await isRadioButtonUnselected( - this.driver, - pageObjects[wizard][accordion][radiobutton] - ) -}) +Then( + 'is not {string} in {string} on {string} selected', + async function (radiobutton, accordion, wizard) { + await isRadioButtonUnselected(this.driver, pageObjects[wizard][accordion][radiobutton]) + } +) -When('select {string} in {string} on {string}', async function( - radiobutton, - accordion, - wizard -) { - await selectRadiobutton( - this.driver, - pageObjects[wizard][accordion][radiobutton] - ) +When('select {string} in {string} on {string}', async function (radiobutton, accordion, wizard) { + await selectRadiobutton(this.driver, pageObjects[wizard][accordion][radiobutton]) }) Then( 'verify options in {string} combobox in {string} on {string} wizard should contains {string}.{string}', - async function(combobox, accordion, wizard, constStorage, constValue) { - await openDropdown( - this.driver, - pageObjects[wizard][accordion][combobox]['dropdown'] - ) + async function (combobox, accordion, wizard, constStorage, constValue) { + await openDropdown(this.driver, pageObjects[wizard][accordion][combobox]['dropdown']) await checkDropdownOptions( this.driver, pageObjects[wizard][accordion][combobox]['dropdown'], @@ -1221,11 +1023,8 @@ Then( ) When( 'select {string} option in {string} combobox on {string} accordion on {string} wizard', - async function(option, comboBox, accordion, wizardName) { - await openDropdown( - this.driver, - pageObjects[wizardName][accordion][comboBox]['dropdown'] - ) + async function (option, comboBox, accordion, wizardName) { + await openDropdown(this.driver, pageObjects[wizardName][accordion][comboBox]['dropdown']) await selectOptionInDropdownWithoutCheck( this.driver, pageObjects[wizardName][accordion][comboBox]['dropdown'], @@ -1236,63 +1035,56 @@ When( When( 'select {string} option in {string} combobox suggestion on {string} accordion on {string} wizard', - async function(option, comboBox, accordion, wizard) { + async function (option, comboBox, accordion, wizard) { await selectOptionInDropdownWithoutCheck( this.driver, pageObjects[wizard][accordion][comboBox]['comboDropdown'], option ) - await this.driver.sleep(200) + await this.driver.sleep(DRIVER_SLEEP || 200) } ) Then( 'select {string} option in {string} suggestions dropdown on {string} wizard', - async function(option, dropdown, wizard) { - await selectOptionInDropdownWithoutCheck( - this.driver, - pageObjects[wizard][dropdown], - option - ) - await this.driver.sleep(200) + async function (option, dropdown, wizard) { + await selectOptionInDropdownWithoutCheck(this.driver, pageObjects[wizard][dropdown], option) + await this.driver.sleep(DRIVER_SLEEP || 200) } ) -Then('select {string} option in action menu on {string} wizard', async function( - option, - wizard -) { +Then('select {string} option in action menu on {string} wizard', async function (option, wizard) { const actionMenu = pageObjects[wizard]['Action_Menu'] await openActionMenu(this.driver, actionMenu) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) await selectOptionInActionMenu(this.driver, actionMenu, option) }) -Then('check that {string} option in action menu on {string} wizard is enabled', async function( - option, - wizard -) { - const actionMenu = pageObjects[wizard]['Action_Menu'] - await openActionMenu(this.driver, actionMenu) - await this.driver.sleep(500) - await verifyOptionInActionMenuEnabled (this.driver, actionMenu, option) -}) +Then( + 'check that {string} option in action menu on {string} wizard is enabled', + async function (option, wizard) { + const actionMenu = pageObjects[wizard]['Action_Menu'] + await openActionMenu(this.driver, actionMenu) + await this.driver.sleep(DRIVER_SLEEP || 500) + await verifyOptionInActionMenuEnabled(this.driver, actionMenu, option) + } +) -Then('check that {string} option in action menu on {string} wizard is disabled', async function( - option, - wizard -) { - const actionMenu = pageObjects[wizard]['Action_Menu'] - await openActionMenu(this.driver, actionMenu) - await this.driver.sleep(500) - await verifyOptionInActionMenuDisabled (this.driver, actionMenu, option) -}) +Then( + 'check that {string} option in action menu on {string} wizard is disabled', + async function (option, wizard) { + const actionMenu = pageObjects[wizard]['Action_Menu'] + await openActionMenu(this.driver, actionMenu) + await this.driver.sleep(DRIVER_SLEEP || 500) + await verifyOptionInActionMenuDisabled(this.driver, actionMenu, option) + } +) Then('check that {string} file is existed on {string} directory', async function (file, filePath) { const path = await generatePath(file, filePath) - await this.driver.sleep(150) + await this.driver.sleep(DRIVER_SLEEP || 150) await determineFileAccess(path, file) - await this.driver.sleep(150) + await this.driver.sleep(DRIVER_SLEEP || 150) }) Then( @@ -1307,10 +1099,7 @@ Then( } ) -Then('verify {string} options rules on {string} wizard', async function( - inputField, - wizardName -) { +Then('verify {string} options rules on {string} wizard', async function (inputField, wizardName) { await checkInputAccordingHintText( this.driver, this.attach, @@ -1319,36 +1108,36 @@ Then('verify {string} options rules on {string} wizard', async function( ) }) -Then('verify {string} options rules on form {string} wizard', async function( - inputField, - wizardName -) { - await checkInputAccordingHintText( - this.driver, - this.attach, - pageObjects[wizardName][inputField], - pageObjects['commonPagesHeader']['Common_Options'], - true - ) -}) +Then( + 'verify {string} options rules on form {string} wizard', + async function (inputField, wizardName) { + await checkInputAccordingHintText( + this.driver, + this.attach, + pageObjects[wizardName][inputField], + pageObjects['commonPagesHeader']['Common_Options'], + true + ) + } +) -Then('verify {string} options rules on {string} wizard with labels', async function( - inputField, - wizardName -) { - await checkInputAccordingHintText( - this.driver, - this.attach, - pageObjects[wizardName][inputField], - pageObjects['commonPagesHeader']['Common_Options'], - false, - true - ) -}) +Then( + 'verify {string} options rules on {string} wizard with labels', + async function (inputField, wizardName) { + await checkInputAccordingHintText( + this.driver, + this.attach, + pageObjects[wizardName][inputField], + pageObjects['commonPagesHeader']['Common_Options'], + false, + true + ) + } +) Then( 'verify breadcrumbs {string} label should be equal {string} value', - async function(labelType, value) { + async function (labelType, value) { await verifyText( this.driver, pageObjects['commonPagesHeader']['Breadcrumbs'][`${labelType}Label`], @@ -1359,20 +1148,16 @@ Then( Then( 'verify value should equal {string} in {string} on {string} wizard', - async function(value, componentName, wizardName) { - await verifyText( - this.driver, - pageObjects[wizardName][componentName]['label'], - value - ) + async function (value, componentName, wizardName) { + await verifyText(this.driver, pageObjects[wizardName][componentName]['label'], value) } ) Then( - 'verify {string} input should contains {string} value on {string} wizard', - async function (component, value, wizard) { - await verifyTypedValue(this.driver, pageObjects[wizard][component], value) - } + 'verify {string} input should contains {string} value on {string} wizard', + async function (component, value, wizard) { + await verifyTypedValue(this.driver, pageObjects[wizard][component], value) + } ) Then( @@ -1384,7 +1169,7 @@ Then( Then( 'verify value should equal {string}.{string} in {string} on {string} wizard', - async function(constStorage, constValue, componentName, wizardName) { + async function (constStorage, constValue, componentName, wizardName) { await verifyText( this.driver, pageObjects[wizardName][componentName]['label'], @@ -1393,14 +1178,8 @@ Then( } ) -Then('select {string} with {string} value in breadcrumbs menu', async function( - itemType, - name -) { - await openDropdown( - this.driver, - pageObjects['commonPagesHeader']['Breadcrumbs'][itemType] - ) +Then('select {string} with {string} value in breadcrumbs menu', async function (itemType, name) { + await openDropdown(this.driver, pageObjects['commonPagesHeader']['Breadcrumbs'][itemType]) await selectOptionInDropdown( this.driver, @@ -1411,50 +1190,32 @@ Then('select {string} with {string} value in breadcrumbs menu', async function( Then( 'verify arrow lines position on {string} on {string} wizard', - async function(graphName, wizardName) { - await checkNodesConnectionsNPandas( - this.driver, - pageObjects[wizardName][graphName] - ) + async function (graphName, wizardName) { + await checkNodesConnectionsNPandas(this.driver, pageObjects[wizardName][graphName]) } ) -When('hover {string} component on {string} wizard', async function( - componentName, - wizardName -) { - await hoverComponent( - this.driver, - pageObjects[wizardName][componentName], - true - ) +When('hover {string} component on {string} wizard', async function (componentName, wizardName) { + await hoverComponent(this.driver, pageObjects[wizardName][componentName], true) }) -When('scroll and hover {string} component on {string} wizard', async function( - componentName, - wizardName -) { - await hoverComponent( - this.driver, - pageObjects[wizardName][componentName], - true - ) -}) +When( + 'scroll and hover {string} component on {string} wizard', + async function (componentName, wizardName) { + await hoverComponent(this.driver, pageObjects[wizardName][componentName], true) + } +) When( 'scroll and hover {string} component in {string} on {string} wizard', - async function(componentName, accordionName, wizardName) { - await hoverComponent( - this.driver, - pageObjects[wizardName][accordionName][componentName], - true - ) + async function (componentName, accordionName, wizardName) { + await hoverComponent(this.driver, pageObjects[wizardName][accordionName][componentName], true) } ) When( 'click on node with name {string} in {string} graph on {string} wizard', - async function(nodeName, graphName, wizardName) { + async function (nodeName, graphName, wizardName) { const arr = await findRowIndexesByColumnValue( this.driver, pageObjects[wizardName][graphName]['nodesTable'], @@ -1469,35 +1230,33 @@ When( } ) -Then('{string} on {string} wizard should be {string}', async function( - componentName, - wizardName, - state -) { - await verifyComponentContainsAttributeValue( - this.driver, - pageObjects[wizardName][componentName], - 'class', - state - ) -}) +Then( + '{string} on {string} wizard should be {string}', + async function (componentName, wizardName, state) { + await verifyComponentContainsAttributeValue( + this.driver, + pageObjects[wizardName][componentName], + 'class', + state + ) + } +) -Then('{string} on {string} wizard should not be {string}', async function( - componentName, - wizardName, - state -) { - await verifyComponentNotContainsAttributeValue( - this.driver, - pageObjects[wizardName][componentName], - 'class', - state - ) -}) +Then( + '{string} on {string} wizard should not be {string}', + async function (componentName, wizardName, state) { + await verifyComponentNotContainsAttributeValue( + this.driver, + pageObjects[wizardName][componentName], + 'class', + state + ) + } +) Then( 'compare {string} element value on {string} wizard with test {string} context value', - async function(componentName, wizardName, fieldName) { + async function (componentName, wizardName, fieldName) { await verifyText( this.driver, pageObjects[wizardName][componentName], @@ -1507,25 +1266,20 @@ Then( ) Then( - 'compare {string} element value in {string} on {string} wizard with test {string} context value', - async function(componentName, accordionName, wizardName, fieldName) { - await verifyText( - this.driver, - pageObjects[wizardName][accordionName][componentName], - this.testContext[fieldName] - ) - } -) - -Then( - 'compare current browser URL with test {string} context value', - async function(savedValue) { - expect(await this.driver.getCurrentUrl()).equal( - this.testContext[savedValue] + 'compare {string} element value in {string} on {string} wizard with test {string} context value', + async function (componentName, accordionName, wizardName, fieldName) { + await verifyText( + this.driver, + pageObjects[wizardName][accordionName][componentName], + this.testContext[fieldName] ) } ) +Then('compare current browser URL with test {string} context value', async function (savedValue) { + expect(await this.driver.getCurrentUrl()).equal(this.testContext[savedValue]) +}) + Then( 'check {string} textarea counter on {string} wizard', async function (componentName, wizardName) { diff --git a/tests/features/support/world.js b/tests/features/support/world.js index 77f41a7e7..a92b09845 100644 --- a/tests/features/support/world.js +++ b/tests/features/support/world.js @@ -40,22 +40,23 @@ class CustomWorld extends World { let browseConfigs - //browseConfigs = new chrome.Options().windowSize(screen_size) - can be used to define a specific screen size if (browser === 'chrome') { + browseConfigs = new chrome.Options() if (headless) { - browseConfigs = new chrome.Options() - .headless() - .addArguments('no-sandbox') - .addArguments('start-maximized') - .addArguments('disable-gpu') - } else browseConfigs = new chrome.Options() - .addArguments('start-maximized') - .excludeSwitches('disable-popup-blocking', 'enable-automation') + browseConfigs.addArguments('headless') + browseConfigs.addArguments('no-sandbox') + browseConfigs.addArguments('disable-gpu') + } + browseConfigs.addArguments('start-maximized') + browseConfigs.excludeSwitches('disable-popup-blocking', 'enable-automation') } + if (browser === 'firefox') { + browseConfigs = new firefox.Options() if (headless) { - browseConfigs = new firefox.Options().headless().windowSize(screen_size) - } else browseConfigs = new firefox.Options().windowSize(screen_size) + browseConfigs.addArguments('-headless') + } + browseConfigs.windowSize(screen_size) } this.driver = new seleniumWebdriver.Builder() From 1b5667598395e814ecc2f05f116e21b7f63a5bd9 Mon Sep 17 00:00:00 2001 From: Pini Shahmurov Date: Thu, 4 Jul 2024 14:10:45 +0300 Subject: [PATCH 28/54] add scripts test for ci-cd flow (#2575) --- package.json | 6 +- .../appendCommentToHttpClient.js | 34 + scripts/ci-cd-scripts/commentedHttpClient.js | 195 ++++ scripts/ci-cd-scripts/tempSmoke1Test.js | 28 + scripts/testui.js | 51 +- tests/config.js | 8 +- tests/features/artifacts.feature | 21 +- tests/features/step-definitions/steps.js | 980 +++++++----------- tests/features/support/world.js | 23 +- 9 files changed, 686 insertions(+), 660 deletions(-) create mode 100644 scripts/ci-cd-scripts/appendCommentToHttpClient.js create mode 100644 scripts/ci-cd-scripts/commentedHttpClient.js create mode 100644 scripts/ci-cd-scripts/tempSmoke1Test.js diff --git a/package.json b/package.json index d0a50ff4f..68e4cc646 100644 --- a/package.json +++ b/package.json @@ -70,9 +70,11 @@ "build-storybook": "build-storybook", "mock-server": "node scripts/mockServer.js", "mock-server:dev": "nodemon --watch tests/mockServer scripts/mockServer.js", + "add-comment-to-http-client": "node scripts/ci-cd-scripts/appendCommentToHttpClient.js", + "test:ci-cd-smoke-1": "DRIVER_SLEEP=5000 HEADLESS=true node scripts/ci-cd-scripts/tempSmoke1Test.js", "test:ui": "node scripts/testui.js", "report": "node tests/report.js", - "test:regression": "npm run test:ui && npm run report", + "test:regression": "DRIVER_SLEEP=5000 HEADLESS=true npm run test:ui && npm run report", "start:regression": "concurrently \"npm:mock-server\" \"npm:start\" \"npm:test:regression\"", "ui-steps": "export BABEL_ENV=test; export NODE_ENV=test; npx -p @babel/core -p @babel/node babel-node --presets @babel/preset-env scripts/collectUITestsSteps.js", "nli": "npm link iguazio.dashboard-react-controls", @@ -128,7 +130,7 @@ "body-parser": "^1.19.0", "case-sensitive-paths-webpack-plugin": "^2.4.0", "chai": "^4.3.4", - "chromedriver": "^124.0.0", + "chromedriver": "^126.0.0", "css-loader": "^6.5.1", "cucumber-html-reporter": "^5.3.0", "eslint": "^8.57.0", diff --git a/scripts/ci-cd-scripts/appendCommentToHttpClient.js b/scripts/ci-cd-scripts/appendCommentToHttpClient.js new file mode 100644 index 000000000..405b872fc --- /dev/null +++ b/scripts/ci-cd-scripts/appendCommentToHttpClient.js @@ -0,0 +1,34 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +const fs = require('fs') +const path = require('path') + +const sourceFilePath = path.join(__dirname, 'commentedHttpClient.js') +const targetFilePath = path.join(__dirname, '../../src/httpClient.js') + +try { + const fileContent = fs.readFileSync(sourceFilePath, 'utf-8') + fs.writeFileSync(targetFilePath, fileContent) + + console.log(`Successfully overwritten ${targetFilePath} with content from ${sourceFilePath}`) +} catch (err) { + console.error(`Error occurred: ${err.message}`) + process.exit(1) +} diff --git a/scripts/ci-cd-scripts/commentedHttpClient.js b/scripts/ci-cd-scripts/commentedHttpClient.js new file mode 100644 index 000000000..4e51b7039 --- /dev/null +++ b/scripts/ci-cd-scripts/commentedHttpClient.js @@ -0,0 +1,195 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import axios from 'axios' +import qs from 'qs' + +// import { ConfirmDialog } from 'igz-controls/components' +import { + CANCEL_REQUEST_TIMEOUT, + LARGE_REQUEST_CANCELED + // PROJECTS_PAGE_PATH +} from './constants' +// import { openPopUp } from 'igz-controls/utils/common.util' +// import { mlrunUnhealthyErrors } from './components/ProjectsPage/projects.util' + +const headers = { + 'Cache-Control': 'no-cache' +} + +console.log('----------- comments ----------') +// serialize a param with an array value as a repeated param, for example: +// { label: ['host', 'owner=admin'] } => 'label=host&label=owner%3Dadmin' +const paramsSerializer = params => qs.stringify(params, { arrayFormat: 'repeat' }) + +// const MAX_CONSECUTIVE_ERRORS_COUNT = 2 +// let consecutiveErrorsCount = 0 + +export const mainBaseUrl = `${process.env.PUBLIC_URL}/api/v1` +export const mainBaseUrlV2 = `${process.env.PUBLIC_URL}/api/v2` + +export const mainHttpClient = axios.create({ + baseURL: mainBaseUrl, + headers, + paramsSerializer +}) + +export const mainHttpClientV2 = axios.create({ + baseURL: mainBaseUrlV2, + headers, + paramsSerializer +}) + +export const functionTemplatesHttpClient = axios.create({ + baseURL: `${process.env.PUBLIC_URL}/function-catalog`, + headers +}) + +export const nuclioHttpClient = axios.create({ + baseURL: `${process.env.PUBLIC_URL}/nuclio/api`, + headers +}) + +export const iguazioHttpClient = axios.create({ + baseURL: process.env.NODE_ENV === 'production' ? '/api' : '/iguazio/api', + headers +}) + +const getAbortSignal = (controller, abortCallback, timeoutMs) => { + let timeoutId = null + const newController = new AbortController() + const abortController = controller || newController + + if (timeoutMs) { + timeoutId = setTimeout(() => abortController.abort(LARGE_REQUEST_CANCELED), timeoutMs) + } + + abortController.signal.onabort = event => { + if (timeoutId) { + clearTimeout(timeoutId) + } + + if (abortCallback) { + abortCallback(event) + } + } + + return [abortController.signal, timeoutId] +} + +let requestId = 1 +let requestTimeouts = {} +let largeResponsePopUpIsOpen = false + +const requestLargeDataOnFulfill = config => { + if (config?.ui?.setLargeRequestErrorMessage) { + const [signal, timeoutId] = getAbortSignal( + config.ui?.controller, + abortEvent => { + if (abortEvent.target.reason === LARGE_REQUEST_CANCELED) { + showLargeResponsePopUp(config.ui.setLargeRequestErrorMessage) + } + }, + CANCEL_REQUEST_TIMEOUT + ) + + config.signal = signal + + requestTimeouts[requestId] = timeoutId + config.ui.requestId = requestId + requestId++ + } + + return config +} +const requestLargeDataOnReject = error => { + return Promise.reject(error) +} +const responseFulfillInterceptor = response => { + // consecutiveErrorsCount = 0 + + if (response.config?.ui?.requestId) { + const isLargeResponse = + response.data?.total_size >= 0 + ? response.data.total_size > 10000 + : Object.values(response.data)?.[0]?.length > 10000 + + clearTimeout(requestTimeouts[response.config.ui.requestId]) + delete requestTimeouts[response.config.ui.requestId] + + if (isLargeResponse) { + showLargeResponsePopUp(response.config.ui.setLargeRequestErrorMessage) + + throw new Error(LARGE_REQUEST_CANCELED) + } else { + response.config.ui.setLargeRequestErrorMessage('') + } + } + + return response +} +const responseRejectInterceptor = error => { + if (error.config?.ui?.requestId) { + clearTimeout(requestTimeouts[error.config.ui.requestId]) + delete requestTimeouts[error.config.ui.requestId] + } + + // if (error.config?.method === 'get') { + // if ( + // mlrunUnhealthyErrors.includes(error.response?.status) && + // consecutiveErrorsCount < MAX_CONSECUTIVE_ERRORS_COUNT + // ) { + // consecutiveErrorsCount++ + // + // if ( + // consecutiveErrorsCount === MAX_CONSECUTIVE_ERRORS_COUNT && + // window.location.pathname !== `/${PROJECTS_PAGE_PATH}` + // ) { + // window.location.href = '/projects' + // } + // } + // } + + return Promise.reject(error) +} + +// Request interceptors +mainHttpClient.interceptors.request.use(requestLargeDataOnFulfill, requestLargeDataOnReject) +mainHttpClientV2.interceptors.request.use(requestLargeDataOnFulfill, requestLargeDataOnReject) + +// Response interceptors +mainHttpClient.interceptors.response.use(responseFulfillInterceptor, responseRejectInterceptor) +mainHttpClientV2.interceptors.response.use(responseFulfillInterceptor, responseRejectInterceptor) + +export const showLargeResponsePopUp = setLargeRequestErrorMessage => { + if (!largeResponsePopUpIsOpen) { + const errorMessage = + 'The query result is too large to display. Add a filter (or narrow it) to retrieve fewer results.' + + setLargeRequestErrorMessage(errorMessage) + largeResponsePopUpIsOpen = true + + // openPopUp(ConfirmDialog, { + // message: errorMessage, + // closePopUp: () => { + // largeResponsePopUpIsOpen = false + // } + // }) + } +} diff --git a/scripts/ci-cd-scripts/tempSmoke1Test.js b/scripts/ci-cd-scripts/tempSmoke1Test.js new file mode 100644 index 000000000..d8aa27c17 --- /dev/null +++ b/scripts/ci-cd-scripts/tempSmoke1Test.js @@ -0,0 +1,28 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +const { execSync } = require('child_process') + +const cucumberCommand = + "./node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t '@smoke1'" +try { + execSync(cucumberCommand, { stdio: 'inherit' }) +} catch (err) { + console.log(err.message) +} diff --git a/scripts/testui.js b/scripts/testui.js index 7d931679b..b7f17bbc7 100644 --- a/scripts/testui.js +++ b/scripts/testui.js @@ -17,48 +17,53 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -'use strict'; -const { report } = require('../tests/config'); -const fs = require('fs'); +const { report } = require('../tests/config') +const fs = require('fs') // Do this as the first thing so that any code reading it knows the right env. -process.env.BABEL_ENV = 'test'; -process.env.NODE_ENV = 'test'; +process.env.BABEL_ENV = 'test' +process.env.NODE_ENV = 'test' // Makes the script crash on unhandled rejections instead of silently // ignoring them. In the future, promise rejections that are not handled will // terminate the Node.js process with a non-zero exit code. process.on('unhandledRejection', err => { - throw err; -}); + throw err +}) // Ensure environment variables are read. -require('../config/env'); +require('../config/env') -const execSync = require('child_process').execSync; -const argv = process.argv.slice(2); +const execSync = require('child_process').execSync +const argv = process.argv.slice(2) // build cucumber executive command -const cucumberCommand = 'cucumber-js --require-module @babel/register --require-module @babel/polyfill ' + - '-f json:' + report + '.json -f html:' + report +'_default.html tests ' + - argv.join(' '); +const cucumberCommand = + 'cucumber-js --require-module @babel/register --require-module @babel/polyfill ' + + '-f json:' + + report + + '.json -f html:' + + report + + '_default.html tests ' + + argv.join(' ') + + '-t @smoke' // check and create report folder -const reportDir = report.split('/').slice(0, -1).join('/'); -console.log(reportDir); +const reportDir = report.split('/').slice(0, -1).join('/') +console.log(reportDir) if (!fs.existsSync(reportDir)) { - fs.mkdirSync(reportDir); + fs.mkdirSync(reportDir) } function runCrossPlatform() { - try { - execSync(cucumberCommand); - return true; - } catch (e) { - return false; - } + try { + execSync(cucumberCommand) + return true + } catch (e) { + return false + } } // cucumber -runCrossPlatform(); +runCrossPlatform() diff --git a/tests/config.js b/tests/config.js index 1d98a1409..987df470b 100644 --- a/tests/config.js +++ b/tests/config.js @@ -17,10 +17,16 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ + +const HEADLESS = process.env.HEADLESS === 'true' || false + +/* eslint-disable-next-line no-console */ +console.log(`DRIVER_SLEEP: ${HEADLESS}`) + module.exports = { timeout: 60000, browser: 'chrome', - headless: false, + headless: HEADLESS, screen_size: { width: 1600, height: 900 }, report: 'tests/reports/cucumber_report', test_url: 'localhost', diff --git a/tests/features/artifacts.feature b/tests/features/artifacts.feature index 5447c29d8..1e0e7ff91 100644 --- a/tests/features/artifacts.feature +++ b/tests/features/artifacts.feature @@ -5,6 +5,7 @@ Feature: Files Page @MLA @passive @smoke + @smoke1 Scenario: MLA001 - Check all mandatory components on Artifacts tab Given open url And wait load page @@ -354,7 +355,7 @@ Feature: Files Page When click on cell with value "survival-curves_km-survival" in "name" column in "Files_Table" table on "Files" wizard Then select "Preview" tab in "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard And wait load page - Then verify "Pop_Out_Button" element visibility on "Files_Info_Pane" wizard + Then verify "Pop_Out_Button" element visibility on "Files_Info_Pane" wizard Then click on "Pop_Out_Button" element on "Files_Info_Pane" wizard And wait load page Then verify "Preview_Row" element visibility on "Artifact_Preview_Popup" wizard @@ -377,7 +378,7 @@ Feature: Files Page @MLA @smoke - Scenario: MLA022 - Verify the Delete option state in Artifacts table and Overview details action menu + Scenario: MLA022 - Verify the Delete option state in Artifacts table and Overview details action menu Given open url And wait load page And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard @@ -514,7 +515,7 @@ Feature: Files Page Then "Header_Download_Pop_Up" element on "Downloads_Popup" should contains "Downloads" value Then click on "Download_Pop_Up_Cross_Cancel_Button" element on "Downloads_Popup" wizard Then click on "Cross_Cancel_Button" element on "Preview_Popup" wizard - + @MLA @passive @smoke @@ -534,7 +535,7 @@ Feature: Files Page Then verify "Cross_Cancel_Button" element visibility on "View_YAML" wizard Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard - @MLA + @MLA @smoke Scenario: MLA020 - Check broken link redirection Given open url @@ -578,8 +579,8 @@ Feature: Files Page Then compare "Header" element value on "Files_Info_Pane" wizard with test "name" context value Then verify that row index 1 is active in "Files_Table" table on "Files" wizard Then verify that row index 2 is NOT active in "Files_Table" table on "Files" wizard - Then click on cell with row index 2 in "name" column in "Files_Table" table on "Files" wizard - Then verify that row index 2 is active in "Files_Table" table on "Files" wizard + Then click on cell with row index 2 in "name" column in "Files_Table" table on "Files" wizard + Then verify that row index 2 is active in "Files_Table" table on "Files" wizard Then verify that row index 1 is NOT active in "Files_Table" table on "Files" wizard Then verify "Info_Pane_Tab_Selector" element visibility on "Files_Info_Pane" wizard Then verify "Overview" tab is active in "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard @@ -603,12 +604,12 @@ Feature: Files Page Then verify "Overview" tab is active in "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard Then verify "Overview_General_Headers" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Overview_General_Headers" Then check "latest" value in "tag" column in "Overview_Table" table on "Files_Info_Pane" wizard - Then click on "Edit_btn_table_view" element on "Files_Info_Pane" wizard + Then click on "Edit_btn_table_view" element on "Files_Info_Pane" wizard Then verify "Version_tag_Input_table_view" on "Files_Info_Pane" wizard should contains "latest" value Then click on "Full_View_Button" element on "Files_Info_Pane" wizard Then verify "Cross_Close_Button" element not exists on "Files_Info_Pane" wizard Then click on "Edit_btn_full_view" element on "Files_Info_Pane" wizard - Then verify "Version_tag_Input_full_view" on "Files_Info_Pane" wizard should contains "latest" value + Then verify "Version_tag_Input_full_view" on "Files_Info_Pane" wizard should contains "latest" value Then click on "Tabel_View_Button" element on "Files_Info_Pane" wizard Then verify "Cross_Close_Button" element visibility on "Files_Info_Pane" wizard @@ -653,7 +654,7 @@ Feature: Files Page When click on cell with row index 1 in "name" column in "Files_Table" table on "Files" wizard Then verify "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Tab_List" Then verify "Overview" tab is active in "Info_Pane_Tab_Selector" on "Files_Info_Pane" wizard - Then verify "Overview_General_Headers" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Overview_General_Headers" + Then verify "Overview_General_Headers" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Overview_General_Headers" Then check "latest" value in "tag" column in "Overview_Table" table on "Files_Info_Pane" wizard Then click on "Edit_btn_table_view" element on "Files_Info_Pane" wizard Then type value "" to "Version_tag_Input" field on "Files_Info_Pane" wizard @@ -696,4 +697,4 @@ Feature: Files Page Then verify "Overview_General_Headers" on "Files_Info_Pane" wizard should contains "Files_Info_Pane"."Overview_General_Headers" Then check "latest123456" value in "tag" column in "Overview_Table" table on "Files_Info_Pane" wizard Then save to context "name" column on 3 row from "Files_Table" table on "Files" wizard - Then compare "Header" element value on "Files_Info_Pane" wizard with test "name" context value \ No newline at end of file + Then compare "Header" element value on "Files_Info_Pane" wizard with test "name" context value diff --git a/tests/features/step-definitions/steps.js b/tests/features/step-definitions/steps.js index eafc0e148..16fa6c759 100644 --- a/tests/features/step-definitions/steps.js +++ b/tests/features/step-definitions/steps.js @@ -105,15 +105,23 @@ import { isRadioButtonUnselected, selectRadiobutton } from '../common/actions/radio-button.action' -import { - openActionMenu, +import { + openActionMenu, selectOptionInActionMenu, verifyOptionInActionMenuEnabled, - verifyOptionInActionMenuDisabled + verifyOptionInActionMenuDisabled } from '../common/actions/action-menu.action' import { expect } from 'chai' +import { toInteger } from 'lodash' + +require('dotenv').config() + +const DRIVER_SLEEP = toInteger(process.env.DRIVER_SLEEP) -Given('open url', async function() { +/* eslint-disable-next-line no-console */ +console.log(`DRIVER_SLEEP: ${DRIVER_SLEEP}`) + +Given('open url', async function () { await navigateToPage(this.driver, `http://${test_url}:${test_port}`) }) @@ -132,29 +140,26 @@ Given('open url', async function() { */ -When('turn on demo mode', async function() { +When('turn on demo mode', async function () { const url = await this.driver.getCurrentUrl() await navigateToPage(this.driver, `${url}?mode=demo`) }) -When('turn on staging mode', async function() { +When('turn on staging mode', async function () { const url = await this.driver.getCurrentUrl() await navigateToPage(this.driver, `${url}?mode=staging`) }) -Then('turn Off MLRun CE mode', async function() { - await this.driver.executeScript(function() { +Then('turn Off MLRun CE mode', async function () { + await this.driver.executeScript(function () { localStorage.setItem('igzFullVersion', '3.5.5') }) -}) +}) -Then('additionally redirect by INVALID-TAB', async function() { +Then('additionally redirect by INVALID-TAB', async function () { const beforeURL = await this.driver.getCurrentUrl() const urlNodesArr = beforeURL.split('/') - const invalidTab = beforeURL.replace( - urlNodesArr[urlNodesArr.length - 1], - 'INVALID-TAB' - ) + const invalidTab = beforeURL.replace(urlNodesArr[urlNodesArr.length - 1], 'INVALID-TAB') await navigateToPage(this.driver, `${invalidTab}`) const afterURL = await this.driver.getCurrentUrl() expect(beforeURL).equal( @@ -163,114 +168,101 @@ Then('additionally redirect by INVALID-TAB', async function() { ) }) -Then( - 'verify redirection from {string} to {string}', - async function (invalidPath, expectedPath) { - const invalidUrl = `http://${test_url}:${test_port}/${invalidPath}` - const expectedUrl = `http://${test_url}:${test_port}/${expectedPath}` +Then('verify redirection from {string} to {string}', async function (invalidPath, expectedPath) { + const invalidUrl = `http://${test_url}:${test_port}/${invalidPath}` + const expectedUrl = `http://${test_url}:${test_port}/${expectedPath}` - await navigateToPage(this.driver, invalidUrl) - await this.driver.sleep(250) - const afterURL = await this.driver.getCurrentUrl() + await navigateToPage(this.driver, invalidUrl) + await this.driver.sleep(DRIVER_SLEEP || 250) + const afterURL = await this.driver.getCurrentUrl() - expect(expectedUrl).equal( - afterURL, - `Redirection from "${invalidUrl}"\nshould be "${expectedUrl}"\nbut is "${afterURL}"` - ) - } -) + expect(expectedUrl).equal( + afterURL, + `Redirection from "${invalidUrl}"\nshould be "${expectedUrl}"\nbut is "${afterURL}"` + ) +}) -Then( - 'verify redirection to {string}', - async function (expectedPath) { - const expectedUrl = `http://${test_url}:${test_port}/${expectedPath}` - const afterURL = await this.driver.getCurrentUrl() +Then('verify redirection to {string}', async function (expectedPath) { + const expectedUrl = `http://${test_url}:${test_port}/${expectedPath}` + const afterURL = await this.driver.getCurrentUrl() - expect(expectedUrl).equal( - afterURL, - `Redirection should be "${expectedUrl}"\nbut is "${afterURL}"` - ) - } -) + expect(expectedUrl).equal( + afterURL, + `Redirection should be "${expectedUrl}"\nbut is "${afterURL}"` + ) +}) -Then('wait load page', async function() { +Then('wait load page', async function () { + console.log(DRIVER_SLEEP) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) }) -Then('navigate forward', async function() { +Then('navigate forward', async function () { await navigateForward(this.driver) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) }) -Then('navigate back', async function() { +Then('navigate back', async function () { await navigateBack(this.driver) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) }) -Then('refresh a page', async function() { +Then('refresh a page', async function () { await refreshPage(this.driver) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) - await this.driver.sleep(250) + await this.driver.sleep(DRIVER_SLEEP || 250) }) -Then('click on {string} element on {string} wizard', async function( - component, - wizard -) { +Then('click on {string} element on {string} wizard', async function (component, wizard) { await waiteUntilComponent(this.driver, pageObjects[wizard][component]) await clickOnComponent(this.driver, pageObjects[wizard][component]) - await this.driver.sleep(250) + await this.driver.sleep(DRIVER_SLEEP || 250) }) -Then('click on breadcrumbs {string} label on {string} wizard', async function( - labelType, - wizard -) { +Then('click on breadcrumbs {string} label on {string} wizard', async function (labelType, wizard) { await waiteUntilComponent(this.driver, pageObjects[wizard]['Breadcrumbs'][`${labelType}Label`]) await clickOnComponent(this.driver, pageObjects[wizard]['Breadcrumbs'][`${labelType}Label`]) - await this.driver.sleep(250) + await this.driver.sleep(DRIVER_SLEEP || 250) }) -Then('verify if {string} popup dialog appears', async function(popup) { +Then('verify if {string} popup dialog appears', async function (popup) { await waiteUntilComponent(this.driver, pageObjects[popup]['Title']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) await componentIsPresent(this.driver, pageObjects[popup]['Title']) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) await componentIsVisible(this.driver, pageObjects[popup]['Title']) }) Then( 'type into {string} on {string} popup dialog {string} value', - async function(component, wizard, value) { + async function (component, wizard, value) { await typeValue(this.driver, pageObjects[wizard][component], value) await verifyTypedValue(this.driver, pageObjects[wizard][component], value) } ) -Then('type value {string} to {string} field on {string} wizard', async function( - value, - inputField, - wizard -) { - await typeValue(this.driver, pageObjects[wizard][inputField], value) - await this.driver.sleep(250) - await verifyTypedValue(this.driver, pageObjects[wizard][inputField], value) - await this.driver.sleep(250) -}) +Then( + 'type value {string} to {string} field on {string} wizard', + async function (value, inputField, wizard) { + await typeValue(this.driver, pageObjects[wizard][inputField], value) + await this.driver.sleep(DRIVER_SLEEP || 250) + await verifyTypedValue(this.driver, pageObjects[wizard][inputField], value) + await this.driver.sleep(DRIVER_SLEEP || 250) + } +) -Then('type value {string} to {string} field on {string} wizard without inputgroup', async function( - value, - inputField, - wizard -) { - await typeValueWithoutInputgroup(this.driver, pageObjects[wizard][inputField], value) - await this.driver.sleep(250) - await verifyTypedValueWithoutInputgroup(this.driver, pageObjects[wizard][inputField], value) - await this.driver.sleep(250) -}) +Then( + 'type value {string} to {string} field on {string} wizard without inputgroup', + async function (value, inputField, wizard) { + await typeValueWithoutInputgroup(this.driver, pageObjects[wizard][inputField], value) + await this.driver.sleep(DRIVER_SLEEP || 250) + await verifyTypedValueWithoutInputgroup(this.driver, pageObjects[wizard][inputField], value) + await this.driver.sleep(DRIVER_SLEEP || 250) + } +) Then( 'verify {string} element on {string} wizard is enabled', @@ -345,74 +337,58 @@ Then( Then( 'verify checkbox {string} element in {string} on {string} wizard is disabled', async function (elementName, accordionName, wizardName) { - await verifyCheckboxDisabled(this.driver, pageObjects[wizardName][accordionName][elementName].root) + await verifyCheckboxDisabled( + this.driver, + pageObjects[wizardName][accordionName][elementName].root + ) } ) Then( 'verify {string} element in {string} on {string} wizard is enabled', - async function(inputField, accordionName, wizardName) { - await verifyInputEnabled( - this.driver, - pageObjects[wizardName][accordionName][inputField] - ) + async function (inputField, accordionName, wizardName) { + await verifyInputEnabled(this.driver, pageObjects[wizardName][accordionName][inputField]) } ) Then( 'verify {string} element in {string} on {string} wizard is enabled by class name', - async function(inputField, accordionName, wizardName) { - await verifyInputClassEnabled( - this.driver, - pageObjects[wizardName][accordionName][inputField] - ) + async function (inputField, accordionName, wizardName) { + await verifyInputClassEnabled(this.driver, pageObjects[wizardName][accordionName][inputField]) } ) Then( 'verify {string} element in {string} on {string} wizard is disabled', - async function(inputField, accordionName, wizardName) { - await verifyInputDisabled( - this.driver, - pageObjects[wizardName][accordionName][inputField] - ) + async function (inputField, accordionName, wizardName) { + await verifyInputDisabled(this.driver, pageObjects[wizardName][accordionName][inputField]) } ) Then( 'verify {string} element in {string} on {string} wizard is disabled by class name', - async function(inputField, accordionName, wizardName) { - await verifyInputClassDisabled( - this.driver, - pageObjects[wizardName][accordionName][inputField] - ) + async function (inputField, accordionName, wizardName) { + await verifyInputClassDisabled(this.driver, pageObjects[wizardName][accordionName][inputField]) } ) Then( 'verify {string} element on {string} wizard is disabled by class name', - async function(inputField, wizardName) { - await verifyClassDisabled( - this.driver, - pageObjects[wizardName][inputField] - ) + async function (inputField, wizardName) { + await verifyClassDisabled(this.driver, pageObjects[wizardName][inputField]) } ) When( 'type searchable fragment {string} into {string} on {string} wizard', - async function(subName, inputGroup, wizard) { - await typeSearchableValue( - this.driver, - pageObjects[wizard][inputGroup], - subName - ) + async function (subName, inputGroup, wizard) { + await typeSearchableValue(this.driver, pageObjects[wizard][inputGroup], subName) } ) When( 'type searchable fragment {string} into {string} combobox input in {string} on {string} wizard', - async function(subName, combobox, accordion, wizard) { + async function (subName, combobox, accordion, wizard) { await typeSearchableValue( this.driver, pageObjects[wizard][accordion][combobox]['comboDropdown'], @@ -423,8 +399,8 @@ When( Then( 'searchable case {string} fragment {string} should be in every suggested option into {string} on {string} wizard', - async function(textCase, subName, inputGroup, wizard) { - await this.driver.sleep(1000) + async function (textCase, subName, inputGroup, wizard) { + await this.driver.sleep(DRIVER_SLEEP || 1000) await isContainsSubstringInSuggestedOptions( this.driver, pageObjects[wizard][inputGroup], @@ -436,8 +412,8 @@ Then( Then( 'searchable fragment {string} should be in every suggested option into {string} combobox input in {string} on {string} wizard', - async function(subName, combobox, accordion, wizard) { - await this.driver.sleep(200) + async function (subName, combobox, accordion, wizard) { + await this.driver.sleep(DRIVER_SLEEP || 200) await isContainsSubstringInSuggestedOptions( this.driver, pageObjects[wizard][accordion][combobox]['comboDropdown'], @@ -448,17 +424,10 @@ Then( Then( 'increase value on {int} points in {string} field on {string} on {string} wizard', - async function(value, inputField, accordion, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][accordion][inputField] - ) + async function (value, inputField, accordion, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][accordion][inputField]) const result = Number.parseInt(txt) + value - await incrementValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) + await incrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) await verifyTypedValue( this.driver, pageObjects[wizard][accordion][inputField], @@ -469,63 +438,38 @@ Then( Then( 'increase value on {int} points in {string} field on {string} wizard', - async function(value, inputField, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][inputField] - ) + async function (value, inputField, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][inputField]) const result = Number.parseInt(txt) + value - await incrementValue( - this.driver, - pageObjects[wizard][inputField], - value - ) - await verifyTypedValue( - this.driver, - pageObjects[wizard][inputField], - result.toString() - ) + await incrementValue(this.driver, pageObjects[wizard][inputField], value) + await verifyTypedValue(this.driver, pageObjects[wizard][inputField], result.toString()) } ) Then( - 'increase value on {int} points in {string} field with {string} on {string} on {string} wizard', - async function(value, inputField, unit, accordion, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][accordion][inputField] - ) - const unitValue = unit === 'cpu' ? value / 1000 : unit === 'millicpu' ? value * 100 : value - let result = Number.parseFloat(txt || '0') + unitValue - if (unit === 'cpu') { - return result.toFixed(3) - } - await incrementValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) - await verifyTypedValue( - this.driver, - pageObjects[wizard][accordion][inputField], - result.toString() - ) + 'increase value on {int} points in {string} field with {string} on {string} on {string} wizard', + async function (value, inputField, unit, accordion, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][accordion][inputField]) + const unitValue = unit === 'cpu' ? value / 1000 : unit === 'millicpu' ? value * 100 : value + let result = Number.parseFloat(txt || '0') + unitValue + if (unit === 'cpu') { + return result.toFixed(3) } + await incrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) + await verifyTypedValue( + this.driver, + pageObjects[wizard][accordion][inputField], + result.toString() + ) + } ) Then( 'decrease value on {int} points in {string} field on {string} on {string} wizard', - async function(value, inputField, accordion, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][accordion][inputField] - ) + async function (value, inputField, accordion, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][accordion][inputField]) const result = Number.parseInt(txt) - value - await decrementValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) + await decrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) await verifyTypedValue( this.driver, pageObjects[wizard][accordion][inputField], @@ -536,22 +480,11 @@ Then( Then( 'decrease value on {int} points in {string} field on {string} wizard', - async function(value, inputField, wizard) { - const txt = await getInputValue( - this.driver, - pageObjects[wizard][inputField] - ) + async function (value, inputField, wizard) { + const txt = await getInputValue(this.driver, pageObjects[wizard][inputField]) const result = Number.parseInt(txt) - value - await decrementValue( - this.driver, - pageObjects[wizard][inputField], - value - ) - await verifyTypedValue( - this.driver, - pageObjects[wizard][inputField], - result.toString() - ) + await decrementValue(this.driver, pageObjects[wizard][inputField], value) + await verifyTypedValue(this.driver, pageObjects[wizard][inputField], result.toString()) } ) @@ -563,8 +496,7 @@ Then( let result = Number.parseFloat(txt) - unitValue if (unit === 'cpu') { return result.toFixed(3) - } - else if (unit !== 'cpu' && result < 1) { + } else if (unit !== 'cpu' && result < 1) { result = 1 } await decrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) @@ -578,23 +510,15 @@ Then( Then( 'type value {string} to {string} field on {string} on {string} wizard', - async function(value, inputField, accordion, wizard) { - await typeValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) - await verifyTypedValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) + async function (value, inputField, accordion, wizard) { + await typeValue(this.driver, pageObjects[wizard][accordion][inputField], value) + await verifyTypedValue(this.driver, pageObjects[wizard][accordion][inputField], value) } ) Then( '{string} component on {string} should contains {string}.{string}', - async function(component, wizard, constStorage, constValue) { + async function (component, wizard, constStorage, constValue) { await waiteUntilComponent(this.driver, pageObjects[wizard][component]) await verifyText( this.driver, @@ -606,19 +530,15 @@ Then( Then( '{string} element on {string} should contains {string} value', - async function(component, wizard, value) { + async function (component, wizard, value) { await verifyText(this.driver, pageObjects[wizard][component], value) } ) Then( '{string} element in {string} on {string} should contains {string} value', - async function(component, accordion, wizard, value) { - await verifyText( - this.driver, - pageObjects[wizard][accordion][component], - value - ) + async function (component, accordion, wizard, value) { + await verifyText(this.driver, pageObjects[wizard][accordion][component], value) } ) @@ -636,7 +556,7 @@ Then( Then( '{string} component on {string} should be equal {string}.{string}', - async function(component, wizard, constStorage, constValue) { + async function (component, wizard, constStorage, constValue) { await verifyTextRegExp( this.driver, pageObjects[wizard][component], @@ -647,11 +567,8 @@ Then( Then( '{string} component in {string} on {string} should contains {string}.{string}', - async function(component, accordion, wizard, constStorage, constValue) { - await waiteUntilComponent( - this.driver, - pageObjects[wizard][accordion][component] - ) + async function (component, accordion, wizard, constStorage, constValue) { + await waiteUntilComponent(this.driver, pageObjects[wizard][accordion][component]) await verifyText( this.driver, pageObjects[wizard][accordion][component], @@ -662,30 +579,23 @@ Then( When( 'select {string} option in {string} dropdown on {string} wizard', - async function(optionValue, dropdownName, wizardName) { + async function (optionValue, dropdownName, wizardName) { await openDropdown(this.driver, pageObjects[wizardName][dropdownName]) - await selectOptionInDropdown( - this.driver, - pageObjects[wizardName][dropdownName], - optionValue - ) - await this.driver.sleep(500) + await selectOptionInDropdown(this.driver, pageObjects[wizardName][dropdownName], optionValue) + await this.driver.sleep(DRIVER_SLEEP || 500) } ) When( 'select {string} option in {string} dropdown on {string} on {string} wizard', - async function(optionValue, dropdownName, accordionName, wizardName) { - await openDropdown( - this.driver, - pageObjects[wizardName][accordionName][dropdownName] - ) + async function (optionValue, dropdownName, accordionName, wizardName) { + await openDropdown(this.driver, pageObjects[wizardName][accordionName][dropdownName]) await selectOptionInDropdown( this.driver, pageObjects[wizardName][accordionName][dropdownName], optionValue ) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) await checkDropdownSelectedOption( this.driver, pageObjects[wizardName][accordionName][dropdownName], @@ -696,7 +606,7 @@ When( Then( 'verify {string} dropdown on {string} wizard selected option value {string}', - async function(dropdownName, wizardName, optionValue) { + async function (dropdownName, wizardName, optionValue) { await checkDropdownSelectedOption( this.driver, pageObjects[wizardName][dropdownName], @@ -707,7 +617,7 @@ Then( Then( 'verify {string} dropdown in {string} on {string} wizard selected option value {string}', - async function(dropdownName, accordionName, wizardName, optionValue) { + async function (dropdownName, accordionName, wizardName, optionValue) { await checkDropdownSelectedOption( this.driver, pageObjects[wizardName][accordionName][dropdownName], @@ -718,7 +628,7 @@ Then( When( 'select {string} option in {string} filter dropdown on {string} wizard', - async function(optionValue, dropdownName, wizardName) { + async function (optionValue, dropdownName, wizardName) { await openDropdown(this.driver, pageObjects[wizardName][dropdownName]) await selectOptionInDropdownWithoutCheck( this.driver, @@ -730,7 +640,7 @@ When( Then( 'verify {string} filter band in {string} filter dropdown on {string} wizard', - async function(optionValue, dropdownName, wizardName) { + async function (optionValue, dropdownName, wizardName) { await verifyTimeFilterBand( this.driver, pageObjects[wizardName][dropdownName], @@ -741,37 +651,27 @@ Then( When( 'pick up {string} from {string} to {string} in {string} via {string} on {string} wizard', - async function( - optionValue, - fromDatetime, - toDatetime, - datetimePicker, - dropdownName, - wizardName - ) { + async function (optionValue, fromDatetime, toDatetime, datetimePicker, dropdownName, wizardName) { await openDropdown(this.driver, pageObjects[wizardName][dropdownName]) await selectOptionInDropdownWithoutCheck( this.driver, pageObjects[wizardName][dropdownName], optionValue ) - await this.driver.sleep(100) + await this.driver.sleep(DRIVER_SLEEP || 100) await pickUpCustomDatetimeRange( this.driver, pageObjects[wizardName][datetimePicker], fromDatetime, toDatetime ) - await applyDatetimePickerRange( - this.driver, - pageObjects[wizardName][datetimePicker] - ) + await applyDatetimePickerRange(this.driver, pageObjects[wizardName][datetimePicker]) } ) Then( 'verify from {string} to {string} filter band in {string} filter dropdown on {string} wizard', - async function(fromDatetime, toDatetime, dropdownName, wizardName) { + async function (fromDatetime, toDatetime, dropdownName, wizardName) { await verifyTimeFilterBand( this.driver, pageObjects[wizardName][dropdownName], @@ -782,7 +682,7 @@ Then( Then( 'verify error message in {string} on {string} wizard with value {string}.{string}', - async function(datetimePicker, wizard, constStorage, constValue) { + async function (datetimePicker, wizard, constStorage, constValue) { await verifyText( this.driver, pageObjects[wizard][datetimePicker].errorMessage, @@ -793,7 +693,7 @@ Then( Then( 'verify {string} element in {string} on {string} wizard should contains {string}.{string}', - async function(dropdown, accordion, wizard, constStorage, constValue) { + async function (dropdown, accordion, wizard, constStorage, constValue) { await openDropdown(this.driver, pageObjects[wizard][accordion][dropdown]) await checkDropdownOptions( this.driver, @@ -801,95 +701,64 @@ Then( pageObjectsConsts[constStorage][constValue] ) // close dropdown options - await clickNearComponent( - this.driver, - pageObjects[wizard][accordion][dropdown]['open_button'] - ) + await clickNearComponent(this.driver, pageObjects[wizard][accordion][dropdown]['open_button']) } ) Then( 'verify {string} dropdown element on {string} wizard should contains {string}.{string}', - async function(dropdownName, wizardName, constStorage, constValue) { + async function (dropdownName, wizardName, constStorage, constValue) { await openDropdown(this.driver, pageObjects[wizardName][dropdownName]) await checkDropdownContainsOptions( this.driver, pageObjects[wizardName][dropdownName], pageObjectsConsts[constStorage][constValue] ) - await clickNearComponent( - this.driver, - pageObjects[wizardName][dropdownName]['open_button'] - ) + await clickNearComponent(this.driver, pageObjects[wizardName][dropdownName]['open_button']) } ) -Then('verify {string} element visibility on {string} wizard', async function( - component, - wizard -) { +Then('verify {string} element visibility on {string} wizard', async function (component, wizard) { await componentIsVisible(this.driver, pageObjects[wizard][component]) }) -Then('verify {string} element invisibility on {string} wizard', async function( - component, - wizard -) { +Then('verify {string} element invisibility on {string} wizard', async function (component, wizard) { await componentIsNotVisible(this.driver, pageObjects[wizard][component]) }) Then( 'verify {string} element visibility in {string} on {string} wizard', - async function(component, accordion, wizard) { - await componentIsVisible( - this.driver, - pageObjects[wizard][accordion][component] - ) + async function (component, accordion, wizard) { + await componentIsVisible(this.driver, pageObjects[wizard][accordion][component]) } ) -Then('verify {string} element not exists on {string} wizard', async function( - component, - wizard -) { +Then('verify {string} element not exists on {string} wizard', async function (component, wizard) { await componentIsNotPresent(this.driver, pageObjects[wizard][component]) }) -Then('verify {string} element not exists in {string} on {string} wizard', async function( - component, - accordion, - wizard -) { - await componentIsNotPresent(this.driver, pageObjects[wizard][accordion][component]) -}) +Then( + 'verify {string} element not exists in {string} on {string} wizard', + async function (component, accordion, wizard) { + await componentIsNotPresent(this.driver, pageObjects[wizard][accordion][component]) + } +) -When('collapse {string} on {string} wizard', async function(accordion, wizard) { - await collapseAccordionSection( - this.driver, - pageObjects[wizard][accordion]['Collapse_Button'] - ) - await this.driver.sleep(100) +When('collapse {string} on {string} wizard', async function (accordion, wizard) { + await collapseAccordionSection(this.driver, pageObjects[wizard][accordion]['Collapse_Button']) + await this.driver.sleep(DRIVER_SLEEP || 100) }) -When('expand {string} on {string} wizard', async function(accordion, wizard) { - await expandAccordionSection( - this.driver, - pageObjects[wizard][accordion]['Collapse_Button'] - ) - await this.driver.sleep(100) +When('expand {string} on {string} wizard', async function (accordion, wizard) { + await expandAccordionSection(this.driver, pageObjects[wizard][accordion]['Collapse_Button']) + await this.driver.sleep(DRIVER_SLEEP || 100) }) -Then('verify {string} is collapsed on {string} wizard', async function( - accordion, - wizard -) { - await isAccordionSectionCollapsed( - this.driver, - pageObjects[wizard][accordion]['Collapse_Button'] - ) +Then('verify {string} is collapsed on {string} wizard', async function (accordion, wizard) { + await isAccordionSectionCollapsed(this.driver, pageObjects[wizard][accordion]['Collapse_Button']) }) -Then('sort projects in ascending order', async function() { +Then('sort projects in ascending order', async function () { const upSorted = await isComponentContainsAttributeValue( this.driver, pageObjects['Projects']['Projects_Sorter'], @@ -897,21 +766,14 @@ Then('sort projects in ascending order', async function() { 'sort_up' ) if (!upSorted) { - await clickOnComponent( - this.driver, - pageObjects['Projects']['Projects_Sorter'] - ) + await clickOnComponent(this.driver, pageObjects['Projects']['Projects_Sorter']) } if (upSorted) { - await isTableColumnSorted( - this.driver, - pageObjects['Projects']['Projects_Table'], - 'name' - ) + await isTableColumnSorted(this.driver, pageObjects['Projects']['Projects_Table'], 'name') } }) -Then('sort projects in descending order', async function() { +Then('sort projects in descending order', async function () { const downSorted = await isComponentContainsAttributeValue( this.driver, pageObjects['Projects']['Projects_Sorter'], @@ -919,22 +781,14 @@ Then('sort projects in descending order', async function() { 'sort_down' ) if (!downSorted) { - await clickOnComponent( - this.driver, - pageObjects['Projects']['Projects_Sorter'] - ) + await clickOnComponent(this.driver, pageObjects['Projects']['Projects_Sorter']) } - await isTableColumnSorted( - this.driver, - pageObjects['Projects']['Projects_Table'], - 'name', - 'desc' - ) + await isTableColumnSorted(this.driver, pageObjects['Projects']['Projects_Table'], 'name', 'desc') }) Then( 'verify {string} tab is active in {string} on {string} wizard', - async function(tabName, tabSelector, wizard) { + async function (tabName, tabSelector, wizard) { const arr = await findRowIndexesByColumnValue( this.driver, pageObjects[wizard][tabSelector], @@ -942,14 +796,14 @@ Then( tabName ) const indx = arr[0] - + await isTabActive(this.driver, pageObjects[wizard][tabSelector], indx) } ) Then( 'verify {string} on {string} wizard should contains {string}.{string}', - async function(tabSelector, wizard, constWizard, constValue) { + async function (tabSelector, wizard, constWizard, constValue) { await checkTableColumnValues( this.driver, pageObjects[wizard][tabSelector], @@ -959,27 +813,26 @@ Then( } ) -When('select {string} tab in {string} on {string} wizard', async function( - tabName, - tabSelector, - wizard -) { - const arr = await findRowIndexesByColumnValue( - this.driver, - pageObjects[wizard][tabSelector], - 'key', - tabName - ) - const indx = arr[0] - await clickOnComponent( - this.driver, - pageObjects[wizard][tabSelector]['tableFields']['key'](indx) - ) -}) +When( + 'select {string} tab in {string} on {string} wizard', + async function (tabName, tabSelector, wizard) { + const arr = await findRowIndexesByColumnValue( + this.driver, + pageObjects[wizard][tabSelector], + 'key', + tabName + ) + const indx = arr[0] + await clickOnComponent( + this.driver, + pageObjects[wizard][tabSelector]['tableFields']['key'](indx) + ) + } +) Then( 'verify {string} on {string} wizard should display {string}.{string}', - async function(inputField, wizard, constStorage, constValue) { + async function (inputField, wizard, constStorage, constValue) { await checkHintText( this.driver, pageObjects[wizard][inputField], @@ -991,7 +844,7 @@ Then( Then( 'verify {string} on {string} wizard should display {string}.{string} in {string}', - async function(inputField, wizard, constStorage, constValue, commonTipType) { + async function (inputField, wizard, constStorage, constValue, commonTipType) { await checkHintText( this.driver, pageObjects[wizard][inputField], @@ -1003,7 +856,7 @@ Then( Then( 'verify {string} on {string} wizard should display options {string}.{string}', - async function(inputField, wizard, constStorage, constValue) { + async function (inputField, wizard, constStorage, constValue) { await checkWarningHintText( this.driver, pageObjects[wizard][inputField], @@ -1015,7 +868,7 @@ Then( Then( 'verify labels warning should display options {string}.{string}', - async function(constStorage, constValue) { + async function (constStorage, constValue) { await checkWarningText( this.driver, pageObjects['commonPagesHeader']['Common_Options'], @@ -1025,20 +878,20 @@ Then( ) Then( - 'verify {string} in {string} on {string} wizard should display options {string}.{string}', - async function(inputField, accordion, wizard, constStorage, constValue) { - await checkWarningHintText( - this.driver, - pageObjects[wizard][accordion][inputField], - pageObjects['commonPagesHeader']['Common_Options'], - pageObjectsConsts[constStorage][constValue] - ) - } + 'verify {string} in {string} on {string} wizard should display options {string}.{string}', + async function (inputField, accordion, wizard, constStorage, constValue) { + await checkWarningHintText( + this.driver, + pageObjects[wizard][accordion][inputField], + pageObjects['commonPagesHeader']['Common_Options'], + pageObjectsConsts[constStorage][constValue] + ) + } ) Then( 'verify {string} element in {string} on {string} wizard should display hint {string}.{string}', - async function(inputField, accordion, wizard, constStorage, constValue) { + async function (inputField, accordion, wizard, constStorage, constValue) { await checkHintText( this.driver, pageObjects[wizard][accordion][inputField], @@ -1050,17 +903,10 @@ Then( Then( 'verify {string} on {string} wizard should display warning {string}.{string}', - async function(input, wizard, constStorage, constValue) { - await clickOnComponent( - this.driver, - pageObjects[wizard][input]['inputField'] - ) - await this.driver.sleep(100) - await hoverComponent( - this.driver, - pageObjects[wizard][input]['inputField'], - false - ) + async function (input, wizard, constStorage, constValue) { + await clickOnComponent(this.driver, pageObjects[wizard][input]['inputField']) + await this.driver.sleep(DRIVER_SLEEP || 100) + await hoverComponent(this.driver, pageObjects[wizard][input]['inputField'], false) await checkWarningHintText( this.driver, pageObjects[wizard][input], @@ -1072,16 +918,10 @@ Then( Then( 'verify {string} element in {string} on {string} wizard should display warning {string}.{string}', - async function(input, accordion, wizard, constStorage, constValue) { - await clickOnComponent( - this.driver, - pageObjects[wizard][accordion][input]['inputField'] - ) - await this.driver.sleep(100) - await clickNearComponent( - this.driver, - pageObjects[wizard][accordion][input]['inputField'] - ) + async function (input, accordion, wizard, constStorage, constValue) { + await clickOnComponent(this.driver, pageObjects[wizard][accordion][input]['inputField']) + await this.driver.sleep(DRIVER_SLEEP || 100) + await clickNearComponent(this.driver, pageObjects[wizard][accordion][input]['inputField']) await checkWarningHintText( this.driver, pageObjects[wizard][accordion][input], @@ -1091,67 +931,47 @@ Then( } ) -When('check {string} element on {string} wizard', async function( - checkbox, - wizard -) { +When('check {string} element on {string} wizard', async function (checkbox, wizard) { await checkCheckbox(this.driver, pageObjects[wizard][checkbox]) }) -When('check {string} element in {string} on {string} wizard', async function( - checkbox, - accordion, - wizard -) { - await checkCheckbox(this.driver, pageObjects[wizard][accordion][checkbox]) -}) +When( + 'check {string} element in {string} on {string} wizard', + async function (checkbox, accordion, wizard) { + await checkCheckbox(this.driver, pageObjects[wizard][accordion][checkbox]) + } +) -When('uncheck {string} element in {string} on {string} wizard', async function( - checkbox, - accordion, - wizard -) { - await uncheckCheckbox(this.driver, pageObjects[wizard][accordion][checkbox]) -}) +When( + 'uncheck {string} element in {string} on {string} wizard', + async function (checkbox, accordion, wizard) { + await uncheckCheckbox(this.driver, pageObjects[wizard][accordion][checkbox]) + } +) -When('uncheck {string} element on {string} wizard', async function( - checkbox, - wizard -) { +When('uncheck {string} element on {string} wizard', async function (checkbox, wizard) { await uncheckCheckbox(this.driver, pageObjects[wizard][checkbox]) }) -Then('{string} element should be unchecked on {string} wizard', async function( - checkbox, - wizard -) { +Then('{string} element should be unchecked on {string} wizard', async function (checkbox, wizard) { await isCheckboxUnchecked(this.driver, pageObjects[wizard][checkbox]) }) Then( '{string} element should be unchecked in {string} on {string} wizard', - async function(checkbox, accordion, wizard) { - await isCheckboxUnchecked( - this.driver, - pageObjects[wizard][accordion][checkbox] - ) + async function (checkbox, accordion, wizard) { + await isCheckboxUnchecked(this.driver, pageObjects[wizard][accordion][checkbox]) } ) -Then('{string} element should be checked on {string} wizard', async function( - checkbox, - wizard -) { +Then('{string} element should be checked on {string} wizard', async function (checkbox, wizard) { await isCheckboxChecked(this.driver, pageObjects[wizard][checkbox]) }) Then( '{string} element should be checked in {string} on {string} wizard', - async function(checkbox, accordion, wizard) { - await isCheckboxChecked( - this.driver, - pageObjects[wizard][accordion][checkbox] - ) + async function (checkbox, accordion, wizard) { + await isCheckboxChecked(this.driver, pageObjects[wizard][accordion][checkbox]) } ) @@ -1160,54 +980,36 @@ When( async function (component, accordion, wizardName) { await waiteUntilComponent(this.driver, pageObjects[wizardName][accordion][component]) await clickOnComponent(this.driver, pageObjects[wizardName][accordion][component]) - await this.driver.sleep(250) + await this.driver.sleep(DRIVER_SLEEP || 250) } ) -Then('is {string} on {string} selected', async function(radiobutton, wizard) { +Then('is {string} on {string} selected', async function (radiobutton, wizard) { await isRadioButtonSelected(this.driver, pageObjects[wizard][radiobutton]) }) -Then('is {string} in {string} on {string} selected', async function( - radiobutton, - accordion, - wizard -) { - await isRadioButtonSelected( - this.driver, - pageObjects[wizard][accordion][radiobutton] - ) -}) +Then( + 'is {string} in {string} on {string} selected', + async function (radiobutton, accordion, wizard) { + await isRadioButtonSelected(this.driver, pageObjects[wizard][accordion][radiobutton]) + } +) -Then('is not {string} in {string} on {string} selected', async function( - radiobutton, - accordion, - wizard -) { - await isRadioButtonUnselected( - this.driver, - pageObjects[wizard][accordion][radiobutton] - ) -}) +Then( + 'is not {string} in {string} on {string} selected', + async function (radiobutton, accordion, wizard) { + await isRadioButtonUnselected(this.driver, pageObjects[wizard][accordion][radiobutton]) + } +) -When('select {string} in {string} on {string}', async function( - radiobutton, - accordion, - wizard -) { - await selectRadiobutton( - this.driver, - pageObjects[wizard][accordion][radiobutton] - ) +When('select {string} in {string} on {string}', async function (radiobutton, accordion, wizard) { + await selectRadiobutton(this.driver, pageObjects[wizard][accordion][radiobutton]) }) Then( 'verify options in {string} combobox in {string} on {string} wizard should contains {string}.{string}', - async function(combobox, accordion, wizard, constStorage, constValue) { - await openDropdown( - this.driver, - pageObjects[wizard][accordion][combobox]['dropdown'] - ) + async function (combobox, accordion, wizard, constStorage, constValue) { + await openDropdown(this.driver, pageObjects[wizard][accordion][combobox]['dropdown']) await checkDropdownOptions( this.driver, pageObjects[wizard][accordion][combobox]['dropdown'], @@ -1221,11 +1023,8 @@ Then( ) When( 'select {string} option in {string} combobox on {string} accordion on {string} wizard', - async function(option, comboBox, accordion, wizardName) { - await openDropdown( - this.driver, - pageObjects[wizardName][accordion][comboBox]['dropdown'] - ) + async function (option, comboBox, accordion, wizardName) { + await openDropdown(this.driver, pageObjects[wizardName][accordion][comboBox]['dropdown']) await selectOptionInDropdownWithoutCheck( this.driver, pageObjects[wizardName][accordion][comboBox]['dropdown'], @@ -1236,63 +1035,56 @@ When( When( 'select {string} option in {string} combobox suggestion on {string} accordion on {string} wizard', - async function(option, comboBox, accordion, wizard) { + async function (option, comboBox, accordion, wizard) { await selectOptionInDropdownWithoutCheck( this.driver, pageObjects[wizard][accordion][comboBox]['comboDropdown'], option ) - await this.driver.sleep(200) + await this.driver.sleep(DRIVER_SLEEP || 200) } ) Then( 'select {string} option in {string} suggestions dropdown on {string} wizard', - async function(option, dropdown, wizard) { - await selectOptionInDropdownWithoutCheck( - this.driver, - pageObjects[wizard][dropdown], - option - ) - await this.driver.sleep(200) + async function (option, dropdown, wizard) { + await selectOptionInDropdownWithoutCheck(this.driver, pageObjects[wizard][dropdown], option) + await this.driver.sleep(DRIVER_SLEEP || 200) } ) -Then('select {string} option in action menu on {string} wizard', async function( - option, - wizard -) { +Then('select {string} option in action menu on {string} wizard', async function (option, wizard) { const actionMenu = pageObjects[wizard]['Action_Menu'] await openActionMenu(this.driver, actionMenu) - await this.driver.sleep(500) + await this.driver.sleep(DRIVER_SLEEP || 500) await selectOptionInActionMenu(this.driver, actionMenu, option) }) -Then('check that {string} option in action menu on {string} wizard is enabled', async function( - option, - wizard -) { - const actionMenu = pageObjects[wizard]['Action_Menu'] - await openActionMenu(this.driver, actionMenu) - await this.driver.sleep(500) - await verifyOptionInActionMenuEnabled (this.driver, actionMenu, option) -}) +Then( + 'check that {string} option in action menu on {string} wizard is enabled', + async function (option, wizard) { + const actionMenu = pageObjects[wizard]['Action_Menu'] + await openActionMenu(this.driver, actionMenu) + await this.driver.sleep(DRIVER_SLEEP || 500) + await verifyOptionInActionMenuEnabled(this.driver, actionMenu, option) + } +) -Then('check that {string} option in action menu on {string} wizard is disabled', async function( - option, - wizard -) { - const actionMenu = pageObjects[wizard]['Action_Menu'] - await openActionMenu(this.driver, actionMenu) - await this.driver.sleep(500) - await verifyOptionInActionMenuDisabled (this.driver, actionMenu, option) -}) +Then( + 'check that {string} option in action menu on {string} wizard is disabled', + async function (option, wizard) { + const actionMenu = pageObjects[wizard]['Action_Menu'] + await openActionMenu(this.driver, actionMenu) + await this.driver.sleep(DRIVER_SLEEP || 500) + await verifyOptionInActionMenuDisabled(this.driver, actionMenu, option) + } +) Then('check that {string} file is existed on {string} directory', async function (file, filePath) { const path = await generatePath(file, filePath) - await this.driver.sleep(150) + await this.driver.sleep(DRIVER_SLEEP || 150) await determineFileAccess(path, file) - await this.driver.sleep(150) + await this.driver.sleep(DRIVER_SLEEP || 150) }) Then( @@ -1307,10 +1099,7 @@ Then( } ) -Then('verify {string} options rules on {string} wizard', async function( - inputField, - wizardName -) { +Then('verify {string} options rules on {string} wizard', async function (inputField, wizardName) { await checkInputAccordingHintText( this.driver, this.attach, @@ -1319,36 +1108,36 @@ Then('verify {string} options rules on {string} wizard', async function( ) }) -Then('verify {string} options rules on form {string} wizard', async function( - inputField, - wizardName -) { - await checkInputAccordingHintText( - this.driver, - this.attach, - pageObjects[wizardName][inputField], - pageObjects['commonPagesHeader']['Common_Options'], - true - ) -}) +Then( + 'verify {string} options rules on form {string} wizard', + async function (inputField, wizardName) { + await checkInputAccordingHintText( + this.driver, + this.attach, + pageObjects[wizardName][inputField], + pageObjects['commonPagesHeader']['Common_Options'], + true + ) + } +) -Then('verify {string} options rules on {string} wizard with labels', async function( - inputField, - wizardName -) { - await checkInputAccordingHintText( - this.driver, - this.attach, - pageObjects[wizardName][inputField], - pageObjects['commonPagesHeader']['Common_Options'], - false, - true - ) -}) +Then( + 'verify {string} options rules on {string} wizard with labels', + async function (inputField, wizardName) { + await checkInputAccordingHintText( + this.driver, + this.attach, + pageObjects[wizardName][inputField], + pageObjects['commonPagesHeader']['Common_Options'], + false, + true + ) + } +) Then( 'verify breadcrumbs {string} label should be equal {string} value', - async function(labelType, value) { + async function (labelType, value) { await verifyText( this.driver, pageObjects['commonPagesHeader']['Breadcrumbs'][`${labelType}Label`], @@ -1359,20 +1148,16 @@ Then( Then( 'verify value should equal {string} in {string} on {string} wizard', - async function(value, componentName, wizardName) { - await verifyText( - this.driver, - pageObjects[wizardName][componentName]['label'], - value - ) + async function (value, componentName, wizardName) { + await verifyText(this.driver, pageObjects[wizardName][componentName]['label'], value) } ) Then( - 'verify {string} input should contains {string} value on {string} wizard', - async function (component, value, wizard) { - await verifyTypedValue(this.driver, pageObjects[wizard][component], value) - } + 'verify {string} input should contains {string} value on {string} wizard', + async function (component, value, wizard) { + await verifyTypedValue(this.driver, pageObjects[wizard][component], value) + } ) Then( @@ -1384,7 +1169,7 @@ Then( Then( 'verify value should equal {string}.{string} in {string} on {string} wizard', - async function(constStorage, constValue, componentName, wizardName) { + async function (constStorage, constValue, componentName, wizardName) { await verifyText( this.driver, pageObjects[wizardName][componentName]['label'], @@ -1393,14 +1178,8 @@ Then( } ) -Then('select {string} with {string} value in breadcrumbs menu', async function( - itemType, - name -) { - await openDropdown( - this.driver, - pageObjects['commonPagesHeader']['Breadcrumbs'][itemType] - ) +Then('select {string} with {string} value in breadcrumbs menu', async function (itemType, name) { + await openDropdown(this.driver, pageObjects['commonPagesHeader']['Breadcrumbs'][itemType]) await selectOptionInDropdown( this.driver, @@ -1411,50 +1190,32 @@ Then('select {string} with {string} value in breadcrumbs menu', async function( Then( 'verify arrow lines position on {string} on {string} wizard', - async function(graphName, wizardName) { - await checkNodesConnectionsNPandas( - this.driver, - pageObjects[wizardName][graphName] - ) + async function (graphName, wizardName) { + await checkNodesConnectionsNPandas(this.driver, pageObjects[wizardName][graphName]) } ) -When('hover {string} component on {string} wizard', async function( - componentName, - wizardName -) { - await hoverComponent( - this.driver, - pageObjects[wizardName][componentName], - true - ) +When('hover {string} component on {string} wizard', async function (componentName, wizardName) { + await hoverComponent(this.driver, pageObjects[wizardName][componentName], true) }) -When('scroll and hover {string} component on {string} wizard', async function( - componentName, - wizardName -) { - await hoverComponent( - this.driver, - pageObjects[wizardName][componentName], - true - ) -}) +When( + 'scroll and hover {string} component on {string} wizard', + async function (componentName, wizardName) { + await hoverComponent(this.driver, pageObjects[wizardName][componentName], true) + } +) When( 'scroll and hover {string} component in {string} on {string} wizard', - async function(componentName, accordionName, wizardName) { - await hoverComponent( - this.driver, - pageObjects[wizardName][accordionName][componentName], - true - ) + async function (componentName, accordionName, wizardName) { + await hoverComponent(this.driver, pageObjects[wizardName][accordionName][componentName], true) } ) When( 'click on node with name {string} in {string} graph on {string} wizard', - async function(nodeName, graphName, wizardName) { + async function (nodeName, graphName, wizardName) { const arr = await findRowIndexesByColumnValue( this.driver, pageObjects[wizardName][graphName]['nodesTable'], @@ -1469,35 +1230,33 @@ When( } ) -Then('{string} on {string} wizard should be {string}', async function( - componentName, - wizardName, - state -) { - await verifyComponentContainsAttributeValue( - this.driver, - pageObjects[wizardName][componentName], - 'class', - state - ) -}) +Then( + '{string} on {string} wizard should be {string}', + async function (componentName, wizardName, state) { + await verifyComponentContainsAttributeValue( + this.driver, + pageObjects[wizardName][componentName], + 'class', + state + ) + } +) -Then('{string} on {string} wizard should not be {string}', async function( - componentName, - wizardName, - state -) { - await verifyComponentNotContainsAttributeValue( - this.driver, - pageObjects[wizardName][componentName], - 'class', - state - ) -}) +Then( + '{string} on {string} wizard should not be {string}', + async function (componentName, wizardName, state) { + await verifyComponentNotContainsAttributeValue( + this.driver, + pageObjects[wizardName][componentName], + 'class', + state + ) + } +) Then( 'compare {string} element value on {string} wizard with test {string} context value', - async function(componentName, wizardName, fieldName) { + async function (componentName, wizardName, fieldName) { await verifyText( this.driver, pageObjects[wizardName][componentName], @@ -1507,25 +1266,20 @@ Then( ) Then( - 'compare {string} element value in {string} on {string} wizard with test {string} context value', - async function(componentName, accordionName, wizardName, fieldName) { - await verifyText( - this.driver, - pageObjects[wizardName][accordionName][componentName], - this.testContext[fieldName] - ) - } -) - -Then( - 'compare current browser URL with test {string} context value', - async function(savedValue) { - expect(await this.driver.getCurrentUrl()).equal( - this.testContext[savedValue] + 'compare {string} element value in {string} on {string} wizard with test {string} context value', + async function (componentName, accordionName, wizardName, fieldName) { + await verifyText( + this.driver, + pageObjects[wizardName][accordionName][componentName], + this.testContext[fieldName] ) } ) +Then('compare current browser URL with test {string} context value', async function (savedValue) { + expect(await this.driver.getCurrentUrl()).equal(this.testContext[savedValue]) +}) + Then( 'check {string} textarea counter on {string} wizard', async function (componentName, wizardName) { diff --git a/tests/features/support/world.js b/tests/features/support/world.js index 77f41a7e7..a92b09845 100644 --- a/tests/features/support/world.js +++ b/tests/features/support/world.js @@ -40,22 +40,23 @@ class CustomWorld extends World { let browseConfigs - //browseConfigs = new chrome.Options().windowSize(screen_size) - can be used to define a specific screen size if (browser === 'chrome') { + browseConfigs = new chrome.Options() if (headless) { - browseConfigs = new chrome.Options() - .headless() - .addArguments('no-sandbox') - .addArguments('start-maximized') - .addArguments('disable-gpu') - } else browseConfigs = new chrome.Options() - .addArguments('start-maximized') - .excludeSwitches('disable-popup-blocking', 'enable-automation') + browseConfigs.addArguments('headless') + browseConfigs.addArguments('no-sandbox') + browseConfigs.addArguments('disable-gpu') + } + browseConfigs.addArguments('start-maximized') + browseConfigs.excludeSwitches('disable-popup-blocking', 'enable-automation') } + if (browser === 'firefox') { + browseConfigs = new firefox.Options() if (headless) { - browseConfigs = new firefox.Options().headless().windowSize(screen_size) - } else browseConfigs = new firefox.Options().windowSize(screen_size) + browseConfigs.addArguments('-headless') + } + browseConfigs.windowSize(screen_size) } this.driver = new seleniumWebdriver.Builder() From 58029da1ab59c6295000254ae14d6213be1c1343 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Thu, 4 Jul 2024 14:13:28 +0300 Subject: [PATCH 29/54] add Jenkinsfile --- ui_ci.groovy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 45643a7aa..ac64ef930 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -28,7 +28,10 @@ common.main { common.conditional_stage('Run Regression Tests', true) { // Run cucumber-js tests - sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' + sh ''' + npm run add-comment-to-http-client + npm run test:ci-cd-smoke-1 + ''' } common.conditional_stage('Post-Test Cleanup', true) { From 6a49557a1b749a00e886f02f7ba39f15c2e2d4b1 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Thu, 4 Jul 2024 14:43:03 +0300 Subject: [PATCH 30/54] add Jenkinsfile --- ui_ci.groovy | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index ac64ef930..1fb52a235 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -15,10 +15,17 @@ common.main { } common.conditional_stage('Set up Environment', true) { - sh 'npm install' } + common.conditional_stage('Prepare Chrome Environment', true) { + sh ''' + export TMPDIR=/home/iguazio/tmp + mkdir -p $TMPDIR && chmod 1777 $TMPDIR + export CHROME_BIN=$(which google-chrome) + ''' + } + common.conditional_stage('Start Services', true) { sh ''' npm run mock-server & @@ -26,11 +33,18 @@ common.main { ''' } - common.conditional_stage('Run Regression Tests', true) { - // Run cucumber-js tests + common.conditional_stage('Run Smoke Tests', true) { + // Run smoke tests sh ''' npm run add-comment-to-http-client - npm run test:ci-cd-smoke-1 + npm run test:ci-cd-smoke-1 -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' + ''' + } + + common.conditional_stage('Run Regression Tests', true) { + // Run regression tests + sh ''' + npm run test:regression -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' ''' } @@ -45,8 +59,6 @@ common.main { common.conditional_stage('Upload Artifacts', true) { sh ''' - # touch tests/reports/cucumber_report_default.html - # Environment variables ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" AUTH="${ARTIFACTORY_CRED}" LOCAL_FILE="tests/reports/cucumber_report_default.html" From ba7e3ac0538782f0cdadf37bacba3e6951408189 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Thu, 4 Jul 2024 14:55:47 +0300 Subject: [PATCH 31/54] add Jenkinsfile --- ui_ci.groovy | 173 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 104 insertions(+), 69 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 1fb52a235..1fba31cd1 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -5,83 +5,118 @@ def node_label = 'ubuntu-ui-runner' common.set_current_display_name("UI_CI_Test") -common.main { - timestamps { - nodes.runner(node_label) { - stage('UI CI Test') { - - common.conditional_stage('Pull Latest Changes', true) { - checkout scm - } - - common.conditional_stage('Set up Environment', true) { - sh 'npm install' +pipeline { + environment { + TMPDIR = '/home/iguazio/tmp' + CHROME_BIN = sh(script: 'which google-chrome', returnStdout: true).trim() + } + agent { + label node_label + } + stages { + stage('Pull Latest Changes') { + steps { + script { + common.conditional_stage('Pull Latest Changes', true) { + checkout scm + } } - - common.conditional_stage('Prepare Chrome Environment', true) { - sh ''' - export TMPDIR=/home/iguazio/tmp - mkdir -p $TMPDIR && chmod 1777 $TMPDIR - export CHROME_BIN=$(which google-chrome) - ''' + } + } + stage('Set up Environment') { + steps { + script { + common.conditional_stage('Set up Environment', true) { + sh 'npm install' + } } - - common.conditional_stage('Start Services', true) { - sh ''' - npm run mock-server & - npm start & - ''' + } + } + stage('Prepare Chrome Environment') { + steps { + sh ''' + mkdir -p $TMPDIR && chmod 1777 $TMPDIR + ''' + } + } + stage('Start Services') { + steps { + script { + common.conditional_stage('Start Services', true) { + sh ''' + npm run mock-server & + npm start & + ''' + } } - - common.conditional_stage('Run Smoke Tests', true) { - // Run smoke tests - sh ''' - npm run add-comment-to-http-client - npm run test:ci-cd-smoke-1 -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' - ''' + } + } + stage('Run Smoke Tests') { + steps { + script { + common.conditional_stage('Run Smoke Tests', true) { + sh ''' + npm run add-comment-to-http-client + npm run test:ci-cd-smoke-1 -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' + ''' + } } - - common.conditional_stage('Run Regression Tests', true) { - // Run regression tests - sh ''' - npm run test:regression -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' - ''' + } + } + stage('Run Regression Tests') { + steps { + script { + common.conditional_stage('Run Regression Tests', true) { + sh ''' + npm run test:regression -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' + ''' + } } - - common.conditional_stage('Post-Test Cleanup', true) { - sh ''' - kill %1 || true - kill %2 || true - # Ensure any remaining background processes are terminated - pkill -f npm || true - ''' + } + } + stage('Post-Test Cleanup') { + steps { + script { + common.conditional_stage('Post-Test Cleanup', true) { + sh ''' + pkill -f npm || true + ''' + } } - - common.conditional_stage('Upload Artifacts', true) { - sh ''' - ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" - AUTH="${ARTIFACTORY_CRED}" - LOCAL_FILE="tests/reports/cucumber_report_default.html" - - # Generate the Artifactory path with the build number - ARTIFACTORY_PATH="cucumber_report_default_${BUILD_NUMBER}.html" - - # Construct the full URL - URL="${ART_URL}/${ARTIFACTORY_PATH}" - - # Upload the file to Artifactory - curl -X PUT -u ${AUTH} "${URL}" --data-binary @"${LOCAL_FILE}" - ''' + } + } + stage('Upload Artifacts') { + steps { + script { + common.conditional_stage('Upload Artifacts', true) { + def files = ["cucumber_report_default.html", "cucumber_report.html"] + for (file in files) { + def local_file = "tests/reports/${file}" + def artifact_path = "${file}_${BUILD_NUMBER}.html" + def url = "http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports/${artifact_path}" + sh """ + curl -X PUT -u ${ARTIFACTORY_CRED} "${url}" --data-binary @"${local_file}" + """ + } + } } - - common.conditional_stage('Cleaning up', true) { - sh ''' - pkill -f npm || true - ''' + } + } + stage('Cleaning up') { + steps { + script { + common.conditional_stage('Cleaning up', true) { + sh ''' + pkill -f npm || true + ''' + } } - - common.conditional_stage('Build Status', true) { - script { + } + } + stage('Build Status') { + steps { + script { + common.conditional_stage('Build Status', true) { if (currentBuild.currentResult == 'SUCCESS') { echo 'Build was successful!' } else { From 9c57db5e20fcf6415bd9fbc0dceb7d69feed2954 Mon Sep 17 00:00:00 2001 From: pinis-gini-apps Date: Thu, 4 Jul 2024 20:58:34 +0300 Subject: [PATCH 32/54] add delay befor runing report script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 68e4cc646..6d3aa0f37 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "test:ci-cd-smoke-1": "DRIVER_SLEEP=5000 HEADLESS=true node scripts/ci-cd-scripts/tempSmoke1Test.js", "test:ui": "node scripts/testui.js", "report": "node tests/report.js", - "test:regression": "DRIVER_SLEEP=5000 HEADLESS=true npm run test:ui && npm run report", + "test:regression": "DRIVER_SLEEP=5000 HEADLESS=true npm run test:ui && sleep 10 && echo 'Finished regression' && sleep 10 && npm run report", "start:regression": "concurrently \"npm:mock-server\" \"npm:start\" \"npm:test:regression\"", "ui-steps": "export BABEL_ENV=test; export NODE_ENV=test; npx -p @babel/core -p @babel/node babel-node --presets @babel/preset-env scripts/collectUITestsSteps.js", "nli": "npm link iguazio.dashboard-react-controls", From 11eebded731c848a8d85a29debd298da2d21d11a Mon Sep 17 00:00:00 2001 From: pinis-gini-apps Date: Thu, 4 Jul 2024 21:08:12 +0300 Subject: [PATCH 33/54] undo changes in ui_ci.groovy --- ui_ci.groovy | 122 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 36 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 45643a7aa..8d7c8d1f4 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -5,68 +5,118 @@ def node_label = 'ubuntu-ui-runner' common.set_current_display_name("UI_CI_Test") -common.main { - timestamps { - nodes.runner(node_label) { - stage('UI CI Test') { - +pipeline { + environment { + TMPDIR = '/home/iguazio/tmp' + CHROME_BIN = sh(script: 'which google-chrome', returnStdout: true).trim() + } + agent { + label node_label + } + stages { + stage('Pull Latest Changes') { + steps { + script { common.conditional_stage('Pull Latest Changes', true) { checkout scm } - + } + } + } + stage('Set up Environment') { + steps { + script { common.conditional_stage('Set up Environment', true) { - sh 'npm install' } - + } + } + } + stage('Prepare Chrome Environment') { + steps { + sh ''' + mkdir -p $TMPDIR && chmod 1777 $TMPDIR + ''' + } + } + stage('Start Services') { + steps { + script { common.conditional_stage('Start Services', true) { sh ''' npm run mock-server & npm start & ''' } - + } + } + } + stage('Run Smoke Tests') { + steps { + script { + common.conditional_stage('Run Smoke Tests', true) { + sh ''' + npm run add-comment-to-http-client + npm run test:ci-cd-smoke-1 -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' + ''' + } + } + } + } + stage('Run Regression Tests') { + steps { + script { common.conditional_stage('Run Regression Tests', true) { - // Run cucumber-js tests - sh './node_modules/.bin/cucumber-js --require-module @babel/register --require-module @babel/polyfill -f json:tests/reports/cucumber_report.json -f html:tests/reports/cucumber_report_default.html tests -t \'@smoke\'' + sh ''' + npm run test:regression -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' + ''' + } } - + } + } + stage('Post-Test Cleanup') { + steps { + script { common.conditional_stage('Post-Test Cleanup', true) { sh ''' - kill %1 || true - kill %2 || true - # Ensure any remaining background processes are terminated pkill -f npm || true ''' } - + } + } + } + stage('Upload Artifacts') { + steps { + script { common.conditional_stage('Upload Artifacts', true) { - sh ''' - # touch tests/reports/cucumber_report_default.html - # Environment variables - ART_URL="http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports" - AUTH="${ARTIFACTORY_CRED}" - LOCAL_FILE="tests/reports/cucumber_report_default.html" - - # Generate the Artifactory path with the build number - ARTIFACTORY_PATH="cucumber_report_default_${BUILD_NUMBER}.html" - - # Construct the full URL - URL="${ART_URL}/${ARTIFACTORY_PATH}" - - # Upload the file to Artifactory - curl -X PUT -u ${AUTH} "${URL}" --data-binary @"${LOCAL_FILE}" - ''' + def files = ["cucumber_report_default.html", "cucumber_report.html"] + for (file in files) { + def local_file = "tests/reports/${file}" + def artifact_path = "${file}_${BUILD_NUMBER}.html" + def url = "http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports/${artifact_path}" + sh """ + curl -X PUT -u ${ARTIFACTORY_CRED} "${url}" --data-binary @"${local_file}" + """ + } + } } - + } + } + stage('Cleaning up') { + steps { + script { common.conditional_stage('Cleaning up', true) { sh ''' pkill -f npm || true ''' } - + } + } + } + stage('Build Status') { + steps { + script { common.conditional_stage('Build Status', true) { - script { if (currentBuild.currentResult == 'SUCCESS') { echo 'Build was successful!' } else { @@ -77,4 +127,4 @@ common.main { } } } -} \ No newline at end of file +} From 94b151db6c7ab62756f55532734faa860b48acac Mon Sep 17 00:00:00 2001 From: Pini Shahmurov Date: Sun, 7 Jul 2024 10:10:48 +0300 Subject: [PATCH 34/54] add delay before running report script (#2582) * add scripts test for ci-cd flow * add delay befor runing report script * undo changes in ui_ci.groovy --- package.json | 2 +- ui_ci.groovy | 54 ++++++++++++++++++++++++++-------------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 68e4cc646..6d3aa0f37 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "test:ci-cd-smoke-1": "DRIVER_SLEEP=5000 HEADLESS=true node scripts/ci-cd-scripts/tempSmoke1Test.js", "test:ui": "node scripts/testui.js", "report": "node tests/report.js", - "test:regression": "DRIVER_SLEEP=5000 HEADLESS=true npm run test:ui && npm run report", + "test:regression": "DRIVER_SLEEP=5000 HEADLESS=true npm run test:ui && sleep 10 && echo 'Finished regression' && sleep 10 && npm run report", "start:regression": "concurrently \"npm:mock-server\" \"npm:start\" \"npm:test:regression\"", "ui-steps": "export BABEL_ENV=test; export NODE_ENV=test; npx -p @babel/core -p @babel/node babel-node --presets @babel/preset-env scripts/collectUITestsSteps.js", "nli": "npm link iguazio.dashboard-react-controls", diff --git a/ui_ci.groovy b/ui_ci.groovy index 1fba31cd1..8d7c8d1f4 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -17,18 +17,18 @@ pipeline { stage('Pull Latest Changes') { steps { script { - common.conditional_stage('Pull Latest Changes', true) { - checkout scm - } + common.conditional_stage('Pull Latest Changes', true) { + checkout scm + } } } } stage('Set up Environment') { steps { script { - common.conditional_stage('Set up Environment', true) { - sh 'npm install' - } + common.conditional_stage('Set up Environment', true) { + sh 'npm install' + } } } } @@ -42,12 +42,12 @@ pipeline { stage('Start Services') { steps { script { - common.conditional_stage('Start Services', true) { - sh ''' - npm run mock-server & - npm start & - ''' - } + common.conditional_stage('Start Services', true) { + sh ''' + npm run mock-server & + npm start & + ''' + } } } } @@ -66,7 +66,7 @@ pipeline { stage('Run Regression Tests') { steps { script { - common.conditional_stage('Run Regression Tests', true) { + common.conditional_stage('Run Regression Tests', true) { sh ''' npm run test:regression -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' ''' @@ -77,18 +77,18 @@ pipeline { stage('Post-Test Cleanup') { steps { script { - common.conditional_stage('Post-Test Cleanup', true) { - sh ''' - pkill -f npm || true - ''' - } + common.conditional_stage('Post-Test Cleanup', true) { + sh ''' + pkill -f npm || true + ''' + } } } } stage('Upload Artifacts') { steps { script { - common.conditional_stage('Upload Artifacts', true) { + common.conditional_stage('Upload Artifacts', true) { def files = ["cucumber_report_default.html", "cucumber_report.html"] for (file in files) { def local_file = "tests/reports/${file}" @@ -100,23 +100,23 @@ pipeline { } } } - } + } } stage('Cleaning up') { steps { script { - common.conditional_stage('Cleaning up', true) { - sh ''' - pkill -f npm || true - ''' - } + common.conditional_stage('Cleaning up', true) { + sh ''' + pkill -f npm || true + ''' + } } } } stage('Build Status') { steps { script { - common.conditional_stage('Build Status', true) { + common.conditional_stage('Build Status', true) { if (currentBuild.currentResult == 'SUCCESS') { echo 'Build was successful!' } else { @@ -127,4 +127,4 @@ pipeline { } } } -} \ No newline at end of file +} From 5acc4928baf2055769b44a0fc78c2ba0fc3f9cfe Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Sun, 7 Jul 2024 11:08:14 +0300 Subject: [PATCH 35/54] add Jenkinsfile --- ui_ci.groovy | 65 ++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 8d7c8d1f4..094afbfd5 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -17,18 +17,18 @@ pipeline { stage('Pull Latest Changes') { steps { script { - common.conditional_stage('Pull Latest Changes', true) { - checkout scm - } + common.conditional_stage('Pull Latest Changes', true) { + checkout scm + } } } } stage('Set up Environment') { steps { script { - common.conditional_stage('Set up Environment', true) { - sh 'npm install' - } + common.conditional_stage('Set up Environment', true) { + sh 'npm install' + } } } } @@ -42,12 +42,12 @@ pipeline { stage('Start Services') { steps { script { - common.conditional_stage('Start Services', true) { - sh ''' - npm run mock-server & - npm start & - ''' - } + common.conditional_stage('Start Services', true) { + sh ''' + npm run mock-server & + npm start & + ''' + } } } } @@ -66,7 +66,7 @@ pipeline { stage('Run Regression Tests') { steps { script { - common.conditional_stage('Run Regression Tests', true) { + common.conditional_stage('Run Regression Tests', true) { sh ''' npm run test:regression -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' ''' @@ -77,46 +77,51 @@ pipeline { stage('Post-Test Cleanup') { steps { script { - common.conditional_stage('Post-Test Cleanup', true) { - sh ''' - pkill -f npm || true - ''' - } + common.conditional_stage('Post-Test Cleanup', true) { + sh ''' + pkill -f npm || true + ''' + } } } } stage('Upload Artifacts') { steps { script { - common.conditional_stage('Upload Artifacts', true) { - def files = ["cucumber_report_default.html", "cucumber_report.html"] + common.conditional_stage('Upload Artifacts', true) { + def dateFormat = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss") + def currentDate = dateFormat.format(new Date()) + def buildFolder = "${BUILD_NUMBER}_${currentDate}" + def baseUrl = "http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports/${buildFolder}" + + def files = ["cucumber_report_default.html", "cucumber_report.html", "cucumber_report.json"] for (file in files) { def local_file = "tests/reports/${file}" - def artifact_path = "${file}_${BUILD_NUMBER}.html" - def url = "http://artifactory.iguazeng.com:8082/artifactory/ui-ci-reports/${artifact_path}" + def artifact_path = "${file}_${currentDate}" + def url = "${baseUrl}/${artifact_path}" sh """ curl -X PUT -u ${ARTIFACTORY_CRED} "${url}" --data-binary @"${local_file}" """ } } } - } + } } stage('Cleaning up') { steps { script { - common.conditional_stage('Cleaning up', true) { - sh ''' - pkill -f npm || true - ''' - } + common.conditional_stage('Cleaning up', true) { + sh ''' + pkill -f npm || true + ''' + } } } } stage('Build Status') { steps { script { - common.conditional_stage('Build Status', true) { + common.conditional_stage('Build Status', true) { if (currentBuild.currentResult == 'SUCCESS') { echo 'Build was successful!' } else { @@ -127,4 +132,4 @@ pipeline { } } } -} +} \ No newline at end of file From 97cc069b5206d7b0c59c6d5331259cd0d26c88ee Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Sun, 7 Jul 2024 14:53:17 +0300 Subject: [PATCH 36/54] add Jenkinsfile --- ui_ci.groovy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 094afbfd5..6cd7c705e 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -13,6 +13,9 @@ pipeline { agent { label node_label } + triggers { + cron('H 0 * * *') + } stages { stage('Pull Latest Changes') { steps { @@ -97,7 +100,7 @@ pipeline { def files = ["cucumber_report_default.html", "cucumber_report.html", "cucumber_report.json"] for (file in files) { def local_file = "tests/reports/${file}" - def artifact_path = "${file}_${currentDate}" + def artifact_path = file.replace(".html", "_${currentDate}.html").replace(".json", "_${currentDate}.json") def url = "${baseUrl}/${artifact_path}" sh """ curl -X PUT -u ${ARTIFACTORY_CRED} "${url}" --data-binary @"${local_file}" From 40bc34dc7acffaf14f61133eac4549d37329ddbe Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Sun, 7 Jul 2024 19:52:37 +0300 Subject: [PATCH 37/54] add Jenkinsfile --- ui_ci.groovy | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 6cd7c705e..ec9b1693b 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -57,7 +57,7 @@ pipeline { stage('Run Smoke Tests') { steps { script { - common.conditional_stage('Run Smoke Tests', true) { + common.conditional_stage('Run Smoke Tests', false) { sh ''' npm run add-comment-to-http-client npm run test:ci-cd-smoke-1 -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' @@ -77,17 +77,7 @@ pipeline { } } } - stage('Post-Test Cleanup') { - steps { - script { - common.conditional_stage('Post-Test Cleanup', true) { - sh ''' - pkill -f npm || true - ''' - } - } - } - } + stage('Upload Artifacts') { steps { script { From 4598a5da97a5ef9d233b72672c8bb09be2b1f008 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Sun, 7 Jul 2024 19:57:42 +0300 Subject: [PATCH 38/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index ec9b1693b..132d2ff1d 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -57,7 +57,7 @@ pipeline { stage('Run Smoke Tests') { steps { script { - common.conditional_stage('Run Smoke Tests', false) { + common.conditional_stage('Run Smoke Tests', true) { sh ''' npm run add-comment-to-http-client npm run test:ci-cd-smoke-1 -- --chrome-options='--headless --no-sandbox --disable-dev-shm-usage --remote-debugging-port=9222 --disable-gpu --window-size=1920,1080 --disable-software-rasterizer --verbose --log-path=$TMPDIR/chrome.log' From 3e4ea9e3b59c4722236ec3c8d606fd8617c77589 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Sun, 7 Jul 2024 20:03:41 +0300 Subject: [PATCH 39/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 132d2ff1d..bdaac7950 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -14,7 +14,7 @@ pipeline { label node_label } triggers { - cron('H 0 * * *') + cron('5 20 * * *') } stages { stage('Pull Latest Changes') { From 803160d4a1dbceff0924df2801e55f2d77b288c9 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Sun, 7 Jul 2024 20:05:52 +0300 Subject: [PATCH 40/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index bdaac7950..07709873c 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -14,7 +14,7 @@ pipeline { label node_label } triggers { - cron('5 20 * * *') + cron('7 20 * * *') } stages { stage('Pull Latest Changes') { From 511282f5df54c9bd45887c7f720bea2a6079a773 Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Sun, 7 Jul 2024 20:07:31 +0300 Subject: [PATCH 41/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 07709873c..132d2ff1d 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -14,7 +14,7 @@ pipeline { label node_label } triggers { - cron('7 20 * * *') + cron('H 0 * * *') } stages { stage('Pull Latest Changes') { From 6e3ed03b79759d696c3b42a88bed8a4335dc64ae Mon Sep 17 00:00:00 2001 From: orkoresh4 Date: Mon, 8 Jul 2024 00:03:12 +0300 Subject: [PATCH 42/54] add Jenkinsfile --- ui_ci.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui_ci.groovy b/ui_ci.groovy index 132d2ff1d..f744d60d5 100644 --- a/ui_ci.groovy +++ b/ui_ci.groovy @@ -14,7 +14,7 @@ pipeline { label node_label } triggers { - cron('H 0 * * *') + cron('4 0 * * *') } stages { stage('Pull Latest Changes') { From 805b6a770d905b8aff3a08ee5695840a86ec3d60 Mon Sep 17 00:00:00 2001 From: pinis-gini-apps Date: Thu, 18 Jul 2024 11:57:01 +0300 Subject: [PATCH 43/54] remove console.log --- scripts/ci-cd-scripts/commentedHttpClient.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/ci-cd-scripts/commentedHttpClient.js b/scripts/ci-cd-scripts/commentedHttpClient.js index 4e51b7039..42d878cbc 100644 --- a/scripts/ci-cd-scripts/commentedHttpClient.js +++ b/scripts/ci-cd-scripts/commentedHttpClient.js @@ -33,7 +33,6 @@ const headers = { 'Cache-Control': 'no-cache' } -console.log('----------- comments ----------') // serialize a param with an array value as a repeated param, for example: // { label: ['host', 'owner=admin'] } => 'label=host&label=owner%3Dadmin' const paramsSerializer = params => qs.stringify(params, { arrayFormat: 'repeat' }) From b17d47174edefc7453aa12d41aefddb18b09476c Mon Sep 17 00:00:00 2001 From: pinis-gini-apps Date: Thu, 18 Jul 2024 12:57:38 +0300 Subject: [PATCH 44/54] change DRIVER_SLEEP to 8000, add git restore for httpClient.js --- package.json | 4 ++-- tests/features/step-definitions/steps.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6d3aa0f37..833945816 100644 --- a/package.json +++ b/package.json @@ -71,10 +71,10 @@ "mock-server": "node scripts/mockServer.js", "mock-server:dev": "nodemon --watch tests/mockServer scripts/mockServer.js", "add-comment-to-http-client": "node scripts/ci-cd-scripts/appendCommentToHttpClient.js", - "test:ci-cd-smoke-1": "DRIVER_SLEEP=5000 HEADLESS=true node scripts/ci-cd-scripts/tempSmoke1Test.js", + "test:ci-cd-smoke-1": "DRIVER_SLEEP=8000 HEADLESS=true node scripts/ci-cd-scripts/tempSmoke1Test.js && git restore src/httpClient.js", "test:ui": "node scripts/testui.js", "report": "node tests/report.js", - "test:regression": "DRIVER_SLEEP=5000 HEADLESS=true npm run test:ui && sleep 10 && echo 'Finished regression' && sleep 10 && npm run report", + "test:regression": "DRIVER_SLEEP=8000 HEADLESS=true npm run test:ui && sleep 10 && echo 'Finished regression' && sleep 10 && npm run report && git restore src/httpClient.js", "start:regression": "concurrently \"npm:mock-server\" \"npm:start\" \"npm:test:regression\"", "ui-steps": "export BABEL_ENV=test; export NODE_ENV=test; npx -p @babel/core -p @babel/node babel-node --presets @babel/preset-env scripts/collectUITestsSteps.js", "nli": "npm link iguazio.dashboard-react-controls", diff --git a/tests/features/step-definitions/steps.js b/tests/features/step-definitions/steps.js index 16fa6c759..ed298a872 100644 --- a/tests/features/step-definitions/steps.js +++ b/tests/features/step-definitions/steps.js @@ -193,7 +193,6 @@ Then('verify redirection to {string}', async function (expectedPath) { }) Then('wait load page', async function () { - console.log(DRIVER_SLEEP) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) await this.driver.sleep(DRIVER_SLEEP || 500) }) From ff6f2cf214c087e640a4ee54f84c1d1d4d2fe6bb Mon Sep 17 00:00:00 2001 From: Pini Shahmurov Date: Mon, 22 Jul 2024 15:02:07 +0300 Subject: [PATCH 45/54] Remote UI regression workflow : clean up the code `ui-ci-orkor` (#2618) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix [UI] Align API GW usage across all nuclio sub-types (#2562) * Fix [UI] Add mock for application Runtime (#2563) * Impl [Models endpoints monitoring, metrics] Use "Apply" button to get all selected metrics (#2564) * Fix [Feature Store] Add 'space' validation into the artifact name part of the path (#2565) * Fix [Features] Table layout issue (#2566) * Fix [Artifacts, Functions] selected item is missing from the list of visible items (#2567) * Fix [Jobs monitoring, scheduled] 'No jobs found.' when filtering for the 'Past 24 hours' (#2568) * remove console log (#2570) * UI ci orkor (#2569) * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add Jenkinsfile * add scripts test for ci-cd flow * add delay befor runing report script * undo changes in ui_ci.groovy * Fix [Model Endpoints] previous project data shown after request cancellation (#2571) * Fix [Features] Remove ‘Targets’ column from Features table (#2572) * Fix [UI] Requests aren't send when project was changed. (#2574) * Fix [Feature store] features request not cancelled on FV creation (#2573) * Fix [UI] bad behavior during automatic refresh in project "General" settings page (#2577) * Fix [Create Vector] UI partial load (#2578) * Fix [Artifacts|Datasets|Models] Load artifact issues (#2579) * Fix [ML Functions] Actions "Run", "Edit", "View YAML" don't work (#2580) * Fix [Model endpoints] multiple duplicates of items during scroll (#2583) * Tests [UI] Add mock for download with allowed prefixes on Artifacts/Datasets/Models (#2584) * Fix [Artifacts] expended rows not collapsing when switching projects (#2585) * Fix build issue due to console.log (#2586) * Bump DRC version `2.1.2` (#2590) * Fix [Feature vectors] incorrect marking of label column (#2587) * Fix [Models Endpoints Monitoring, Metrics] clear search results when switching model endpoints (#2588) * Fix [Functions] Add mock for GET `functions` request (#2589) * Fix [Metrics] UI metrics UX fix (#2552) * Fix [Models Endpoints Monitoring, Metrics] metrics are deselected after click on Apply button (#2591) * Fix [Models Endpoints Monitoring, Metrics] displaying single value as a point on Invocation and Metric graph (#2592) * Fix [Functions/Feature Sets panel] Labels are not validated properly (#2593) * Impl [UI] Delete single artifact data (#2595) * Fix [Workflows, cross-projects] Project name doesn't specify for Batch re-run (#2596) * Fix [Models Endpoints Monitoring, Metrics] add the date to the tooltips in addition to the value (#2581) * Fix [Models endpoints monitoring, metrics] The scroll is disappeared when collapsed the Endpoint call count (#2594) * Impl [Models endpoints monitoring, metrics] Use "Clear" button to unselect all metrics (#2598) * Fix [MM Endpoints] Updates - clarify view and support 1.7 data (#2599) * Fix [Projects] Cannot scroll while data is fetched (#2600) * Fix [Models Endpoints Monitoring, Metrics] Percentage change calculation for Invocation graph (#2601) * Fix [Project Monitoring] change general status indicator title (#2597) * Fix [Models endpoints monitoring, metrics] Redundant invocation requests are sent (#2602) * Fix [Models endpoints monitoring, metrics] The "Choose metrics to view endpoint’s data" empty card is missing when the metrics are unselected (#2603) * Fix [Jobs, cross-project] Incorrect multiple status filter number (#2604) * Fix [Feature vectors] 403 Forbidden error on PUT /feature-vectors/{name}/references/{reference} (#2605) * Impl [Models Endpoints Monitoring, Metrics] Change X-Axis Label to Display Bin Range (#2606) * Impl [Artifacts] Delete artifact without a tag (#2607) * Fix [Cross Project - Internal Job view] Wrong Filter By Type list (#2608) * Fix [Models endpoints monitoring, metrics] increase the distance between metric histogram and line chart (#2609) * Fix [Batch Run, Function wizard] Number input for CPU is malfunctioning (#2610) * Fix [Artifacts] The 'Retry' deletion request does not include 'deletion_strategy' data (#2611) * Fix [Feature Store] "Discard changes" does not enable the "Save" and "Save and ingest" buttons (#2612) * Fix [Models endpoints monitoring, metrics] Y-Axis Labels Absent in Expanded Invocation Graph (#2613) * remove console.log * change DRIVER_SLEEP to 8000, add git restore for httpClient.js * Fix [Models endpoints monitoring, metrics] metric histogram chart does not display 100% of values (#2616) * Fix [Feature vectors] features version tags aren't changed when changing the project (#2615) --------- Co-authored-by: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Co-authored-by: Taras-Hlukhovetskyi <155433425+Taras-Hlukhovetskyi@users.noreply.github.com> Co-authored-by: orkoresh4 <138975214+orkoresh4@users.noreply.github.com> Co-authored-by: Andrew Mavdryk Co-authored-by: Ilank <63646693+ilan7empest@users.noreply.github.com> --- package.json | 6 +- scripts/ci-cd-scripts/commentedHttpClient.js | 1 - src/actions/details.js | 109 +- src/actions/featureStore.js | 5 - src/actions/functions.js | 36 +- src/actions/modelEndpoints.js | 135 + src/api/artifacts-api.js | 17 +- src/api/details-api.js | 18 +- src/api/mlrun-nuclio-api.js | 3 - src/api/modelEndpoints-api.js | 52 + src/common/ActionsMenu/ActionsMenu.js | 1 + src/common/TabsSlider/tabsSlider.scss | 2 +- src/common/TargetPath/targetPath.util.js | 2 +- src/components/ActionBar/ActionBar.js | 20 +- .../AddToFeatureVectorPage.js | 59 +- .../AddToFeatureVectorView.js | 53 +- src/components/Datasets/Datasets.js | 4 +- src/components/Datasets/datasets.util.js | 48 +- src/components/Details/Details.js | 34 +- .../Details/DetailsHeader/DetailsHeader.js | 44 +- src/components/Details/details.scss | 1 - src/components/DetailsInfo/DetailsInfoView.js | 2 +- src/components/DetailsInfo/detailsInfo.scss | 6 + .../DetailsInfo/detailsInfo.util.js | 9 +- .../DetailsMetrics/DetailsMetrics.js | 410 +- .../DetailsMetrics/DetailsMetrics.scss | 198 +- .../DetailsMetrics/IncvocationMetricCard.js | 162 +- .../DetailsMetrics/detailsMetrics.util.js | 102 +- .../DetailsRequestedFeaturesView.js | 9 +- .../FeatureSetsPanel/FeatureSetsPanel.js | 72 +- .../FeatureSetsPanelDataSource.js | 8 + .../FeatureSetsPanelDataSourceView.js | 3 + .../FeatureSetsPanelTargetStore.js | 8 + .../FeatureSetsPanelTargetStoreView.js | 3 + .../FeatureSetsPanelTitle.js | 47 +- .../FeatureSetsPanelTitleView.js | 34 +- .../FeatureSetsPanel/FeatureSetsPanelView.js | 4 + .../FeatureSetsPanel/UrlPath.utils.js | 6 +- src/components/FeatureStore/FeatureStore.js | 25 +- .../FeatureStore/Features/Features.js | 21 +- .../FeatureStore/Features/features.util.js | 23 +- .../FeatureStore/featureStore.util.js | 2 +- src/components/Files/Files.js | 4 +- src/components/Files/files.util.js | 7 +- src/components/FilterMenu/FilterMenu.js | 4 +- .../FilterMenu/filterMenu.settings.js | 11 +- src/components/FunctionsPage/Functions.js | 79 +- src/components/FunctionsPage/FunctionsView.js | 286 +- .../FunctionsPage/functions.util.js | 67 +- .../FunctionsPanel/FunctionsPanel.js | 87 +- .../FunctionsPanel/FunctionsPanelView.js | 4 +- src/components/Jobs/Jobs.js | 29 +- src/components/MetricChart/MetricChart.js | 7 +- .../MetricChart/metricChart.util.js | 11 +- .../ModelEndpoints/ModelEndpoints.js | 8 +- src/components/ModelsPage/Models/Models.js | 4 +- .../ModelsPage/Models/models.util.js | 10 +- .../projectsJobsMonitoring.scss | 4 - .../ProjectsMonitoring/ProjectsMonitoring.js | 4 +- src/components/ProjectsPage/ProjectsView.js | 1 - src/constants.js | 5 - .../ArtifactsTableRow/ArtifactsTableRow.js | 1 + .../DeleteArtifactPopUp.js | 14 +- .../DetailsInfoItem/DetailsInfoItem.js | 58 +- .../FeatureStoreTableRow.js | 6 +- .../formResourcesUnits.util.js | 4 +- .../FunctionsPanelGeneral.js | 36 +- .../FunctionsPanelGeneralView.js | 31 +- .../MetricsSelector/MetricsSelector.js | 61 +- .../MetricsSelector/metricsSelector.scss | 27 +- .../ProjectSettingsGeneral.js | 80 +- .../JobsCounters.js | 11 +- .../WorkflowsCounters.js | 11 +- .../projectsMonitoringCounters.scss | 7 +- src/elements/WorkflowsTable/WorkflowsTable.js | 5 +- src/hooks/useVirtualization.hook.js | 143 +- src/reducers/artifactsReducer.js | 7 +- src/reducers/featureStoreReducer.js | 13 - src/reducers/functionReducer.js | 50 +- src/scss/main.scss | 10 +- src/utils/artifacts.util.js | 2 +- src/utils/createArtifactsContent.js | 44 +- src/utils/createFeatureStoreContent.js | 4 - src/utils/generateMonitoringData.js | 116 +- src/utils/getMetricChartConfig.js | 2 +- src/utils/getUniqueIdentifier.js | 21 +- src/utils/handleDeleteArtifact.js | 12 +- src/utils/notifications.util.js | 2 +- src/utils/panelResources.util.js | 4 +- src/utils/parseArtifacts.js | 10 +- src/utils/parseFunction.js | 23 +- src/utils/parseFunctions.js | 7 +- tests/features/step-definitions/steps.js | 1 - tests/mockServer/data/artifacts.json | 4093 +++++++++-------- .../data/artifacts/html_mock_data.html | 37 + .../data/artifacts/image_mock_data.png | Bin 0 -> 18481 bytes .../data/artifacts/json_mock_data.json | 452 ++ .../data/artifacts/text_mock_data.txt | 1 + .../data/artifacts/yaml_mock_data.yaml | 405 ++ tests/mockServer/data/frontendSpec.json | 43 +- tests/mockServer/data/funcs.json | 136 +- tests/mockServer/data/metrics.json | 30 + tests/mockServer/mock.js | 272 +- ui_ci.groovy | 2 +- 104 files changed, 5219 insertions(+), 3511 deletions(-) create mode 100644 src/actions/modelEndpoints.js create mode 100644 src/api/modelEndpoints-api.js create mode 100644 tests/mockServer/data/artifacts/html_mock_data.html create mode 100644 tests/mockServer/data/artifacts/image_mock_data.png create mode 100644 tests/mockServer/data/artifacts/json_mock_data.json create mode 100644 tests/mockServer/data/artifacts/text_mock_data.txt create mode 100644 tests/mockServer/data/artifacts/yaml_mock_data.yaml diff --git a/package.json b/package.json index 6d3aa0f37..f4f6ec1ec 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "final-form-arrays": "^3.1.0", "fs-extra": "^10.0.0", "identity-obj-proxy": "^3.0.0", - "iguazio.dashboard-react-controls": "2.1.1", + "iguazio.dashboard-react-controls": "2.1.3", "is-wsl": "^1.1.0", "js-base64": "^2.5.2", "js-yaml": "^4.1.0", @@ -71,10 +71,10 @@ "mock-server": "node scripts/mockServer.js", "mock-server:dev": "nodemon --watch tests/mockServer scripts/mockServer.js", "add-comment-to-http-client": "node scripts/ci-cd-scripts/appendCommentToHttpClient.js", - "test:ci-cd-smoke-1": "DRIVER_SLEEP=5000 HEADLESS=true node scripts/ci-cd-scripts/tempSmoke1Test.js", + "test:ci-cd-smoke-1": "DRIVER_SLEEP=8000 HEADLESS=true node scripts/ci-cd-scripts/tempSmoke1Test.js && git restore src/httpClient.js", "test:ui": "node scripts/testui.js", "report": "node tests/report.js", - "test:regression": "DRIVER_SLEEP=5000 HEADLESS=true npm run test:ui && sleep 10 && echo 'Finished regression' && sleep 10 && npm run report", + "test:regression": "DRIVER_SLEEP=8000 HEADLESS=true npm run test:ui && sleep 10 && echo 'Finished regression' && sleep 10 && npm run report && git restore src/httpClient.js", "start:regression": "concurrently \"npm:mock-server\" \"npm:start\" \"npm:test:regression\"", "ui-steps": "export BABEL_ENV=test; export NODE_ENV=test; npx -p @babel/core -p @babel/node babel-node --presets @babel/preset-env scripts/collectUITestsSteps.js", "nli": "npm link iguazio.dashboard-react-controls", diff --git a/scripts/ci-cd-scripts/commentedHttpClient.js b/scripts/ci-cd-scripts/commentedHttpClient.js index 4e51b7039..42d878cbc 100644 --- a/scripts/ci-cd-scripts/commentedHttpClient.js +++ b/scripts/ci-cd-scripts/commentedHttpClient.js @@ -33,7 +33,6 @@ const headers = { 'Cache-Control': 'no-cache' } -console.log('----------- comments ----------') // serialize a param with an array value as a repeated param, for example: // { label: ['host', 'owner=admin'] } => 'label=host&label=owner%3Dadmin' const paramsSerializer = params => qs.stringify(params, { arrayFormat: 'repeat' }) diff --git a/src/actions/details.js b/src/actions/details.js index e004faa8a..7237c9f9c 100644 --- a/src/actions/details.js +++ b/src/actions/details.js @@ -22,21 +22,11 @@ import { FETCH_JOB_PODS_BEGIN, FETCH_JOB_PODS_FAILURE, FETCH_JOB_PODS_SUCCESS, - FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_BEGIN, - FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_FAILURE, - FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_SUCCESS, FETCH_MODEL_FEATURE_VECTOR_BEGIN, FETCH_MODEL_FEATURE_VECTOR_FAILURE, FETCH_MODEL_FEATURE_VECTOR_SUCCESS, - FETCH_ENDPOINT_METRICS_BEGIN, - FETCH_ENDPOINT_METRICS_SUCCESS, - FETCH_ENDPOINT_METRICS_FAILURE, - FETCH_ENDPOINT_METRICS_VALUES_BEGIN, - FETCH_ENDPOINT_METRICS_VALUES_SUCCESS, - FETCH_ENDPOINT_METRICS_VALUES_FAILURE, REMOVE_INFO_CONTENT, REMOVE_JOB_PODS, - REMOVE_MODEL_ENDPOINT, REMOVE_MODEL_FEATURE_VECTOR, RESET_CHANGES, SET_CHANGES, @@ -48,42 +38,11 @@ import { SET_INFO_CONTENT, SET_ITERATION, SET_ITERATION_OPTIONS, - SET_SELECTED_METRICS_OPTIONS, - SHOW_WARNING, - DEFAULT_ABORT_MSG + SHOW_WARNING } from '../constants' import { generatePods } from '../utils/generatePods' -import { - generateMetricsItems, - parseMetrics -} from '../components/DetailsMetrics/detailsMetrics.util' -import { TIME_FRAME_LIMITS } from '../utils/datePicker.util' const detailsActions = { - fetchModelEndpointWithAnalysis: (project, uid) => dispatch => { - dispatch(detailsActions.fetchModelEndpointWithAnalysisBegin()) - - return detailsApi - .getModelEndpoint(project, uid) - .then(({ data }) => { - dispatch(detailsActions.fetchModelEndpointWithAnalysisSuccess(data)) - - return data - }) - .catch(err => { - dispatch(detailsActions.fetchModelEndpointWithAnalysisFailure(err)) - }) - }, - fetchModelEndpointWithAnalysisBegin: () => ({ - type: FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_BEGIN - }), - fetchModelEndpointWithAnalysisFailure: () => ({ - type: FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_FAILURE - }), - fetchModelEndpointWithAnalysisSuccess: model => ({ - type: FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_SUCCESS, - payload: model - }), fetchModelFeatureVector: (project, name, reference) => dispatch => { dispatch(detailsActions.fetchModelFeatureVectorBegin()) @@ -136,71 +95,9 @@ const detailsActions = { type: FETCH_JOB_PODS_SUCCESS, payload: pods }), - fetchModelEndpointMetrics: (project, uid) => dispatch => { - dispatch(detailsActions.fetchEndpointMetricsBegin()) - - return detailsApi - .getModelEndpointMetrics(project, uid) - .then(({ data = [] }) => { - const metrics = generateMetricsItems(data) - - dispatch(detailsActions.fetchEndpointMetricsSuccess({ endpointUid: uid, metrics })) - - return metrics - }) - .catch(error => { - dispatch(detailsActions.fetchEndpointMetricsFailure(error)) - }) - }, - fetchEndpointMetricsBegin: () => ({ - type: FETCH_ENDPOINT_METRICS_BEGIN - }), - fetchEndpointMetricsFailure: error => ({ - type: FETCH_ENDPOINT_METRICS_FAILURE, - payload: error - }), - fetchEndpointMetricsSuccess: payload => ({ - type: FETCH_ENDPOINT_METRICS_SUCCESS, - payload - }), - fetchModelEndpointMetricsValues: (project, uid, params, signal) => dispatch => { - dispatch(detailsActions.fetchEndpointMetricsValuesBegin()) - - return detailsApi - .getModelEndpointMetricsValues(project, uid, params, signal) - .then(({ data = [] }) => { - const differenceInDays = params.end - params.start - const timeUnit = differenceInDays > TIME_FRAME_LIMITS['24_HOURS'] ? 'days' : 'hours' - const metrics = parseMetrics(data, timeUnit) - - dispatch(detailsActions.fetchEndpointMetricsValuesSuccess()) - - return metrics - }) - .catch(error => { - dispatch( - detailsActions.fetchEndpointMetricsValuesFailure( - error?.message === DEFAULT_ABORT_MSG ? null : error - ) - ) - }) - }, - fetchEndpointMetricsValuesBegin: () => ({ - type: FETCH_ENDPOINT_METRICS_VALUES_BEGIN - }), - fetchEndpointMetricsValuesFailure: error => ({ - type: FETCH_ENDPOINT_METRICS_VALUES_FAILURE, - payload: error - }), - fetchEndpointMetricsValuesSuccess: () => ({ - type: FETCH_ENDPOINT_METRICS_VALUES_SUCCESS - }), removeInfoContent: () => ({ type: REMOVE_INFO_CONTENT }), - removeModelEndpoint: () => ({ - type: REMOVE_MODEL_ENDPOINT - }), removeModelFeatureVector: () => ({ type: REMOVE_MODEL_FEATURE_VECTOR }), @@ -249,10 +146,6 @@ const detailsActions = { showWarning: show => ({ type: SHOW_WARNING, payload: show - }), - setSelectedMetricsOptions: payload => ({ - type: SET_SELECTED_METRICS_OPTIONS, - payload }) } diff --git a/src/actions/featureStore.js b/src/actions/featureStore.js index be98e810e..e5fa748aa 100644 --- a/src/actions/featureStore.js +++ b/src/actions/featureStore.js @@ -59,7 +59,6 @@ import { SET_NEW_FEATURE_SET_DATA_SOURCE_TIMESTAMP_COLUMN, SET_NEW_FEATURE_SET_DATA_SOURCE_URL, SET_NEW_FEATURE_SET_DESCRIPTION, - SET_NEW_FEATURE_SET_LABELS, SET_NEW_FEATURE_SET_NAME, SET_NEW_FEATURE_SET_PASSTHROUGH, SET_NEW_FEATURE_SET_SCHEDULE, @@ -412,10 +411,6 @@ const featureStoreActions = { type: SET_NEW_FEATURE_SET_DESCRIPTION, payload: description }), - setNewFeatureSetLabels: labels => ({ - type: SET_NEW_FEATURE_SET_LABELS, - payload: labels - }), setNewFeatureSetName: name => ({ type: SET_NEW_FEATURE_SET_NAME, payload: name diff --git a/src/actions/functions.js b/src/actions/functions.js index ab59176e6..f9c3044c4 100644 --- a/src/actions/functions.js +++ b/src/actions/functions.js @@ -70,7 +70,6 @@ import { SET_NEW_FUNCTION_HANDLER, SET_NEW_FUNCTION_IMAGE, SET_NEW_FUNCTION_KIND, - SET_NEW_FUNCTION_LABELS, SET_NEW_FUNCTION_NAME, SET_NEW_FUNCTION_PARAMETERS, SET_NEW_FUNCTION_PREEMTION_MODE, @@ -83,10 +82,7 @@ import { SET_NEW_FUNCTION_TAG, SET_NEW_FUNCTION_TRACK_MODELS, SET_NEW_FUNCTION_VOLUMES, - SET_NEW_FUNCTION_VOLUME_MOUNTS, - FETCH_FUNCTION_API_GATEWAYS_BEGIN, - FETCH_FUNCTION_API_GATEWAYS_FAILURE, - FETCH_FUNCTION_API_GATEWAYS_SUCCESS + SET_NEW_FUNCTION_VOLUME_MOUNTS } from '../constants' import { FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' import { generateCategories, generateHubCategories } from '../utils/generateTemplatesCategories' @@ -153,30 +149,6 @@ const functionsActions = { deployFunctionSuccess: () => ({ type: DEPLOY_FUNCTION_SUCCESS }), - fetchApiGateways: project => dispatch => { - dispatch(functionsActions.fetchApiGatewaysBegin()) - - return mlrunNuclioApi - .getApiGateways(project) - .then(({ data }) => { - dispatch(functionsActions.fetchApiGatewaysSuccess()) - - return data?.api_gateways - }) - .catch(error => { - dispatch(functionsActions.fetchApiGatewaysFailure(error)) - }) - }, - fetchApiGatewaysBegin: () => ({ - type: FETCH_FUNCTION_API_GATEWAYS_BEGIN - }), - fetchApiGatewaysFailure: error => ({ - type: FETCH_FUNCTION_API_GATEWAYS_FAILURE, - payload: error - }), - fetchApiGatewaysSuccess: () => ({ - type: FETCH_FUNCTION_API_GATEWAYS_SUCCESS - }), fetchFunctionLogs: (project, name, tag) => dispatch => { dispatch(functionsActions.fetchFunctionLogsBegin()) @@ -247,7 +219,7 @@ const functionsActions = { }) .catch(error => { dispatch(functionsActions.fetchFunctionTemplateFailure(error)) - showErrorNotification(dispatch, error, 'Function\'s template failed to load') + showErrorNotification(dispatch, error, "Function's template failed to load") }) }, fetchFunctionTemplateSuccess: selectFunction => ({ @@ -469,10 +441,6 @@ const functionsActions = { type: SET_NEW_FUNCTION_KIND, payload: kind }), - setNewFunctionLabels: labels => ({ - type: SET_NEW_FUNCTION_LABELS, - payload: labels - }), setNewFunctionCredentialsAccessKey: accessKey => ({ type: SET_NEW_FUNCTION_CREDENTIALS_ACCESS_KEY, payload: accessKey diff --git a/src/actions/modelEndpoints.js b/src/actions/modelEndpoints.js new file mode 100644 index 000000000..02196bb75 --- /dev/null +++ b/src/actions/modelEndpoints.js @@ -0,0 +1,135 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import modelEndpointsApi from '../api/modelEndpoints-api' +import { + DEFAULT_ABORT_MSG, + FETCH_ENDPOINT_METRICS_BEGIN, + FETCH_ENDPOINT_METRICS_SUCCESS, + FETCH_ENDPOINT_METRICS_FAILURE, + FETCH_ENDPOINT_METRICS_VALUES_BEGIN, + FETCH_ENDPOINT_METRICS_VALUES_SUCCESS, + FETCH_ENDPOINT_METRICS_VALUES_FAILURE, + FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_BEGIN, + FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_FAILURE, + FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_SUCCESS, + REMOVE_MODEL_ENDPOINT, + SET_SELECTED_METRICS_OPTIONS +} from '../constants' + +import { + generateMetricsItems, + parseMetrics +} from '../components/DetailsMetrics/detailsMetrics.util' +import { TIME_FRAME_LIMITS } from '../utils/datePicker.util' + +const modelEndpointsActions = { + fetchModelEndpointWithAnalysis: (project, uid) => dispatch => { + dispatch(modelEndpointsActions.fetchModelEndpointWithAnalysisBegin()) + + return modelEndpointsApi + .getModelEndpoint(project, uid) + .then(({ data }) => { + dispatch(modelEndpointsActions.fetchModelEndpointWithAnalysisSuccess(data)) + + return data + }) + .catch(err => { + dispatch(modelEndpointsActions.fetchModelEndpointWithAnalysisFailure(err)) + }) + }, + fetchModelEndpointWithAnalysisBegin: () => ({ + type: FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_BEGIN + }), + fetchModelEndpointWithAnalysisFailure: () => ({ + type: FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_FAILURE + }), + fetchModelEndpointWithAnalysisSuccess: model => ({ + type: FETCH_MODEL_ENDPOINT_WITH_ANALYSIS_SUCCESS, + payload: model + }), + fetchModelEndpointMetrics: (project, uid) => dispatch => { + dispatch(modelEndpointsActions.fetchEndpointMetricsBegin()) + + return modelEndpointsApi + .getModelEndpointMetrics(project, uid) + .then(({ data = [] }) => { + const metrics = generateMetricsItems(data) + + dispatch(modelEndpointsActions.fetchEndpointMetricsSuccess({ endpointUid: uid, metrics })) + + return metrics + }) + .catch(error => { + dispatch(modelEndpointsActions.fetchEndpointMetricsFailure(error)) + }) + }, + fetchEndpointMetricsBegin: () => ({ + type: FETCH_ENDPOINT_METRICS_BEGIN + }), + fetchEndpointMetricsFailure: error => ({ + type: FETCH_ENDPOINT_METRICS_FAILURE, + payload: error + }), + fetchEndpointMetricsSuccess: payload => ({ + type: FETCH_ENDPOINT_METRICS_SUCCESS, + payload + }), + fetchModelEndpointMetricsValues: (project, uid, params, signal) => dispatch => { + dispatch(modelEndpointsActions.fetchEndpointMetricsValuesBegin()) + + return modelEndpointsApi + .getModelEndpointMetricsValues(project, uid, params, signal) + .then(({ data = [] }) => { + const differenceInDays = params.end - params.start + const timeUnit = differenceInDays > TIME_FRAME_LIMITS['24_HOURS'] ? 'days' : 'hours' + const metrics = parseMetrics(data, timeUnit) + + dispatch(modelEndpointsActions.fetchEndpointMetricsValuesSuccess()) + + return metrics + }) + .catch(error => { + dispatch( + modelEndpointsActions.fetchEndpointMetricsValuesFailure( + error?.message === DEFAULT_ABORT_MSG ? null : error + ) + ) + }) + }, + fetchEndpointMetricsValuesBegin: () => ({ + type: FETCH_ENDPOINT_METRICS_VALUES_BEGIN + }), + fetchEndpointMetricsValuesFailure: error => ({ + type: FETCH_ENDPOINT_METRICS_VALUES_FAILURE, + payload: error + }), + fetchEndpointMetricsValuesSuccess: () => ({ + type: FETCH_ENDPOINT_METRICS_VALUES_SUCCESS + }), + removeModelEndpoint: () => ({ + type: REMOVE_MODEL_ENDPOINT + }), + setSelectedMetricsOptions: payload => ({ + type: SET_SELECTED_METRICS_OPTIONS, + payload + }) +} + +export default modelEndpointsActions diff --git a/src/api/artifacts-api.js b/src/api/artifacts-api.js index 41c3854e5..6f410b020 100644 --- a/src/api/artifacts-api.js +++ b/src/api/artifacts-api.js @@ -56,11 +56,10 @@ const fetchArtifacts = (project, filters, config = {}, withLatestTag) => { const artifactsApi = { addTag: (project, tag, data) => mainHttpClient.put(`/projects/${project}/tags/${tag}`, data), buildFunction: data => mainHttpClient.post('/build/function', data), - deleteArtifact: (project, key, tag, tree, deletion_strategy, secrets) => { + deleteArtifact: (project, key, uid, deletion_strategy, secrets) => { const config = { params: { - tag, - tree + 'object-uid': uid } } @@ -201,18 +200,6 @@ const artifactsApi = { } ) }, - getModelEndpoints: (project, filters, config = {}, params = {}) => { - const newConfig = { - ...config, - params - } - - if (filters?.labels) { - newConfig.params.label = filters.labels?.split(',') - } - - return mainHttpClient.get(`/projects/${project}/model-endpoints`, newConfig) - }, getModels: (project, filters, config = {}) => { const newConfig = { ...config, diff --git a/src/api/details-api.js b/src/api/details-api.js index 98e92edee..56d183d27 100644 --- a/src/api/details-api.js +++ b/src/api/details-api.js @@ -31,23 +31,9 @@ const detailsApi = { return mainHttpClient.get(`/projects/${project}/runtime-resources`, { params }) }, - getModelEndpoint: (project, uid) => - mainHttpClient.get(`/projects/${project}/model-endpoints/${uid}?feature_analysis=true`), - getModelFeatureVector: (project, name, reference) => - mainHttpClient.get(`/projects/${project}/feature-vectors/${name}/references/${reference}`), - getModelEndpointMetrics: (project, uid, type = 'all') => - mainHttpClient.get(`/projects/${project}/model-endpoints/${uid}/metrics?type=${type}`), // type=results/metrics/all - getModelEndpointMetricsValues: (project, uid, params, signal) => { - const config = { - params - } - if (signal) { - config.signal = signal - } - - return mainHttpClient.get(`/projects/${project}/model-endpoints/${uid}/metrics-values`, config) - } + getModelFeatureVector: (project, name, reference) => + mainHttpClient.get(`/projects/${project}/feature-vectors/${name}/references/${reference}`) } export default detailsApi diff --git a/src/api/mlrun-nuclio-api.js b/src/api/mlrun-nuclio-api.js index 7f259a163..1e37ecac2 100644 --- a/src/api/mlrun-nuclio-api.js +++ b/src/api/mlrun-nuclio-api.js @@ -20,9 +20,6 @@ such restriction. import { mainHttpClient } from '../httpClient' const mlrunNuclioApi = { - getApiGateways: (projectName, config) => { - return mainHttpClient.get(`/projects/${projectName}/api-gateways`, config) - }, getDeployLogs: (projectName, functionName, config) => { return mainHttpClient.get(`/projects/${projectName}/nuclio/${functionName}/deploy`, config) } diff --git a/src/api/modelEndpoints-api.js b/src/api/modelEndpoints-api.js new file mode 100644 index 000000000..c76db7fd3 --- /dev/null +++ b/src/api/modelEndpoints-api.js @@ -0,0 +1,52 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import { mainHttpClient } from '../httpClient' + +const modelEndpointsApi = { + getModelEndpoint: (project, uid) => + mainHttpClient.get(`/projects/${project}/model-endpoints/${uid}?feature_analysis=true`), + getModelEndpoints: (project, filters, config = {}, params = {}) => { + const newConfig = { + ...config, + params + } + + if (filters?.labels) { + newConfig.params.label = filters.labels?.split(',') + } + + return mainHttpClient.get(`/projects/${project}/model-endpoints`, newConfig) + }, + getModelEndpointMetrics: (project, uid, type = 'all') => + mainHttpClient.get(`/projects/${project}/model-endpoints/${uid}/metrics?type=${type}`), // type=results/metrics/all + getModelEndpointMetricsValues: (project, uid, params, signal) => { + const config = { + params + } + + if (signal) { + config.signal = signal + } + + return mainHttpClient.get(`/projects/${project}/model-endpoints/${uid}/metrics-values`, config) + } +} + +export default modelEndpointsApi diff --git a/src/common/ActionsMenu/ActionsMenu.js b/src/common/ActionsMenu/ActionsMenu.js index 2ad0f2e48..e94375401 100644 --- a/src/common/ActionsMenu/ActionsMenu.js +++ b/src/common/ActionsMenu/ActionsMenu.js @@ -127,6 +127,7 @@ const ActionsMenu = ({ dataItem, menu, menuPosition, time, withQuickActions }) = setIsShowMenu(prevValue => !prevValue) }} ref={actionMenuBtnRef} + tooltipText="More actions" > diff --git a/src/common/TabsSlider/tabsSlider.scss b/src/common/TabsSlider/tabsSlider.scss index 6d1938b19..0bbdbc456 100644 --- a/src/common/TabsSlider/tabsSlider.scss +++ b/src/common/TabsSlider/tabsSlider.scss @@ -5,7 +5,7 @@ display: flex; align-items: stretch; background-color: $white; - padding-bottom: 30px; + padding-bottom: 15px; &__tabs-wrapper { overflow-x: hidden; diff --git a/src/common/TargetPath/targetPath.util.js b/src/common/TargetPath/targetPath.util.js index ec2e97bf2..b0f2957c7 100644 --- a/src/common/TargetPath/targetPath.util.js +++ b/src/common/TargetPath/targetPath.util.js @@ -43,7 +43,7 @@ const targetPathRegex = const httpTargetPathRegex = /^(http|https):(\/\/\/|\/\/)(?!.*:\/\/)([\w\-._~:/?#[\]%@!$&'()*+,;=]+)$/i const mlrunTargetPathRegex = - /^(artifacts|feature-vectors|datasets|models)\/(.+?)\/(.+?)(#(.+?))?(:(.+?))?(@(.+))?$/ + /^(artifacts|feature-vectors|datasets|models)\/(\S+?)\/(\S+?)(#(\S+?))?(:(\S+?))?(@(\S+))?$/ export const pathPlaceholders = { [MLRUN_STORAGE_INPUT_PATH_SCHEME]: 'artifacts/my-project/my-artifact:my-tag', diff --git a/src/components/ActionBar/ActionBar.js b/src/components/ActionBar/ActionBar.js index a1b7159a6..894306cb8 100644 --- a/src/components/ActionBar/ActionBar.js +++ b/src/components/ActionBar/ActionBar.js @@ -298,13 +298,19 @@ const ActionBar = ({ ActionBar.propTypes = { actionButtons: PropTypes.arrayOf( - PropTypes.shape({ - className: PropTypes.string, - hidden: PropTypes.bool, - label: PropTypes.string.isRequired, - onClick: PropTypes.func.isRequired, - variant: PropTypes.string.isRequired - }) + PropTypes.oneOfType([ + PropTypes.shape({ + className: PropTypes.string, + hidden: PropTypes.bool, + label: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + variant: PropTypes.string + }), + PropTypes.shape({ + hidden: PropTypes.bool.isRequired, + template: PropTypes.object.isRequired, + }) + ]) ), cancelRequest: PropTypes.func, expand: PropTypes.bool, diff --git a/src/components/AddToFeatureVectorPage/AddToFeatureVectorPage.js b/src/components/AddToFeatureVectorPage/AddToFeatureVectorPage.js index f093b6ea2..6c1dace7f 100644 --- a/src/components/AddToFeatureVectorPage/AddToFeatureVectorPage.js +++ b/src/components/AddToFeatureVectorPage/AddToFeatureVectorPage.js @@ -30,24 +30,30 @@ import { GROUP_BY_NAME, GROUP_BY_NONE, TAG_FILTER_ALL_ITEMS, - REQUEST_CANCELED + REQUEST_CANCELED, + LARGE_REQUEST_CANCELED, + CANCEL_REQUEST_TIMEOUT } from '../../constants' import featureStoreActions from '../../actions/featureStore' import { FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' import { createFeaturesRowData } from '../../utils/createFeatureStoreContent' import { filters } from './addToFeatureVectorPage.util' import { getFeatureIdentifier } from '../../utils/getUniqueIdentifier' +import { handleFeaturesResponse } from '../FeatureStore/Features/features.util' import { isEveryObjectValueEmpty } from '../../utils/isEveryObjectValueEmpty' -import { setFilters } from '../../reducers/filtersReducer' +import { getFilterTagOptions, setFilters } from '../../reducers/filtersReducer' import { setNotification } from '../../reducers/notificationReducer' import { setTablePanelOpen } from '../../reducers/tableReducer' import { showErrorNotification } from '../../utils/notifications.util' import { useGetTagOptions } from '../../hooks/useGetTagOptions.hook' import { useGroupContent } from '../../hooks/groupContent.hook' +import { useVirtualization } from '../../hooks/useVirtualization.hook' import { useYaml } from '../../hooks/yaml.hook' import { ReactComponent as Yaml } from 'igz-controls/images/yaml.svg' +import cssVariables from '../FeatureStore/Features/features.scss' + const AddToFeatureVectorPage = ({ createNewFeatureVector, featureStore, @@ -59,6 +65,7 @@ const AddToFeatureVectorPage = ({ }) => { const [content, setContent] = useState([]) const [selectedRowData, setSelectedRowData] = useState({}) + const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') const [convertedYaml, toggleConvertedYaml] = useYaml('') const addToFeatureVectorPageRef = useRef(null) const abortControllerRef = useRef(new AbortController()) @@ -141,21 +148,39 @@ const AddToFeatureVectorPage = ({ async filters => { abortControllerRef.current = new AbortController() + const cancelRequestTimeout = setTimeout(() => { + abortControllerRef.current.abort(LARGE_REQUEST_CANCELED) + }, CANCEL_REQUEST_TIMEOUT) + const config = { signal: abortControllerRef.current.signal } - fetchFeatures(filters.project, filters, config).then(result => { - if (result) { - setContent(result) - } - - return result - }) + fetchFeatures(filters.project, filters, config) + .then(features => { + return handleFeaturesResponse( + features, + setContent, + abortControllerRef, + setLargeRequestErrorMessage + ) + }) + .finally(() => clearTimeout(cancelRequestTimeout)) }, [fetchFeatures] ) + const handleRefresh = filters => { + dispatch( + getFilterTagOptions({ fetchTags: fetchFeatureSetsTags, project: filters.project }) + ) + + setContent([]) + setSelectedRowData({}) + + return fetchData(filters) + } + const handleRemoveFeature = useCallback( feature => { const newStoreSelectedRowData = { @@ -274,21 +299,35 @@ const AddToFeatureVectorPage = ({ } }, [dispatch]) + const virtualizationConfig = useVirtualization({ + rowsData: { + content: tableContent, + expandedRowsData: selectedRowData + }, + heightData: { + headerRowHeight: cssVariables.featuresHeaderRowHeight, + rowHeight: cssVariables.featuresRowHeight, + rowHeightExtended: cssVariables.featuresRowHeightExtended + } + }) + return ( ) } diff --git a/src/components/AddToFeatureVectorPage/AddToFeatureVectorView.js b/src/components/AddToFeatureVectorPage/AddToFeatureVectorView.js index e6bab5982..08dca1056 100644 --- a/src/components/AddToFeatureVectorPage/AddToFeatureVectorView.js +++ b/src/components/AddToFeatureVectorPage/AddToFeatureVectorView.js @@ -29,9 +29,11 @@ import Table from '../Table/Table' import FeatureStoreTableRow from '../../elements/FeatureStoreTableRow/FeatureStoreTableRow' import YamlModal from '../../common/YamlModal/YamlModal' -import { filters } from './addToFeatureVectorPage.util' import { ADD_TO_FEATURE_VECTOR_TAB, FEATURE_STORE_PAGE } from '../../constants' +import { VIRTUALIZATION_CONFIG } from '../../types' +import { filters } from './addToFeatureVectorPage.util' import { getNoDataMessage } from '../../utils/getNoDataMessage' +import { isRowRendered } from '../../hooks/useVirtualization.hook' const AddToFeatureVectorView = React.forwardRef( ( @@ -40,14 +42,16 @@ const AddToFeatureVectorView = React.forwardRef( content, convertedYaml, featureStore, - fetchData, filtersStore, handleExpandRow, + handleRefresh, + largeRequestErrorMessage, pageData, selectedRowData, tableContent, tableStore, - toggleConvertedYaml + toggleConvertedYaml, + virtualizationConfig }, ref ) => { @@ -63,7 +67,7 @@ const AddToFeatureVectorView = React.forwardRef(
@@ -73,7 +77,7 @@ const AddToFeatureVectorView = React.forwardRef( message={getNoDataMessage( filtersStore, filters, - null, + largeRequestErrorMessage, FEATURE_STORE_PAGE, ADD_TO_FEATURE_VECTOR_TAB )} @@ -84,23 +88,28 @@ const AddToFeatureVectorView = React.forwardRef( actionsMenu={actionsMenu} hideActionsMenu={tableStore.isTablePanelOpen} pageData={pageData} - retryRequest={fetchData} + retryRequest={handleRefresh} tab={ADD_TO_FEATURE_VECTOR_TAB} + tableClassName="features-table" tableHeaders={tableContent[0]?.content ?? []} + virtualizationConfig={virtualizationConfig} > - {tableContent.map((tableItem, index) => ( - - ))} + {tableContent.map( + (tableItem, index) => + isRowRendered(virtualizationConfig, index) && ( + + ) + )} )} @@ -119,14 +128,16 @@ AddToFeatureVectorView.propTypes = { content: PropTypes.arrayOf(PropTypes.object).isRequired, convertedYaml: PropTypes.string.isRequired, featureStore: PropTypes.object.isRequired, - fetchData: PropTypes.func.isRequired, filtersStore: PropTypes.object.isRequired, handleExpandRow: PropTypes.func.isRequired, + handleRefresh: PropTypes.func.isRequired, + largeRequestErrorMessage: PropTypes.string.isRequired, pageData: PropTypes.object.isRequired, selectedRowData: PropTypes.object.isRequired, tableContent: PropTypes.arrayOf(PropTypes.object).isRequired, tableStore: PropTypes.object.isRequired, - toggleConvertedYaml: PropTypes.func.isRequired + toggleConvertedYaml: PropTypes.func.isRequired, + virtualizationConfig: VIRTUALIZATION_CONFIG.isRequired } export default AddToFeatureVectorView diff --git a/src/components/Datasets/Datasets.js b/src/components/Datasets/Datasets.js index e29bce017..25f8d8e46 100644 --- a/src/components/Datasets/Datasets.js +++ b/src/components/Datasets/Datasets.js @@ -366,6 +366,7 @@ const Datasets = () => { setDatasets([]) dispatch(removeDataSets()) setSelectedDatasetMin({}) + setSelectedRowData({}) abortControllerRef.current.abort(REQUEST_CANCELED) tagAbortControllerCurrent.abort(REQUEST_CANCELED) } @@ -392,7 +393,8 @@ const Datasets = () => { headerRowHeight: cssVariables.datasetsHeaderRowHeight, rowHeight: cssVariables.datasetsRowHeight, rowHeightExtended: cssVariables.datasetsRowHeightExtended - } + }, + activateTableScroll: true }) return ( diff --git a/src/components/Datasets/datasets.util.js b/src/components/Datasets/datasets.util.js index f44adad3c..e94c372c5 100644 --- a/src/components/Datasets/datasets.util.js +++ b/src/components/Datasets/datasets.util.js @@ -21,6 +21,7 @@ import React from 'react' import { isEqual } from 'lodash' import JobWizard from '../JobWizard/JobWizard' +import DeleteArtifactPopUp from '../../elements/DeleteArtifactPopUp/DeleteArtifactPopUp' import { ACTION_MENU_PARENT_ROW, @@ -257,6 +258,7 @@ export const generateActionsMenu = ( selectedDataset ) => { const isTargetPathValid = getIsTargetPathValid(datasetMin ?? {}, frontendSpec) + const datasetDataCouldBeDeleted = datasetMin?.target_path?.endsWith('.pq') || datasetMin?.target_path?.endsWith('.parquet') const getFullDataset = datasetMin => { return chooseOrFetchArtifact(dispatch, DATASETS_TAB, selectedDataset, datasetMin) @@ -304,29 +306,32 @@ export const generateActionsMenu = ( { label: 'Delete', icon: , - disabled: !datasetMin?.tag, hidden: [ACTION_MENU_PARENT_ROW, ACTION_MENU_PARENT_ROW_EXPANDED].includes(menuPosition), - tooltip: !datasetMin?.tag - ? 'A tag is required to delete a dataset. Open the dataset, click on the edit icon, and assign a tag before proceeding with the deletion' - : '', className: 'danger', onClick: () => - openDeleteConfirmPopUp( - 'Delete dataset?', - `Do you want to delete the dataset "${datasetMin.db_key}"? Deleted datasets can not be restored.`, - () => { - handleDeleteArtifact( - dispatch, - projectName, - datasetMin.db_key, - datasetMin.tag, - datasetMin.tree, - handleRefresh, - datasetsFilters, - DATASET_TYPE - ) - } - ) + datasetDataCouldBeDeleted ? + openPopUp(DeleteArtifactPopUp, { + artifact: datasetMin, + artifactType: DATASET_TYPE, + category: DATASET_TYPE, + filters: datasetsFilters, + handleRefresh + }) + : openDeleteConfirmPopUp( + 'Delete dataset?', + `Do you want to delete the dataset "${datasetMin.db_key}"? Deleted datasets can not be restored.`, + () => { + handleDeleteArtifact( + dispatch, + projectName, + datasetMin.db_key, + datasetMin.uid, + handleRefresh, + datasetsFilters, + DATASET_TYPE + ) + } + ) }, { label: 'Delete all', @@ -342,8 +347,7 @@ export const generateActionsMenu = ( dispatch, projectName, datasetMin.db_key, - datasetMin.tag, - datasetMin.tree, + datasetMin.uid, handleRefresh, datasetsFilters, DATASET_TYPE, diff --git a/src/components/Details/Details.js b/src/components/Details/Details.js index 88b82dcb6..ac8d39029 100644 --- a/src/components/Details/Details.js +++ b/src/components/Details/Details.js @@ -27,11 +27,12 @@ import { Form } from 'react-final-form' import { isEqual, pickBy } from 'lodash' import classnames from 'classnames' -import DetailsTabsContent from './DetailsTabsContent/DetailsTabsContent' import { ConfirmDialog } from 'igz-controls/components' -import Loader from '../../common/Loader/Loader' import ErrorMessage from '../../common/ErrorMessage/ErrorMessage' +import DetailsTabsContent from './DetailsTabsContent/DetailsTabsContent' + import DetailsHeader from './DetailsHeader/DetailsHeader' +import Loader from '../../common/Loader/Loader' import TabsSlider from '../../common/TabsSlider/TabsSlider' import { TERTIARY_BUTTON, PRIMARY_BUTTON } from 'igz-controls/constants' @@ -55,7 +56,6 @@ import { import { isEveryObjectValueEmpty } from '../../utils/isEveryObjectValueEmpty' import { showArtifactsPreview } from '../../reducers/artifactsReducer' import { setFieldState } from 'igz-controls/utils/form.util' -import { datePickerPastOptions, PAST_24_HOUR_DATE_OPTION } from '../../utils/datePicker.util' import './details.scss' @@ -78,11 +78,9 @@ const Details = ({ setChanges, setChangesCounter, setChangesData, - setDetailsDates, setInfoContent, setIteration, setIterationOption, - setSelectedMetricsOptions, setFiltersWasHandled, showWarning, tab @@ -248,30 +246,6 @@ const Details = ({ setFiltersWasHandled ]) - const handleChangeDates = useCallback( - (dates, isPredefined, selectedOptionId) => { - const generatedDates = [...dates] - - if (generatedDates.length === 1) { - generatedDates.push(new Date()) - } - setDetailsDates({ - value: generatedDates, - selectedOptionId, - isPredefined - }) - }, - [setDetailsDates] - ) - - useEffect(() => { - const past24hoursOption = datePickerPastOptions.find( - option => option.id === PAST_24_HOUR_DATE_OPTION - ) - - handleChangeDates(past24hoursOption.handler(), true, PAST_24_HOUR_DATE_OPTION) - }, [handleChangeDates]) - return (
{}}> {formState => ( @@ -287,13 +261,11 @@ const Details = ({ getCloseDetailsLink={getCloseDetailsLink} isDetailsScreen={isDetailsScreen} handleCancel={handleCancel} - handleChangeDates={handleChangeDates} handleRefresh={handleRefresh} handleShowWarning={handleShowWarning} pageData={pageData} selectedItem={selectedItem} setIteration={setIteration} - setSelectedMetricsOptions={setSelectedMetricsOptions} tab={tab} /> diff --git a/src/components/Details/DetailsHeader/DetailsHeader.js b/src/components/Details/DetailsHeader/DetailsHeader.js index 1e189afa4..94de7d231 100644 --- a/src/components/Details/DetailsHeader/DetailsHeader.js +++ b/src/components/Details/DetailsHeader/DetailsHeader.js @@ -28,20 +28,12 @@ import { Button, Tooltip, TextTooltipTemplate, RoundedIcon } from 'igz-controls/ import LoadButton from '../../../common/LoadButton/LoadButton' import Select from '../../../common/Select/Select' import ActionsMenu from '../../../common/ActionsMenu/ActionsMenu' -import MetricsSelector from '../../../elements/MetricsSelector/MetricsSelector' -import DatePicker from '../../../common/DatePicker/DatePicker' -import { - DETAILS_ARTIFACTS_TAB, - DETAILS_METRICS_TAB, - FULL_VIEW_MODE, - JOBS_PAGE -} from '../../../constants' +import { DETAILS_ARTIFACTS_TAB, FULL_VIEW_MODE, JOBS_PAGE } from '../../../constants' import { formatDatetime } from '../../../utils' import { LABEL_BUTTON } from 'igz-controls/constants' import { ACTIONS_MENU } from '../../../types' import { getViewMode } from '../../../utils/helper' -import { TIME_FRAME_LIMITS } from '../../../utils/datePicker.util' import { ReactComponent as Close } from 'igz-controls/images/close.svg' import { ReactComponent as Back } from 'igz-controls/images/back-arrow.svg' @@ -56,14 +48,12 @@ const DetailsHeader = ({ cancelChanges, getCloseDetailsLink, handleCancel, - handleChangeDates, handleRefresh, handleShowWarning, isDetailsScreen, pageData, selectedItem, setIteration, - setSelectedMetricsOptions, tab }) => { const [headerIsMultiline, setHeaderIsMultiline] = useState(false) @@ -101,7 +91,6 @@ const DetailsHeader = ({ let prevHeaderHeight = 0 const resizeObserver = new ResizeObserver(entries => { for (let entry of entries) { - if (entry.contentRect.height !== prevHeaderHeight) { prevHeaderHeight = entry.contentRect.height if (entry.contentRect.height > 100) { @@ -111,7 +100,7 @@ const DetailsHeader = ({ } } } - },) + }) resizeObserver.observe(headerRef.current) @@ -200,29 +189,6 @@ const DetailsHeader = ({
- {params.tab === DETAILS_METRICS_TAB && ( - <> - - setSelectedMetricsOptions({ endpointUid: selectedItem.metadata.uid, metrics }) - } - preselectedMetrics={detailsStore.metricsOptions.preselected} - /> - - - )} {params.tab === DETAILS_ARTIFACTS_TAB && detailsStore.iteration && ( { +export const generateStatusFilter = useFailedStatus => { const status = useFailedStatus ? 'failed' : 'error' - if (tab === JOBS_MONITORING_JOBS_TAB) { - return jobsStatus - } else if (tab === JOBS_MONITORING_WORKFLOWS_TAB) { - return workflowsStatus - } else { - return [ - { label: 'All', id: FILTER_ALL_ITEMS, status: 'all' }, - { label: 'Completed', id: 'completed', status: 'completed' }, - { label: 'Running', id: 'running', status: 'running' }, - { label: 'Pending', id: 'pending', status: 'pending' }, - { label: 'Error', id: status, status: status }, - { label: 'Aborted', id: 'aborted', status: 'aborted' } - ] - } + return [ + { label: 'All', id: FILTER_ALL_ITEMS, status: FILTER_ALL_ITEMS }, + { label: 'Completed', id: 'completed', status: 'completed' }, + { label: 'Running', id: 'running', status: 'running' }, + { label: 'Pending', id: 'pending', status: 'pending' }, + { label: 'Error', id: status, status: status }, + { label: 'Aborted', id: 'aborted', status: 'aborted' } + ] } export const generateTypeFilter = () => { diff --git a/src/components/FilterMenuModal/FilterMenuModal.js b/src/components/FilterMenuModal/FilterMenuModal.js index 46bac4fd3..ac4b1945e 100644 --- a/src/components/FilterMenuModal/FilterMenuModal.js +++ b/src/components/FilterMenuModal/FilterMenuModal.js @@ -30,7 +30,11 @@ import { PopUpDialog, RoundedIcon, Button } from 'igz-controls/components' import { FILTER_MENU_MODAL } from '../../constants' import { isTargetElementInContainerElement } from '../../utils/checkElementsPosition.utils' -import { setModalFiltersInitialValues, setModalFiltersValues } from '../../reducers/filtersReducer' +import { + resetModalFilter, + setModalFiltersInitialValues, + setModalFiltersValues +} from '../../reducers/filtersReducer' import { ReactComponent as FilterIcon } from 'igz-controls/images/filter.svg' @@ -39,17 +43,17 @@ import './filterMenuModal.scss' export const FilterMenuWizardContext = React.createContext({}) const FilterMenuModal = ({ - applyChanges, - applyButton, - cancelButton, + applyChanges = null, + applyButton = { label: 'Apply', variant: 'secondary' }, + cancelButton = { label: 'Clear', variant: 'tertiary' }, children, filterMenuName, - header, + header = 'Filter by', initialValues, - restartFormTrigger, + restartFormTrigger = null, values, - withoutApplyButton, - wizardClassName + withoutApplyButton = false, + wizardClassName = '' }) => { const [filtersWizardIsShown, setFiltersWizardIsShown] = useState(false) const filtersIconButtonRef = useRef() @@ -69,12 +73,6 @@ const FilterMenuModal = ({ const filtersWizardClassnames = classnames('filters-wizard', wizardClassName) - useEffect(() => { - if (formRef.current && !isEqual(formRef.current.getState().values, values)) { - formRef.current.reset(values) - } - }, [initialValues, values]) - useEffect(() => { if (!has(filtersData, 'initialValues')) { dispatch(setModalFiltersInitialValues({ name: filterMenuName, value: initialValues })) @@ -108,7 +106,10 @@ const FilterMenuModal = ({ }, []) useEffect(() => { - const throttledHideFiltersWizard = throttle(hideFiltersWizard, 500, { leading: true, trailing: true }) + const throttledHideFiltersWizard = throttle(hideFiltersWizard, 500, { + leading: true, + trailing: true + }) window.addEventListener('click', hideFiltersWizard) window.addEventListener('scroll', throttledHideFiltersWizard, true) @@ -123,8 +124,16 @@ const FilterMenuModal = ({ return () => { ref.restart(initialValues) + dispatch(resetModalFilter(filterMenuName)) } - }, [params.pageTab, params.projectName, restartFormTrigger, dispatch, initialValues]) + }, [ + params.pageTab, + params.projectName, + restartFormTrigger, + dispatch, + initialValues, + filterMenuName + ]) const getFilterCounter = formState => { const initialValues = applyChanges ? filtersData?.initialValues : formState.initialValues @@ -232,16 +241,6 @@ const FilterMenuModal = ({ ) } -FilterMenuModal.defaultProps = { - applyChanges: null, - applyButton: { label: 'Apply', variant: 'secondary' }, - cancelButton: { label: 'Clear', variant: 'tertiary' }, - header: 'Filter by', - restartFormTrigger: null, - withoutApplyButton: false, - wizardClassName: '' -} - FilterMenuModal.propTypes = { applyChanges: PropTypes.func, applyButton: PropTypes.shape({ diff --git a/src/components/FunctionsPage/Functions.js b/src/components/FunctionsPage/Functions.js index 76a9b3eef..d89f09c16 100644 --- a/src/components/FunctionsPage/Functions.js +++ b/src/components/FunctionsPage/Functions.js @@ -27,34 +27,41 @@ import JobWizard from '../JobWizard/JobWizard' import NewFunctionPopUp from '../../elements/NewFunctionPopUp/NewFunctionPopUp' import { - FUNCTION_FILTERS, FUNCTIONS_PAGE, GROUP_BY_NAME, - SHOW_UNTAGGED_ITEMS, TAG_LATEST, REQUEST_CANCELED, DETAILS_BUILD_LOG_TAB, - JOB_DEFAULT_OUTPUT_PATH + JOB_DEFAULT_OUTPUT_PATH, + DATES_FILTER, + NAME_FILTER, + SHOW_UNTAGGED_FILTER, + FUNCTION_FILTERS } from '../../constants' -import createFunctionsContent from '../../utils/createFunctionsContent' -import functionsActions from '../../actions/functions' -import jobsActions from '../../actions/jobs' -import { DANGER_BUTTON, LABEL_BUTTON } from 'igz-controls/constants' import { + fetchInitialFunctions, generateActionsMenu, - filters, generateFunctionsPageData, pollDeletingFunctions, setFullSelectedFunction } from './functions.util' +import { + ANY_TIME_DATE_OPTION, + datePickerPastOptions, + getDatePickerFilterValue +} from '../../utils/datePicker.util' +import createFunctionsContent from '../../utils/createFunctionsContent' +import functionsActions from '../../actions/functions' +import jobsActions from '../../actions/jobs' +import { DANGER_BUTTON, LABEL_BUTTON } from 'igz-controls/constants' import { getFunctionIdentifier } from '../../utils/getUniqueIdentifier' import { getFunctionNuclioLogs, getFunctionLogs } from '../../utils/getFunctionLogs' +import { isBackgroundTaskRunning } from '../../utils/poll.util' import { isDetailsTabExists } from '../../utils/isDetailsTabExists' import { openPopUp } from 'igz-controls/utils/common.util' import { parseFunctions } from '../../utils/parseFunctions' -import { setFilters } from '../../reducers/filtersReducer' +import { setFilters, setFiltersValues, setModalFiltersValues } from '../../reducers/filtersReducer' import { setNotification } from '../../reducers/notificationReducer' -import { isBackgroundTaskRunning } from '../../utils/poll.util' import { showErrorNotification } from '../../utils/notifications.util' import { useGroupContent } from '../../hooks/groupContent.hook' import { useMode } from '../../hooks/mode.hook' @@ -81,19 +88,19 @@ const Functions = ({ const [selectedFunctionMin, setSelectedFunctionMin] = useState({}) const [selectedFunction, setSelectedFunction] = useState({}) const [editableItem, setEditableItem] = useState(null) - const [taggedFunctions, setTaggedFunctions] = useState([]) const [functionsPanelIsOpen, setFunctionsPanelIsOpen] = useState(false) const [jobWizardIsOpened, setJobWizardIsOpened] = useState(false) const [jobWizardMode, setJobWizardMode] = useState(null) const filtersStore = useSelector(store => store.filtersStore) const [selectedRowData, setSelectedRowData] = useState({}) - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') const [deletingFunctions, setDeletingFunctions] = useState({}) const abortControllerRef = useRef(new AbortController()) const fetchFunctionLogsTimeout = useRef(null) const fetchFunctionNuclioLogsTimeout = useRef(null) const nameFilterRef = useRef('') const terminatePollRef = useRef(null) + const functionsAreInitializedRef = useRef(false) const { isDemoMode, isStagingMode } = useMode() const params = useParams() const navigate = useNavigate() @@ -114,7 +121,7 @@ const Functions = ({ return fetchFunctions(params.projectName, filters, { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage }, params: { format: 'minimal' @@ -207,7 +214,7 @@ const Functions = ({ } const { latestItems, handleExpandRow, expand, handleExpandAll } = useGroupContent( - taggedFunctions, + functions, getFunctionIdentifier, handleCollapse, handleExpand, @@ -477,7 +484,13 @@ const Functions = ({ ] ) - const functionsFilters = useMemo(() => [filters[0]], []) + const functionsFiltersConfig = useMemo(() => { + return { + [NAME_FILTER]: { label: 'Name:' }, + [DATES_FILTER]: { label: 'Updated:' }, + [SHOW_UNTAGGED_FILTER]: { label: 'Show untagged:' } + } + }, []) useEffect(() => { setFullSelectedFunction( @@ -490,24 +503,48 @@ const Functions = ({ ) }, [dispatch, fetchFunction, navigate, params.projectName, selectedFunctionMin]) + useLayoutEffect(() => { + if ( + !functionsAreInitializedRef.current && + (params.funcName || (params.hash && params.hash.includes('@'))) + ) { + const funcName = params.funcName || params.hash.split('@')[0] + + dispatch( + setFiltersValues({ + name: FUNCTION_FILTERS, + value: { + [NAME_FILTER]: funcName, + [DATES_FILTER]: getDatePickerFilterValue(datePickerPastOptions, ANY_TIME_DATE_OPTION) + } + }) + ) + dispatch( + setModalFiltersValues({ + name: FUNCTION_FILTERS, + value: { + [SHOW_UNTAGGED_FILTER]: true + } + }) + ) + } + }, [dispatch, params]) + + useEffect(() => { + fetchInitialFunctions(filtersStore, fetchData, functionsAreInitializedRef) + }, [filtersStore, fetchData]) + useEffect(() => { - fetchData() + const abortController = abortControllerRef.current return () => { + functionsAreInitializedRef.current = false setSelectedFunctionMin({}) setFunctions([]) setSelectedRowData({}) - abortControllerRef.current.abort(REQUEST_CANCELED) + abortController.abort(REQUEST_CANCELED) } - }, [params.projectName, fetchData]) - - useEffect(() => { - setTaggedFunctions( - !filtersStore.filterMenuModal[FUNCTION_FILTERS].values.showUntagged - ? functions.filter(func => func.tag.length) - : functions - ) - }, [filtersStore.filterMenuModal, functions]) + }, [params.projectName]) useEffect(() => { if (params.hash && pageData.details.menu.length > 0) { @@ -563,21 +600,11 @@ const Functions = ({ ]) useEffect(() => { - dispatch(setFilters({ groupBy: GROUP_BY_NAME, showUntagged: SHOW_UNTAGGED_ITEMS })) + dispatch(setFilters({ groupBy: GROUP_BY_NAME })) }, [dispatch, params.projectName]) const filtersChangeCallback = filters => { - if ( - !filters.showUntagged && - filters.showUntagged !== filtersStore.filterMenuModal[FUNCTION_FILTERS].values.showUntagged && - selectedFunction.hash - ) { - navigate(`/projects/${params.projectName}/functions`) - } else if ( - filters.showUntagged === filtersStore.filterMenuModal[FUNCTION_FILTERS].values.showUntagged - ) { - refreshFunctions(filters) - } + refreshFunctions(filters) } const handleSelectFunction = item => { @@ -707,14 +734,15 @@ const Functions = ({ diff --git a/src/components/FunctionsPage/FunctionsFilters.js b/src/components/FunctionsPage/FunctionsFilters.js index f5c581f54..6d003c8e6 100644 --- a/src/components/FunctionsPage/FunctionsFilters.js +++ b/src/components/FunctionsPage/FunctionsFilters.js @@ -18,23 +18,17 @@ under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ import React from 'react' -import { useForm } from 'react-final-form' -import { FormCheckBox, FormOnChange } from 'igz-controls/components' +import { FormCheckBox } from 'igz-controls/components' -import { SHOW_UNTAGGED_FILTER, SHOW_UNTAGGED_ITEMS } from '../../constants' +import { SHOW_UNTAGGED_FILTER } from '../../constants' const FunctionsFilters = () => { - const { change } = useForm() - const handleIter = value => { - change(SHOW_UNTAGGED_ITEMS, value ? SHOW_UNTAGGED_ITEMS : '') - } return ( <>
-
) diff --git a/src/components/FunctionsPage/FunctionsView.js b/src/components/FunctionsPage/FunctionsView.js index 57d8dd513..13ebb4e25 100644 --- a/src/components/FunctionsPage/FunctionsView.js +++ b/src/components/FunctionsPage/FunctionsView.js @@ -39,8 +39,7 @@ import { PANEL_EDIT_MODE } from '../../constants' import { SECONDARY_BUTTON } from 'igz-controls/constants' -import { VIRTUALIZATION_CONFIG } from '../../types' -import { filters } from './functions.util' +import { FILTERS_CONFIG, VIRTUALIZATION_CONFIG } from '../../types' import { getNoDataMessage } from '../../utils/getNoDataMessage' import { isRowRendered } from '../../hooks/useVirtualization.hook' @@ -54,7 +53,8 @@ const FunctionsView = ({ expand, filtersChangeCallback, filtersStore, - functionsFilters, + functions, + functionsFiltersConfig, functionsPanelIsOpen, functionsStore, getPopUpTemplate, @@ -65,13 +65,12 @@ const FunctionsView = ({ handleExpandRow, handleSelectFunction, isDemoMode, - largeRequestErrorMessage, pageData, refreshFunctions, + requestErrorMessage, selectedFunction, selectedRowData, tableContent, - taggedFunctions, toggleConvertedYaml, virtualizationConfig }) => { @@ -88,7 +87,7 @@ const FunctionsView = ({ {functionsStore.loading ? ( - ) : taggedFunctions.length === 0 ? ( + ) : functions.length === 0 ? ( @@ -197,14 +197,15 @@ FunctionsView.defaultPropTypes = { FunctionsView.propTypes = { actionsMenu: PropTypes.func.isRequired, closePanel: PropTypes.func.isRequired, - createFunctionSuccess: PropTypes.func.isRequired, confirmData: PropTypes.object, convertedYaml: PropTypes.string.isRequired, + createFunctionSuccess: PropTypes.func.isRequired, editableItem: PropTypes.object, expand: PropTypes.bool.isRequired, filtersChangeCallback: PropTypes.func.isRequired, filtersStore: PropTypes.object.isRequired, - functionsFilters: PropTypes.arrayOf(PropTypes.object).isRequired, + functions: PropTypes.arrayOf(PropTypes.object).isRequired, + functionsFiltersConfig: FILTERS_CONFIG.isRequired, functionsPanelIsOpen: PropTypes.bool.isRequired, functionsStore: PropTypes.object.isRequired, getPopUpTemplate: PropTypes.func.isRequired, @@ -214,13 +215,12 @@ FunctionsView.propTypes = { handleExpandAll: PropTypes.func.isRequired, handleExpandRow: PropTypes.func.isRequired, handleSelectFunction: PropTypes.func.isRequired, - largeRequestErrorMessage: PropTypes.string.isRequired, pageData: PropTypes.object.isRequired, refreshFunctions: PropTypes.func.isRequired, + requestErrorMessage: PropTypes.string.isRequired, selectedFunction: PropTypes.object.isRequired, selectedRowData: PropTypes.object.isRequired, tableContent: PropTypes.arrayOf(PropTypes.object).isRequired, - taggedFunctions: PropTypes.arrayOf(PropTypes.object).isRequired, toggleConvertedYaml: PropTypes.func.isRequired, virtualizationConfig: VIRTUALIZATION_CONFIG.isRequired } diff --git a/src/components/FunctionsPage/functions.util.js b/src/components/FunctionsPage/functions.util.js index d3488be32..d98dfadad 100644 --- a/src/components/FunctionsPage/functions.util.js +++ b/src/components/FunctionsPage/functions.util.js @@ -22,9 +22,12 @@ import { debounce, get, isEmpty } from 'lodash' import { DETAILS_BUILD_LOG_TAB, + FILTER_MENU, + FILTER_MENU_MODAL, FUNCTION_CREATING_STATE, FUNCTION_ERROR_STATE, FUNCTION_FAILED_STATE, + FUNCTION_FILTERS, FUNCTION_INITIALIZED_STATE, FUNCTION_PENDINDG_STATE, FUNCTION_READY_STATE, @@ -37,9 +40,7 @@ import { FUNCTION_TYPE_REMOTE, FUNCTION_TYPE_SERVING, FUNCTIONS_PAGE, - NAME_FILTER, - PANEL_FUNCTION_CREATE_MODE, - SHOW_UNTAGGED_FILTER + PANEL_FUNCTION_CREATE_MODE } from '../../constants' import jobsActions from '../../actions/jobs' import tasksApi from '../../api/tasks-api' @@ -89,10 +90,6 @@ export const infoHeaders = [ { label: 'Image', id: 'image' }, { label: 'Description', id: 'description' } ] -export const filters = [ - { type: NAME_FILTER, initialValue: '', label: 'Name:' }, - { type: SHOW_UNTAGGED_FILTER, label: 'Show untagged' } -] export const TRANSIENT_FUNCTION_STATUSES = [FUNCTION_PENDINDG_STATE, FUNCTION_RUNNING_STATE] export const generateFunctionsPageData = ( @@ -292,6 +289,18 @@ export const generateActionsMenu = ( ] } +export const fetchInitialFunctions = debounce( + (filtersStore, fetchData, functionsAreInitializedRef) => { + if (!functionsAreInitializedRef.current) { + fetchData({ + ...filtersStore[FILTER_MENU][FUNCTION_FILTERS], + ...filtersStore[FILTER_MENU_MODAL][FUNCTION_FILTERS].values + }) + functionsAreInitializedRef.current = true + } + } +) + export const pollDeletingFunctions = ( project, terminatePollRef, diff --git a/src/components/FunctionsPanel/FunctionsPanel.js b/src/components/FunctionsPanel/FunctionsPanel.js index 717bdcf5f..e0e6e1d44 100644 --- a/src/components/FunctionsPanel/FunctionsPanel.js +++ b/src/components/FunctionsPanel/FunctionsPanel.js @@ -46,7 +46,7 @@ const FunctionsPanel = ({ closePanel, createFunctionSuccess, createNewFunction, - defaultData, + defaultData = null, deployFunction, fetchFunction, functionsStore, @@ -89,7 +89,9 @@ const FunctionsPanel = ({ const navigate = useNavigate() const formRef = React.useRef( createForm({ - initialValues: { labels: parseChipsData(defaultData?.labels || {}, frontendSpec.internal_labels) }, + initialValues: { + labels: parseChipsData(defaultData?.labels || {}, frontendSpec.internal_labels) + }, mutators: { ...arrayMutators, setFieldState }, onSubmit: () => {} }) @@ -191,7 +193,7 @@ const FunctionsPanel = ({ }, metadata: { ...functionsStore.newFunction.metadata, - labels: convertChipsData(formRef.current.getFieldState('labels')?.value), + labels: convertChipsData(formRef.current.getFieldState('labels')?.value) } }, skip_deployed, @@ -322,18 +324,12 @@ const FunctionsPanel = ({ /> ) - } - } - - , + }} + , document.getElementById('overlay_container') ) } -FunctionsPanel.defaultProps = { - defaultData: null -} - FunctionsPanel.propTypes = { closePanel: PropTypes.func.isRequired, createFunctionSuccess: PropTypes.func.isRequired, diff --git a/src/components/FunctionsPanel/FunctionsPanelView.js b/src/components/FunctionsPanel/FunctionsPanelView.js index 4fde13d1d..12f6352bf 100644 --- a/src/components/FunctionsPanel/FunctionsPanelView.js +++ b/src/components/FunctionsPanel/FunctionsPanelView.js @@ -45,8 +45,8 @@ const FunctionsPanelView = ({ checkValidation, closePanel, confirmData, - defaultData, - error, + defaultData = {}, + error = '', formState, frontendSpec, functionsStore, @@ -92,7 +92,11 @@ const FunctionsPanelView = ({ iconClassName="new-item-side-panel__expand-icon" openByDefault > - + {}, + onWizardClose = null, params, - prePopulatedData, + prePopulatedData = {}, removeJobFunction, removeHubFunctions, runNewJob, - wizardTitle + wizardTitle = 'Batch run' }) => { const formRef = React.useRef( createForm({ @@ -289,10 +289,7 @@ const JobWizard = ({ } ] - const formIsSubmittedAndInvalid = - formState.submitting || (formState.invalid && formState.submitFailed) - - if (formIsSubmittedAndInvalid) { + if (isSubmitDisabled(formState)) { stepsConfig.forEach(stepConfig => { if (stepConfig.id in formState.errors) { stepConfig.invalid = true @@ -406,8 +403,7 @@ const JobWizard = ({ const getActions = useCallback( ({ allStepsAreEnabled, goToFirstInvalidStep }, formState) => { - const formIsSubmittedAndInvalid = - formState.submitting || (formState.invalid && formState.submitFailed) + const submitIsDisabled = isSubmitDisabled(formState) return [ { @@ -426,7 +422,7 @@ const JobWizard = ({ goToFirstInvalidStep() } }, - disabled: !allStepsAreEnabled || formIsSubmittedAndInvalid, + disabled: !allStepsAreEnabled || submitIsDisabled, variant: 'tertiary', ref: scheduleButtonRef }, @@ -443,7 +439,7 @@ const JobWizard = ({ onClick: () => { submitRequest(formState, false, goToFirstInvalidStep) }, - disabled: !allStepsAreEnabled || formIsSubmittedAndInvalid, + disabled: !allStepsAreEnabled || submitIsDisabled, variant: 'secondary' } ] @@ -531,7 +527,10 @@ const JobWizard = ({ /> )} - {(functionsStore.loading || functionsStore.funcLoading || jobsStore.loading || projectIsLoading) && } + {(functionsStore.loading || + functionsStore.funcLoading || + jobsStore.loading || + projectIsLoading) && } ) }} @@ -539,17 +538,6 @@ const JobWizard = ({ ) } -JobWizard.defaultProps = { - defaultData: {}, - isBatchInference: false, - isTrain: false, - mode: PANEL_CREATE_MODE, - onSuccessRequest: () => {}, - onWizardClose: () => {}, - prePopulatedData: {}, - wizardTitle: 'Batch run' -} - JobWizard.propTypes = { defaultData: PropTypes.shape({}), isBatchInference: PropTypes.bool, diff --git a/src/components/JobWizard/JobWizard.util.js b/src/components/JobWizard/JobWizard.util.js index 9211e7a7f..0814957ba 100644 --- a/src/components/JobWizard/JobWizard.util.js +++ b/src/components/JobWizard/JobWizard.util.js @@ -699,7 +699,7 @@ export const parseDefaultParameters = (funcParams = {}, runParams = {}, runHyper isRequired: parametersIsRequired, isDefault: true, isPredefined: true, - parameterTypeOptions: getParameterTypeOptions(parameterType) + parameterTypeOptions: getParameterTypeOptions(parameter.type) } }) .sort(sortParameters) @@ -1152,15 +1152,15 @@ export const getSaveJobErrorMsg = error => { } export const getParameterTypeOptions = (parameterType = '') => { - const match = parameterType.match(/Union\[(.*?)\]$/) + const unionTypes = parameterType.match(/Union\[(.*?)\]$/) - if (match) { + if (unionTypes) { const uniqueUnionTypesList = [ ...new Set( - match[1].split(',').map(unionType => { + unionTypes[1].split(',').map(unionType => { const trimmedUnionType = unionType.trim().toLowerCase() - return trimmedUnionType.startsWith('list') ? 'list' : trimmedUnionType + return trimmedUnionType.startsWith(parameterTypeList) ? parameterTypeList : trimmedUnionType }) ) ] @@ -1174,6 +1174,15 @@ export const getParameterTypeOptions = (parameterType = '') => { } return parametersValueTypeOptions + } else if (parameterType) { + const filteredValueTypeOptions = parametersValueTypeOptions.filter(function (option) { + const parsedParameterType = parameterType?.toLocaleLowerCase()?.startsWith(parameterTypeList) + ? parameterTypeList + : parameterType + return parsedParameterType === option.id + }) + + return !isEmpty(filteredValueTypeOptions) ? filteredValueTypeOptions : parametersValueTypeOptions } return parametersValueTypeOptions diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardAdvanced/JobWizardAdvanced.js b/src/components/JobWizard/JobWizardSteps/JobWizardAdvanced/JobWizardAdvanced.js index d57f959fd..bb60f12bf 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardAdvanced/JobWizardAdvanced.js +++ b/src/components/JobWizard/JobWizardSteps/JobWizardAdvanced/JobWizardAdvanced.js @@ -28,7 +28,7 @@ import { secretsKindOptions } from './JobWizardAdvanced.util' import './jobWizardAdvanced.scss' -const JobWizardAdvanced = ({ formState, stepIsActive }) => { +const JobWizardAdvanced = ({ formState, stepIsActive = false }) => { const [showSecrets] = useState(false) return ( @@ -90,10 +90,6 @@ const JobWizardAdvanced = ({ formState, stepIsActive }) => { ) } -JobWizardAdvanced.defaultProps = { - stepIsActive: false -} - JobWizardAdvanced.propTypes = { formState: PropTypes.shape({}).isRequired, stepIsActive: PropTypes.bool diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardDataInputs/JobWizardDataInputs.js b/src/components/JobWizard/JobWizardSteps/JobWizardDataInputs/JobWizardDataInputs.js index c13078c19..27faa7ed3 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardDataInputs/JobWizardDataInputs.js +++ b/src/components/JobWizard/JobWizardSteps/JobWizardDataInputs/JobWizardDataInputs.js @@ -24,7 +24,7 @@ import FormDataInputsTable from '../../../../elements/FormDataInputsTable/FormDa import { DATA_INPUTS_STEP } from '../../../../constants' -const JobWizardDataInputs = ({ formState, params, stepIsActive }) => { +const JobWizardDataInputs = ({ formState, params, stepIsActive = false }) => { return (
@@ -36,17 +36,13 @@ const JobWizardDataInputs = ({ formState, params, stepIsActive }) => { fieldsPath={`${DATA_INPUTS_STEP}.dataInputsTable`} formState={formState} params={params} - rowCanBeAdded={formState.values.runDetails.handlerData?.has_kwargs} + hasKwargs={formState.values.runDetails.handlerData?.has_kwargs} />
) } -JobWizardDataInputs.defaultProps = { - stepIsActive: false -} - JobWizardDataInputs.propTypes = { formState: PropTypes.shape({}).isRequired, params: PropTypes.shape({}).isRequired, diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.js b/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.js index 8382e85dd..3ce8e9336 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.js +++ b/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.js @@ -57,7 +57,7 @@ import './jobWizardFunctionSelection.scss' const JobWizardFunctionSelection = ({ activeTab, - currentProject, + currentProject = null, defaultData, filteredFunctions, filteredTemplates, @@ -79,7 +79,7 @@ const JobWizardFunctionSelection = ({ setShowSchedule, setTemplates, setTemplatesCategories, - stepIsActive, + stepIsActive = false, templates, templatesCategories }) => { @@ -89,14 +89,17 @@ const JobWizardFunctionSelection = ({ const [filterByName, setFilterByName] = useState('') const [filterMatches, setFilterMatches] = useState([]) const [projects, setProjects] = useState(generateProjectsList(projectNames, params.projectName)) + const [functionsRequestErrorMessage, setFunctionsRequestErrorMessage] = useState('') + const [hubFunctionsRequestErrorMessage, setHubFunctionsRequestErrorMessage] = useState('') const selectedActiveTab = useRef(null) const functionSelectionRef = useRef(null) + const hubFunctionLoadedRef = useRef(false) const filtersStoreHubCategories = useSelector( store => store.filtersStore[FILTER_MENU_MODAL][JOB_WIZARD_FILTERS]?.values?.[HUB_CATEGORIES_FILTER] ) - const { hubFunctions, hubFunctionsCatalog, loading } = useSelector(store => store.functionsStore) + const { loading } = useSelector(store => store.functionsStore) const dispatch = useDispatch() @@ -239,7 +242,9 @@ const JobWizardFunctionSelection = ({ } const onSelectedProjectNameChange = currentValue => { - dispatch(functionsActions.fetchFunctions(currentValue, {})).then(functions => { + dispatch( + functionsActions.fetchFunctions(currentValue, {}, {}, setFunctionsRequestErrorMessage) + ).then(functions => { if (functions) { const validFunctions = functions.filter(func => { return includes(FUNCTION_RUN_KINDS, func.kind) @@ -276,39 +281,32 @@ const JobWizardFunctionSelection = ({ } useEffect(() => { - if ( - activeTab === FUNCTIONS_SELECTION_HUB_TAB && - (isEmpty(hubFunctions) || isEmpty(hubFunctionsCatalog)) - ) { - dispatch(functionsActions.fetchHubFunctions()).then(templatesObject => { - if (templatesObject) { - setTemplatesCategories(templatesObject.hubFunctionsCategories) - setTemplates(templatesObject.hubFunctions) - - formState.initialValues[FUNCTION_SELECTION_STEP].templatesLabels = - templatesObject.hubFunctions.reduce((labels, template) => { - labels[template.metadata.name] = template.ui.categories.map(categoryId => { - return { - id: categoryId, - key: getCategoryName(categoryId), - isKeyOnly: true - } - }) - - return labels - }, {}) + if (activeTab === FUNCTIONS_SELECTION_HUB_TAB && !hubFunctionLoadedRef.current) { + dispatch(functionsActions.fetchHubFunctions({}, setHubFunctionsRequestErrorMessage)).then( + templatesObject => { + if (templatesObject) { + setTemplatesCategories(templatesObject.hubFunctionsCategories) + setTemplates(templatesObject.hubFunctions) + + formState.initialValues[FUNCTION_SELECTION_STEP].templatesLabels = + templatesObject.hubFunctions.reduce((labels, template) => { + labels[template.metadata.name] = template.ui.categories.map(categoryId => { + return { + id: categoryId, + key: getCategoryName(categoryId), + isKeyOnly: true + } + }) + + return labels + }, {}) + + hubFunctionLoadedRef.current = true + } } - }) + ) } - }, [ - activeTab, - dispatch, - formState.initialValues, - hubFunctions, - hubFunctionsCatalog, - setTemplates, - setTemplatesCategories - ]) + }, [activeTab, dispatch, formState.initialValues, setTemplates, setTemplatesCategories]) const selectProjectFunction = functionData => { const selectNewFunction = () => { @@ -404,7 +402,7 @@ const JobWizardFunctionSelection = ({ ((filterByName.length > 0 && (filterMatches.length === 0 || isEmpty(filteredFunctions))) || isEmpty(functions)) ? ( - + ) : (
{(filteredFunctions.length > 0 ? filteredFunctions : functions) @@ -455,7 +453,7 @@ const JobWizardFunctionSelection = ({ ((filterByName.length > 0 && (filterMatches.length === 0 || isEmpty(filteredTemplates))) || isEmpty(templates)) ? ( - + ) : (
{filteredTemplates @@ -494,11 +492,6 @@ const JobWizardFunctionSelection = ({ ) } -JobWizardFunctionSelection.defaultProps = { - currentProject: null, - stepIsActive: false -} - JobWizardFunctionSelection.propTypes = { activeTab: PropTypes.string.isRequired, currentProject: PropTypes.shape({}), diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardParameters/JobWizardParameters.js b/src/components/JobWizard/JobWizardSteps/JobWizardParameters/JobWizardParameters.js index 9ff505383..bc01cb52f 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardParameters/JobWizardParameters.js +++ b/src/components/JobWizard/JobWizardSteps/JobWizardParameters/JobWizardParameters.js @@ -33,7 +33,7 @@ import { import './jobWizardParameters.scss' -const JobWizardParameters = ({ formState, stepIsActive }) => { +const JobWizardParameters = ({ formState, stepIsActive = false }) => { const parametersFromPath = `${PARAMETERS_STEP}.parametersFrom` const parametersFromFileUrlPath = `${PARAMETERS_STEP}.parametersFromFileUrl` @@ -70,7 +70,7 @@ const JobWizardParameters = ({ formState, stepIsActive }) => { fieldsPath={`${PARAMETERS_STEP}.parametersTable`} formState={formState} parametersFromPath={parametersFromPath} - rowCanBeAdded={formState.values.runDetails.handlerData?.has_kwargs} + hasKwargs={formState.values.runDetails.handlerData?.has_kwargs} withHyperparameters={ hyperParametersAreEnabled && selectedFromValue === PARAMETERS_FROM_UI_VALUE } @@ -79,10 +79,6 @@ const JobWizardParameters = ({ formState, stepIsActive }) => { ) } -JobWizardParameters.defaultProps = { - stepIsActive: false -} - JobWizardParameters.propTypes = { formState: PropTypes.shape({}).isRequired, stepIsActive: PropTypes.bool diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardResources/JobWizardResources.js b/src/components/JobWizard/JobWizardSteps/JobWizardResources/JobWizardResources.js index a929b99c3..6590731d7 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardResources/JobWizardResources.js +++ b/src/components/JobWizard/JobWizardSteps/JobWizardResources/JobWizardResources.js @@ -31,7 +31,7 @@ import { getValidationRules } from 'igz-controls/utils/validation.util' import './jobWizardResources.scss' -const JobWizardResources = ({ formState, frontendSpec, stepIsActive }) => { +const JobWizardResources = ({ formState, frontendSpec, stepIsActive = false }) => { const validFunctionPriorityClassNames = useMemo(() => { return (frontendSpec.valid_function_priority_class_names ?? []).map(className => ({ id: className, @@ -94,10 +94,6 @@ const JobWizardResources = ({ formState, frontendSpec, stepIsActive }) => { ) } -JobWizardResources.defaultProps = { - stepIsActive: false -} - JobWizardResources.propTypes = { formState: PropTypes.shape({}).isRequired, frontendSpec: PropTypes.shape({}).isRequired, diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardRunDetails/JobWizardRunDetails.js b/src/components/JobWizard/JobWizardSteps/JobWizardRunDetails/JobWizardRunDetails.js index c9e1332eb..1ab85e1b1 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardRunDetails/JobWizardRunDetails.js +++ b/src/components/JobWizard/JobWizardSteps/JobWizardRunDetails/JobWizardRunDetails.js @@ -44,7 +44,10 @@ import { import { SECONDARY_BUTTON, TERTIARY_BUTTON } from 'igz-controls/constants' import { areFormValuesChanged } from 'igz-controls/utils/form.util' import { getChipOptions } from '../../../../utils/getChipOptions' -import { getValidationRules, getInternalLabelsValidationRule } from 'igz-controls/utils/validation.util' +import { + getValidationRules, + getInternalLabelsValidationRule +} from 'igz-controls/utils/validation.util' import { openPopUp } from 'igz-controls/utils/common.util' import { generateJobWizardData, @@ -57,7 +60,7 @@ import { import './jobWizardRunDetails.scss' const JobWizardRunDetails = ({ - currentProject, + currentProject = null, formState, frontendSpec, isBatchInference, @@ -268,7 +271,10 @@ const JobWizardRunDetails = ({ shortChips visibleChipsMaxLength="all" validationRules={{ - key: getValidationRules('job.label.key', getInternalLabelsValidationRule(frontendSpec.internal_labels)), + key: getValidationRules( + 'job.label.key', + getInternalLabelsValidationRule(frontendSpec.internal_labels) + ), value: getValidationRules('job.label.value') }} /> @@ -380,9 +386,6 @@ const JobWizardRunDetails = ({ ) ) } -JobWizardRunDetails.defaultProps = { - currentProject: null -} JobWizardRunDetails.propTypes = { currentProject: PropTypes.shape({}), diff --git a/src/components/Jobs/MonitorJobs/MonitorJobs.js b/src/components/Jobs/MonitorJobs/MonitorJobs.js index d27a1ebf9..e3aa10a16 100644 --- a/src/components/Jobs/MonitorJobs/MonitorJobs.js +++ b/src/components/Jobs/MonitorJobs/MonitorJobs.js @@ -48,7 +48,7 @@ const MonitorJobs = ({ fetchAllJobRuns, fetchJobs }) => { const appStore = useSelector(store => store.appStore) const filtersStore = useSelector(store => store.filtersStore) const jobsStore = useSelector(store => store.jobsStore) - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') const params = useParams() const dispatch = useDispatch() const { isStagingMode } = useMode() @@ -103,7 +103,7 @@ const MonitorJobs = ({ fetchAllJobRuns, fetchJobs }) => { { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage }, params: { ...newParams } }, @@ -267,7 +267,7 @@ const MonitorJobs = ({ fetchAllJobRuns, fetchJobs }) => { filters={filters} jobRuns={jobRuns} jobs={jobs} - largeRequestErrorMessage={largeRequestErrorMessage} + requestErrorMessage={requestErrorMessage} navigateLink={`/projects/${params.projectName}/jobs/${MONITOR_JOBS_TAB}`} refreshJobs={refreshJobs} selectedJob={selectedJob} diff --git a/src/components/Jobs/MonitorWorkflows/MonitorWorkflows.js b/src/components/Jobs/MonitorWorkflows/MonitorWorkflows.js index 99d41442d..46c69b455 100644 --- a/src/components/Jobs/MonitorWorkflows/MonitorWorkflows.js +++ b/src/components/Jobs/MonitorWorkflows/MonitorWorkflows.js @@ -47,7 +47,7 @@ import './monitorWorkflows.scss' const MonitorWorkflows = ({ deleteWorkflows, fetchFunctionLogs, fetchWorkflows }) => { const [selectedFunction, setSelectedFunction] = useState({}) - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') const [workflowsAreLoaded, setWorkflowsAreLoaded] = useState(false) const [workflowIsLoaded, setWorkflowIsLoaded] = useState(false) const [itemIsSelected, setItemIsSelected] = useState(false) @@ -82,11 +82,11 @@ const MonitorWorkflows = ({ deleteWorkflows, fetchFunctionLogs, fetchWorkflows } fetchWorkflows(params.projectName, filter, { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage } }) }, - [fetchWorkflows, params.projectName, setLargeRequestErrorMessage] + [fetchWorkflows, params.projectName, setRequestErrorMessage] ) useEffect(() => { @@ -189,8 +189,8 @@ const MonitorWorkflows = ({ deleteWorkflows, fetchFunctionLogs, fetchWorkflows } filters={filters} getWorkflows={getWorkflows} itemIsSelected={itemIsSelected} - largeRequestErrorMessage={largeRequestErrorMessage} ref={{ abortJobRef }} + requestErrorMessage={requestErrorMessage} selectedFunction={selectedFunction} selectedJob={selectedJob} setItemIsSelected={setItemIsSelected} diff --git a/src/components/Jobs/ScheduledJobs/ScheduledJobs.js b/src/components/Jobs/ScheduledJobs/ScheduledJobs.js index e1f5eb248..342f7a025 100644 --- a/src/components/Jobs/ScheduledJobs/ScheduledJobs.js +++ b/src/components/Jobs/ScheduledJobs/ScheduledJobs.js @@ -41,7 +41,7 @@ import { setFilters } from '../../../reducers/filtersReducer' const ScheduledJobs = ({ fetchScheduledJobs }) => { const [jobs, setJobs] = useState([]) const [dataIsLoaded, setDataIsLoaded] = useState(false) - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') const abortControllerRef = useRef(new AbortController()) const dispatch = useDispatch() @@ -65,7 +65,7 @@ const ScheduledJobs = ({ fetchScheduledJobs }) => { fetchScheduledJobs(params.projectName, filters, { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage } }).then(jobs => { if (jobs) { @@ -111,7 +111,7 @@ const ScheduledJobs = ({ fetchScheduledJobs }) => { context={JobsContext} filters={filters} jobs={jobs} - largeRequestErrorMessage={largeRequestErrorMessage} + requestErrorMessage={requestErrorMessage} refreshJobs={refreshJobs} tableContent={tableContent} /> diff --git a/src/components/Jobs/jobs.util.js b/src/components/Jobs/jobs.util.js index 96f88138a..9b83e21a9 100644 --- a/src/components/Jobs/jobs.util.js +++ b/src/components/Jobs/jobs.util.js @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { capitalize, chain, defaultsDeep, get, isEmpty } from 'lodash' +import { get } from 'lodash' import tasksApi from '../../api/tasks-api' @@ -39,8 +39,6 @@ import jobsActions from '../../actions/jobs' import { generateKeyValues, truncateUid } from '../../utils' import { BG_TASK_FAILED, BG_TASK_SUCCEEDED, pollTask } from '../../utils/poll.util' import { setNotification } from '../../reducers/notificationReducer' -import { generateFunctionPriorityLabel } from '../../utils/generateFunctionPriorityLabel' -import { parseKeyValues } from '../../utils' import { showErrorNotification } from '../../utils/notifications.util' export const page = JOBS_PAGE @@ -330,63 +328,6 @@ export const monitorJob = (jobs_dashboard_url, item, projectName) => { window.open(redirectUrl, '_blank') } -/** - * Enriches a job run object with the associated function tag(s) - * @param {Object} jobRun - The job run object to enrich - * @param {Object} dispatch - dispatch method - * @param {Function} fetchJobFunctions - The function to fetch job functions from an API - * @param {Object} fetchJobFunctionsPromiseRef - A ref object used to store a reference to - * the promise returned by the `fetchJobFunctions` function. - * @returns {Promise} A Promise that resolves with the enriched job run object - */ -export const enrichRunWithFunctionFields = ( - dispatch, - jobRun, - fetchJobFunctions, - fetchJobFunctionsPromiseRef -) => { - fetchJobFunctionsPromiseRef.current = Promise.resolve() - - if (jobRun?.function) { - const [, functionProject = '', functionName = '', functionHash = ''] = - jobRun.function?.match?.(/(.+)\/(.+)@(.+)/) || [] - - if (functionProject && functionName && functionHash) { - fetchJobFunctionsPromiseRef.current = fetchJobFunctions(functionProject, functionHash) - } - } - - return fetchJobFunctionsPromiseRef.current - .then(funcs => { - if (!isEmpty(funcs)) { - const tagsList = chain(funcs).map('metadata.tag').compact().uniq().value() - - defaultsDeep(jobRun, { - ui: { - functionTag: tagsList.join(', '), - runOnSpot: capitalize(funcs[0].spec.preemption_mode ?? ''), - nodeSelectorChips: parseKeyValues(funcs[0].spec.node_selector || {}), - priority: generateFunctionPriorityLabel(funcs[0].spec.priority_class_name ?? '') - } - }) - } else { - defaultsDeep(jobRun, { - ui: { - functionTag: '', - runOnSpot: '', - nodeSelectorChips: [], - priority: '' - } - }) - } - - return jobRun - }) - .catch(error => { - showErrorNotification(dispatch, error, 'Failed to fetch function tag', '') - }) -} - export const pollAbortingJobs = (project, terminatePollRef, abortingJobs, refresh, dispatch) => { const taskIds = Object.keys(abortingJobs) diff --git a/src/components/JobsPanel/JobsPanel.js b/src/components/JobsPanel/JobsPanel.js index 66ab316a5..3fe4a4556 100644 --- a/src/components/JobsPanel/JobsPanel.js +++ b/src/components/JobsPanel/JobsPanel.js @@ -46,17 +46,17 @@ import './jobsPanel.scss' const JobsPanel = ({ closePanel, - defaultData, + defaultData = null, fetchFunctionTemplate, frontendSpec, functionsStore, - groupedFunctions, + groupedFunctions = {}, jobsStore, mode, - onEditJob, + onEditJob = () => {}, onSuccessRun, project, - redirectToDetailsPane, + redirectToDetailsPane = false, removeFunctionTemplate, removeFunctionsError, removeJobError, @@ -65,7 +65,7 @@ const JobsPanel = ({ setNewJob, setNewJobInputs, setNewJobSecretSources, - withSaveChanges + withSaveChanges = false }) => { const [panelState, panelDispatch] = useReducer(panelReducer, initialState) const [openScheduleJob, setOpenScheduleJob] = useState(false) @@ -450,14 +450,6 @@ const JobsPanel = ({ ) } -JobsPanel.defaultProps = { - defaultData: null, - groupedFunctions: {}, - onEditJob: () => {}, - redirectToDetailsPane: false, - withSaveChanges: false -} - JobsPanel.propTypes = { closePanel: PropTypes.func.isRequired, defaultData: PropTypes.shape({}), diff --git a/src/components/JobsPanel/JobsPanelView.js b/src/components/JobsPanel/JobsPanelView.js index 4296254c4..2195e9d79 100644 --- a/src/components/JobsPanel/JobsPanelView.js +++ b/src/components/JobsPanel/JobsPanelView.js @@ -40,7 +40,7 @@ import JobsPanelCredentialsAccessKey from '../../elements/JobsPanelCredentialsAc const JobsPanelView = ({ checkValidation, closePanel, - defaultData, + defaultData = null, functionData, handleEditJob, handleRunJob, @@ -184,10 +184,6 @@ const JobsPanelView = ({ ) } -JobsPanelView.defaultProps = { - defaultData: null -} - JobsPanelView.propTypes = { checkValidation: PropTypes.bool.isRequired, closePanel: PropTypes.func.isRequired, diff --git a/src/components/JobsPanel/jobsPanel.util.js b/src/components/JobsPanel/jobsPanel.util.js index 5ca586524..3bfbc47ae 100644 --- a/src/components/JobsPanel/jobsPanel.util.js +++ b/src/components/JobsPanel/jobsPanel.util.js @@ -33,6 +33,7 @@ import { PANEL_EDIT_MODE, TAG_LATEST } from '../../constants' +import { parameterTypeFloat, parameterTypeList } from '../../elements/FormParametersTable/formParametersTable.util' import { generateEnvVariable } from '../../utils/generateEnvironmentVariable' import { parseEnvVariables } from '../../utils/parseEnvironmentVariables' import { getPreemptionMode } from '../../utils/getPreemptionMode' @@ -393,9 +394,9 @@ const parameterTypes = { const getParameterType = parameter => { return typeof parameter === 'string' && parameter.match(',') - ? 'list' + ? parameterTypeList : typeof parameter === 'number' && parameter % 1 !== 0 - ? 'float' + ? parameterTypeFloat : parameterTypes[typeof parameter] ?? '' } diff --git a/src/components/MetricChart/MetricChart.js b/src/components/MetricChart/MetricChart.js index 9635a239c..599dd3b4f 100644 --- a/src/components/MetricChart/MetricChart.js +++ b/src/components/MetricChart/MetricChart.js @@ -17,18 +17,24 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState, memo } from 'react' import { Chart } from 'chart.js/auto' import classnames from 'classnames' +import { throttle } from 'lodash' import Loader from '../../common/Loader/Loader' import { CHART_TYPE_BAR } from '../../constants' -import { calculateMaxTicksLimit, generateMetricChartTooltip, hexToRGB } from './metricChart.util' +import { + calculateMaxTicksLimit, + generateMetricChartTooltip, + setChartGradient +} from './metricChart.util' const GenericMetricChart = ({ chartConfig, isInvocationCardExpanded }) => { const chartRef = useRef(null) const chartInstance = useRef(null) + const contextRef = useRef(null) const [isLoading, setIsLoading] = useState(true) const canvasClassNames = classnames(isLoading && 'hidden-canvas') const customPoints = useMemo(() => { @@ -46,17 +52,20 @@ const GenericMetricChart = ({ chartConfig, isInvocationCardExpanded }) => { borderColor: () => 'white' } }, [chartConfig]) + const backgroundColor = useMemo(() => { + return chartConfig?.data?.datasets[0].backgroundColor + }, [chartConfig]) useEffect(() => { - const ctx = chartRef.current.getContext('2d') + contextRef.current = chartRef.current.getContext('2d') - if (ctx) { + if (contextRef.current) { if (chartInstance.current) { chartInstance.current.destroy() } const container = chartRef.current const maxTicksLimit = calculateMaxTicksLimit(container, chartConfig.type) - chartInstance.current = new Chart(ctx, { + chartInstance.current = new Chart(contextRef.current, { type: chartConfig.type, data: chartConfig.data, options: { @@ -121,24 +130,8 @@ const GenericMetricChart = ({ chartConfig, isInvocationCardExpanded }) => { } if (chartConfig.gradient) { - const canvasHeight = isInvocationCardExpanded ? 200 : 80 - if (chartInstance.current.options.scales.x.grid.display !== isInvocationCardExpanded) { - chartInstance.current.options.scales.x.grid.display = isInvocationCardExpanded - chartInstance.current.options.scales.y.grid.display = isInvocationCardExpanded - chartInstance.current.options.scales.y.display = isInvocationCardExpanded - chartInstance.current.options.scales.x.grid.ticks = true - chartInstance.current.options.scales.y.grid.ticks = true - } + setChartGradient(chartInstance.current, contextRef.current, backgroundColor, 200) - const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight) - gradient.addColorStop( - 0, - hexToRGB(chartConfig?.data?.datasets[0].backgroundColor || '#FFF', 0.7) - ) - gradient.addColorStop(1, hexToRGB(chartConfig?.data?.datasets[0].backgroundColor)) - chartInstance.current.data.datasets.forEach(dataset => { - dataset.backgroundColor = gradient - }) chartInstance.current.update() } @@ -152,14 +145,33 @@ const GenericMetricChart = ({ chartConfig, isInvocationCardExpanded }) => { } } }, [ + backgroundColor, chartConfig.data, - chartConfig.type, chartConfig.gradient, chartConfig.options, - isInvocationCardExpanded, + chartConfig.type, customPoints ]) + useEffect(() => { + if (chartInstance.current) { + if (chartInstance.current.options.scales.x.grid.display !== isInvocationCardExpanded) { + chartInstance.current.options.scales.x.grid.display = isInvocationCardExpanded + chartInstance.current.options.scales.y.grid.display = isInvocationCardExpanded + chartInstance.current.options.scales.y.display = isInvocationCardExpanded + chartInstance.current.options.scales.x.grid.ticks = true + chartInstance.current.options.scales.y.grid.ticks = true + } + + if (chartConfig.gradient && contextRef.current) { + const canvasHeight = isInvocationCardExpanded ? 200 : 80 + setChartGradient(chartInstance.current, contextRef.current, backgroundColor, canvasHeight) + } + + chartInstance.current.update() + } + }, [backgroundColor, chartConfig.gradient, isInvocationCardExpanded]) + useEffect(() => { const handleResize = () => { if (chartInstance.current) { @@ -170,10 +182,12 @@ const GenericMetricChart = ({ chartConfig, isInvocationCardExpanded }) => { chartInstance.current.update() } } - window.addEventListener('resize', handleResize) + const throttledResizeHandler = throttle(handleResize, 500, { leading: true, trailing: true }) + + window.addEventListener('resize', throttledResizeHandler) return () => { - window.removeEventListener('resize', handleResize) + window.removeEventListener('resize', throttledResizeHandler) } }, [chartConfig.type]) @@ -185,4 +199,4 @@ const GenericMetricChart = ({ chartConfig, isInvocationCardExpanded }) => { ) } -export default GenericMetricChart +export default memo(GenericMetricChart) diff --git a/src/components/MetricChart/metricChart.util.js b/src/components/MetricChart/metricChart.util.js index 97204a9a9..8bdd8fa76 100644 --- a/src/components/MetricChart/metricChart.util.js +++ b/src/components/MetricChart/metricChart.util.js @@ -122,3 +122,12 @@ export const generateMetricChartTooltip = context => { tooltipEl.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px' tooltipEl.style.pointerEvents = 'none' } + +export const setChartGradient = (chart, ctx, backgroundColor, canvasHeight = 200) => { + const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight) + gradient.addColorStop(0, hexToRGB(backgroundColor|| '#FFF', 0.7)) + gradient.addColorStop(1, hexToRGB(backgroundColor)) + chart.data.datasets.forEach(dataset => { + dataset.backgroundColor = gradient + }) +} diff --git a/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.js b/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.js index d465a0d4f..0a32a84d4 100644 --- a/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.js +++ b/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.js @@ -51,7 +51,7 @@ import { ReactComponent as Yaml } from 'igz-controls/images/yaml.svg' import cssVariables from './modelEndpoints.scss' const ModelEndpoints = () => { - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') const [modelEndpoints, setModelEndpoints] = useState([]) const [selectedModelEndpoint, setSelectedModelEndpoint] = useState({}) const frontendSpec = useSelector(store => store.appStore.frontendSpec) @@ -115,7 +115,7 @@ const ModelEndpoints = () => { config: { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage } }, params: { @@ -250,7 +250,7 @@ const ModelEndpoints = () => { message={getNoDataMessage( filtersStore, filters, - largeRequestErrorMessage, + requestErrorMessage, MODELS_PAGE, MODEL_ENDPOINTS_TAB )} diff --git a/src/components/ModelsPage/Models/Models.js b/src/components/ModelsPage/Models/Models.js index 350c02f4c..6eaf64804 100644 --- a/src/components/ModelsPage/Models/Models.js +++ b/src/components/ModelsPage/Models/Models.js @@ -19,7 +19,7 @@ such restriction. */ import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react' import { connect, useDispatch, useSelector } from 'react-redux' -import { chain, isEmpty } from 'lodash' +import { chain, isEmpty, isNil } from 'lodash' import { useLocation, useNavigate, useParams } from 'react-router-dom' import AddArtifactTagPopUp from '../../../elements/AddArtifactTagPopUp/AddArtifactTagPopUp' @@ -81,7 +81,8 @@ import { useInitialArtifactsFetch } from '../../../hooks/artifacts.hook' const Models = ({ fetchModelFeatureVector }) => { const [models, setModels] = useState([]) - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') + const [maxArtifactsErrorIsShown, setMaxArtifactsErrorIsShown] = useState(false) const [selectedModel, setSelectedModel] = useState({}) const [selectedModelMin, setSelectedModelMin] = useState({}) const [selectedRowData, setSelectedRowData] = useState({}) @@ -150,7 +151,7 @@ const Models = ({ fetchModelFeatureVector }) => { config: { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage }, params: { format: 'minimal' } } @@ -160,6 +161,7 @@ const Models = ({ fetchModelFeatureVector }) => { .then(result => { if (result) { setModels(result) + setMaxArtifactsErrorIsShown(result.length === 1000) } return result @@ -170,29 +172,36 @@ const Models = ({ fetchModelFeatureVector }) => { const handleDeployModel = useCallback( model => { + abortControllerRef.current = new AbortController() + dispatch( fetchArtifactsFunctions({ project: model.project, filters: {}, - config: { params: { format: 'minimal' } } + config: { + signal: abortControllerRef.current.signal, + params: { format: 'minimal' } + } }) ) .unwrap() .then(functions => { - const functionOptions = chain(functions) - .filter(func => func.type === FUNCTION_TYPE_SERVING && func.graph?.kind === 'router') - .uniqBy('name') - .map(func => ({ label: func.name, id: func.name })) - .value() - - if (functionOptions.length > 0) { - openPopUp(DeployModelPopUp, { - model, - functionList: functions, - functionOptionList: functionOptions - }) - } else { - handleDeployModelFailure(params.projectName, model.db_key) + if (!isNil(functions)) { + const functionOptions = chain(functions) + .filter(func => func.type === FUNCTION_TYPE_SERVING && func.graph?.kind === 'router') + .uniqBy('name') + .map(func => ({ label: func.name, id: func.name })) + .value() + + if (functionOptions.length > 0) { + openPopUp(DeployModelPopUp, { + model, + functionList: functions, + functionOptionList: functionOptions + }) + } else { + handleDeployModelFailure(params.projectName, model.db_key) + } } }) }, @@ -210,12 +219,13 @@ const Models = ({ fetchModelFeatureVector }) => { config: { ui: { controller: tagAbortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage } } }) ) setSelectedRowData({}) + setSelectedModelMin({}) setModels([]) //temporarily commented till ML-5606 will be done // setTableHeaders([]) @@ -368,7 +378,6 @@ const Models = ({ fetchModelFeatureVector }) => { useInitialArtifactsFetch( fetchData, urlTagOption, - models.length, setSelectedRowData, createModelsRowData ) @@ -517,12 +526,14 @@ const Models = ({ fetchModelFeatureVector }) => { handleRegisterModel={handleRegisterModel} handleTrainModel={handleTrainModel} isDemoMode={isDemoMode} - largeRequestErrorMessage={largeRequestErrorMessage} + maxArtifactsErrorIsShown={maxArtifactsErrorIsShown} models={models} pageData={pageData} ref={{ modelsRef }} + requestErrorMessage={requestErrorMessage} selectedModel={selectedModel} selectedRowData={selectedRowData} + setMaxArtifactsErrorIsShown={setMaxArtifactsErrorIsShown} setModels={setModels} setSelectedModelMin={setSelectedModelMin} setSelectedRowData={setSelectedRowData} diff --git a/src/components/ModelsPage/Models/ModelsView.js b/src/components/ModelsPage/Models/ModelsView.js index 6ccadd2b1..c4619be13 100644 --- a/src/components/ModelsPage/Models/ModelsView.js +++ b/src/components/ModelsPage/Models/ModelsView.js @@ -27,6 +27,7 @@ import ModelsPageTabs from '../ModelsPageTabs/ModelsPageTabs' import NoData from '../../../common/NoData/NoData' import Table from '../../Table/Table' import Details from '../../Details/Details' +import WarningMessage from '../../../common/WarningMessage/WarningMessage' import { ACTIONS_MENU, VIRTUALIZATION_CONFIG } from '../../../types' import { FULL_VIEW_MODE, MODELS_FILTERS, MODELS_PAGE, MODELS_TAB } from '../../../constants' @@ -51,19 +52,21 @@ const ModelsView = React.forwardRef( handleRegisterModel, handleTrainModel, isDemoMode, - largeRequestErrorMessage, + maxArtifactsErrorIsShown, models, pageData, + requestErrorMessage, selectedModel, selectedRowData, + setMaxArtifactsErrorIsShown, setModels, setSelectedModelMin, setSelectedRowData, - sortProps, + sortProps = null, tableContent, tableHeaders, - urlTagOption, - viewMode, + urlTagOption = null, + viewMode = null, virtualizationConfig }, { modelsRef } @@ -107,7 +110,7 @@ const ModelsView = React.forwardRef( message={getNoDataMessage( filtersStore, filters, - largeRequestErrorMessage, + requestErrorMessage, MODELS_PAGE, MODELS_TAB, MODELS_FILTERS @@ -115,7 +118,15 @@ const ModelsView = React.forwardRef( /> ) : ( <> - {(selectedRowData.loading || artifactsStore.models.modelLoading) && } + {(selectedRowData.loading || + artifactsStore.models.modelLoading || + artifactsStore.pipelines.loading) && } + {maxArtifactsErrorIsShown && ( + setMaxArtifactsErrorIsShown(false)} + /> + )} { - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') const [pipelines, setPipelines] = useState([]) const artifactsStore = useSelector(store => store.artifactsStore) const filtersStore = useSelector(store => store.filtersStore) @@ -91,23 +91,22 @@ const RealTimePipelines = () => { config: { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage } } }) ) .unwrap() .then(result => { - setPipelines( - result.filter( - func => - !Object.keys(func.labels).some(labelKey => labelKey.includes('parent-function')) + if(!isNil(result)) { + setPipelines( + result.filter( + func => + !Object.keys(func.labels).some(labelKey => labelKey.includes('parent-function')) + ) ) - ) + } }) - .catch(error => - largeResponseCatchHandler(error, 'Failed to fetch real-time pipelines', dispatch) - ) }, [dispatch, params.projectName] ) @@ -183,7 +182,7 @@ const RealTimePipelines = () => { message={getNoDataMessage( filtersStore, filters, - largeRequestErrorMessage, + requestErrorMessage, MODELS_PAGE, REAL_TIME_PIPELINES_TAB )} diff --git a/src/components/Project/ProjectAction/ProjectAction.js b/src/components/Project/ProjectAction/ProjectAction.js index ab9bac5ae..18ec535b1 100644 --- a/src/components/Project/ProjectAction/ProjectAction.js +++ b/src/components/Project/ProjectAction/ProjectAction.js @@ -24,7 +24,7 @@ import { Tip } from 'igz-controls/components' import './ProjectAction.scss' -const ProjectAction = ({ actions, onClick }) => { +const ProjectAction = ({ actions = [], onClick = () => {} }) => { return (
    {actions.map(({ icon, id, hidden, label, handleClick, tooltip }) => { @@ -45,16 +45,9 @@ const ProjectAction = ({ actions, onClick }) => { ) } -ProjectAction.defaultProps = { - actions: [], - onClick: () => {}, - showActions: true -} - ProjectAction.propTypes = { actions: PropTypes.array.isRequired, - onClick: PropTypes.func.isRequired, - showActions: PropTypes.bool + onClick: PropTypes.func.isRequired } export default ProjectAction diff --git a/src/components/Project/ProjectLabels/ProjectLabels.js b/src/components/Project/ProjectLabels/ProjectLabels.js index 3e5cd44d8..acc240eea 100644 --- a/src/components/Project/ProjectLabels/ProjectLabels.js +++ b/src/components/Project/ProjectLabels/ProjectLabels.js @@ -27,12 +27,12 @@ import { generateKeyValues, parseKeyValues } from '../../../utils' import { getChipOptions } from '../../../utils/getChipOptions' const ProjectLabels = ({ - addProjectLabel, - isEditMode, - labels, - shortChips, - updateProjectLabel, - visibleChipsMaxLength + addProjectLabel = () => {}, + isEditMode = false, + labels = {}, + shortChips = false, + updateProjectLabel = () => {}, + visibleChipsMaxLength = 'all' }) => { const projectLabels = useMemo( () => (!isEmpty(labels) ? parseKeyValues(labels || {}) : []), @@ -73,25 +73,13 @@ const ProjectLabels = ({ ) } -ProjectLabels.defaultProps = { - addProjectLabel: () => {}, - isEditMode: false, - labels: {}, - shortChips: false, - updateProjectLabel: () => {}, - visibleChipsMaxLength: 'all' -} - ProjectLabels.propTypes = { addProjectLabel: PropTypes.func, isEditMode: PropTypes.bool, labels: PropTypes.object, shortChips: PropTypes.bool, updateProjectLabel: PropTypes.func, - visibleChipsMaxLength: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number - ]) + visibleChipsMaxLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) } export default ProjectLabels diff --git a/src/components/Project/ProjectMonitorView.js b/src/components/Project/ProjectMonitorView.js index 6b27ead48..805d7f9e0 100644 --- a/src/components/Project/ProjectMonitorView.js +++ b/src/components/Project/ProjectMonitorView.js @@ -44,7 +44,7 @@ import './project.scss' const ProjectMonitorView = ({ closeFeatureSetPanel, closeFunctionsPanel, - confirmData, + confirmData = null, createFeatureSetPanelIsOpen, createFeatureSetSuccess, createFunctionSuccess, @@ -201,10 +201,6 @@ const ProjectMonitorView = ({ ) } -ProjectMonitorView.defaultProps = { - confirmData: null -} - ProjectMonitorView.propTypes = { closeFeatureSetPanel: PropTypes.func.isRequired, closeFunctionsPanel: PropTypes.func.isRequired, diff --git a/src/components/Project/ProjectOverview/ProjectOverview.util.js b/src/components/Project/ProjectOverview/ProjectOverview.util.js index ff5e54ee4..dcf2ef8a9 100644 --- a/src/components/Project/ProjectOverview/ProjectOverview.util.js +++ b/src/components/Project/ProjectOverview/ProjectOverview.util.js @@ -26,6 +26,7 @@ import RegisterModelModal from '../../../elements/RegisterModelModal/RegisterMod import { ARTIFACT_TYPE, DATASET_TYPE } from '../../../constants' import { SECONDARY_BUTTON, TERTIARY_BUTTON } from 'igz-controls/constants' import { generateNuclioLink } from '../../../utils' +import { isSubmitDisabled } from 'igz-controls/utils/form.util' import { ReactComponent as BatchInferenceIcon } from 'igz-controls/images/ic-batch-inference.svg' import { ReactComponent as CreatFunctionIcon } from 'igz-controls/images/ic-create-new-function.svg' @@ -53,8 +54,8 @@ export const handleClick = (navigate, openPopUp) => handler => { return target.type && target.type === 'modal' ? openPopUp(target.component, target.props) : target.externalLink - ? (window.top.location.href = target.path) - : navigate(target.path) + ? (window.top.location.href = target.path) + : navigate(target.path) } export const getInitialCards = (params, navigate, isDemoMode) => { @@ -97,7 +98,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { variant: TERTIARY_BUTTON }, { - disabled: formState.submitting || (formState.invalid && formState.submitFailed), + disabled: isSubmitDisabled(formState), label: 'Register', onClick: async () => { const submitSuccess = await formState.handleSubmit() @@ -111,7 +112,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { ], // TODO: un-comment for 1.3 // [{ - // disabled: formState.submitting || (formState.invalid && formState.submitFailed), + // disabled: isSubmitDisabled(formState), // label: 'Register and view', // onClick: async () => { // await formState.handleSubmit() @@ -120,7 +121,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { // } // }, // { - // disabled: formState.submitting || (formState.invalid && formState.submitFailed), + // disabled: isSubmitDisabled(formState), // label: 'Register', // onClick: formState.handleSubmit, // variant: SECONDARY_BUTTON @@ -151,7 +152,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { variant: TERTIARY_BUTTON }, { - disabled: formState.submitting || (formState.invalid && formState.submitFailed), + disabled: isSubmitDisabled(formState), label: 'Register', onClick: async () => { const submitSuccess = await formState.handleSubmit() @@ -164,7 +165,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { } ], // [{ - // disabled: formState.submitting || (formState.invalid && formState.submitFailed), + // disabled: isSubmitDisabled(formState), // label: 'Register and view', // onClick: async () => { // await formState.handleSubmit() @@ -173,7 +174,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { // } // }, // { - // disabled: formState.submitting || (formState.invalid && formState.submitFailed), + // disabled: isSubmitDisabled(formState), // label: 'Register', // onClick: formState.handleSubmit, // variant: SECONDARY_BUTTON @@ -276,7 +277,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { variant: TERTIARY_BUTTON }, { - disabled: formState.submitting || (formState.invalid && formState.submitFailed), + disabled: isSubmitDisabled(formState), label: 'Register', onClick: async () => { await formState.handleSubmit() @@ -289,7 +290,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { ], // TODO: un-comment for 1.3 // [{ - // disabled: formState.submitting || (formState.invalid && formState.submitFailed), + // disabled: isSubmitDisabled(formState), // label: 'Register and view', // onClick: async () => { // await formState.handleSubmit() @@ -298,7 +299,7 @@ export const getInitialCards = (params, navigate, isDemoMode) => { // } // }, // { - // disabled: formState.submitting || (formState.invalid && formState.submitFailed), + // disabled: isSubmitDisabled(formState), // label: 'Register', // onClick: formState.handleSubmit, // variant: SECONDARY_BUTTON diff --git a/src/components/ProjectsJobsMonitoring/JobsMonitoring/JobsMonitoring.js b/src/components/ProjectsJobsMonitoring/JobsMonitoring/JobsMonitoring.js index da6f42711..3945d22e5 100644 --- a/src/components/ProjectsJobsMonitoring/JobsMonitoring/JobsMonitoring.js +++ b/src/components/ProjectsJobsMonitoring/JobsMonitoring/JobsMonitoring.js @@ -29,13 +29,10 @@ import { ProjectJobsMonitoringContext } from '../ProjectsJobsMonitoring' import { createJobsMonitoringContent } from '../../../utils/createJobsContent' import { useMode } from '../../../hooks/mode.hook' import { - FILTER_ALL_ITEMS, JOBS_MONITORING_JOBS_TAB, JOBS_MONITORING_PAGE, REQUEST_CANCELED } from '../../../constants' -import { setFilters, setModalFiltersValues } from '../../../reducers/filtersReducer' -import { datePickerPastOptions, PAST_24_HOUR_DATE_OPTION } from '../../../utils/datePicker.util' const JobsMonitoring = () => { const [selectedJob, setSelectedJob] = useState({}) @@ -43,14 +40,18 @@ const JobsMonitoring = () => { const params = useParams() const dispatch = useDispatch() const { isStagingMode } = useMode() - const filtersStore = useSelector(store => store.filtersStore) + const [jobsFilterMenu, jobsFilterMenuModal] = useSelector(state => [ + state.filtersStore.filterMenu[JOBS_MONITORING_JOBS_TAB], + state.filtersStore.filterMenuModal[JOBS_MONITORING_JOBS_TAB] + ]) const { abortControllerRef, abortJobRef, abortingJobs, jobRuns, jobs, - largeRequestErrorMessage, + jobsFiltersConfig, + requestErrorMessage, refreshJobs, setAbortingJobs, setJobRuns, @@ -72,35 +73,9 @@ const JobsMonitoring = () => { useEffect(() => { if (isEmpty(selectedJob) && !params.jobId && !dataIsLoaded) { - let filters = {} - - if (filtersStore.saveFilters || !isJobDataEmpty()) { - filters = { - ...filtersStore, - state: filtersStore.filterMenuModal[JOBS_MONITORING_JOBS_TAB].values.state - } - - dispatch(setModalFiltersValues({ - name: JOBS_MONITORING_JOBS_TAB, - value: { state: filters.state } - })) - dispatch(setFilters({ ...filtersStore, saveFilters: false })) - } else { - const past24HourOption = datePickerPastOptions.find( - option => option.id === PAST_24_HOUR_DATE_OPTION - ) - const generatedDates = [...past24HourOption.handler()] - - filters = { - dates: { - value: generatedDates, - isPredefined: past24HourOption.isPredefined, - initialSelectedOptionId: past24HourOption.id - }, - state: FILTER_ALL_ITEMS - } - - dispatch(setFilters({ dates: filters.dates })) + let filters = { + ...jobsFilterMenu, + ...jobsFilterMenuModal.values } refreshJobs(filters) @@ -110,30 +85,32 @@ const JobsMonitoring = () => { isJobDataEmpty, dataIsLoaded, dispatch, - filtersStore, params.jobId, params.jobName, refreshJobs, - selectedJob + selectedJob, + jobsFilterMenu, + jobsFilterMenuModal.values ]) useEffect(() => { const abortController = abortControllerRef.current return () => { + setDataIsLoaded(false) setJobs([]) setJobRuns([]) abortController.abort(REQUEST_CANCELED) terminateAbortTasksPolling() } - }, [abortControllerRef, setJobRuns, setJobs, terminateAbortTasksPolling]) - - useEffect(() => { - return () => { - setDataIsLoaded(false) - terminateAbortTasksPolling() - } - }, [params.jobName, params.jobId, terminateAbortTasksPolling]) + }, [ + params.jobName, + params.jobId, + terminateAbortTasksPolling, + abortControllerRef, + setJobs, + setJobRuns + ]) return ( <> @@ -147,14 +124,16 @@ const JobsMonitoring = () => { abortingJobs={abortingJobs} ref={{ abortJobRef }} context={ProjectJobsMonitoringContext} + filterMenuName={JOBS_MONITORING_JOBS_TAB} + filtersConfig={jobsFiltersConfig} jobRuns={jobRuns} jobs={jobs} - largeRequestErrorMessage={largeRequestErrorMessage} + requestErrorMessage={requestErrorMessage} navigateLink={`/projects/${JOBS_MONITORING_PAGE}/${JOBS_MONITORING_JOBS_TAB}`} refreshJobs={() => refreshJobs({ - ...filtersStore, - ...filtersStore.filterMenuModal[JOBS_MONITORING_JOBS_TAB].values + ...jobsFilterMenu, + ...jobsFilterMenuModal.values }) } selectedJob={selectedJob} diff --git a/src/components/ProjectsJobsMonitoring/JobsMonitoring/JobsMonitoringFilters.js b/src/components/ProjectsJobsMonitoring/JobsMonitoring/JobsMonitoringFilters.js index cbe112d51..96e581371 100644 --- a/src/components/ProjectsJobsMonitoring/JobsMonitoring/JobsMonitoringFilters.js +++ b/src/components/ProjectsJobsMonitoring/JobsMonitoring/JobsMonitoringFilters.js @@ -17,32 +17,22 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React, { useMemo } from 'react' +import React from 'react' import { useForm } from 'react-final-form' import { FormInput, FormOnChange, FormSelect } from 'igz-controls/components' +import StatusFilter from '../../../common/StatusFilter/StatusFilter' -import { - JOBS_MONITORING_JOBS_TAB, - LABELS_FILTER, - PROJECT_FILTER -} from '../../../constants' -import { generateStatusFilter, generateTypeFilter } from '../../FilterMenu/filterMenu.settings' -import { handleFilterStateChange } from '../projectsJobsMotinoring.util' +import { LABELS_FILTER, PROJECT_FILTER, STATUS_FILTER_NAME } from '../../../constants' +import { generateTypeFilter, jobsStatuses } from '../../FilterMenu/filterMenu.settings' const JobsMonitoringFilters = () => { const form = useForm() - const statusList = useMemo(() => generateStatusFilter(false, JOBS_MONITORING_JOBS_TAB), []) - const handleInputChange = (value, inputName) => { form.change(inputName, value || '') } - const handleStateChange = (selectedValue, currentValue) => { - handleFilterStateChange(selectedValue, currentValue, form, statusList) - } - return (
    @@ -53,8 +43,7 @@ const JobsMonitoringFilters = () => { />
    - - handleStateChange(value, some)} name="state" /> +
    diff --git a/src/components/ProjectsJobsMonitoring/ProjectsJobsMonitoring.js b/src/components/ProjectsJobsMonitoring/ProjectsJobsMonitoring.js index d511d992f..331aad21e 100644 --- a/src/components/ProjectsJobsMonitoring/ProjectsJobsMonitoring.js +++ b/src/components/ProjectsJobsMonitoring/ProjectsJobsMonitoring.js @@ -32,6 +32,7 @@ import WorkflowsMonitoringFilters from './WorkflowsMonitoring/WorkflowsMonitorin import { actionCreator, STATS_TOTAL_CARD, tabs } from './projectsJobsMotinoring.util' import { + DATES_FILTER, FILTER_ALL_ITEMS, GROUP_BY_WORKFLOW, JOB_KIND_LOCAL, @@ -39,18 +40,16 @@ import { JOBS_MONITORING_PAGE, JOBS_MONITORING_SCHEDULED_TAB, JOBS_MONITORING_WORKFLOWS_TAB, + LABELS_FILTER, NAME_FILTER, - SCHEDULE_TAB + PROJECT_FILTER, + SCHEDULE_TAB, + STATUS_FILTER, + TYPE_FILTER } from '../../constants' import { monitorJob, pollAbortingJobs, rerunJob } from '../Jobs/jobs.util' import { TERTIARY_BUTTON } from 'igz-controls/constants' import { parseJob } from '../../utils/parseJob' -import { - datePickerFutureOptions, - datePickerPastOptions, - NEXT_24_HOUR_DATE_OPTION, - PAST_24_HOUR_DATE_OPTION -} from '../../utils/datePicker.util' import jobsActions from '../../actions/jobs' import workflowActions from '../../actions/workflow' @@ -69,7 +68,7 @@ const ProjectsJobsMonitoring = ({ fetchAllJobRuns, fetchJobFunction, fetchJobs } const [jobRuns, setJobRuns] = useState([]) const [jobs, setJobs] = useState([]) const [selectedRunProject, setSelectedRunProject] = useState('') - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') const { jobsMonitoringData } = useSelector(store => store.projectStore) const [selectedCard, setSelectedCard] = useState( jobsMonitoringData.filters?.status || STATS_TOTAL_CARD @@ -83,57 +82,36 @@ const ProjectsJobsMonitoring = ({ fetchAllJobRuns, fetchJobFunction, fetchJobs } const appStore = useSelector(store => store.appStore) const artifactsStore = useSelector(store => store.artifactsStore) - const jobsFilters = useMemo( - () => [ - { type: NAME_FILTER, label: 'Name:', initialValue: '', hidden: Boolean(params.jobName) }, - { - type: 'dates', - initialValue: { - value: datePickerPastOptions - .find(option => option.id === PAST_24_HOUR_DATE_OPTION) - .handler(), - isPredefined: true, - initialSelectedOptionId: PAST_24_HOUR_DATE_OPTION - } - } - ], - [params.jobName] - ) + const jobsFiltersConfig = useMemo(() => { + return { + [NAME_FILTER]: { label: 'Name:', hidden: Boolean(params.jobName) }, + [DATES_FILTER]: { label: 'Start time:' }, + [PROJECT_FILTER]: { label: 'Project:' }, + [STATUS_FILTER]: { label: 'Status:' }, + [TYPE_FILTER]: { label: 'Type:' }, + [LABELS_FILTER]: { label: 'Labels:' } + } + }, [params.jobName]) - const scheduledFilters = useMemo( - () => [ - { type: NAME_FILTER, label: 'Name:', initialValue: '' }, - { - type: 'dates', - initialValue: { - value: datePickerFutureOptions - .find(option => option.id === NEXT_24_HOUR_DATE_OPTION) - .handler(true), - isPredefined: true, - initialSelectedOptionId: NEXT_24_HOUR_DATE_OPTION - }, - isFuture: true - } - ], - [] - ) + const workflowsFiltersConfig = useMemo(() => { + return { + [NAME_FILTER]: { label: 'Name:' }, + [DATES_FILTER]: { label: 'Created at:' }, + [PROJECT_FILTER]: { label: 'Project:' }, + [STATUS_FILTER]: { label: 'Status:' }, + [LABELS_FILTER]: { label: 'Labels:' } + } + }, []) - const workflowsFilters = useMemo( - () => [ - { type: NAME_FILTER, label: 'Name:', initialValue: '' }, - { - type: 'dates', - initialValue: { - value: datePickerPastOptions - .find(option => option.id === PAST_24_HOUR_DATE_OPTION) - .handler(), - isPredefined: true, - initialSelectedOptionId: PAST_24_HOUR_DATE_OPTION - } - } - ], - [] - ) + const scheduledFiltersConfig = useMemo(() => { + return { + [NAME_FILTER]: { label: 'Name:' }, + [DATES_FILTER]: { label: 'Scheduled at:', isFuture: true }, + [PROJECT_FILTER]: { label: 'Project:' }, + [TYPE_FILTER]: { label: 'Type:' }, + [LABELS_FILTER]: { label: 'Labels:' } + } + }, []) const handleTabChange = tabName => { setSelectedCard(STATS_TOTAL_CARD) @@ -182,7 +160,7 @@ const ProjectsJobsMonitoring = ({ fetchAllJobRuns, fetchJobFunction, fetchJobs } { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage }, params: { ...newParams } }, @@ -250,7 +228,7 @@ const ProjectsJobsMonitoring = ({ fetchAllJobRuns, fetchJobFunction, fetchJobs } jobsActions.fetchScheduledJobs('*', filters, { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage } }) ).then(jobs => { @@ -309,7 +287,7 @@ const ProjectsJobsMonitoring = ({ fetchAllJobRuns, fetchJobFunction, fetchJobs } { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage } }, true @@ -322,22 +300,29 @@ const ProjectsJobsMonitoring = ({ fetchAllJobRuns, fetchJobFunction, fetchJobs } const tabData = useMemo(() => { return { [JOBS_MONITORING_JOBS_TAB]: { - filters: jobsFilters, + filtersConfig: jobsFiltersConfig, handleRefresh: refreshJobs, modalFilters: }, [JOBS_MONITORING_WORKFLOWS_TAB]: { - filters: workflowsFilters, + filtersConfig: workflowsFiltersConfig, handleRefresh: getWorkflows, modalFilters: }, [JOBS_MONITORING_SCHEDULED_TAB]: { - filters: scheduledFilters, + filtersConfig: scheduledFiltersConfig, handleRefresh: refreshScheduled, modalFilters: } } - }, [getWorkflows, jobsFilters, refreshJobs, refreshScheduled, scheduledFilters, workflowsFilters]) + }, [ + getWorkflows, + jobsFiltersConfig, + refreshJobs, + refreshScheduled, + scheduledFiltersConfig, + workflowsFiltersConfig + ]) return ( <> @@ -345,68 +330,69 @@ const ProjectsJobsMonitoring = ({ fetchAllJobRuns, fetchJobFunction, fetchJobs }
    - { - selectedTab && ( -
    -
    - - -
    -
    - - - -
    + {selectedTab && ( +
    +
    + +
    - ) - } +
    + + + +
    +
    + )}
    {confirmData && ( { const [dataIsLoaded, setDataIsLoaded] = useState(false) const dispatch = useDispatch() - const filtersStore = useSelector(store => store.filtersStore) - const { scheduledJobs, setScheduledJobs, largeRequestErrorMessage, refreshScheduled } = React.useContext( - ProjectJobsMonitoringContext - ) + const [schedulesFilterMenu, schedulesFilterMenuModal] = useSelector(state => [ + state.filtersStore.filterMenu[JOBS_MONITORING_SCHEDULED_TAB], + state.filtersStore.filterMenuModal[JOBS_MONITORING_SCHEDULED_TAB] + ]) + const { + requestErrorMessage, + refreshScheduled, + scheduledFiltersConfig, + scheduledJobs, + setScheduledJobs + } = React.useContext(ProjectJobsMonitoringContext) const tableContent = useMemo(() => { return createScheduleJobsMonitoringContent(scheduledJobs) @@ -42,36 +47,20 @@ const ScheduledMonitoring = () => { useEffect(() => { if (!dataIsLoaded) { - let filters = {} - - if (filtersStore.saveFilters) { - filters = { - dates: filtersStore.dates, - type: filtersStore.filterMenuModal[JOBS_MONITORING_SCHEDULED_TAB].values.type - } - - dispatch(setFilters({ saveFilters: false })) - } else { - const next24HourOption = datePickerFutureOptions.find( - option => option.id === NEXT_24_HOUR_DATE_OPTION - ) - - filters = { - dates: { - value: next24HourOption.handler(true), - isPredefined: next24HourOption.isPredefined, - initialSelectedOptionId: next24HourOption.id - }, - type: filtersStore.filterMenuModal[JOBS_MONITORING_SCHEDULED_TAB].values.type - } - - dispatch(setFilters({ dates: filters.dates })) + let filters = { + ...schedulesFilterMenu, + ...schedulesFilterMenuModal.values } - refreshScheduled(filters) setDataIsLoaded(true) } - }, [filtersStore, dataIsLoaded, dispatch, refreshScheduled]) + }, [ + dataIsLoaded, + dispatch, + refreshScheduled, + schedulesFilterMenu, + schedulesFilterMenuModal.values + ]) useEffect(() => { return () => { @@ -83,12 +72,14 @@ const ScheduledMonitoring = () => { return ( refreshScheduled({ - ...filtersStore, - ...filtersStore.filterMenuModal[JOBS_MONITORING_SCHEDULED_TAB].values + ...schedulesFilterMenu, + ...schedulesFilterMenuModal.values }) } tableContent={tableContent} diff --git a/src/components/ProjectsJobsMonitoring/WorkflowsMonitoring/WorkflowsMonitoring.js b/src/components/ProjectsJobsMonitoring/WorkflowsMonitoring/WorkflowsMonitoring.js index 1bfaef169..3467b5551 100644 --- a/src/components/ProjectsJobsMonitoring/WorkflowsMonitoring/WorkflowsMonitoring.js +++ b/src/components/ProjectsJobsMonitoring/WorkflowsMonitoring/WorkflowsMonitoring.js @@ -24,18 +24,14 @@ import { isEmpty } from 'lodash' import WorkflowsTable from '../../../elements/WorkflowsTable/WorkflowsTable' import { ProjectJobsMonitoringContext } from '../ProjectsJobsMonitoring' +import Loader from '../../../common/Loader/Loader' import { - FILTER_ALL_ITEMS, - GROUP_BY_NONE, - GROUP_BY_WORKFLOW, JOBS_MONITORING_PAGE, JOBS_MONITORING_WORKFLOWS_TAB, REQUEST_CANCELED } from '../../../constants' import { createWorkflowsMonitoringContent } from '../../../utils/createJobsContent' -import { datePickerPastOptions, PAST_24_HOUR_DATE_OPTION } from '../../../utils/datePicker.util' -import { setFilters, setModalFiltersValues } from '../../../reducers/filtersReducer' import { useMode } from '../../../hooks/mode.hook' import { usePods } from '../../../hooks/usePods.hook' import detailsActions from '../../../actions/details' @@ -49,15 +45,19 @@ const WorkflowsMonitoring = ({ fetchFunctionLogs }) => { const [itemIsSelected, setItemIsSelected] = useState(false) const [selectedJob, setSelectedJob] = useState({}) const workflowsStore = useSelector(state => state.workflowsStore) - const filtersStore = useSelector(state => state.filtersStore) + const [workflowsFilterMenu, workflowsFilterMenuModal] = useSelector(state => [ + state.filtersStore.filterMenu[JOBS_MONITORING_WORKFLOWS_TAB], + state.filtersStore.filterMenuModal[JOBS_MONITORING_WORKFLOWS_TAB] + ]) + const jobIsLoading = useSelector(store => store.jobsStore.loading) + const funcIsLoading = useSelector(store => store.functionsStore.funcLoading) const params = useParams() const dispatch = useDispatch() const { isStagingMode } = useMode() const abortControllerRef = useRef(new AbortController()) - const { abortJobRef, getWorkflows, largeRequestErrorMessage } = React.useContext( - ProjectJobsMonitoringContext - ) + const { abortJobRef, getWorkflows, requestErrorMessage, workflowsFiltersConfig } = + React.useContext(ProjectJobsMonitoringContext) usePods(dispatch, detailsActions.fetchJobPods, detailsActions.removePods, selectedJob) @@ -92,51 +92,17 @@ const WorkflowsMonitoring = ({ fetchFunctionLogs }) => { }, [dispatch]) useEffect(() => { - if (!workflowsAreLoaded) { - if (params.workflowId) { - dispatch(setFilters({ groupBy: GROUP_BY_NONE })) - } else { - if (workflowsStore.workflows.data.length === 0 && !filtersStore.saveFilters) { - const past24HourOption = datePickerPastOptions.find( - option => option.id === PAST_24_HOUR_DATE_OPTION - ) - const generatedDates = [...past24HourOption.handler()] - - const filters = { - groupBy: GROUP_BY_WORKFLOW, - dates: { - value: generatedDates, - isPredefined: past24HourOption.isPredefined, - initialSelectedOptionId: past24HourOption.id - } - } - - dispatch(setFilters(filters)) - dispatch(setModalFiltersValues({ - name: JOBS_MONITORING_WORKFLOWS_TAB, - value: { state: [FILTER_ALL_ITEMS] } - })) - getWorkflows({ - ...filters, - state: FILTER_ALL_ITEMS - }) - } else { - getWorkflows({ - ...filtersStore, - groupBy: filtersStore.groupBy, - state: - filtersStore.filterMenuModal[JOBS_MONITORING_WORKFLOWS_TAB].values.state || - FILTER_ALL_ITEMS - }) - dispatch(setFilters({ groupBy: GROUP_BY_WORKFLOW, saveFilters: false })) - } - - setWorkflowsAreLoaded(true) - } + if (!workflowsAreLoaded && !params.workflowId) { + getWorkflows({ + ...workflowsFilterMenu, + ...workflowsFilterMenuModal.values + }) + + setWorkflowsAreLoaded(true) } }, [ - dispatch, - filtersStore, + workflowsFilterMenu, + workflowsFilterMenuModal, getWorkflows, params.workflowId, workflowsAreLoaded, @@ -145,14 +111,17 @@ const WorkflowsMonitoring = ({ fetchFunctionLogs }) => { return ( <> + {(jobIsLoading || funcIsLoading || workflowsStore.activeWorkflow.loading) && } { const form = useForm() - - const statusList = useMemo(() => generateStatusFilter(false, JOBS_MONITORING_WORKFLOWS_TAB), []) - - const handleStateChange = (selectedValue, currentValue) => { - handleFilterStateChange(selectedValue, currentValue, form, statusList) - } + const { isDemoMode } = useMode() const handleInputChange = (value, inputName) => { form.change(inputName, value || '') @@ -49,16 +45,17 @@ const WorkflowsMonitoringFilters = () => { />
    - - handleStateChange(value, some)} name="state" /> -
    -
    - - handleInputChange(value, LABELS_FILTER)} - name={LABELS_FILTER} - /> +
    + {isDemoMode && ( +
    + + handleInputChange(value, LABELS_FILTER)} + name={LABELS_FILTER} + /> +
    + )}
    ) } diff --git a/src/components/ProjectsJobsMonitoring/projectsJobsMotinoring.util.js b/src/components/ProjectsJobsMonitoring/projectsJobsMotinoring.util.js index 73e05cfe7..0af32b199 100644 --- a/src/components/ProjectsJobsMonitoring/projectsJobsMotinoring.util.js +++ b/src/components/ProjectsJobsMonitoring/projectsJobsMotinoring.util.js @@ -20,21 +20,12 @@ such restriction. import { JOBS_MONITORING_JOBS_TAB, JOBS_MONITORING_WORKFLOWS_TAB, - JOBS_MONITORING_SCHEDULED_TAB, - NAME_FILTER, - GROUP_BY_FILTER, - DATE_RANGE_TIME_FILTER, - FILTER_ALL_ITEMS + JOBS_MONITORING_SCHEDULED_TAB } from '../../constants' import jobsActions from '../../actions/jobs' import functionsActions from '../../actions/functions' export const STATS_TOTAL_CARD = 'total' -export const STATS_RUNNING_CARD = 'running' -export const STATS_FAILED_CARD = 'failed' -export const STATS_COMPLETED_CARD = 'completed' -export const STATS_JOBS_CARD = 'jobs' -export const STATS_WORKFLOWS_CARD = 'workflows' export const tabs = [ { id: JOBS_MONITORING_JOBS_TAB, label: 'Jobs' }, @@ -42,94 +33,9 @@ export const tabs = [ { id: JOBS_MONITORING_SCHEDULED_TAB, label: 'Scheduled' } ] -export const jobMonitoringFilters = [ - { type: NAME_FILTER, label: 'Search by name:' }, - { type: GROUP_BY_FILTER, label: 'Group by' }, - { type: DATE_RANGE_TIME_FILTER, label: 'Timeframe:' } -] - -export const generateStatsData = (cardsData, tab) => - tab === JOBS_MONITORING_SCHEDULED_TAB - ? [ - { - id: STATS_TOTAL_CARD, - className: 'total', - counter: cardsData.all.length, - subTitle: 'Total', - tooltip: 'Click to see all' - }, - { - id: STATS_JOBS_CARD, - className: 'total', - counter: cardsData.jobs.length, - subTitle: 'Jobs', - tooltip: 'Click to see only jobs' - }, - { - id: STATS_WORKFLOWS_CARD, - className: 'total', - counter: cardsData.workflows.length, - subTitle: 'Workflows', - tooltip: 'Click to see only workflows' - } - ] - : [ - { - id: STATS_TOTAL_CARD, - className: 'total', - counter: cardsData.all.length, - subTitle: 'Total', - tooltip: 'Click to see all' - }, - { - id: STATS_RUNNING_CARD, - className: 'running', - counter: cardsData.running.length, - statusClass: 'running', - subTitle: 'Running', - tooltip: 'Click to filter by status running' - }, - { - id: STATS_FAILED_CARD, - className: 'failed', - counter: cardsData.failed.length, - statusClass: 'failed', - subTitle: 'Failed', - tooltip: 'Click to filter by status failed' - }, - { - id: STATS_COMPLETED_CARD, - className: 'completed', - counter: cardsData.completed.length, - statusClass: 'completed', - subTitle: 'Completed', - tooltip: 'Click to filter by status completed' - } - ] - export const actionCreator = { fetchAllJobRuns: jobsActions.fetchAllJobRuns, fetchFunctionLogs: functionsActions.fetchFunctionLogs, fetchJobFunction: jobsActions.fetchJobFunction, fetchJobs: jobsActions.fetchJobs } - -export const handleFilterStateChange = (selectedValue, currentValue, form, options) => { - if ( - selectedValue.length > 1 && - selectedValue.includes(FILTER_ALL_ITEMS) && - selectedValue.indexOf(FILTER_ALL_ITEMS) === 0 - ) { - form.change( - 'state', - selectedValue.filter(value => value !== FILTER_ALL_ITEMS) - ) - } else if ( - (!currentValue.includes(FILTER_ALL_ITEMS) && - selectedValue.includes(FILTER_ALL_ITEMS) && - selectedValue.indexOf(FILTER_ALL_ITEMS) > 0) || - options.filter(option => option.id !== FILTER_ALL_ITEMS).length === selectedValue.length - ) { - form.change('state', [FILTER_ALL_ITEMS]) - } -} diff --git a/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.js b/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.js index 120ff1993..9e74f4a7e 100644 --- a/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.js +++ b/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.js @@ -22,16 +22,19 @@ import PropTypes from 'prop-types' import arrayMutators from 'final-form-arrays' import { Form } from 'react-final-form' import { useSelector } from 'react-redux' +import { createForm } from 'final-form' import ErrorMessage from '../../../common/ErrorMessage/ErrorMessage' import Loader from '../../../common/Loader/Loader' import { Button, FormChipCell, FormInput, FormTextarea, PopUpDialog } from 'igz-controls/components' import { SECONDARY_BUTTON, TERTIARY_BUTTON } from 'igz-controls/constants' -import { createForm } from 'final-form' import { getChipOptions } from '../../../utils/getChipOptions' -import { getValidationRules, getInternalLabelsValidationRule } from 'igz-controls/utils/validation.util' -import { setFieldState } from 'igz-controls/utils/form.util' +import { + getValidationRules, + getInternalLabelsValidationRule +} from 'igz-controls/utils/validation.util' +import { setFieldState, isSubmitDisabled } from 'igz-controls/utils/form.util' import './createProjectDialog.scss' @@ -51,7 +54,7 @@ const CreateProjectDialog = ({ createForm({ initialValues, mutators: { ...arrayMutators, setFieldState }, - onSubmit: () => {} + onSubmit: handleCreateProject }) ) @@ -62,12 +65,13 @@ const CreateProjectDialog = ({ closePopUp={closeNewProjectPopUp} > {projectStore.loading && } -
    {}}> + {formState => { return ( <>
    @@ -115,10 +122,10 @@ const CreateProjectDialog = ({ onClick={closeNewProjectPopUp} />
    diff --git a/src/components/ProjectsPage/Projects.js b/src/components/ProjectsPage/Projects.js index 23f4283de..aca777b2c 100644 --- a/src/components/ProjectsPage/Projects.js +++ b/src/components/ProjectsPage/Projects.js @@ -56,6 +56,7 @@ const Projects = () => { const [selectedProjectsState, setSelectedProjectsState] = useState('active') const [sortProjectId, setSortProjectId] = useState('byName') const [deletingProjects, setDeletingProjects] = useState({}) + const [projectsRequestErrorMessage, setProjectsRequestErrorMessage] = useState('') const abortControllerRef = useRef(new AbortController()) const terminatePollRef = useRef(null) @@ -67,7 +68,7 @@ const Projects = () => { const tasksStore = useSelector(store => store.tasksStore) const fetchMinimalProjects = useCallback(() => { - dispatch(projectsAction.fetchProjects({ format: 'minimal' })) + dispatch(projectsAction.fetchProjects({ format: 'minimal' }, setProjectsRequestErrorMessage)) }, [dispatch]) const isValidProjectState = useCallback( @@ -384,32 +385,28 @@ such as jobs, artifacts, and features.`, setCreateProject(false) }, [projectStore.newProject.error, removeNewProjectError]) - const handleCreateProject = (e, formState) => { - e.preventDefault() - - if (e.currentTarget.checkValidity() && formState.valid) { - dispatch( - projectsAction.createNewProject({ - metadata: { - name: formState.values.name, - labels: - formState.values.labels?.reduce((acc, labelData) => { - acc[labelData.key] = labelData.value - return acc - }, {}) ?? {} - }, - spec: { - description: formState.values.description - } - }) - ).then(result => { - if (result) { - setCreateProject(false) - refreshProjects() - dispatch(projectsAction.fetchProjectsNames()) + const handleCreateProject = values => { + dispatch( + projectsAction.createNewProject({ + metadata: { + name: values.name, + labels: + values.labels?.reduce((acc, labelData) => { + acc[labelData.key] = labelData.value + return acc + }, {}) ?? {} + }, + spec: { + description: values.description } }) - } + ).then(result => { + if (result) { + setCreateProject(false) + refreshProjects() + dispatch(projectsAction.fetchProjectsNames()) + } + }) } return ( @@ -417,16 +414,17 @@ such as jobs, artifacts, and features.`, actionsMenu={actionsMenu} closeNewProjectPopUp={closeNewProjectPopUp} confirmData={confirmData} - convertToYaml={convertToYaml} convertedYaml={convertedYaml} + convertToYaml={convertToYaml} createProject={createProject} filterByName={filterByName} - filterMatches={filterMatches} filteredProjects={filteredProjects} + filterMatches={filterMatches} handleCreateProject={handleCreateProject} handleSearchOnFocus={handleSearchOnFocus} handleSelectSortOption={handleSelectSortOption} isDescendingOrder={isDescendingOrder} + projectsRequestErrorMessage={projectsRequestErrorMessage} projectStore={projectStore} refreshProjects={refreshProjects} removeNewProjectError={removeNewProjectError} diff --git a/src/components/ProjectsPage/ProjectsView.js b/src/components/ProjectsPage/ProjectsView.js index a0bf12f21..2e2ebfb1b 100644 --- a/src/components/ProjectsPage/ProjectsView.js +++ b/src/components/ProjectsPage/ProjectsView.js @@ -44,7 +44,7 @@ import './projects.scss' const ProjectsView = ({ actionsMenu, closeNewProjectPopUp, - confirmData, + confirmData = null, convertedYaml, convertToYaml, createProject, @@ -55,6 +55,7 @@ const ProjectsView = ({ handleSearchOnFocus, handleSelectSortOption, isDescendingOrder, + projectsRequestErrorMessage, projectStore, refreshProjects, removeNewProjectError, @@ -74,9 +75,7 @@ const ProjectsView = ({ return (
    - {(projectStore.loading || - projectStore.project.loading || - tasksStore.loading) && } + {(projectStore.loading || projectStore.project.loading || tasksStore.loading) && } {projectStore.mlrunUnhealthy.isUnhealthy && ( MLRun seems to be down. Try again in a few minutes. @@ -191,7 +190,7 @@ const ProjectsView = ({ message={ projectStore.mlrunUnhealthy.retrying ? 'Retrieving projects.' - : 'Your projects list is empty.' + : projectsRequestErrorMessage || 'Your projects list is empty.' } /> )} @@ -203,10 +202,6 @@ const ProjectsView = ({ ) } -ProjectsView.defaultProps = { - confirmData: null -} - ProjectsView.propTypes = { actionsMenu: PropTypes.shape({}).isRequired, closeNewProjectPopUp: PropTypes.func.isRequired, @@ -220,6 +215,7 @@ ProjectsView.propTypes = { handleCreateProject: PropTypes.func.isRequired, handleSearchOnFocus: PropTypes.func.isRequired, handleSelectSortOption: PropTypes.func.isRequired, + projectsRequestErrorMessage: PropTypes.func.isRequired, refreshProjects: PropTypes.func.isRequired, removeNewProjectError: PropTypes.func.isRequired, selectedProjectsState: PropTypes.string.isRequired, diff --git a/src/components/RegisterArtifactModal/RegisterArtifactModal.js b/src/components/RegisterArtifactModal/RegisterArtifactModal.js index 493364f29..34cd77165 100644 --- a/src/components/RegisterArtifactModal/RegisterArtifactModal.js +++ b/src/components/RegisterArtifactModal/RegisterArtifactModal.js @@ -41,7 +41,7 @@ import artifactApi from '../../api/artifacts-api' import { ARTIFACT_TYPE } from '../../constants' import { convertChipsData } from '../../utils/convertChipsData' import { createArtifactMessages } from '../../utils/createArtifact.util' -import { setFieldState } from 'igz-controls/utils/form.util' +import { setFieldState, isSubmitDisabled } from 'igz-controls/utils/form.util' import { setNotification } from '../../reducers/notificationReducer' import { showErrorNotification } from '../../utils/notifications.util' import { openPopUp } from 'igz-controls/utils/common.util' @@ -136,7 +136,9 @@ const RegisterArtifactModal = ({ label: 'Overwrite', variant: PRIMARY_BUTTON, handler: (...args) => { - handleRegisterArtifact(...args).then(resolve).catch(reject) + handleRegisterArtifact(...args) + .then(resolve) + .catch(reject) } }, cancelButton: { @@ -146,7 +148,9 @@ const RegisterArtifactModal = ({ }, closePopUp: () => reject(), header: messagesByKind.overwriteConfirmTitle, - message: messagesByKind.getOverwriteConfirmMessage(response.data.artifacts[0].kind || ARTIFACT_TYPE) + message: messagesByKind.getOverwriteConfirmMessage( + response.data.artifacts[0].kind || ARTIFACT_TYPE + ) }) }) } else { @@ -177,7 +181,7 @@ const RegisterArtifactModal = ({ variant: TERTIARY_BUTTON }, { - disabled: formState.submitting || (formState.invalid && formState.submitFailed), + disabled: isSubmitDisabled(formState), label: 'Register', onClick: formState.handleSubmit, variant: SECONDARY_BUTTON diff --git a/src/components/ScheduleJob/ScheduleJob.js b/src/components/ScheduleJob/ScheduleJob.js index ae5e0572b..4d509d001 100644 --- a/src/components/ScheduleJob/ScheduleJob.js +++ b/src/components/ScheduleJob/ScheduleJob.js @@ -30,8 +30,8 @@ import { generateCronInitialValue } from '../../utils/generateCronInitialValue' import { getDefaultSchedule } from '../../utils/getDefaultSchedule' const ScheduleJob = ({ - defaultCron, - handleEditJob, + defaultCron = '', + handleEditJob = () => {}, handleRunJob, panelDispatch, panelState, @@ -142,11 +142,6 @@ const ScheduleJob = ({ ) } -ScheduleJob.defaultProps = { - defaultCron: '', - handleEditJob: () => {} -} - ScheduleJob.propTypes = { defaultCron: PropTypes.string, handleEditJob: PropTypes.func, diff --git a/src/components/Table/Table.js b/src/components/Table/Table.js index 85858dd92..635a30ccb 100644 --- a/src/components/Table/Table.js +++ b/src/components/Table/Table.js @@ -33,22 +33,26 @@ const Table = React.forwardRef( ( { actionsMenu, - applyDetailsChanges, - applyDetailsChangesCallback, + applyDetailsChanges = () => {}, + applyDetailsChangesCallback = () => {}, children, - detailsFormInitialValues, - getCloseDetailsLink, - handleCancel, - hideActionsMenu, - mainRowItemsCount, + detailsFormInitialValues = {}, + getCloseDetailsLink = null, + handleCancel = () => {}, + hideActionsMenu = false, + mainRowItemsCount = 1, pageData, - retryRequest, - selectedItem, - sortProps, - tab, - tableClassName, - tableHeaders, - virtualizationConfig + retryRequest = () => {}, + selectedItem = {}, + sortProps = null, + tab = '', + tableClassName = '', + tableHeaders = [], + virtualizationConfig = { + tableBodyPaddingTop: 0, + startIndex: -1, + endIndex: -1 + } }, ref ) => { @@ -141,40 +145,13 @@ const Table = React.forwardRef( } ) -Table.defaultProps = { - applyDetailsChanges: () => {}, - applyDetailsChangesCallback: () => {}, - detailsFormInitialValues: {}, - getCloseDetailsLink: null, - groupedContent: {}, - handleCancel: () => {}, - handleExpandRow: () => {}, - handleSelectItem: () => {}, - hideActionsMenu: false, - mainRowItemsCount: 1, - retryRequest: () => {}, - selectedItem: {}, - sortProps: null, - tab: '', - tableClassName: '', - tableHeaders: [], - virtualizationConfig: { - tableBodyPaddingTop: 0, - startIndex: -1, - endIndex: -1 - } -} - Table.propTypes = { actionsMenu: ACTIONS_MENU.isRequired, applyDetailsChanges: PropTypes.func, applyDetailsChangesCallback: PropTypes.func, detailsFormInitialValues: PropTypes.object, getCloseDetailsLink: PropTypes.func, - groupedContent: PropTypes.shape({}), handleCancel: PropTypes.func, - handleExpandRow: PropTypes.func, - handleSelectItem: PropTypes.func, hideActionsMenu: PropTypes.bool, mainRowItemsCount: PropTypes.number, pageData: PropTypes.shape({}).isRequired, diff --git a/src/components/Table/TableHead.js b/src/components/Table/TableHead.js index f87f2a9a2..8e1444bb7 100644 --- a/src/components/Table/TableHead.js +++ b/src/components/Table/TableHead.js @@ -27,7 +27,10 @@ import { Tip, Tooltip, TextTooltipTemplate } from 'igz-controls/components' import { SORT_PROPS } from 'igz-controls/types' const TableHead = React.forwardRef( - ({ content, hideActionsMenu, mainRowItemsCount, selectedItem, sortProps }, ref) => { + ( + { content, hideActionsMenu = false, mainRowItemsCount, selectedItem, sortProps = null }, + ref + ) => { const getHeaderCellClasses = ( headerId, isSortable, @@ -79,11 +82,6 @@ const TableHead = React.forwardRef( } ) -TableHead.defaultProps = { - hideActionsMenu: false, - sortProps: null -} - TableHead.propTypes = { content: PropTypes.array.isRequired, hideActionsMenu: PropTypes.bool, diff --git a/src/components/Table/TableView.js b/src/components/Table/TableView.js index f83215d70..ec7fed65c 100644 --- a/src/components/Table/TableView.js +++ b/src/components/Table/TableView.js @@ -31,11 +31,11 @@ import { SORT_PROPS } from 'igz-controls/types' const TableView = ({ actionsMenu, - applyDetailsChanges, - applyDetailsChangesCallback, + applyDetailsChanges = () => {}, + applyDetailsChangesCallback = () => {}, children, detailsFormInitialValues, - getCloseDetailsLink, + getCloseDetailsLink = null, handleCancel, hideActionsMenu, isTablePanelOpen, @@ -43,7 +43,7 @@ const TableView = ({ pageData, retryRequest, selectedItem, - sortProps, + sortProps = null, tab, tableBodyRef, tableClassName, @@ -117,14 +117,6 @@ const TableView = ({ ) } -TableView.defaultProps = { - applyDetailsChanges: () => {}, - applyDetailsChangesCallback: () => {}, - getCloseDetailsLink: null, - groupLatestJob: {}, - sortProps: null -} - TableView.propTypes = { actionsMenu: ACTIONS_MENU.isRequired, applyDetailsChanges: PropTypes.func, diff --git a/src/components/Workflow/JobsFunctionsTableRow/JobsFunctionsTableRow.js b/src/components/Workflow/JobsFunctionsTableRow/JobsFunctionsTableRow.js index 64b9c65e0..8c1572640 100644 --- a/src/components/Workflow/JobsFunctionsTableRow/JobsFunctionsTableRow.js +++ b/src/components/Workflow/JobsFunctionsTableRow/JobsFunctionsTableRow.js @@ -27,7 +27,7 @@ import TableCell from '../../../elements/TableCell/TableCell' import { DETAILS_OVERVIEW_TAB } from '../../../constants' import { isWorkflowJobSelected } from '../workflow.util' -const JobsFunctionsTableRow = ({ handleSelectItem, rowItem, selectedItem }) => { +const JobsFunctionsTableRow = ({ handleSelectItem = () => {}, rowItem, selectedItem = {} }) => { const params = useParams() const rowClassNames = classnames( 'table-row', @@ -60,11 +60,6 @@ const JobsFunctionsTableRow = ({ handleSelectItem, rowItem, selectedItem }) => { ) } -JobsFunctionsTableRow.defaultProps = { - handleSelectItem: () => {}, - selectedItem: {} -} - JobsFunctionsTableRow.propTypes = { handleSelectItem: PropTypes.func, rowItem: PropTypes.shape({}).isRequired, diff --git a/src/components/Workflow/Workflow.js b/src/components/Workflow/Workflow.js index e31f02388..3964dc0f1 100644 --- a/src/components/Workflow/Workflow.js +++ b/src/components/Workflow/Workflow.js @@ -77,10 +77,10 @@ const Workflow = ({ pageData, refresh, refreshJobs, - selectedFunction, - selectedJob, + selectedFunction = {}, + selectedJob = {}, setWorkflowsViewMode, - workflow, + workflow = {}, workflowsViewMode }) => { const [jobsContent, setJobsContent] = useState([]) @@ -301,12 +301,6 @@ const Workflow = ({ ) } -Workflow.defaultProps = { - selectedFunction: {}, - selectedJob: {}, - workflow: {} -} - Workflow.propTypes = { actionsMenu: ACTIONS_MENU.isRequired, backLink: PropTypes.string.isRequired, diff --git a/src/constants.js b/src/constants.js index bcf5e1e00..657ebcb77 100644 --- a/src/constants.js +++ b/src/constants.js @@ -112,6 +112,8 @@ export const PROJECT_QUICK_ACTIONS_PAGE = 'quick-actions' export const CONSUMER_GROUP_PAGE = 'CONSUMER_GROUP' export const CONSUMER_GROUPS_PAGE = 'CONSUMER_GROUPS' +export const CONSUMER_GROUPS_FILTER = 'CONSUMER_GROUPS_FILTER' +export const CONSUMER_GROUP_FILTER = 'CONSUMER_GROUP_FILTER' /*=========== DATASETS =============*/ export const DATASETS_FILTERS = 'DATASETS_FILTERS' @@ -541,7 +543,6 @@ export const GROUP_BY_NAME = 'name' export const GROUP_BY_NONE = 'none' export const GROUP_BY_WORKFLOW = 'workflow' export const SHOW_ITERATIONS = 'iter' -export const SHOW_UNTAGGED_ITEMS = 'showUntagged' export const FILTER_ALL_ITEMS = 'all' export const TAG_FILTER_ALL_ITEMS = 'All' export const TAG_FILTER_LATEST = 'latest' @@ -552,15 +553,19 @@ export const GROUP_BY_FILTER = 'groupBy' export const ITERATIONS_FILTER = 'iter' export const LABELS_FILTER = 'labels' export const NAME_FILTER = 'name' +export const DATES_FILTER = 'dates' export const PROJECT_FILTER = 'project' +export const TYPE_FILTER = 'type' export const SHOW_UNTAGGED_FILTER = 'showUntagged' export const SORT_BY = 'sortBy' -export const STATUS_FILTER = 'status' +export const STATUS_FILTER = 'state' export const TAG_FILTER = 'tag' export const AUTO_REFRESH_ID = 'auto-refresh' export const AUTO_REFRESH = 'Auto Refresh' export const ANY_TIME = 'Any time' +export const STATUS_FILTER_NAME = 'state' +export const FILTER_MENU = 'filterMenu' export const FILTER_MENU_MODAL = 'filterMenuModal' export const JOB_WIZARD_FILTERS = 'jobWizardFilters' export const HUB_CATEGORIES_FILTER = 'hubCategories' diff --git a/src/elements/ActionMenuItem/ActionsMenuItem.js b/src/elements/ActionMenuItem/ActionsMenuItem.js index 9125bb4ba..197f04353 100644 --- a/src/elements/ActionMenuItem/ActionsMenuItem.js +++ b/src/elements/ActionMenuItem/ActionsMenuItem.js @@ -25,7 +25,7 @@ import './actionsMenuItem.scss' import { Tooltip, TextTooltipTemplate } from 'igz-controls/components' -const ActionsMenuItem = ({ dataItem, index, isIconDisplayed, menuItem }) => { +const ActionsMenuItem = ({ dataItem = {}, index, isIconDisplayed, menuItem }) => { const iconClassNames = classnames( 'actions-menu__icon', isIconDisplayed && 'actions-menu__icon_visible' @@ -41,8 +41,6 @@ const ActionsMenuItem = ({ dataItem, index, isIconDisplayed, menuItem }) => { data-testid={`actions-menu__option-${index}`} className={menuClassNames} onClick={event => { - event.stopPropagation() - if (!menuItem.disabled) { menuItem.onClick(dataItem) } @@ -60,12 +58,9 @@ const ActionsMenuItem = ({ dataItem, index, isIconDisplayed, menuItem }) => { ) } -ActionsMenuItem.defaultProps = { - dataItem: {} -} - ActionsMenuItem.propTypes = { dataItem: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.string]), + index: PropTypes.number.isRequired, isIconDisplayed: PropTypes.bool.isRequired, menuItem: PropTypes.shape({}).isRequired } diff --git a/src/elements/AddArtifactTagPopUp/AddArtifactTagPopUp.js b/src/elements/AddArtifactTagPopUp/AddArtifactTagPopUp.js index 86b619dbf..13248bcb9 100644 --- a/src/elements/AddArtifactTagPopUp/AddArtifactTagPopUp.js +++ b/src/elements/AddArtifactTagPopUp/AddArtifactTagPopUp.js @@ -33,12 +33,13 @@ import { getValidationRules } from 'igz-controls/utils/validation.util' import { setNotification } from '../../reducers/notificationReducer' import { showErrorNotification } from '../../utils/notifications.util' import { useModalBlockHistory } from '../../hooks/useModalBlockHistory.hook' +import { isSubmitDisabled } from 'igz-controls/utils/form.util' const AddArtifactTagPopUp = ({ artifact, getArtifact, isOpen, - onAddTag, + onAddTag = () => {}, onResolve, projectName }) => { @@ -116,7 +117,7 @@ const AddArtifactTagPopUp = ({ variant: TERTIARY_BUTTON }, { - disabled: formState.submitting || (formState.invalid && formState.submitFailed), + disabled: isSubmitDisabled(formState), label: 'Add', onClick: formState.handleSubmit, variant: SECONDARY_BUTTON @@ -147,8 +148,8 @@ const AddArtifactTagPopUp = ({ artifact.kind === MODEL_TYPE ? 'Model tag' : artifact.kind === DATASET_TYPE - ? 'Dataset tag' - : 'Artifact tag' + ? 'Dataset tag' + : 'Artifact tag' }`} focused required @@ -170,10 +171,6 @@ const AddArtifactTagPopUp = ({ ) } -AddArtifactTagPopUp.defaultProps = { - onAddTag: () => {} -} - AddArtifactTagPopUp.propTypes = { getArtifact: PropTypes.func.isRequired, isOpen: PropTypes.bool.isRequired, diff --git a/src/elements/ArtifactsFilterTree/ArtifactsFilterTreeDropDown.js b/src/elements/ArtifactsFilterTree/ArtifactsFilterTreeDropDown.js index 29e2144be..83bc1419a 100644 --- a/src/elements/ArtifactsFilterTree/ArtifactsFilterTreeDropDown.js +++ b/src/elements/ArtifactsFilterTree/ArtifactsFilterTreeDropDown.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types' const ArtifactFilterTreeDropDown = ({ filterTree, - filterTreeOptions, + filterTreeOptions = [], handleSelectFilter, setIsDropDownMenu }) => { @@ -51,10 +51,6 @@ const ArtifactFilterTreeDropDown = ({ ) } -ArtifactFilterTreeDropDown.defaultProps = { - filterTreeOptions: [] -} - ArtifactFilterTreeDropDown.propTypes = { filterTree: PropTypes.string.isRequired, filterTreeOptions: PropTypes.array, diff --git a/src/elements/ArtifactsTableRow/ArtifactsTableRow.js b/src/elements/ArtifactsTableRow/ArtifactsTableRow.js index 27758385c..61168873f 100644 --- a/src/elements/ArtifactsTableRow/ArtifactsTableRow.js +++ b/src/elements/ArtifactsTableRow/ArtifactsTableRow.js @@ -40,15 +40,15 @@ import { isRowExpanded, PARENT_ROW_EXPANDED_CLASS } from '../../utils/tableRows. const ArtifactsTableRow = ({ actionsMenu, - handleExpandRow, - handleSelectItem, - hideActionsMenu, + handleExpandRow = null, + handleSelectItem = () => {}, + hideActionsMenu = false, rowIndex, - mainRowItemsCount, + mainRowItemsCount = 1, rowItem, selectedItem, selectedRowData, - tab + tab = '' }) => { const parent = useRef() const params = useParams() @@ -226,15 +226,6 @@ const ArtifactsTableRow = ({ ) } -ArtifactsTableRow.defaultProps = { - handleExpandRow: null, - handleSelectItem: () => {}, - hideActionsMenu: false, - tableContent: null, - mainRowItemsCount: 1, - tab: '' -} - ArtifactsTableRow.propTypes = { actionsMenu: ACTIONS_MENU.isRequired, handleExpandRow: PropTypes.func, @@ -243,7 +234,6 @@ ArtifactsTableRow.propTypes = { rowIndex: PropTypes.number.isRequired, rowItem: PropTypes.shape({}).isRequired, selectedItem: PropTypes.shape({}).isRequired, - tableContent: PropTypes.arrayOf(PropTypes.shape({})), tab: PropTypes.string } diff --git a/src/elements/BreadcrumbsDropdown/BreadcrumbsDropdown.js b/src/elements/BreadcrumbsDropdown/BreadcrumbsDropdown.js index 588111b45..fe2dc7a84 100644 --- a/src/elements/BreadcrumbsDropdown/BreadcrumbsDropdown.js +++ b/src/elements/BreadcrumbsDropdown/BreadcrumbsDropdown.js @@ -31,7 +31,17 @@ import './breadcrumbsDropdown.scss' const BreadcrumbsDropdown = forwardRef( ( - { link, list, onClick, screen, searchValue, setSearchValue, selectedItem, tab, withSearch }, + { + link, + list, + onClick = () => {}, + screen = '', + searchValue, + setSearchValue, + selectedItem, + tab = '', + withSearch = false + }, ref ) => { return ( @@ -98,14 +108,6 @@ const BreadcrumbsDropdown = forwardRef( } ) -BreadcrumbsDropdown.defaultProps = { - onClick: () => {}, - screen: '', - searchOnChange: () => {}, - tab: '', - withSearch: false -} - BreadcrumbsDropdown.propTypes = { link: PropTypes.string.isRequired, list: PropTypes.arrayOf(PropTypes.shape({})).isRequired, diff --git a/src/elements/ContentMenu/ContentMenu.js b/src/elements/ContentMenu/ContentMenu.js index ce6a23d0e..7ca8b5a55 100644 --- a/src/elements/ContentMenu/ContentMenu.js +++ b/src/elements/ContentMenu/ContentMenu.js @@ -26,7 +26,7 @@ import { CONTENT_MENU_TABS } from '../../types' import './contentMenu.scss' -const ContentMenu = ({ activeTab, disabled, screen, tabs, onClick }) => { +const ContentMenu = ({ activeTab = '', disabled = false, screen, tabs = [], onClick }) => { const params = useParams() const handleClick = (e, tabId) => { if (!disabled) { @@ -80,12 +80,6 @@ const ContentMenu = ({ activeTab, disabled, screen, tabs, onClick }) => { ) } -ContentMenu.defaultProps = { - activeTab: '', - disabled: false, - tabs: [] -} - ContentMenu.propTypes = { activeTab: PropTypes.string.isRequired, disabled: PropTypes.bool, diff --git a/src/elements/ContentMenu/contentMenu.scss b/src/elements/ContentMenu/contentMenu.scss index cd4899b62..bd22800e7 100644 --- a/src/elements/ContentMenu/contentMenu.scss +++ b/src/elements/ContentMenu/contentMenu.scss @@ -4,8 +4,9 @@ .content-menu { display: inline-flex; - align-items: flex-end; + align-items: center; width: 100%; + min-height: 40px; &__list { display: flex; diff --git a/src/elements/CreateFeatureVectorPopUp/CreateFeatureVectorPopUp.js b/src/elements/CreateFeatureVectorPopUp/CreateFeatureVectorPopUp.js index 043e438bd..732fdf2c0 100644 --- a/src/elements/CreateFeatureVectorPopUp/CreateFeatureVectorPopUp.js +++ b/src/elements/CreateFeatureVectorPopUp/CreateFeatureVectorPopUp.js @@ -36,58 +36,67 @@ import { Tooltip } from 'igz-controls/components' -import { getValidationRules, getInternalLabelsValidationRule } from 'igz-controls/utils/validation.util' -import { setFieldState } from 'igz-controls/utils/form.util' -import { TAG_LATEST } from '../../constants' +import { + getValidationRules, + getInternalLabelsValidationRule +} from 'igz-controls/utils/validation.util' +import { setFieldState, isSubmitDisabled } from 'igz-controls/utils/form.util' import { LABEL_BUTTON, PRIMARY_BUTTON } from 'igz-controls/constants' import { getChipOptions } from '../../utils/getChipOptions' import { convertChipsData, parseChipsData } from '../../utils/convertChipsData' import './createFeatureVectorPopUp.scss' -const CreateFeatureVectorPopUp = ({ closePopUp, createFeatureVector, featureVectorData }) => { +const CreateFeatureVectorPopUp = ({ + closePopUp, + createFeatureVector, + featureVectorData = { + name: '', + tag: '', + description: '', + labels: {} + } +}) => { const [tagTooltipIsHidden, setTagTooltipIsHidden] = useState(false) const frontendSpec = useSelector(store => store.appStore.frontendSpec) const initialValues = { name: featureVectorData.name, - tag: featureVectorData.tag || TAG_LATEST, + tag: featureVectorData.tag, description: featureVectorData.description, labels: parseChipsData(featureVectorData.labels, frontendSpec.internal_labels) } + + const handleCreateFeatureVector = values => { + createFeatureVector({ + name: values.name, + tag: values.tag, + description: values.description, + labels: convertChipsData(values.labels) + }) + } + const formRef = React.useRef( createForm({ initialValues, mutators: { ...arrayMutators, setFieldState }, - onSubmit: () => {} + onSubmit: handleCreateFeatureVector }) ) - const handleCreateFeatureVector = (e, formState) => { - e.preventDefault() - - if (formState.valid) { - createFeatureVector({ - name: formState.values.name, - tag: formState.values.tag, - description: formState.values.description, - labels: convertChipsData(formState.values.labels) - }) - } - } - return ( - {}}> + {formState => { return ( <>
    setTagTooltipIsHidden(false)} + placeholder="latest" /> { @@ -146,10 +155,10 @@ const CreateFeatureVectorPopUp = ({ closePopUp, createFeatureVector, featureVect onClick={closePopUp} />
    @@ -160,15 +169,6 @@ const CreateFeatureVectorPopUp = ({ closePopUp, createFeatureVector, featureVect ) } -CreateFeatureVectorPopUp.defaultProps = { - featureVectorData: { - name: '', - tag: '', - description: '', - labels: {} - } -} - CreateFeatureVectorPopUp.propTypes = { closePopUp: PropTypes.func.isRequired, featureVectorData: PropTypes.shape({ diff --git a/src/elements/CreateJobCardTemplate/CreateJobCardTemplate.js b/src/elements/CreateJobCardTemplate/CreateJobCardTemplate.js index 59ded69ee..1fefc3812 100644 --- a/src/elements/CreateJobCardTemplate/CreateJobCardTemplate.js +++ b/src/elements/CreateJobCardTemplate/CreateJobCardTemplate.js @@ -27,24 +27,13 @@ import { truncateUid } from '../../utils' import './createJobCardTemplate.scss' -const CreateJobCardTemplate = ({ - className, - func, - handleSelectGroupFunctions -}) => { +const CreateJobCardTemplate = ({ className = '', func, handleSelectGroupFunctions }) => { const templateClassName = classnames('card-template', className) return ( -
    handleSelectGroupFunctions(func)} - > +
    handleSelectGroupFunctions(func)}>
    - - } - > + }> {func.name || func?.metadata.name}
    @@ -57,19 +46,13 @@ const CreateJobCardTemplate = ({ {truncateUid(func.metadata.hash)} {func.metadata.tag} -
    - {func.metadata.description} -
    +
    {func.metadata.description}
    )}
    ) } -CreateJobCardTemplate.defaultProps = { - className: '' -} - CreateJobCardTemplate.propTypes = { className: PropTypes.string, func: PropTypes.shape({}).isRequired, diff --git a/src/elements/DeployModelPopUp/DeployModelPopUp.js b/src/elements/DeployModelPopUp/DeployModelPopUp.js index ec5083b80..17ff6e73d 100644 --- a/src/elements/DeployModelPopUp/DeployModelPopUp.js +++ b/src/elements/DeployModelPopUp/DeployModelPopUp.js @@ -40,7 +40,7 @@ import { MODAL_SM, SECONDARY_BUTTON, TERTIARY_BUTTON } from 'igz-controls/consta import { buildFunction } from '../../reducers/artifactsReducer' import { generateUri } from '../../utils/resources' import { getValidationRules } from 'igz-controls/utils/validation.util' -import { setFieldState } from 'igz-controls/utils/form.util' +import { setFieldState, isSubmitDisabled } from 'igz-controls/utils/form.util' import { setNotification } from '../../reducers/notificationReducer' import { showErrorNotification } from '../../utils/notifications.util' import { useModalBlockHistory } from '../../hooks/useModalBlockHistory.hook' @@ -49,7 +49,13 @@ import { ReactComponent as QuestionMarkIcon } from 'igz-controls/images/question import './deployModelPopUp.scss' -const DeployModelPopUp = ({ functionList, functionOptionList, isOpen, model, onResolve }) => { +const DeployModelPopUp = ({ + functionList, + functionOptionList, + isOpen, + model, + onResolve = () => {} +}) => { const [tagOptionList, setTagOptionList] = useState([]) const [initialValues, setInitialValues] = useState({ modelName: '', @@ -166,7 +172,7 @@ const DeployModelPopUp = ({ functionList, functionOptionList, isOpen, model, onR variant: TERTIARY_BUTTON }, { - disabled: formState.submitting || (formState.invalid && formState.submitFailed), + disabled: isSubmitDisabled(formState), label: 'Deploy', onClick: formState.handleSubmit, variant: SECONDARY_BUTTON @@ -271,10 +277,6 @@ const DeployModelPopUp = ({ functionList, functionOptionList, isOpen, model, onR ) } -DeployModelPopUp.defaultProps = { - onResolve: () => {} -} - DeployModelPopUp.propTypes = { isOpen: PropTypes.bool.isRequired, model: PropTypes.shape({}).isRequired, diff --git a/src/elements/DetailsInfoItem/DetailsInfoItem.js b/src/elements/DetailsInfoItem/DetailsInfoItem.js index 05408ec51..e69a83bbe 100644 --- a/src/elements/DetailsInfoItem/DetailsInfoItem.js +++ b/src/elements/DetailsInfoItem/DetailsInfoItem.js @@ -26,12 +26,13 @@ import { useSelector } from 'react-redux' import ChipCell from '../../common/ChipCell/ChipCell' import CopyToClipboard from '../../common/CopyToClipboard/CopyToClipboard' +import DetailsInfoItemChip from '../DetailsInfoItemChip/DetailsInfoItemChip' import Input from '../../common/Input/Input' -import { Tooltip, TextTooltipTemplate, RoundedIcon } from 'igz-controls/components' import { FormInput, FormOnChange, FormTextarea } from 'igz-controls/components' -import DetailsInfoItemChip from '../DetailsInfoItemChip/DetailsInfoItemChip' +import { Tooltip, TextTooltipTemplate, RoundedIcon } from 'igz-controls/components' import { CHIP_OPTIONS } from '../../types' +import { generateFunctionDetailsLink } from '../../utils/generateFunctionDetailsLink' import { getValidationRules } from 'igz-controls/utils/validation.util' import { ReactComponent as Checkmark } from 'igz-controls/images/checkmark2.svg' @@ -42,23 +43,26 @@ const DetailsInfoItem = React.forwardRef( ( { changesData, - chipsClassName, - chipsData, - currentField, - detailsInfoDispatch, - detailsInfoState, - editableFieldType, + chipsClassName = '', + chipsData = { + chips: [], + chipOptions: {}, + delimiter: null + }, + currentField = '', + detailsInfoDispatch = () => {}, + detailsInfoState = {}, + editableFieldType = null, formState, - func, + func = '', handleDiscardChanges, - handleFinishEdit, - info, - isFieldInEditMode, - item, - onClick, - params, - setChangesData, - state + handleFinishEdit = () => {}, + info = null, + isFieldInEditMode = false, + item = {}, + onClick = null, + setChangesData = () => {}, + state = '' }, ref ) => { @@ -151,14 +155,16 @@ const DetailsInfoItem = React.forwardRef( return (
    {(Array.isArray(info) ? info : [info]).map((infoItem, index) => { - return - {infoItem} - + return ( + + {infoItem} + + ) })}
    ) @@ -206,7 +212,7 @@ const DetailsInfoItem = React.forwardRef(
    ) } else if (!isEmpty(func)) { - const [functionProject, functionNameWithHash] = func.split('/') + const funcLink = generateFunctionDetailsLink(func) return ( {func} ) } else if ((item.link || item.externalLink) && info) { - return
    - { - (Array.isArray(info) ? info : [info]).map((infoItem, index) => { + return ( +
    + {(Array.isArray(info) ? info : [info]).map((infoItem, index) => { + if (!infoItem) return null + return item.link ? ( - + }>{infoItem} ) : ( @@ -242,9 +258,9 @@ const DetailsInfoItem = React.forwardRef( ) - }) - } -
    + })} +
    + ) } else if ((typeof info !== 'object' || Array.isArray(info)) && item?.editModeEnabled) { return (
    @@ -307,28 +323,6 @@ const DetailsInfoItem = React.forwardRef( } ) -DetailsInfoItem.defaultProps = { - chipsClassName: '', - chipsData: { - chips: [], - chipOptions: {}, - delimiter: null - }, - currentField: '', - detailsInfoDispatch: () => {}, - detailsInfoState: {}, - editableFieldType: null, - func: '', - handleFinishEdit: () => {}, - info: null, - isFieldInEditMode: false, - item: {}, - onClick: null, - params: {}, - setChangesData: () => {}, - state: '' -} - DetailsInfoItem.propTypes = { changesData: PropTypes.object, chipsClassName: PropTypes.string, @@ -348,7 +342,6 @@ DetailsInfoItem.propTypes = { isFieldInEditMode: PropTypes.bool, item: PropTypes.shape({}), onClick: PropTypes.func, - params: PropTypes.shape({}), setChangesData: PropTypes.func, state: PropTypes.string } diff --git a/src/elements/EnvironmentVariables/EnvironmentVariables.js b/src/elements/EnvironmentVariables/EnvironmentVariables.js index 072e5233e..c3ff1ad82 100644 --- a/src/elements/EnvironmentVariables/EnvironmentVariables.js +++ b/src/elements/EnvironmentVariables/EnvironmentVariables.js @@ -34,12 +34,12 @@ import { ReactComponent as Edit } from 'igz-controls/images/edit.svg' import { ReactComponent as Delete } from 'igz-controls/images/delete.svg' const EnvironmentVariables = ({ - className, + className = '', envVariables, handleAddNewEnv, handleDeleteEnv, handleEditEnv, - isPanelEditMode + isPanelEditMode = false }) => { const [newEnvVariable, setNewEnvVariable] = useState(newVariableInitialState) const [validation, setValidation] = useState(validationInitialState) @@ -174,11 +174,6 @@ const EnvironmentVariables = ({ ) } -EnvironmentVariables.defaultProps = { - className: '', - isPanelEditMode: false -} - EnvironmentVariables.propTypes = { className: PropTypes.string, envVariables: PropTypes.array.isRequired, diff --git a/src/elements/EnvironmentVariables/EnvironmentVariablesView.js b/src/elements/EnvironmentVariables/EnvironmentVariablesView.js index 749e882e1..c121b01ab 100644 --- a/src/elements/EnvironmentVariables/EnvironmentVariablesView.js +++ b/src/elements/EnvironmentVariables/EnvironmentVariablesView.js @@ -35,14 +35,14 @@ import './enviromnetVariables.scss' const EnvironmentVariablesView = ({ addEnvVariable, - className, + className = '', discardChanges, editEnvVariable, envVariables, generateActionsMenu, - isPanelEditMode, + isPanelEditMode = false, newEnvVariable, - selectedEnvVariable, + selectedEnvVariable = null, setNewEnvVariable, setSelectedEnvVariable, setShowAddNewEnvVariableRow, @@ -63,10 +63,7 @@ const EnvironmentVariablesView = ({
    {tableHeaders.map(header => { - const tableHeaderClassNames = classnames( - 'table__cell', - header.className - ) + const tableHeaderClassNames = classnames('table__cell', header.className) return (
    @@ -77,7 +74,8 @@ const EnvironmentVariablesView = ({
    {envVariables.map((envVariable, index) => - selectedEnvVariable && !isPanelEditMode && + selectedEnvVariable && + !isPanelEditMode && selectedEnvVariable.name === envVariable.name ? (
    - } - > + }> {envVariable.name}
    - } - > + }> {envVariable.type}
    - } - > + }> {envVariable.value}
    - { - !isPanelEditMode && ( -
    - -
    - ) - } + {!isPanelEditMode && ( +
    + +
    + )}
    ) )} @@ -151,12 +138,6 @@ const EnvironmentVariablesView = ({ ) } -EnvironmentVariablesView.defaultProps = { - className: '', - isPanelEditMode: false, - selectedEnvVariable: null -} - EnvironmentVariablesView.propTypes = { addEnvVariable: PropTypes.func.isRequired, className: PropTypes.string, diff --git a/src/elements/FeatureStoreTableRow/FeatureStoreTableRow.js b/src/elements/FeatureStoreTableRow/FeatureStoreTableRow.js index 04ff995ff..b64ee692b 100644 --- a/src/elements/FeatureStoreTableRow/FeatureStoreTableRow.js +++ b/src/elements/FeatureStoreTableRow/FeatureStoreTableRow.js @@ -37,14 +37,14 @@ import { isRowExpanded, PARENT_ROW_EXPANDED_CLASS } from '../../utils/tableRows. const FeatureStoreTableRow = ({ actionsMenu, - handleExpandRow, - handleSelectItem, - hideActionsMenu, - mainRowItemsCount, + handleExpandRow = () => {}, + handleSelectItem = () => {}, + hideActionsMenu = false, + mainRowItemsCount = 1, pageTab, rowIndex, rowItem, - selectedItem, + selectedItem = {}, selectedRowData }) => { const parent = useRef() @@ -210,14 +210,6 @@ const FeatureStoreTableRow = ({ ) } -FeatureStoreTableRow.defaultProps = { - handleExpandRow: () => {}, - handleSelectItem: () => {}, - hideActionsMenu: false, - mainRowItemsCount: 1, - selectedItem: {} -} - FeatureStoreTableRow.propTypes = { actionsMenu: ACTIONS_MENU.isRequired, handleExpandRow: PropTypes.func, diff --git a/src/elements/FeaturesTablePanel/FeaturesTablePanel.js b/src/elements/FeaturesTablePanel/FeaturesTablePanel.js index d449b1f00..e1244b4d4 100644 --- a/src/elements/FeaturesTablePanel/FeaturesTablePanel.js +++ b/src/elements/FeaturesTablePanel/FeaturesTablePanel.js @@ -39,8 +39,8 @@ import { showErrorNotification } from '../../utils/notifications.util' const FeaturesTablePanel = ({ createNewFeatureVector, filtersStore, - handleCancel, - onSubmit, + handleCancel = null, + onSubmit = null, updateFeatureVectorData }) => { const [isCreateFeaturePopUpOpen, setIsCreateFeaturePopUpOpen] = useState(false) @@ -172,11 +172,6 @@ const FeaturesTablePanel = ({ ) } -FeaturesTablePanel.defaultProps = { - handleCancel: null, - onSubmit: null -} - FeaturesTablePanel.propTypes = { handleCancel: PropTypes.func, onSubmit: PropTypes.func diff --git a/src/elements/FormDataInputsTable/FormDataInputsRow/FormDataInputsRow.js b/src/elements/FormDataInputsTable/FormDataInputsRow/FormDataInputsRow.js index 6ae97f7fc..04f61b2e7 100644 --- a/src/elements/FormDataInputsTable/FormDataInputsRow/FormDataInputsRow.js +++ b/src/elements/FormDataInputsTable/FormDataInputsRow/FormDataInputsRow.js @@ -46,14 +46,15 @@ const FormDataInputsRow = ({ applyChanges, dataInputState, deleteRow, - disabled, + disabled = false, discardOrDelete, - editingItem, + editingItem = null, enterEditMode, fields, fieldsPath, formState, getTableArrayErrors, + hasKwargs = false, index, isCurrentRowEditing, params, @@ -190,7 +191,7 @@ const FormDataInputsRow = ({
    { const [dataInputState, setDataInputState] = useState(targetPathInitialState) const tableClassNames = classnames('form-table', className) @@ -106,6 +106,7 @@ const FormDataInputsTable = ({ fieldsPath={fieldsPath} formState={formState} getTableArrayErrors={getTableArrayErrors} + hasKwargs={hasKwargs} index={index} isCurrentRowEditing={isCurrentRowEditing} key={rowPath} @@ -117,7 +118,7 @@ const FormDataInputsTable = ({ /> ) })} - {rowCanBeAdded && ( + {hasKwargs && (
    @@ -431,7 +435,7 @@ const FormParametersRow = ({
    { const withRequiredParametersRef = useRef(true) const predefinedPath = `${fieldsPath}.predefined` @@ -134,6 +134,7 @@ const FormParametersTable = ({ fieldsPath={predefinedPath} formState={formState} getTableArrayErrors={getTableArrayErrors} + hasKwargs={hasKwargs} index={index} isCurrentRowEditing={isCurrentRowEditing} key={rowPath} @@ -163,6 +164,7 @@ const FormParametersTable = ({ fieldsPath={customPath} formState={formState} getTableArrayErrors={getTableArrayErrors} + hasKwargs={hasKwargs} index={index} isCurrentRowEditing={isCurrentRowEditing} key={rowPath} @@ -173,7 +175,7 @@ const FormParametersTable = ({ /> ) })} - {rowCanBeAdded && ( + {hasKwargs && ( { +const FormResourcesUnits = ({ formState, onChangeEnabled = true }) => { const gpuType = useMemo( () => getLimitsGpuType(formState.values.resources?.currentLimits), [formState.values.resources] @@ -235,10 +235,6 @@ const FormResourcesUnits = ({ formState, onChangeEnabled }) => { ) } -FormResourcesUnits.defaultProps = { - onChangeEnabled: true -} - FormResourcesUnits.propTypes = { formState: PropTypes.shape({}).isRequired, onChangeEnabled: PropTypes.bool diff --git a/src/elements/FormVolumesTable/FormVolumesRow/FormVolumesRow.js b/src/elements/FormVolumesTable/FormVolumesRow/FormVolumesRow.js index 9c61a64ef..e2ad647d2 100644 --- a/src/elements/FormVolumesTable/FormVolumesRow/FormVolumesRow.js +++ b/src/elements/FormVolumesTable/FormVolumesRow/FormVolumesRow.js @@ -39,9 +39,9 @@ import './formVolumeRow.scss' const FormVolumesRow = ({ applyChanges, deleteRow, - disabled, + disabled = false, discardOrDelete, - editingItem, + editingItem = null, enterEditMode, fields, fieldsPath, @@ -173,11 +173,6 @@ const FormVolumesRow = ({ ) } -FormVolumesRow.defaultProps = { - disabled: false, - editingItem: null -} - FormVolumesRow.propTypes = { applyChanges: PropTypes.func.isRequired, deleteRow: PropTypes.func.isRequired, diff --git a/src/elements/FormVolumesTable/FormVolumesTable.js b/src/elements/FormVolumesTable/FormVolumesTable.js index 38b75b3a3..1bd7738a6 100644 --- a/src/elements/FormVolumesTable/FormVolumesTable.js +++ b/src/elements/FormVolumesTable/FormVolumesTable.js @@ -29,7 +29,12 @@ import { Tooltip, TextTooltipTemplate } from 'igz-controls/components' import { useFormTable } from 'igz-controls/hooks' import { V3IO_VOLUME_TYPE } from '../../constants' -const FormVolumesTable = ({ disabled, exitEditModeTriggerItem, fieldsPath, formState }) => { +const FormVolumesTable = ({ + disabled = false, + exitEditModeTriggerItem = null, + fieldsPath, + formState +}) => { const tableClassNames = classnames('form-table', disabled && 'disabled') const { addNewRow, @@ -110,11 +115,6 @@ const FormVolumesTable = ({ disabled, exitEditModeTriggerItem, fieldsPath, formS ) } -FormVolumesTable.defaultProps = { - disabled: false, - exitEditModeTriggerItem: null -} - FormVolumesTable.propTypes = { disabled: PropTypes.bool, exitEditModeTriggerItem: PropTypes.any, diff --git a/src/elements/FunctionCardTemplate/FunctionCardTemplate.js b/src/elements/FunctionCardTemplate/FunctionCardTemplate.js index f455b2f8a..dde557377 100644 --- a/src/elements/FunctionCardTemplate/FunctionCardTemplate.js +++ b/src/elements/FunctionCardTemplate/FunctionCardTemplate.js @@ -29,12 +29,12 @@ import { getChipOptions } from '../../utils/getChipOptions' import './functionCardTemplate.scss' const FunctionCardTemplate = ({ - className, - formState, - dense, + className = '', + dense = false, + formState = {}, functionData, onSelectCard, - selected + selected = false }) => { const templateClassName = classnames( 'job-card-template', @@ -96,13 +96,6 @@ const FunctionCardTemplate = ({ ) } -FunctionCardTemplate.defaultProps = { - className: '', - dense: false, - formState: {}, - selected: false -} - FunctionCardTemplate.propTypes = { className: PropTypes.string, dense: PropTypes.bool, diff --git a/src/elements/FunctionsPanelCode/FunctionsPanelCode.js b/src/elements/FunctionsPanelCode/FunctionsPanelCode.js index 9097a0329..8d121e0a1 100644 --- a/src/elements/FunctionsPanelCode/FunctionsPanelCode.js +++ b/src/elements/FunctionsPanelCode/FunctionsPanelCode.js @@ -38,7 +38,7 @@ import { useParams } from 'react-router-dom' const FunctionsPanelCode = ({ appStore, - defaultData, + defaultData = {}, functionsStore, imageType, mode, @@ -287,10 +287,6 @@ const FunctionsPanelCode = ({ ) } -FunctionsPanelCode.defaultProps = { - defaultData: {} -} - FunctionsPanelCode.propTypes = { defaultData: PropTypes.shape({}), imageType: PropTypes.string.isRequired, diff --git a/src/elements/FunctionsPanelGeneral/FunctionsPanelGeneral.js b/src/elements/FunctionsPanelGeneral/FunctionsPanelGeneral.js index 5e15ac595..f9b5b5db0 100644 --- a/src/elements/FunctionsPanelGeneral/FunctionsPanelGeneral.js +++ b/src/elements/FunctionsPanelGeneral/FunctionsPanelGeneral.js @@ -26,7 +26,7 @@ import FunctionsPanelGeneralView from './FunctionsPanelGeneralView' import functionsActions from '../../actions/functions' const FunctionsPanelGeneral = ({ - defaultData, + defaultData = {}, formState, frontendSpec, functionsStore, @@ -57,10 +57,6 @@ const FunctionsPanelGeneral = ({ ) } -FunctionsPanelGeneral.defaultProps = { - defaultData: {} -} - FunctionsPanelGeneral.propTypes = { defaultData: PropTypes.shape({}) } diff --git a/src/elements/FunctionsPanelParameters/FunctionsPanelParametersView.js b/src/elements/FunctionsPanelParameters/FunctionsPanelParametersView.js index 84e7950a7..0503d3205 100644 --- a/src/elements/FunctionsPanelParameters/FunctionsPanelParametersView.js +++ b/src/elements/FunctionsPanelParameters/FunctionsPanelParametersView.js @@ -41,7 +41,7 @@ const FunctionsPanelParametersView = ({ handleAddNewParameter, newParameter, parameters, - selectedParameter, + selectedParameter = null, setNewParameter, setSelectedParameter, setShowAddNewParameterRow, @@ -59,7 +59,7 @@ const FunctionsPanelParametersView = ({
    - {tableHeaders.map((header) => ( + {tableHeaders.map(header => (
    {header.label}
    @@ -67,8 +67,7 @@ const FunctionsPanelParametersView = ({
    {parameters.map((parameter, index) => - selectedParameter && - selectedParameter.data.name === parameter.data.name ? ( + selectedParameter && selectedParameter.data.name === parameter.data.name ? ( { return (
    - }> - {value} - + }>{value}
    ) })}
    - +
    ) @@ -110,10 +104,7 @@ const FunctionsPanelParametersView = ({ /> ) : (
    -
    setShowAddNewParameterRow(true)} - > +
    setShowAddNewParameterRow(true)}>
-
+
{`${formState.values.metrics?.length ?? 0}/${maxSelectionNumber}`}
-
+
@@ -294,12 +307,6 @@ const MetricsSelector = ({ maxSelectionNumber, metrics, name, onSelect, preselec ) } -MetricsSelector.defaultProps = { - maxSelectionNumber: 20, - onSelect: () => {}, - preselectedMetrics: [] -} - MetricsSelector.propTypes = { maxSelectionNumber: PropTypes.number, metrics: METRICS_SELECTOR_OPTIONS.isRequired, diff --git a/src/elements/NavbarLink/NavbarLink.js b/src/elements/NavbarLink/NavbarLink.js index be2d186aa..f51ff827c 100644 --- a/src/elements/NavbarLink/NavbarLink.js +++ b/src/elements/NavbarLink/NavbarLink.js @@ -23,7 +23,7 @@ import PropTypes from 'prop-types' import './NavbarLink.scss' -const NavbarLink = ({ externalLink, icon, label, link, ...props }) => { +const NavbarLink = ({ externalLink = false, icon = {}, label = '', link = '', ...props }) => { return (
  • {externalLink ? ( @@ -46,13 +46,6 @@ const NavbarLink = ({ externalLink, icon, label, link, ...props }) => { ) } -NavbarLink.defaultProps = { - externalLink: false, - icon: {}, - label: '', - link: '' -} - NavbarLink.propTypes = { externalLink: PropTypes.bool, icon: PropTypes.object, diff --git a/src/elements/NewFunctionPopUp/NewFunctionPopUp.js b/src/elements/NewFunctionPopUp/NewFunctionPopUp.js index ec70c7403..e5042d56b 100644 --- a/src/elements/NewFunctionPopUp/NewFunctionPopUp.js +++ b/src/elements/NewFunctionPopUp/NewFunctionPopUp.js @@ -37,11 +37,11 @@ import { FUNCTION_TYPE_JOB } from '../../constants' import './newFunctionPopUp.scss' const NewFunctionPopUp = ({ - action, - closePopUp, + action = null, + closePopUp = null, functionsStore, - isCustomPosition, - isOpened, + isCustomPosition = false, + isOpened = false, setNewFunctionKind, setNewFunctionName, setNewFunctionTag, @@ -209,13 +209,6 @@ const NewFunctionPopUp = ({ ) } -NewFunctionPopUp.defaultProps = { - action: null, - closePopUp: null, - isCustomPosition: false, - isOpened: false -} - NewFunctionPopUp.propTypes = { action: PropTypes.shape({}), closePopUp: PropTypes.func, diff --git a/src/elements/PageHeader/PageHeader.js b/src/elements/PageHeader/PageHeader.js index 12835b903..1974b24be 100644 --- a/src/elements/PageHeader/PageHeader.js +++ b/src/elements/PageHeader/PageHeader.js @@ -27,7 +27,7 @@ import { ReactComponent as Back } from 'igz-controls/images/back-arrow.svg' import './pageHeader.scss' -const PageHeader = ({ title, description, backLink }) => { +const PageHeader = ({ title, description = '', backLink = '' }) => { return (
    {backLink && ( @@ -47,11 +47,6 @@ const PageHeader = ({ title, description, backLink }) => { ) } -PageHeader.defaultProps = { - backLink: '', - description: '' -} - PageHeader.propTypes = { backLink: PropTypes.string, description: PropTypes.string, diff --git a/src/elements/PanelCredentialsAccessKey/PanelCredentialsAccessKey.js b/src/elements/PanelCredentialsAccessKey/PanelCredentialsAccessKey.js index a0952b617..4109995a8 100644 --- a/src/elements/PanelCredentialsAccessKey/PanelCredentialsAccessKey.js +++ b/src/elements/PanelCredentialsAccessKey/PanelCredentialsAccessKey.js @@ -29,10 +29,10 @@ import Input from '../../common/Input/Input' import './panelCredentialsAccessKey.scss' const PanelCredentialsAccessKey = ({ - className, + className = '', credentialsAccessKey, - isPanelEditMode, - required, + isPanelEditMode = false, + required = false, setCredentialsAccessKey, setValidation, validation @@ -94,12 +94,6 @@ const PanelCredentialsAccessKey = ({ ) } -PanelCredentialsAccessKey.defaultProps = { - className: '', - isPanelEditMode: false, - required: false -} - PanelCredentialsAccessKey.propTypes = { className: PropTypes.string, credentialsAccessKey: PropTypes.string.isRequired, diff --git a/src/elements/PanelResourcesUnits/PanelResourcesUnits.js b/src/elements/PanelResourcesUnits/PanelResourcesUnits.js index 62f5d21b9..17bbec59c 100644 --- a/src/elements/PanelResourcesUnits/PanelResourcesUnits.js +++ b/src/elements/PanelResourcesUnits/PanelResourcesUnits.js @@ -41,7 +41,7 @@ const PanelResourcesUnits = ({ gpuType, handleSelectCpuUnit, handleSelectMemoryUnit, - isPanelEditMode, + isPanelEditMode = false, setCpuValue, setGpuValue, setMemoryValue, @@ -172,10 +172,6 @@ const PanelResourcesUnits = ({ ) } -PanelResourcesUnits.defaultProps = { - isPanelEditMode: false -} - PanelResourcesUnits.propTypes = { data: PropTypes.shape({}).isRequired, gpuType: PropTypes.string.isRequired, diff --git a/src/elements/PanelSection/PanelSection.js b/src/elements/PanelSection/PanelSection.js index 4d5cf674f..ad6b34738 100644 --- a/src/elements/PanelSection/PanelSection.js +++ b/src/elements/PanelSection/PanelSection.js @@ -24,7 +24,7 @@ import { Tip } from 'igz-controls/components' import './panelSection.scss' -const PanelSection = ({ children, tip, title, className }) => { +const PanelSection = ({ children, tip = '', title, className = '' }) => { return (
    @@ -36,11 +36,6 @@ const PanelSection = ({ children, tip, title, className }) => { ) } -PanelSection.defaultProps = { - className: '', - tip: '' -} - PanelSection.propTypes = { className: PropTypes.string, tip: PropTypes.string, diff --git a/src/elements/ProjectCard/ProjectCardView.js b/src/elements/ProjectCard/ProjectCardView.js index 7635662aa..519845f0e 100644 --- a/src/elements/ProjectCard/ProjectCardView.js +++ b/src/elements/ProjectCard/ProjectCardView.js @@ -44,7 +44,8 @@ const ProjectCardView = React.forwardRef(({ actionsMenu, project, statistics }, if ( event.target.tagName !== 'A' && !ref.current.contains(event.target) && - !chipRef.current?.contains(event.target) + !chipRef.current?.contains(event.target) && + !event.target.closest('#overlay_container') ) { navigate(`/projects/${project.metadata.name}/monitor`) } diff --git a/src/elements/ProjectDataCard/ProjectDataCard.js b/src/elements/ProjectDataCard/ProjectDataCard.js index 0011bf26c..273f077c7 100644 --- a/src/elements/ProjectDataCard/ProjectDataCard.js +++ b/src/elements/ProjectDataCard/ProjectDataCard.js @@ -28,7 +28,16 @@ import ProjectStatistics from '../ProjectStatistics/ProjectStatistics' import ProjectTable from '../ProjectTable/ProjectTable' import { Tip } from 'igz-controls/components' -const ProjectDataCard = ({ content, href, link, params, statistics, table, tip, title }) => { +const ProjectDataCard = ({ + content, + href = '', + link = '', + params, + statistics = {}, + table = {}, + tip = null, + title +}) => { return (
    @@ -83,14 +92,6 @@ const ProjectDataCard = ({ content, href, link, params, statistics, table, tip, ) } -ProjectDataCard.defaultProps = { - href: '', - link: '', - statistics: {}, - table: {}, - tip: null, -} - ProjectDataCard.propTypes = { content: PropTypes.shape({}).isRequired, href: PropTypes.string, diff --git a/src/elements/ProjectSummaryCard/ProjectSummaryCard.js b/src/elements/ProjectSummaryCard/ProjectSummaryCard.js index bb278f4ae..a6863b9a0 100644 --- a/src/elements/ProjectSummaryCard/ProjectSummaryCard.js +++ b/src/elements/ProjectSummaryCard/ProjectSummaryCard.js @@ -24,7 +24,14 @@ import PropTypes from 'prop-types' import Loader from '../../common/Loader/Loader' import { Tip, Tooltip, TextTooltipTemplate } from 'igz-controls/components' -const ProjectSummaryCard = ({ counterValue, link, projectSummary, tip, title, tooltipText }) => { +const ProjectSummaryCard = ({ + counterValue, + link, + projectSummary, + tip = '', + title, + tooltipText = null +}) => { return ( { const kindOptions = useMemo( () => [ @@ -137,11 +137,6 @@ const RegisterArtifactModalForm = ({ ) } -RegisterArtifactModalForm.defaultProps = { - showType: true, - messageByKind: '' -} - RegisterArtifactModalForm.propTypes = { formState: PropTypes.object.isRequired, initialValues: PropTypes.object.isRequired, diff --git a/src/elements/RegisterModelModal/RegisterModelModal.js b/src/elements/RegisterModelModal/RegisterModelModal.js index 47adffcdb..8fbabb0c4 100644 --- a/src/elements/RegisterModelModal/RegisterModelModal.js +++ b/src/elements/RegisterModelModal/RegisterModelModal.js @@ -38,7 +38,7 @@ import { convertChipsData } from '../../utils/convertChipsData' import { getChipOptions } from '../../utils/getChipOptions' import { getValidationRules } from 'igz-controls/utils/validation.util' import { createModelMessages } from '../../utils/createArtifact.util' -import { setFieldState } from 'igz-controls/utils/form.util' +import { setFieldState, isSubmitDisabled } from 'igz-controls/utils/form.util' import { setNotification } from '../../reducers/notificationReducer' import { showErrorNotification } from '../../utils/notifications.util' import { openPopUp } from 'igz-controls/utils/common.util' @@ -126,7 +126,9 @@ function RegisterModelModal({ actions, isOpen, onResolve, params, refresh }) { label: 'Overwrite', variant: PRIMARY_BUTTON, handler: (...args) => { - handleRegisterModel(...args).then(resolve).catch(reject) + handleRegisterModel(...args) + .then(resolve) + .catch(reject) } }, cancelButton: { @@ -136,7 +138,9 @@ function RegisterModelModal({ actions, isOpen, onResolve, params, refresh }) { }, closePopUp: () => reject(), header: createModelMessages.overwriteConfirmTitle, - message: createModelMessages.getOverwriteConfirmMessage(response.data.artifacts[0].kind) + message: createModelMessages.getOverwriteConfirmMessage( + response.data.artifacts[0].kind + ) }) }) } else { @@ -164,7 +168,7 @@ function RegisterModelModal({ actions, isOpen, onResolve, params, refresh }) { variant: TERTIARY_BUTTON }, { - disabled: formState.submitting || (formState.invalid && formState.submitFailed), + disabled: isSubmitDisabled(formState), label: 'Register', onClick: formState.handleSubmit, variant: SECONDARY_BUTTON diff --git a/src/elements/ScheduledJobsTable/ScheduledJobsTable.js b/src/elements/ScheduledJobsTable/ScheduledJobsTable.js index 35769d885..afe368ed0 100644 --- a/src/elements/ScheduledJobsTable/ScheduledJobsTable.js +++ b/src/elements/ScheduledJobsTable/ScheduledJobsTable.js @@ -29,16 +29,17 @@ import Table from '../../components/Table/Table' import NoData from '../../common/NoData/NoData' import Loader from '../../common/Loader/Loader' -import { JOB_KIND_WORKFLOW, JOBS_PAGE, PANEL_EDIT_MODE, SCHEDULE_TAB } from '../../constants' -import { isRowRendered, useVirtualization } from '../../hooks/useVirtualization.hook' +import functionsActions from '../../actions/functions' +import jobsActions from '../../actions/jobs' import { DANGER_BUTTON, FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' +import { FILTERS_CONFIG } from '../../types' +import { JOB_KIND_WORKFLOW, JOBS_PAGE, PANEL_EDIT_MODE, SCHEDULE_TAB } from '../../constants' import { getErrorMsg, openPopUp } from 'igz-controls/utils/common.util' -import { showErrorNotification } from '../../utils/notifications.util' import { getJobFunctionData } from '../../components/Jobs/jobs.util' import { getNoDataMessage } from '../../utils/getNoDataMessage' +import { isRowRendered, useVirtualization } from '../../hooks/useVirtualization.hook' import { setNotification } from '../../reducers/notificationReducer' -import functionsActions from '../../actions/functions' -import jobsActions from '../../actions/jobs' +import { showErrorNotification } from '../../utils/notifications.util' import { useYaml } from '../../hooks/yaml.hook' import { ReactComponent as Delete } from 'igz-controls/images/delete.svg' @@ -50,10 +51,12 @@ import cssVariables from './scheduledJobsTable.scss' const ScheduledJobsTable = ({ context, - filters, + filters = null, + filtersConfig = null, + filtersMenuName = '', jobs, - largeRequestErrorMessage, refreshJobs, + requestErrorMessage, tableContent }) => { const dispatch = useDispatch() @@ -256,10 +259,11 @@ const ScheduledJobsTable = ({ ) : ( @@ -289,16 +293,14 @@ const ScheduledJobsTable = ({ ) } -ScheduledJobsTable.defaultProps = { - filters: [] -} - ScheduledJobsTable.propTypes = { context: PropTypes.object.isRequired, filters: PropTypes.array, + filtersConfig: FILTERS_CONFIG, + filtersMenuName: PropTypes.string, jobs: PropTypes.array.isRequired, - largeRequestErrorMessage: PropTypes.string.isRequired, refreshJobs: PropTypes.func.isRequired, + requestErrorMessage: PropTypes.string.isRequired, tableContent: PropTypes.array.isRequired } diff --git a/src/elements/TableCell/TableCell.js b/src/elements/TableCell/TableCell.js index 10a3ed32d..65f6e4eb3 100644 --- a/src/elements/TableCell/TableCell.js +++ b/src/elements/TableCell/TableCell.js @@ -38,15 +38,18 @@ import { ReactComponent as ArtifactView } from 'igz-controls/images/eye-icon.svg import { ReactComponent as Arrow } from 'igz-controls/images/arrow.svg' const TableCell = ({ - className, + className = '', data, firstCell, - handleExpandRow, - item, - link, - selectItem, - selectedItem, - showExpandButton + handleExpandRow = null, + item = { + target_path: '', + schema: '' + }, + link = '', + selectItem = () => {}, + selectedItem = {}, + showExpandButton = false }) => { const dispatch = useDispatch() const { value: stateValue, label: stateLabel, className: stateClassName } = item.state ?? {} @@ -128,10 +131,10 @@ const TableCell = ({ data.disabled ? '' : item.kind === MODEL_TYPE - ? 'Model Preview' - : item.kind === DATASET_TYPE - ? 'Dataset Preview' - : 'Artifact Preview' + ? 'Model Preview' + : item.kind === DATASET_TYPE + ? 'Dataset Preview' + : 'Artifact Preview' } disabled={data.disabled} onClick={() => { @@ -186,7 +189,7 @@ const TableCell = ({ ) } else { return ( -
  • + } @@ -198,22 +201,6 @@ const TableCell = ({ } } -TableCell.defaultProps = { - className: '', - expandLink: false, - firstRow: false, - handleExpandRow: null, - item: { - target_path: '', - schema: '' - }, - link: '', - match: null, - selectItem: () => {}, - selectedItem: {}, - showExpandButton: false -} - TableCell.propTypes = { className: PropTypes.string, data: PropTypes.shape({}).isRequired, diff --git a/src/elements/TableLinkCell/TableLinkCell.js b/src/elements/TableLinkCell/TableLinkCell.js index 8f90a6da7..be5755afc 100644 --- a/src/elements/TableLinkCell/TableLinkCell.js +++ b/src/elements/TableLinkCell/TableLinkCell.js @@ -31,14 +31,14 @@ import { ReactComponent as Arrow } from 'igz-controls/images/arrow.svg' import './tableLinkCell.scss' const TableLinkCell = ({ - className, - data, + className = '', + data = {}, handleExpandRow, item, link, selectItem, - selectedItem, - showExpandButton + selectedItem = {}, + showExpandButton = false }) => { const tableCellClassNames = classnames( 'table-body__cell', @@ -131,14 +131,6 @@ const TableLinkCell = ({ ) } -TableLinkCell.defaultProps = { - className: '', - data: {}, - expandLink: false, - selectedItem: {}, - showExpandButton: false -} - TableLinkCell.propTypes = { className: PropTypes.string, data: PropTypes.shape({}), diff --git a/src/elements/TableProducerCell/TableProducerCell.js b/src/elements/TableProducerCell/TableProducerCell.js index 434672e4a..611c87d30 100644 --- a/src/elements/TableProducerCell/TableProducerCell.js +++ b/src/elements/TableProducerCell/TableProducerCell.js @@ -29,7 +29,7 @@ import { getJobsDetailsMenu } from '../../components/Jobs/jobs.util' import { DETAILS_OVERVIEW_TAB, MONITOR_JOBS_TAB } from '../../constants' -const TableProducerCell = ({ bodyCellClassName, className, id, producer }) => { +const TableProducerCell = ({ bodyCellClassName = '', className = '', id, producer }) => { const [project, uid] = producer.uri?.split('/') || [] const overviewTab = getJobsDetailsMenu().find(tab => tab.id === DETAILS_OVERVIEW_TAB) || {} const cellClassNames = classnames('table-body__cell', className, bodyCellClassName) @@ -67,11 +67,6 @@ const TableProducerCell = ({ bodyCellClassName, className, id, producer }) => { ) } -TableProducerCell.defaultProps = { - bodyCellClassName: '', - className: '' -} - TableProducerCell.propTypes = { bodyCellClassName: PropTypes.string, className: PropTypes.string, diff --git a/src/elements/TableTop/TableTop.js b/src/elements/TableTop/TableTop.js index 9ba918178..e2dec03d9 100644 --- a/src/elements/TableTop/TableTop.js +++ b/src/elements/TableTop/TableTop.js @@ -27,7 +27,7 @@ import { ReactComponent as Back } from 'igz-controls/images/back-arrow.svg' import './tableTop.scss' -const TableTop = ({ children, link, text }) => { +const TableTop = ({ children, link, text = '' }) => { return (
    @@ -47,10 +47,6 @@ const TableTop = ({ children, link, text }) => { ) } -TableTop.defaultProps = { - text: '' -} - TableTop.propTypes = { link: PropTypes.string.isRequired, text: PropTypes.string diff --git a/src/elements/TableTypeCell/TableTypeCell.js b/src/elements/TableTypeCell/TableTypeCell.js index 39d89acaf..6fc068769 100644 --- a/src/elements/TableTypeCell/TableTypeCell.js +++ b/src/elements/TableTypeCell/TableTypeCell.js @@ -51,7 +51,7 @@ import { JOB_KIND_WORKFLOW } from '../../constants' -const TableTypeCell = ({ className, data }) => { +const TableTypeCell = ({ className = '', data }) => { const typesOfJob = { '': { label: 'Local', icon: }, [FUNCTION_TYPE_APPLICATION]: { label: 'Application', icon: }, @@ -88,10 +88,6 @@ const TableTypeCell = ({ className, data }) => { ) } -TableTypeCell.defaultProps = { - className: '' -} - TableTypeCell.propTypes = { className: PropTypes.string, data: PropTypes.shape({}).isRequired diff --git a/src/elements/TooltipTemplate/ProducerTooltipTemplate.js b/src/elements/TooltipTemplate/ProducerTooltipTemplate.js index 313918f62..9a4cd4460 100644 --- a/src/elements/TooltipTemplate/ProducerTooltipTemplate.js +++ b/src/elements/TooltipTemplate/ProducerTooltipTemplate.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types' import './producerTooltipTemplate.scss' -const ProducerTooltipTemplate = ({ kind, owner }) => { +const ProducerTooltipTemplate = ({ kind = '', owner = '' }) => { return (
    @@ -35,11 +35,6 @@ const ProducerTooltipTemplate = ({ kind, owner }) => { ) } -ProducerTooltipTemplate.defaultProps = { - kind: '', - owner: '' -} - ProducerTooltipTemplate.propTypes = { kind: PropTypes.string.isRequired, owner: PropTypes.string.isRequired diff --git a/src/elements/VolumesTable/VolumesTable.js b/src/elements/VolumesTable/VolumesTable.js index bd0510755..ae056a959 100644 --- a/src/elements/VolumesTable/VolumesTable.js +++ b/src/elements/VolumesTable/VolumesTable.js @@ -28,11 +28,11 @@ import { ReactComponent as Edit } from 'igz-controls/images/edit.svg' import { ReactComponent as Delete } from 'igz-controls/images/delete.svg' export const VolumesTable = ({ - className, + className = '', handleAddNewVolume, handleEdit, handleDelete, - isPanelEditMode, + isPanelEditMode = false, volumeMounts, volumes }) => { @@ -265,11 +265,6 @@ export const VolumesTable = ({ ) } -VolumesTable.defaultProps = { - className: '', - isPanelEditMode: false -} - VolumesTable.propTypes = { className: PropTypes.string, handleAddNewVolume: PropTypes.func.isRequired, diff --git a/src/elements/WorkflowsTable/WorkflowsTable.js b/src/elements/WorkflowsTable/WorkflowsTable.js index 8394bb0dc..5dec17081 100644 --- a/src/elements/WorkflowsTable/WorkflowsTable.js +++ b/src/elements/WorkflowsTable/WorkflowsTable.js @@ -20,17 +20,17 @@ such restriction. import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useLocation, useNavigate, useParams } from 'react-router-dom' +import PropTypes from 'prop-types' import { find, isEmpty } from 'lodash' +import JobWizard from '../../components/JobWizard/JobWizard' +import JobsTableRow from '../JobsTableRow/JobsTableRow' +import Loader from '../../common/Loader/Loader' import NoData from '../../common/NoData/NoData' -import Workflow from '../../components/Workflow/Workflow' import Table from '../../components/Table/Table' -import JobsTableRow from '../JobsTableRow/JobsTableRow' +import Workflow from '../../components/Workflow/Workflow' import YamlModal from '../../common/YamlModal/YamlModal' -import Loader from '../../common/Loader/Loader' -import JobWizard from '../../components/JobWizard/JobWizard' -import { getNoDataMessage } from '../../utils/getNoDataMessage' import { JOB_KIND_JOB, JOBS_PAGE, @@ -39,31 +39,33 @@ import { PANEL_RERUN_MODE, WORKFLOW_GRAPH_VIEW } from '../../constants' -import { isRowRendered, useVirtualization } from '../../hooks/useVirtualization.hook' import { generateActionsMenu, generatePageData } from '../../components/Jobs/MonitorWorkflows/monitorWorkflows.util' -import { isJobKindLocal, pollAbortingJobs } from '../../components/Jobs/jobs.util' -import { DANGER_BUTTON } from 'igz-controls/constants' -import { setNotification } from '../../reducers/notificationReducer' -import { enrichRunWithFunctionFields, handleAbortJob, handleDeleteJob } from '../../utils/jobs.util' +import functionsActions from '../../actions/functions' +import getState from '../../utils/getState' import jobsActions from '../../actions/jobs' import workflowsActions from '../../actions/workflow' -import { parseJob } from '../../utils/parseJob' -import getState from '../../utils/getState' -import { useYaml } from '../../hooks/yaml.hook' +import { DANGER_BUTTON } from 'igz-controls/constants' +import { FILTERS_CONFIG } from '../../types' +import { enrichRunWithFunctionFields, handleAbortJob, handleDeleteJob } from '../../utils/jobs.util' import { getFunctionLogs } from '../../utils/getFunctionLogs' import { getJobLogs } from '../../utils/getJobLogs.util' -import cssVariables from '../../components/Jobs/MonitorWorkflows/monitorWorkflows.scss' +import { getNoDataMessage } from '../../utils/getNoDataMessage' +import { isDetailsTabExists } from '../../utils/isDetailsTabExists' +import { isJobKindLocal, pollAbortingJobs } from '../../components/Jobs/jobs.util' +import { isRowRendered, useVirtualization } from '../../hooks/useVirtualization.hook' import { isWorkflowStepExecutable } from '../../components/Workflow/workflow.util' +import { openPopUp } from 'igz-controls/utils/common.util' import { parseFunction } from '../../utils/parseFunction' -import functionsActions from '../../actions/functions' +import { parseJob } from '../../utils/parseJob' +import { setNotification } from '../../reducers/notificationReducer' import { showErrorNotification } from '../../utils/notifications.util' -import { isDetailsTabExists } from '../../utils/isDetailsTabExists' -import { openPopUp } from 'igz-controls/utils/common.util' import { useSortTable } from '../../hooks/useSortTable.hook' -import PropTypes from 'prop-types' +import { useYaml } from '../../hooks/yaml.hook' + +import cssVariables from '../../components/Jobs/MonitorWorkflows/monitorWorkflows.scss' const WorkflowsTable = React.forwardRef( ( @@ -71,22 +73,25 @@ const WorkflowsTable = React.forwardRef( backLink, context, fetchFunctionLogs, - filters, + filterMenuName = '', + filters = null, + filtersConfig = null, getWorkflows, itemIsSelected, - largeRequestErrorMessage, + requestErrorMessage, selectedFunction, selectedJob, setItemIsSelected, setSelectedFunction, setSelectedJob, setWorkflowIsLoaded, - tableContent, + tableContent = [], workflowIsLoaded }, abortJobRef ) => { const [convertedYaml, toggleConvertedYaml] = useYaml('') + const [dataIsLoading, setDataIsLoading] = useState(false) const [workflowsViewMode, setWorkflowsViewMode] = useState(WORKFLOW_GRAPH_VIEW) const workflowsStore = useSelector(state => state.workflowsStore) const filtersStore = useSelector(state => state.filtersStore) @@ -426,11 +431,13 @@ const WorkflowsTable = React.forwardRef( !fetchJobFunctionsPromiseRef.current && params.jobId && (isEmpty(selectedJob) || params.jobId !== selectedJob.uid) && - checkIfWorkflowItemIsJob() + checkIfWorkflowItemIsJob() && !dataIsLoading ) { - fetchRun() + setDataIsLoading(true) + + fetchRun().finally(() => setDataIsLoading(false)) } - }, [fetchRun, params.jobId, selectedJob, checkIfWorkflowItemIsJob]) + }, [fetchRun, params.jobId, selectedJob, checkIfWorkflowItemIsJob, dataIsLoading]) useEffect(() => { const functionToBeSelected = findSelectedWorkflowFunction(true) @@ -627,14 +634,15 @@ const WorkflowsTable = React.forwardRef( {(!workflowsStore.workflows.loading && !params.workflowId && workflowsStore.workflows.data.length === 0) || - largeRequestErrorMessage ? ( + requestErrorMessage ? ( ) : ( @@ -696,10 +704,12 @@ WorkflowsTable.propTypes = { backLink: PropTypes.string.isRequired, context: PropTypes.object.isRequired, fetchFunctionLogs: PropTypes.func.isRequired, + filterMenuName: PropTypes.string, filters: PropTypes.arrayOf(PropTypes.shape({})), + filtersConfig: FILTERS_CONFIG, getWorkflows: PropTypes.func.isRequired, itemIsSelected: PropTypes.bool.isRequired, - largeRequestErrorMessage: PropTypes.string.isRequired, + requestErrorMessage: PropTypes.string.isRequired, selectedFunction: PropTypes.object.isRequired, selectedJob: PropTypes.object.isRequired, setItemIsSelected: PropTypes.func.isRequired, diff --git a/src/hooks/artifacts.hook.js b/src/hooks/artifacts.hook.js index 9aeeff141..660459fb5 100644 --- a/src/hooks/artifacts.hook.js +++ b/src/hooks/artifacts.hook.js @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' import { useParams, useSearchParams } from 'react-router-dom' import { useDispatch } from 'react-redux' @@ -28,16 +28,16 @@ import { sortListByDate } from '../utils' export const useInitialArtifactsFetch = ( fetchData, urlTagOption, - artifactsLength, setExpandedRowsData, createArtifactsRowData ) => { const [searchParams] = useSearchParams() const params = useParams() const dispatch = useDispatch() + const isInitialRequestSent = useRef(false) useEffect(() => { - if (urlTagOption && artifactsLength === 0) { + if (!isInitialRequestSent.current && urlTagOption) { let filtersLocal = { tag: urlTagOption, iter: SHOW_ITERATIONS } if (searchParams.get('useUrlParamsAsFilter') === 'true') { @@ -64,16 +64,12 @@ export const useInitialArtifactsFetch = ( })) } }) + + isInitialRequestSent.current = true } - }, [ - dispatch, - fetchData, - artifactsLength, - params.name, - params.projectName, - searchParams, - urlTagOption, - setExpandedRowsData, - createArtifactsRowData - ]) + }, [dispatch, fetchData, params.name, params.projectName, searchParams, urlTagOption, setExpandedRowsData, createArtifactsRowData]) + + useEffect(() => () => { + isInitialRequestSent.current = false + }, [params.projectName]) } diff --git a/src/hooks/useGetTagOptions.hook.js b/src/hooks/useGetTagOptions.hook.js index 5558a7b4c..d2a10ced4 100644 --- a/src/hooks/useGetTagOptions.hook.js +++ b/src/hooks/useGetTagOptions.hook.js @@ -33,7 +33,7 @@ import { getFilterTagOptions, setFilters, setModalFiltersValues } from '../reduc export const useGetTagOptions = (fetchTags, filters, category, modalFiltersName) => { const [urlTagOption, setUrlTagOption] = useState(null) - const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('') + const [requestErrorMessage, setRequestErrorMessage] = useState('') const { projectName, tag: paramTag } = useParams() const tagOptions = useSelector(store => store.filtersStore.tagOptions) const dispatch = useDispatch() @@ -63,7 +63,7 @@ export const useGetTagOptions = (fetchTags, filters, category, modalFiltersName) config: { ui: { controller: abortControllerRef.current, - setLargeRequestErrorMessage + setRequestErrorMessage } } }) @@ -110,5 +110,5 @@ export const useGetTagOptions = (fetchTags, filters, category, modalFiltersName) } }, [category, dispatch, fetchTags, filters, modalFiltersName, paramTag, projectName, tagOptions]) - return [urlTagOption, abortControllerRef, largeRequestErrorMessage] + return [urlTagOption, abortControllerRef, requestErrorMessage] } diff --git a/src/httpClient.js b/src/httpClient.js index d1b84204b..f36606b90 100755 --- a/src/httpClient.js +++ b/src/httpClient.js @@ -93,12 +93,12 @@ let requestTimeouts = {} let largeResponsePopUpIsOpen = false const requestLargeDataOnFulfill = config => { - if (config?.ui?.setLargeRequestErrorMessage) { + if (config?.ui?.setRequestErrorMessage) { const [signal, timeoutId] = getAbortSignal( config.ui?.controller, abortEvent => { if (abortEvent.target.reason === LARGE_REQUEST_CANCELED) { - showLargeResponsePopUp(config.ui.setLargeRequestErrorMessage) + showLargeResponsePopUp(config.ui.setRequestErrorMessage) } }, CANCEL_REQUEST_TIMEOUT @@ -129,11 +129,11 @@ const responseFulfillInterceptor = response => { delete requestTimeouts[response.config.ui.requestId] if (isLargeResponse) { - showLargeResponsePopUp(response.config.ui.setLargeRequestErrorMessage) + showLargeResponsePopUp(response.config.ui.setRequestErrorMessage) throw new Error(LARGE_REQUEST_CANCELED) } else { - response.config.ui.setLargeRequestErrorMessage('') + response.config.ui.setRequestErrorMessage('') } } @@ -146,10 +146,16 @@ const responseRejectInterceptor = error => { } if (error.config?.method === 'get') { - if (mlrunUnhealthyErrors.includes(error.response?.status) && consecutiveErrorsCount < MAX_CONSECUTIVE_ERRORS_COUNT) { + if ( + mlrunUnhealthyErrors.includes(error.response?.status) && + consecutiveErrorsCount < MAX_CONSECUTIVE_ERRORS_COUNT + ) { consecutiveErrorsCount++ - if (consecutiveErrorsCount === MAX_CONSECUTIVE_ERRORS_COUNT && window.location.pathname !== `/${PROJECTS_PAGE_PATH}`) { + if ( + consecutiveErrorsCount === MAX_CONSECUTIVE_ERRORS_COUNT && + window.location.pathname !== `/${PROJECTS_PAGE_PATH}` + ) { window.location.href = '/projects' } } @@ -158,7 +164,6 @@ const responseRejectInterceptor = error => { return Promise.reject(error) } - // Request interceptors mainHttpClient.interceptors.request.use(requestLargeDataOnFulfill, requestLargeDataOnReject) mainHttpClientV2.interceptors.request.use(requestLargeDataOnFulfill, requestLargeDataOnReject) @@ -167,12 +172,12 @@ mainHttpClientV2.interceptors.request.use(requestLargeDataOnFulfill, requestLarg mainHttpClient.interceptors.response.use(responseFulfillInterceptor, responseRejectInterceptor) mainHttpClientV2.interceptors.response.use(responseFulfillInterceptor, responseRejectInterceptor) -export const showLargeResponsePopUp = setLargeRequestErrorMessage => { +export const showLargeResponsePopUp = setRequestErrorMessage => { if (!largeResponsePopUpIsOpen) { const errorMessage = 'The query result is too large to display. Add a filter (or narrow it) to retrieve fewer results.' - setLargeRequestErrorMessage(errorMessage) + setRequestErrorMessage(errorMessage) largeResponsePopUpIsOpen = true openPopUp(ConfirmDialog, { diff --git a/src/layout/Content/Content.js b/src/layout/Content/Content.js index ee356766b..cfb5aec56 100644 --- a/src/layout/Content/Content.js +++ b/src/layout/Content/Content.js @@ -51,23 +51,23 @@ import { ReactComponent as Yaml } from 'igz-controls/images/yaml.svg' const Content = ({ applyDetailsChanges, artifactsStore, - cancelRequest, + cancelRequest = () => {}, children, content, - filtersChangeCallback, + filtersChangeCallback = null, filtersStore, getIdentifier, - handleActionsMenuClick, - handleCancel, + handleActionsMenuClick = () => {}, + handleCancel = () => {}, handleRemoveRequestData, - handleSelectItem, + handleSelectItem = () => {}, header, loading, pageData, projectStore, refresh, - selectedItem, - tableTop + selectedItem = {}, + tableTop = null }) => { const [convertedYaml, toggleConvertedYaml] = useYaml('') const [showActionsMenu, setShowActionsMenu] = useState(false) @@ -190,17 +190,6 @@ const Content = ({ ) } -Content.defaultProps = { - activeScreenTab: '', - cancelRequest: () => {}, - filtersChangeCallback: null, - handleActionsMenuClick: () => {}, - handleCancel: () => {}, - handleSelectItem: () => {}, - selectedItem: {}, - tableTop: null -} - Content.propTypes = { cancelRequest: PropTypes.func, content: PropTypes.arrayOf(PropTypes.shape({})).isRequired, diff --git a/src/layout/Page/Page.js b/src/layout/Page/Page.js index 36090d148..d7a8e4f83 100644 --- a/src/layout/Page/Page.js +++ b/src/layout/Page/Page.js @@ -26,7 +26,6 @@ import { createPortal } from 'react-dom' import ModalContainer from 'react-modal-promise' import Notification from '../../common/Notification/Notification' -import DownloadContainer from '../../common/Download/DownloadContainer' import Navbar from '../Navbar/Navbar' import { getTransitionEndEventName } from 'igz-controls/utils/common.util' @@ -92,8 +91,7 @@ const Page = () => {
    - - + {createPortal(, document.getElementById('overlay_container'))} {createPortal(, document.getElementById('overlay_container'))} ) diff --git a/src/reducers/artifactsReducer.js b/src/reducers/artifactsReducer.js index 440820b50..716e4f324 100644 --- a/src/reducers/artifactsReducer.js +++ b/src/reducers/artifactsReducer.js @@ -120,13 +120,28 @@ export const fetchArtifact = createAsyncThunk('fetchArtifact', ({ project, artif return filterArtifacts(result) }) }) -export const fetchArtifacts = createAsyncThunk('fetchArtifacts', ({ project, filters, config }) => { - return artifactsApi.getArtifacts(project, filters, config).then(({ data }) => { - const result = parseArtifacts(data.artifacts) +export const fetchArtifacts = createAsyncThunk( + 'fetchArtifacts', + ({ project, filters, config, setRequestErrorMessage = () => {} }, thunkAPI) => { + setRequestErrorMessage('') - return generateArtifacts(filterArtifacts(result)) - }) -}) + return artifactsApi + .getArtifacts(project, filters, config) + .then(({ data }) => { + const result = parseArtifacts(data.artifacts) + + return generateArtifacts(filterArtifacts(result)) + }) + .catch(error => { + largeResponseCatchHandler( + error, + 'Failed to fetch artifacts', + thunkAPI.dispatch, + setRequestErrorMessage + ) + }) + } +) export const fetchArtifactTags = createAsyncThunk( 'fetchArtifactTags', ({ project, category, config }, thunkAPI) => { @@ -158,6 +173,8 @@ export const fetchDataSet = createAsyncThunk( export const fetchDataSets = createAsyncThunk( 'fetchDataSets', ({ project, filters, config }, thunkAPI) => { + config?.ui?.setRequestErrorMessage?.('') + return artifactsApi .getDataSets(project, filters, config) .then(({ data }) => { @@ -165,9 +182,14 @@ export const fetchDataSets = createAsyncThunk( return generateArtifacts(filterArtifacts(result), DATASETS_TAB, data.artifacts) }) - .catch(error => - largeResponseCatchHandler(error, 'Failed to fetch datasets', thunkAPI.dispatch) - ) + .catch(error => { + largeResponseCatchHandler( + error, + 'Failed to fetch datasets', + thunkAPI.dispatch, + config?.ui?.setRequestErrorMessage + ) + }) } ) export const fetchExpandedFile = createAsyncThunk( @@ -193,6 +215,8 @@ export const fetchFile = createAsyncThunk( export const fetchFiles = createAsyncThunk( 'fetchFiles', ({ project, filters, config }, thunkAPI) => { + config?.ui?.setRequestErrorMessage?.('') + return artifactsApi .getFiles(project, filters, config) .then(({ data }) => { @@ -200,32 +224,58 @@ export const fetchFiles = createAsyncThunk( return generateArtifacts(filterArtifacts(result), ARTIFACTS_TAB, data.artifacts) }) - .catch(error => - largeResponseCatchHandler(error, 'Failed to fetch artifacts', thunkAPI.dispatch) - ) + .catch(error => { + largeResponseCatchHandler( + error, + 'Failed to fetch artifacts', + thunkAPI.dispatch, + config?.ui?.setRequestErrorMessage + ) + }) } ) export const fetchArtifactsFunctions = createAsyncThunk( 'fetchArtifactsFunctions', - ({ project, filters, config }) => { - return functionsApi.getFunctions(project, filters, config, null).then(({ data }) => { - return parseFunctions( - data.funcs.filter(func => func.kind === FUNCTION_TYPE_SERVING && func.metadata.tag?.length) - ) - }) + ({ project, filters, config }, thunkAPI) => { + config?.ui?.setRequestErrorMessage?.('') + + return functionsApi + .getFunctions(project, filters, config, null) + .then(({ data }) => { + return parseFunctions( + data.funcs.filter( + func => func.kind === FUNCTION_TYPE_SERVING && func.metadata.tag?.length + ) + ) + }) + .catch(error => { + largeResponseCatchHandler( + error, + 'Failed to fetch real-time pipelines', + thunkAPI.dispatch, + config?.ui?.setRequestErrorMessage + ) + }) } ) export const fetchModelEndpoints = createAsyncThunk( 'fetchModelEndpoints', ({ project, filters, config, params }, thunkAPI) => { + config?.ui?.setRequestErrorMessage?.('') + return modelEndpointsApi .getModelEndpoints(project, filters, config, params) .then(({ data: { endpoints = [] } }) => { return generateModelEndpoints(endpoints) }) - .catch(error => - largeResponseCatchHandler(error, 'Failed to fetch model endpoints', thunkAPI.dispatch) - ) + .catch(error => { + largeResponseCatchHandler( + error, + 'Failed to fetch model endpoints', + thunkAPI.dispatch, + config?.ui?.setRequestErrorMessage + ) + }) } ) export const fetchExpandedModel = createAsyncThunk( @@ -251,6 +301,8 @@ export const fetchModel = createAsyncThunk( export const fetchModels = createAsyncThunk( 'fetchModels', ({ project, filters, config }, thunkAPI) => { + config?.ui?.setRequestErrorMessage?.('') + return artifactsApi .getModels(project, filters, config) .then(({ data }) => { @@ -259,7 +311,12 @@ export const fetchModels = createAsyncThunk( return generateArtifacts(result, MODELS_TAB, data.artifacts) }) .catch(error => { - largeResponseCatchHandler(error, 'Failed to fetch models', thunkAPI.dispatch) + largeResponseCatchHandler( + error, + 'Failed to fetch models', + thunkAPI.dispatch, + config?.ui?.setRequestErrorMessage + ) }) } ) diff --git a/src/reducers/filtersReducer.js b/src/reducers/filtersReducer.js index 7099452a0..fd48dbdae 100644 --- a/src/reducers/filtersReducer.js +++ b/src/reducers/filtersReducer.js @@ -19,25 +19,45 @@ such restriction. */ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { set } from 'lodash' + import { ARTIFACT_OTHER_TYPE, - DATASET_TYPE, + CONSUMER_GROUPS_FILTER, + CONSUMER_GROUP_FILTER, DATASETS_FILTERS, + DATASET_TYPE, + DATES_FILTER, DATE_FILTER_ANY_TIME, FILES_FILTERS, + FILTER_ALL_ITEMS, + FILTER_MENU, FILTER_MENU_MODAL, FUNCTION_FILTERS, GROUP_BY_NAME, + ITERATIONS_FILTER, JOBS_MONITORING_JOBS_TAB, - MODEL_TYPE, + JOBS_MONITORING_SCHEDULED_TAB, + JOBS_MONITORING_WORKFLOWS_TAB, + LABELS_FILTER, MODELS_FILTERS, + MODEL_TYPE, + NAME_FILTER, + PROJECT_FILTER, SHOW_ITERATIONS, - SHOW_UNTAGGED_ITEMS, - FILTER_ALL_ITEMS, + SHOW_UNTAGGED_FILTER, + STATUS_FILTER, + TAG_FILTER, TAG_FILTER_LATEST, - JOBS_MONITORING_WORKFLOWS_TAB, - JOBS_MONITORING_SCHEDULED_TAB + TYPE_FILTER } from '../constants' +import { + NEXT_24_HOUR_DATE_OPTION, + PAST_24_HOUR_DATE_OPTION, + PAST_WEEK_DATE_OPTION, + datePickerFutureOptions, + datePickerPastOptions, + getDatePickerFilterValue +} from '../utils/datePicker.util' const initialState = { saveFilters: false, @@ -52,65 +72,116 @@ const initialState = { labels: '', name: '', project: '', - showUntagged: '', state: FILTER_ALL_ITEMS, sortBy: '', tag: TAG_FILTER_LATEST, tagOptions: null, projectOptions: [], + [FILTER_MENU]: { + [JOBS_MONITORING_JOBS_TAB]: { + [NAME_FILTER]: '', + [DATES_FILTER]: getDatePickerFilterValue(datePickerPastOptions, PAST_24_HOUR_DATE_OPTION) + }, + [JOBS_MONITORING_WORKFLOWS_TAB]: { + [NAME_FILTER]: '', + [DATES_FILTER]: getDatePickerFilterValue(datePickerPastOptions, PAST_24_HOUR_DATE_OPTION) + }, + [JOBS_MONITORING_SCHEDULED_TAB]: { + [NAME_FILTER]: '', + [DATES_FILTER]: getDatePickerFilterValue( + datePickerFutureOptions, + NEXT_24_HOUR_DATE_OPTION, + true + ) + }, + [FUNCTION_FILTERS]: { + [NAME_FILTER]: '', + [DATES_FILTER]: getDatePickerFilterValue(datePickerPastOptions, PAST_WEEK_DATE_OPTION) + }, + [CONSUMER_GROUPS_FILTER]: { + [NAME_FILTER]: '', + }, + [CONSUMER_GROUP_FILTER]: { + [NAME_FILTER]: '', + } + }, [FILTER_MENU_MODAL]: { [DATASETS_FILTERS]: { - initialValues: { tag: TAG_FILTER_LATEST, labels: '', iter: SHOW_ITERATIONS }, - values: { tag: TAG_FILTER_LATEST, labels: '', iter: SHOW_ITERATIONS } + initialValues: { + [TAG_FILTER]: TAG_FILTER_LATEST, + [LABELS_FILTER]: '', + [ITERATIONS_FILTER]: SHOW_ITERATIONS + }, + values: { + [TAG_FILTER]: TAG_FILTER_LATEST, + [LABELS_FILTER]: '', + [ITERATIONS_FILTER]: SHOW_ITERATIONS + } }, [FILES_FILTERS]: { - initialValues: { tag: TAG_FILTER_LATEST, labels: '', iter: SHOW_ITERATIONS }, - values: { tag: TAG_FILTER_LATEST, labels: '', iter: SHOW_ITERATIONS } - }, - [FUNCTION_FILTERS]: { - initialValues: { showUntagged: SHOW_UNTAGGED_ITEMS }, - values: { showUntagged: SHOW_UNTAGGED_ITEMS } + initialValues: { + [TAG_FILTER]: TAG_FILTER_LATEST, + [LABELS_FILTER]: '', + [ITERATIONS_FILTER]: SHOW_ITERATIONS + }, + values: { + [TAG_FILTER]: TAG_FILTER_LATEST, + [LABELS_FILTER]: '', + [ITERATIONS_FILTER]: SHOW_ITERATIONS + } }, [MODELS_FILTERS]: { - initialValues: { tag: TAG_FILTER_LATEST, labels: '', iter: SHOW_ITERATIONS }, - values: { tag: TAG_FILTER_LATEST, labels: '', iter: SHOW_ITERATIONS } + initialValues: { + [TAG_FILTER]: TAG_FILTER_LATEST, + [LABELS_FILTER]: '', + [ITERATIONS_FILTER]: SHOW_ITERATIONS + }, + values: { + [TAG_FILTER]: TAG_FILTER_LATEST, + [LABELS_FILTER]: '', + [ITERATIONS_FILTER]: SHOW_ITERATIONS + } + }, + [FUNCTION_FILTERS]: { + initialValues: { [SHOW_UNTAGGED_FILTER]: false }, + values: { [SHOW_UNTAGGED_FILTER]: false } }, [JOBS_MONITORING_JOBS_TAB]: { initialValues: { - labels: '', - project: '', - state: [FILTER_ALL_ITEMS], - type: FILTER_ALL_ITEMS + [LABELS_FILTER]: '', + [PROJECT_FILTER]: '', + [STATUS_FILTER]: [FILTER_ALL_ITEMS], + [TYPE_FILTER]: FILTER_ALL_ITEMS }, values: { - labels: '', - project: '', - state: [FILTER_ALL_ITEMS], - type: FILTER_ALL_ITEMS + [LABELS_FILTER]: '', + [PROJECT_FILTER]: '', + [STATUS_FILTER]: [FILTER_ALL_ITEMS], + [TYPE_FILTER]: FILTER_ALL_ITEMS } }, - [JOBS_MONITORING_SCHEDULED_TAB]: { + [JOBS_MONITORING_WORKFLOWS_TAB]: { initialValues: { - labels: '', - project: '', - type: FILTER_ALL_ITEMS + [LABELS_FILTER]: '', + [PROJECT_FILTER]: '', + [STATUS_FILTER]: [FILTER_ALL_ITEMS] }, values: { - labels: '', - project: '', - type: FILTER_ALL_ITEMS + [LABELS_FILTER]: '', + [PROJECT_FILTER]: '', + [STATUS_FILTER]: [FILTER_ALL_ITEMS] } }, - [JOBS_MONITORING_WORKFLOWS_TAB]: { + [JOBS_MONITORING_SCHEDULED_TAB]: { initialValues: { - labels: '', - project: '', - state: [FILTER_ALL_ITEMS] + [LABELS_FILTER]: '', + [PROJECT_FILTER]: '', + [TYPE_FILTER]: FILTER_ALL_ITEMS }, values: { - labels: '', - project: '', - state: [FILTER_ALL_ITEMS] + [LABELS_FILTER]: '', + [PROJECT_FILTER]: '', + [TYPE_FILTER]: FILTER_ALL_ITEMS } } } @@ -144,14 +215,26 @@ const filtersSlice = createSlice({ removeFilters() { return initialState }, + resetFilter(state, action) { + state[FILTER_MENU][action.payload] = initialState[FILTER_MENU][action.payload] + }, resetModalFilter(state, action) { - delete state[FILTER_MENU_MODAL][action.payload] + state[FILTER_MENU_MODAL][action.payload] = initialState[FILTER_MENU_MODAL][action.payload] }, setFilters(state, action) { for (let filterProp in action.payload) { state[filterProp] = action.payload[filterProp] } }, + setFiltersValues(state, action) { + const payloadValue = action.payload.value ?? {} + const newFilterValues = { + ...state[FILTER_MENU][action.payload.name], + ...payloadValue + } + + set(state, [FILTER_MENU, action.payload.name], newFilterValues) + }, setModalFiltersValues(state, action) { const payloadValue = action.payload.value ?? {} const newFilterValues = { @@ -183,8 +266,10 @@ const filtersSlice = createSlice({ export const { removeFilters, + resetFilter, resetModalFilter, setFilters, + setFiltersValues, setModalFiltersValues, setModalFiltersInitialValues, setFilterProjectOptions diff --git a/src/scss/main.scss b/src/scss/main.scss index 80a8de8b1..0596a1a15 100644 --- a/src/scss/main.scss +++ b/src/scss/main.scss @@ -24,8 +24,8 @@ body { width: 100%; &-container { - display: flex; position: relative; + display: flex; width: 100%; &.has-header { @@ -39,7 +39,7 @@ body { } } -/*=========== CONTENT =============*/ +/* =========== CONTENT ============= */ .content { position: relative; @@ -117,7 +117,7 @@ body { } } -/*=========== MAIN =============*/ +/* =========== MAIN ============= */ main { position: relative; @@ -148,7 +148,7 @@ main { } } -/*=========== PAGE =============*/ +/* =========== PAGE ============= */ .page { flex: 1 1; @@ -191,12 +191,12 @@ main { } } -/*=========== TABLE =============*/ +/* =========== TABLE ============= */ .table { position: relative; - text-align: left; width: 100%; + text-align: left; border-spacing: 0; .table { @@ -224,17 +224,17 @@ main { &-name { position: sticky; left: 0; + z-index: 1; min-width: 250px; padding-right: 10px; - z-index: 1; } &-icon { position: sticky; right: 0; justify-content: center; - padding: 0; max-width: 85px; + padding: 0; @include tableColumnFlex(0, 50px); @@ -256,12 +256,13 @@ main { .table-cell-icon { .actions-menu { &__container_extended { - &:before { - background: linear-gradient( - 90deg, - rgba(255, 255, 255, 0) 0%, - rgba(245, 247, 255, 1) 100% - ); + &::before { + background: + linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 0%, + rgba(245, 247, 255, 1) 100% + ); } } } @@ -276,12 +277,13 @@ main { &_extended { background-color: $ghostWhite; - &:before { - background: linear-gradient( - 90deg, - rgba(255, 255, 255, 0) 0%, - rgba(245, 247, 255, 1) 100% - ); + &::before { + background: + linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 0%, + rgba(245, 247, 255, 1) 100% + ); } } @@ -334,9 +336,9 @@ main { .row_grouped-by { position: sticky; top: #{$headerRowHeight}px; - background-color: $white; - padding: 0; z-index: 3; + padding: 0; + background-color: $white; .expand-arrow { transform: rotate(90deg); @@ -355,8 +357,8 @@ main { &-header { position: sticky; top: 0; - min-width: 100%; z-index: 3; + min-width: 100%; &-row { min-height: #{$headerRowHeight}px; @@ -387,8 +389,8 @@ main { flex: 0 1 12px; align-items: center; justify-content: center; - margin-left: 5px; min-width: 12px; + margin-left: 5px; & > * { margin: 0; @@ -413,8 +415,8 @@ main { &-main { display: flex; - flex-flow: column nowrap; flex: 1; + flex-flow: column nowrap; overflow-y: auto; will-change: scroll-position; } @@ -431,7 +433,7 @@ main { } } -/*=========== SORT =============*/ +/* =========== SORT ============= */ .sortable-header { &-cell { @@ -441,11 +443,11 @@ main { cursor: pointer; .sort-icon { - display: none; position: absolute; - right: 2px; top: auto; + right: 2px; bottom: auto; + display: none; } &_active { @@ -475,7 +477,7 @@ main { } } -/*=========== STATE =============*/ +/* =========== STATE ============= */ [class^='state-active'], [class^='state-completed'], @@ -546,7 +548,7 @@ main { @include statusState($topaz, false); } -/*=========== ERROR =============*/ +/* =========== ERROR ============= */ .error-container { margin: auto; @@ -563,7 +565,7 @@ main { } } -/*=========== ACTIONS =============*/ +/* =========== ACTIONS ============= */ .actions { display: flex; @@ -589,7 +591,7 @@ main { } } -/*=========== INPUT =============*/ +/* =========== INPUT ============= */ .auto-resizable-input { position: relative; @@ -621,7 +623,7 @@ main { } } -/*=========== COMBOBOX =============*/ +/* =========== COMBOBOX ============= */ .combobox { .path-type { @@ -647,7 +649,7 @@ main { } } -/*=========== GRAPH =============*/ +/* =========== GRAPH ============= */ .graph-container { position: relative; @@ -686,7 +688,7 @@ main { } } -/*=========== FORM =============*/ +/* =========== FORM ============= */ .form { color: $primary; @@ -767,10 +769,10 @@ main { &-table-title { display: block; - font-weight: 500; - font-size: 20px; margin-bottom: 10px; padding-top: 10px; + font-weight: 500; + font-size: 20px; .tip-container { margin-left: 5px; @@ -779,6 +781,43 @@ main { } } +/*=========== CHART TOOLTIP =============*/ + +div[id^='chartjs-tooltip'] { + position: fixed; + z-index: 5; + width: auto; + padding: 5px; + color: $white; + background-color: $primary; + border-radius: 4px; + transform: translate(-50%, calc(-100% - 10px)); + opacity: 1; + transition: all 0.1s linear; + pointer-events: none; + + &.hidden { + opacity: 0; + } + + &::after { + position: absolute; + top: 100%; + left: 50%; + width: 0; + height: 0; + margin-left: -5px; + border: 5px solid transparent; + border-top-color: $primary; + content: ""; + pointer-events: none; + } + + td { + white-space: nowrap; + } +} + /*=========== SORT ICON =============*/ .sort-icon { @@ -827,7 +866,29 @@ main { } } -/*=========== GENERAL =============*/ +/* =========== NOTIFICATION BUTTON ============= */ + +.notification__button-close { + position: absolute; + top: -9px; + right: -9px; + display: flex; + align-items: center; + width: 18px; + height: 18px; + padding: 2px; + background-color: $darkPurple; + border: 1px solid $white; + border-radius: 50%; + + & svg { + & > * { + fill: $white; + } + } +} + +/* =========== GENERAL ============= */ .visibility-hidden { visibility: hidden; diff --git a/src/types.js b/src/types.js index 94dd5454a..82e4cfb12 100644 --- a/src/types.js +++ b/src/types.js @@ -237,6 +237,32 @@ export const METRICS_SELECTOR_OPTIONS = PropTypes.arrayOf( }) ) +export const DRIFT_STATUS = PropTypes.shape({ + className: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + chartColor: PropTypes.string.isRequired, + index: PropTypes.number +}) + +export const METRIC_DATA = PropTypes.shape({ + type: PropTypes.string.isRequired, + data: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]).isRequired, + full_name: PropTypes.string.isRequired, + resultKind: PropTypes.number, + app: PropTypes.string.isRequired, + id: PropTypes.number.isRequired, + labels: PropTypes.arrayOf(PropTypes.string).isRequired, + dates: PropTypes.arrayOf(PropTypes.string).isRequired, + points: PropTypes.arrayOf(PropTypes.number).isRequired, + title: PropTypes.string.isRequired, + driftStatusList: PropTypes.arrayOf(DRIFT_STATUS), + totalDriftStatus: DRIFT_STATUS, + minPointValue: PropTypes.number.isRequired, + maxPointValue: PropTypes.number.isRequired, + metric_computed_avg_points: PropTypes.string.isRequired, + metric_raw_avg_points: PropTypes.string.isRequired +}) + export const DATE_PICKER_TIME_FRAME_LIMITS = PropTypes.oneOf([ TIME_FRAME_LIMITS.HOUR, TIME_FRAME_LIMITS['24_HOURS'], @@ -244,4 +270,21 @@ export const DATE_PICKER_TIME_FRAME_LIMITS = PropTypes.oneOf([ TIME_FRAME_LIMITS.MONTH, TIME_FRAME_LIMITS.YEAR, Infinity -]) \ No newline at end of file +]) + +export const FILTERS_CONFIG = PropTypes.objectOf( + PropTypes.shape({ + label: PropTypes.string, + hidden: PropTypes.bool, + isFuture: PropTypes.bool + }) +) + +export const STATUS_LIST = PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + disabled: PropTypes.bool + }) +) diff --git a/src/utils/copyToClipboard.js b/src/utils/copyToClipboard.js index d165e60cc..6fc648aab 100644 --- a/src/utils/copyToClipboard.js +++ b/src/utils/copyToClipboard.js @@ -21,6 +21,15 @@ import { setNotification } from '../reducers/notificationReducer' import { showErrorNotification } from './notifications.util' export const copyToClipboard = (textToCopy, dispatch) => { + if (!navigator.clipboard?.writeText) { + return showErrorNotification( + dispatch, + null, + '', + 'Copy to clipboard failed due to unsecured connection' + ) + } + navigator.clipboard .writeText(textToCopy) .then(() => { diff --git a/src/utils/createArtifactsContent.js b/src/utils/createArtifactsContent.js index a42c36d0e..23b8f87cf 100644 --- a/src/utils/createArtifactsContent.js +++ b/src/utils/createArtifactsContent.js @@ -169,7 +169,13 @@ export const createModelsRowData = ( headerId: 'producer', headerLabel: 'Producer', value: artifact.producer?.name || '', - template: , + template: ( + + ), className: 'table-cell-1', type: 'producer' }, @@ -335,7 +341,11 @@ export const createFilesRowData = (artifact, project, frontendSpec, showExpandBu headerLabel: 'Producer', value: artifact.producer?.name || '', template: ( - + ), className: 'table-cell-1', type: 'producer' @@ -371,25 +381,38 @@ const getDriftStatusData = driftStatus => { case '0': case 'NO_DRIFT': return { - value: , - tooltip: 'No drift' + value: ( + + + + ), + tooltip: 'No drift', + testId: 'no-drift' } case '1': case 'POSSIBLE_DRIFT': return { - value: , + value: ( + + + + ), tooltip: 'Possible drift' } case '2': case 'DRIFT_DETECTED': return { - value: , + value: ( + + + + ), tooltip: 'Drift detected' } case '-1': default: return { - value: 'N/A', + value: N/A, tooltip: 'N/A' } } @@ -564,7 +587,11 @@ export const createDatasetsRowData = (artifact, project, frontendSpec, showExpan headerLabel: 'Producer', value: artifact.producer?.name || '', template: ( - + ), className: 'table-cell-1', type: 'producer' diff --git a/src/utils/createFunctionsContent.js b/src/utils/createFunctionsContent.js index 0ef11b404..577183f6a 100644 --- a/src/utils/createFunctionsContent.js +++ b/src/utils/createFunctionsContent.js @@ -34,7 +34,7 @@ const createFunctionsContent = (functions, projectName, showExpandButton) => value: func.name, className: 'table-cell-name', getLink: (hash, tab) => { - return `/projects/${projectName}/functions/${hash}${`/${tab}`}` + return `/projects/${projectName}/functions/${func.name}@${hash}${`/${tab}`}` }, expandedCellContent: { value: formatDatetime(func.updated, 'N/A'), diff --git a/src/utils/createJobsContent.js b/src/utils/createJobsContent.js index 53adfffb8..53216ca84 100644 --- a/src/utils/createJobsContent.js +++ b/src/utils/createJobsContent.js @@ -19,7 +19,6 @@ such restriction. */ import { - FUNCTIONS_PAGE, JOB_KIND_WORKFLOW, JOBS_MONITORING_JOBS_TAB, JOBS_MONITORING_PAGE, @@ -27,15 +26,16 @@ import { MONITOR_JOBS_TAB, MONITOR_WORKFLOWS_TAB } from '../constants' -import { formatDatetime } from './datetime' -import measureTime from './measureTime' -import { parseKeyValues } from './object' -import { generateLinkToDetailsPanel } from './generateLinkToDetailsPanel' -import { getJobIdentifier, getWorkflowJobIdentifier } from './getUniqueIdentifier' import { getWorkflowDetailsLink, getWorkflowMonitoringDetailsLink } from '../components/Workflow/workflow.util' +import measureTime from './measureTime' +import { formatDatetime } from './datetime' +import { generateFunctionDetailsLink } from './generateFunctionDetailsLink' +import { generateLinkToDetailsPanel } from './generateLinkToDetailsPanel' +import { getJobIdentifier, getWorkflowJobIdentifier } from './getUniqueIdentifier' +import { parseKeyValues } from './object' import { validateArguments } from './validateArguments' export const createJobsMonitorTabContent = (jobs, jobName, isStagingMode) => { @@ -156,7 +156,6 @@ export const createJobsMonitorTabContent = (jobs, jobName, isStagingMode) => { export const createJobsScheduleTabContent = jobs => { return jobs.map(job => { const identifierUnique = getJobIdentifier(job, true) - const [, , scheduleJobFunctionUid] = job.func?.match(/\w[\w'-]*/g, '') || [] const [, projectName, jobUid] = job.lastRunUri?.match(/(.+)@(.+)#([^:]+)(?::(.+))?/) || [] const jobName = job.name const lastRunLink = () => @@ -182,17 +181,7 @@ export const createJobsScheduleTabContent = jobs => { value: job.name, className: 'table-cell-name', showStatus: true, - getLink: tab => - validateArguments(scheduleJobFunctionUid, tab) - ? generateLinkToDetailsPanel( - job.project, - FUNCTIONS_PAGE, - null, - scheduleJobFunctionUid, - null, - tab - ) - : '', + getLink: () => generateFunctionDetailsLink(job.func), type: 'link' }, { @@ -363,18 +352,15 @@ export const createJobsWorkflowContent = ( className: 'table-cell-name', type: 'link', getLink: tab => { - return workflowProjectName ? - getWorkflowMonitoringDetailsLink( - workflowProjectName, - workflowId, - job.customData - ) : getWorkflowDetailsLink( - projectName, - workflowId, - job.customData, - tab, - MONITOR_WORKFLOWS_TAB - ) + return workflowProjectName + ? getWorkflowMonitoringDetailsLink(workflowProjectName, workflowId, job.customData) + : getWorkflowDetailsLink( + projectName, + workflowId, + job.customData, + tab, + MONITOR_WORKFLOWS_TAB + ) }, showStatus: true, showUidRow: true @@ -545,7 +531,6 @@ export const createJobsMonitoringContent = (jobs, jobName, isStagingMode) => { export const createScheduleJobsMonitoringContent = jobs => { return jobs.map(job => { const identifierUnique = getJobIdentifier(job, true) - const [, , scheduleJobFunctionUid] = job.func?.match(/\w[\w'-]*/g, '') || [] const [, projectName, jobUid] = job.lastRunUri?.match(/(.+)@(.+)#([^:]+)(?::(.+))?/) || [] const jobName = job.name const lastRunLink = () => @@ -571,17 +556,7 @@ export const createScheduleJobsMonitoringContent = jobs => { value: job.name, className: 'table-cell-name', showStatus: true, - getLink: tab => - validateArguments(scheduleJobFunctionUid, tab) - ? generateLinkToDetailsPanel( - job.project, - FUNCTIONS_PAGE, - null, - scheduleJobFunctionUid, - null, - tab - ) - : '', + getLink: () => generateFunctionDetailsLink(job.func), type: 'link' }, { diff --git a/src/utils/createRealTimePipelinesContent.js b/src/utils/createRealTimePipelinesContent.js index 21adc7676..f42005342 100644 --- a/src/utils/createRealTimePipelinesContent.js +++ b/src/utils/createRealTimePipelinesContent.js @@ -64,7 +64,14 @@ const createRealTimePipelinesContent = (pipelines, projectName) => className: 'table-cell-2', getLink: tab => validateArguments(pipeline.hash, tab) - ? generateLinkToDetailsPanel(pipeline.project, FUNCTIONS_PAGE, null, pipeline.hash, null, tab) + ? generateLinkToDetailsPanel( + pipeline.project, + FUNCTIONS_PAGE, + null, + `${pipeline.name}@${pipeline.hash}`, + null, + tab + ) : '' }, { diff --git a/src/utils/datePicker.util.js b/src/utils/datePicker.util.js index ceb34c4a2..d2aa5870c 100644 --- a/src/utils/datePicker.util.js +++ b/src/utils/datePicker.util.js @@ -356,6 +356,13 @@ export const getTimeFrameWarningMsg = (timeFrameLimit) => { return `Maximum time range is ${mappedTime[timeFrameLimit]}` } +export const getDatePickerFilterValue = (options, optionId, isRange) => { + return { + value: options.find(option => option.id === optionId).handler(isRange), + isPredefined: true, + initialSelectedOptionId: optionId + } +} export const roundSeconds = (date = new Date(), top = false) => { const seconds = top ? 59 : 0 diff --git a/src/utils/generateArtifacts.js b/src/utils/generateArtifacts.js index 2e857d7e4..44e5b8011 100644 --- a/src/utils/generateArtifacts.js +++ b/src/utils/generateArtifacts.js @@ -51,7 +51,10 @@ export const generateArtifacts = (artifacts, tab, originalContent) => { item.extra_data ??= [] } - item.producer.uid = item.tree + item.producer = { + ...item.producer, + uid: item.tree + } if (!generatedArtifact.ui) { item.ui = { diff --git a/src/utils/generateFunctionDetailsLink.js b/src/utils/generateFunctionDetailsLink.js index 4e7c13395..0e5ca0af2 100644 --- a/src/utils/generateFunctionDetailsLink.js +++ b/src/utils/generateFunctionDetailsLink.js @@ -17,7 +17,23 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { generateLinkPath } from './parseUri' + +export const parseFunctionUri = functionUri => { + let [project, rest] = functionUri.split('/') + let name = rest + let hash = null + let tag = null + let nameWithHash = null + + if (rest.includes('@')) { + ;[name, hash] = rest.split('@') + nameWithHash = `${name}@${hash}` + } else if (rest.includes(':')) { + ;[name, tag] = rest.split(':') + } + + return { project, name, hash, tag, nameWithHash } +} export const generateFunctionDetailsLink = (uri = '') => { // remove 'latest' when function_uri will contain hash or tag @@ -25,9 +41,15 @@ export const generateFunctionDetailsLink = (uri = '') => { // 'my_proj/func_name@func_hash' -> projects/my_proj/functions/func_hash/overview // 'my_proj/func_name' -> projects/my_proj/functions/func_name/latest/overview // 'my_proj/func_name:custom_tag' -> projects/my_proj/functions/func_name/custom_tag/overview - return uri - ? `${generateLinkPath(`store://functions/${uri}`, uri.includes('@'))}${ - uri.includes(':') || uri.includes('@') ? '' : '/latest' - }/overview` - : '' + let generatedLink = '' + + if (uri) { + const { project, name, nameWithHash, tag } = parseFunctionUri(uri) + + if (project && (name || nameWithHash)) { + generatedLink = `/projects/${project}/functions/${nameWithHash ? nameWithHash : name}${nameWithHash ? '' : '/' + (tag ?? 'latest')}/overview` + } + } + + return generatedLink } diff --git a/src/utils/generateMonitoringData.js b/src/utils/generateMonitoringData.js index f98c91036..6b7fe11e9 100644 --- a/src/utils/generateMonitoringData.js +++ b/src/utils/generateMonitoringData.js @@ -18,39 +18,25 @@ under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ import { + DATES_FILTER, + FILTER_ALL_ITEMS, JOBS_MONITORING_JOBS_TAB, JOBS_MONITORING_WORKFLOWS_TAB, JOB_KIND_JOB, JOB_KIND_WORKFLOW, - GROUP_BY_WORKFLOW + STATUS_FILTER, + TYPE_FILTER } from '../constants' -import { setFilters, setModalFiltersValues } from '../reducers/filtersReducer' import { - datePickerFutureOptions, + ANY_TIME_DATE_OPTION, datePickerPastOptions, - NEXT_24_HOUR_DATE_OPTION, - PAST_24_HOUR_DATE_OPTION + getDatePickerFilterValue } from './datePicker.util' +import { setFiltersValues, setModalFiltersValues } from '../reducers/filtersReducer' export const generateMonitoringStats = (data, navigate, dispatch, tab) => { - const navigateToJobsMonitoringPage = (modalFilters, filters = {}, dateFutureOption) => { - const datePickerOptions = dateFutureOption ? datePickerFutureOptions : datePickerPastOptions - let date = datePickerOptions.find( - option => - option.id === (dateFutureOption ? NEXT_24_HOUR_DATE_OPTION : PAST_24_HOUR_DATE_OPTION) - ) - - dispatch( - setFilters({ - ...filters, - saveFilters: true, - dates: { - value: date.handler(dateFutureOption), - isPredefined: date.isPredefined, - initialSelectedOptionId: date.id - } - }) - ) + const navigateToJobsMonitoringPage = (modalFilters, filters = {}) => { + dispatch(setFiltersValues({ name: tab, value: filters })) dispatch(setModalFiltersValues({ name: tab, value: modalFilters })) navigate(`/projects/jobs-monitoring/${tab}`) } @@ -59,27 +45,35 @@ export const generateMonitoringStats = (data, navigate, dispatch, tab) => { ? { all: { counter: data.all, - link: () => navigateToJobsMonitoringPage({ state: ['all'] }) + link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: [FILTER_ALL_ITEMS] }) }, counters: [ { counter: data.running, link: () => - navigateToJobsMonitoringPage({ - state: ['running', 'pending', 'aborting'] - }), + navigateToJobsMonitoringPage( + { + [STATUS_FILTER]: ['running', 'pending', 'aborting'] + }, + { + [DATES_FILTER]: getDatePickerFilterValue( + datePickerPastOptions, + ANY_TIME_DATE_OPTION + ) + } + ), statusClass: 'running', tooltip: 'Aborting, Pending, Running' }, { counter: data.failed, - link: () => navigateToJobsMonitoringPage({ state: ['error', 'aborted'] }), + link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: ['error', 'aborted'] }), statusClass: 'failed', tooltip: 'Aborted, Error' }, { counter: data.completed, - link: () => navigateToJobsMonitoringPage({ state: ['completed'] }), + link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: ['completed'] }), statusClass: 'completed', tooltip: 'Completed' } @@ -89,37 +83,33 @@ export const generateMonitoringStats = (data, navigate, dispatch, tab) => { ? { all: { counter: data.all, - link: () => - navigateToJobsMonitoringPage({ state: ['all'] }, { groupBy: GROUP_BY_WORKFLOW }) + link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: [FILTER_ALL_ITEMS] }) }, counters: [ { counter: data.running, link: () => navigateToJobsMonitoringPage( - { state: ['running'] }, - { groupBy: GROUP_BY_WORKFLOW } + { [STATUS_FILTER]: ['running'] }, + { + [DATES_FILTER]: getDatePickerFilterValue( + datePickerPastOptions, + ANY_TIME_DATE_OPTION + ) + } ), statusClass: 'running', tooltip: 'Running' }, { counter: data.failed, - link: () => - navigateToJobsMonitoringPage( - { state: ['error', 'failed'] }, - { groupBy: GROUP_BY_WORKFLOW } - ), + link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: ['error', 'failed'] }), statusClass: 'failed', tooltip: 'Error, Failed' }, { counter: data.completed, - link: () => - navigateToJobsMonitoringPage( - { state: ['completed'] }, - { groupBy: GROUP_BY_WORKFLOW } - ), + link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: ['completed'] }), statusClass: 'completed', tooltip: 'Completed' } @@ -128,11 +118,11 @@ export const generateMonitoringStats = (data, navigate, dispatch, tab) => { : { jobs: { counter: data.jobs, - link: () => navigateToJobsMonitoringPage({ type: JOB_KIND_JOB }, {}, true) + link: () => navigateToJobsMonitoringPage({ [TYPE_FILTER]: JOB_KIND_JOB }, {}) }, workflows: { counter: data.workflows, - link: () => navigateToJobsMonitoringPage({ type: JOB_KIND_WORKFLOW }, {}, true) + link: () => navigateToJobsMonitoringPage({ [TYPE_FILTER]: JOB_KIND_WORKFLOW }, {}) } } } diff --git a/src/utils/getNoDataMessage.js b/src/utils/getNoDataMessage.js index da38805b5..2a06eb993 100644 --- a/src/utils/getNoDataMessage.js +++ b/src/utils/getNoDataMessage.js @@ -17,8 +17,8 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { isEqual, isNil } from 'lodash' -import { formatDate } from '../utils/datePicker.util' +import { isEqual, isNil, keyBy } from 'lodash' +import { formatDate } from './datePicker.util' import { ADD_TO_FEATURE_VECTOR_TAB, ANY_TIME, @@ -43,11 +43,17 @@ import { REAL_TIME_PIPELINES_TAB, SHOW_ITERATIONS, SHOW_UNTAGGED_FILTER, - SHOW_UNTAGGED_ITEMS, FILTER_ALL_ITEMS, STATUS_FILTER, TAG_FILTER, - TAG_FILTER_ALL_ITEMS + TAG_FILTER_ALL_ITEMS, + DATES_FILTER, + FILTER_MENU_MODAL, + FILTER_MENU, + PROJECT_FILTER, + TYPE_FILTER, + CONSUMER_GROUP_PAGE, + CONSUMER_GROUPS_PAGE } from '../constants' const messageNamesList = { @@ -87,12 +93,18 @@ const messageNamesList = { [REAL_TIME_PIPELINES_TAB]: { plural: 'Real-time pipelines' }, + [CONSUMER_GROUP_PAGE]: { + plural: 'v3io stream shard lags' + }, + [CONSUMER_GROUPS_PAGE]: { + plural: 'Consumer groups' + }, default: '' } export const getNoDataMessage = ( filtersStore, - filters, + filtersConfig, defaultMessage, page, tab, @@ -100,16 +112,21 @@ export const getNoDataMessage = ( ) => { if (defaultMessage) return defaultMessage + if (Array.isArray(filtersConfig)) { + filtersConfig = keyBy(filtersConfig, 'type') + } + const messageNames = messageNamesList[tab] || messageNamesList[page] || messageNamesList.default if (!messageNames) { return 'No data to show' } else { - const changedFiltersList = getChangedFiltersList(filters, filtersStore, filtersStoreKey) + const visibleFilterTypes = getVisibleFilterTypes(filtersConfig, filtersStore, filtersStoreKey) - return changedFiltersList.length > 0 + return visibleFilterTypes.length > 0 ? generateNoEntriesFoundMessage( - changedFiltersList, + visibleFilterTypes, + filtersConfig, filtersStore, messageNames, filtersStoreKey @@ -118,71 +135,111 @@ export const getNoDataMessage = ( } } -const getSelectedDateValue = (filter, filtersStore) => { +const getSelectedDateValue = (filterType, filtersStore, filtersStoreKey) => { const date = formatDate( true, true, '/', - filtersStore.dates.value[0], - filtersStore.dates.value[1] ?? new Date() + (filtersStoreKey + ? filtersStore[FILTER_MENU][filtersStoreKey][DATES_FILTER].value[0] + : filtersStore.dates.value[0]) ?? new Date(), + (filtersStoreKey + ? filtersStore[FILTER_MENU][filtersStoreKey][DATES_FILTER].value[1] + : filtersStore.dates.value[1]) ?? new Date() ) - return filter.type === DATE_RANGE_TIME_FILTER && - !isEqual(filtersStore.dates.value, DATE_FILTER_ANY_TIME) + return (filterType === DATE_RANGE_TIME_FILTER && + !isEqual(filtersStore.dates.value, DATE_FILTER_ANY_TIME)) || + (filterType === DATES_FILTER && + !isEqual( + filtersStore[FILTER_MENU][filtersStoreKey][DATES_FILTER].value, + DATE_FILTER_ANY_TIME + )) ? date : ANY_TIME } const generateNoEntriesFoundMessage = ( - changedFilters, + visibleFilterTypes, + filtersConfig, filtersStore, messageNames, filtersStoreKey ) => { - return changedFilters.reduce((message, filter, index) => { - const label = [ITERATIONS_FILTER, SHOW_UNTAGGED_ITEMS].includes(filter.type) - ? `${filter.label}:` - : filter.type === DATE_RANGE_TIME_FILTER - ? 'Date:' - : filter.label - const value = [ITERATIONS_FILTER, SHOW_UNTAGGED_ITEMS].includes(filter.type) + return visibleFilterTypes.reduce((message, filterType, index) => { + const label = filtersConfig[filterType].label + const value = [ITERATIONS_FILTER].includes(filterType) ? 'true' - : filter.type === DATE_RANGE_TIME_FILTER - ? getSelectedDateValue(filter, filtersStore) - : filter.type === STATUS_FILTER - ? filtersStore['state'] - : filtersStore.filterMenuModal[filtersStoreKey]?.values?.[filter.type] ?? - filtersStore[filter.type] - const isLastElement = index === changedFilters.length - 1 + : filterType === DATE_RANGE_TIME_FILTER || filterType === DATES_FILTER + ? getSelectedDateValue(filterType, filtersStore, filtersStoreKey) + : filtersStore[FILTER_MENU][filtersStoreKey]?.[filterType] ?? + filtersStore[FILTER_MENU_MODAL][filtersStoreKey]?.values?.[filterType] ?? + filtersStore[filterType] + const isLastElement = index === visibleFilterTypes.length - 1 return message + `${label} ${value}${isLastElement ? '"' : ', '}` }, 'No data matches the filter: "') } -const getChangedFiltersList = (filters, filtersStore, filtersStoreKey) => { - if (!filters || !filtersStore) { +const getVisibleFilterTypes = (filtersConfig, filtersStore, filtersStoreKey) => { + if (!filtersConfig || !filtersStore) { return [] } - return filters.filter(({ type }) => { - const isTagChanged = + return Object.keys(filtersConfig).filter(type => { + const isTagVisible = + type === TAG_FILTER && filtersStore.tag !== TAG_FILTER_ALL_ITEMS && - filtersStore.filterMenuModal[filtersStoreKey]?.values?.tag !== TAG_FILTER_ALL_ITEMS - const isIterChanged = !isNil(filtersStore.filterMenuModal[filtersStoreKey]?.values?.iter) - ? filtersStore.filterMenuModal[filtersStoreKey].values.iter === SHOW_ITERATIONS - : filtersStore.iter === SHOW_ITERATIONS + filtersStore[FILTER_MENU_MODAL][filtersStoreKey]?.values?.[TAG_FILTER] !== + TAG_FILTER_ALL_ITEMS + const isIterVisible = + type === ITERATIONS_FILTER && + (!isNil(filtersStore[FILTER_MENU_MODAL][filtersStoreKey]?.values?.iter) + ? filtersStore[FILTER_MENU_MODAL][filtersStoreKey].values.iter === SHOW_ITERATIONS + : filtersStore.iter === SHOW_ITERATIONS) + const isInputVisible = + (type === NAME_FILTER || + type === LABELS_FILTER || + type === ENTITIES_FILTER || + type === PROJECT_FILTER) && + (filtersStore[type].length > 0 || + filtersStore[FILTER_MENU][filtersStoreKey]?.[type]?.length > 0 || + filtersStore[FILTER_MENU_MODAL][filtersStoreKey]?.values?.[type]?.length > 0) + const isStatusVisible = + type === STATUS_FILTER && + (filtersStore.state !== FILTER_ALL_ITEMS || + (filtersStore[FILTER_MENU_MODAL][filtersStoreKey]?.values?.[STATUS_FILTER] && + !isEqual(filtersStore[FILTER_MENU_MODAL][filtersStoreKey]?.values?.[STATUS_FILTER], [ + FILTER_ALL_ITEMS + ]))) + const isTypeVisible = + type === TYPE_FILTER && + !isEqual( + filtersStore[FILTER_MENU_MODAL][filtersStoreKey]?.values?.[TYPE_FILTER], + FILTER_ALL_ITEMS + ) + const isDateVisible = + (type === DATE_RANGE_TIME_FILTER && + !isEqual(filtersStore.dates.value, DATE_FILTER_ANY_TIME)) || + (type === DATES_FILTER && + !isEqual( + filtersStore[FILTER_MENU][filtersStoreKey]?.[DATES_FILTER]?.value, + DATE_FILTER_ANY_TIME + )) + const isShowUntaggedVisible = + type === SHOW_UNTAGGED_FILTER && + !filtersStore[FILTER_MENU_MODAL][filtersStoreKey]?.values?.[SHOW_UNTAGGED_FILTER] + const isGroupByVisible = type === GROUP_BY_FILTER && filtersStore.groupBy !== GROUP_BY_NONE return ( - (type === LABELS_FILTER && - filtersStore.filterMenuModal[filtersStoreKey]?.values?.labels.length > 0) || - (type === TAG_FILTER && isTagChanged) || - ((type === NAME_FILTER || type === LABELS_FILTER || type === ENTITIES_FILTER) && - filtersStore[type].length > 0) || - (type === STATUS_FILTER && filtersStore.state !== FILTER_ALL_ITEMS) || - (type === DATE_RANGE_TIME_FILTER && !isEqual(filtersStore.dates.value, DATE_FILTER_ANY_TIME)) || - (type === ITERATIONS_FILTER && isIterChanged) || - (type === SHOW_UNTAGGED_FILTER && filtersStore.showUntagged === SHOW_UNTAGGED_ITEMS) || - (type === GROUP_BY_FILTER && filtersStore.groupBy !== GROUP_BY_NONE) + isTagVisible || + isIterVisible || + isInputVisible || + isStatusVisible || + isTypeVisible || + isDateVisible || + isShowUntaggedVisible || + isGroupByVisible ) }) } diff --git a/src/utils/jobs.util.js b/src/utils/jobs.util.js index 7d352b292..30d97c1ca 100644 --- a/src/utils/jobs.util.js +++ b/src/utils/jobs.util.js @@ -21,7 +21,6 @@ import { capitalize, chain, defaultsDeep, get, isEmpty } from 'lodash' import { pollAbortingJobs } from '../components/Jobs/jobs.util' import { showErrorNotification } from './notifications.util' -import { parseKeyValues } from './object' import { generateFunctionPriorityLabel } from './generateFunctionPriorityLabel' import { setNotification } from '../reducers/notificationReducer' @@ -137,7 +136,6 @@ export const enrichRunWithFunctionFields = ( ui: { functionTag: tagsList.join(', '), runOnSpot: capitalize(funcs[0].spec.preemption_mode ?? ''), - nodeSelectorChips: parseKeyValues(funcs[0].spec.node_selector || {}), priority: generateFunctionPriorityLabel(funcs[0].spec.priority_class_name ?? '') } }) @@ -146,7 +144,6 @@ export const enrichRunWithFunctionFields = ( ui: { functionTag: '', runOnSpot: '', - nodeSelectorChips: [], priority: '' } }) diff --git a/src/utils/largeResponseCatchHandler.js b/src/utils/largeResponseCatchHandler.js index 9896b52b4..d365eee04 100644 --- a/src/utils/largeResponseCatchHandler.js +++ b/src/utils/largeResponseCatchHandler.js @@ -20,12 +20,12 @@ such restriction. import { DEFAULT_ABORT_MSG, LARGE_REQUEST_CANCELED, REQUEST_CANCELED } from '../constants' import { showErrorNotification } from './notifications.util' -export const largeResponseCatchHandler = (error, defaultError, dispatch) => { +export const largeResponseCatchHandler = (error, defaultError, dispatch, showNotificationCallback = () => {}) => { if ( ![LARGE_REQUEST_CANCELED, REQUEST_CANCELED, DEFAULT_ABORT_MSG].includes(error.message) && error && dispatch ) { - showErrorNotification(dispatch, error, defaultError) + showErrorNotification(dispatch, error, defaultError, null, null, showNotificationCallback) } } diff --git a/src/utils/notifications.util.js b/src/utils/notifications.util.js index 0ba2acfe8..2129259f8 100644 --- a/src/utils/notifications.util.js +++ b/src/utils/notifications.util.js @@ -22,7 +22,7 @@ import { setNotification } from '../reducers/notificationReducer' import { getErrorMsg } from 'igz-controls/utils/common.util' import { FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' -export const showErrorNotification = (dispatch, error, defaultErrorMsg, customErrorMsg, retryCallback) => { +export const showErrorNotification = (dispatch, error, defaultErrorMsg, customErrorMsg, retryCallback, showNotificationCallback) => { const notificationData = { status: error?.response?.status || 400, id: Math.random(), @@ -34,5 +34,6 @@ export const showErrorNotification = (dispatch, error, defaultErrorMsg, customEr notificationData.retry = retryCallback } + showNotificationCallback?.(defaultErrorMsg) dispatch(setNotification(notificationData)) } diff --git a/src/utils/parseJob.js b/src/utils/parseJob.js index 716b35628..3dc31f209 100644 --- a/src/utils/parseJob.js +++ b/src/utils/parseJob.js @@ -78,6 +78,7 @@ export const parseJob = (job, tab) => { ...parseKeyValues(jobParameters), ...parseKeyValues(job.spec?.hyperparams || {}) ], + nodeSelectorChips: parseKeyValues(job.spec?.node_selector || {}), project: job.metadata.project, reason: job.status?.reason ?? '', results: job.status?.results || {}, diff --git a/src/utils/parseUri.js b/src/utils/parseUri.js index c52016812..4b6f276b4 100644 --- a/src/utils/parseUri.js +++ b/src/utils/parseUri.js @@ -92,11 +92,11 @@ const kindToScreen = { models: `models/${MODELS_TAB}` } -const generateLinkPath = (uri = '', ignoreKey = false) => { +const generateLinkPath = (uri = '') => { const { kind, project, key, tag, uid } = parseUri(uri) const screen = kindToScreen[kind] ?? FILES_TAB const reference = tag ?? uid - return `/projects/${project}/${screen}${ignoreKey ? '' : `/${key}`}${reference ? `/${reference}` : ''}` + return `/projects/${project}/${screen}${key}${reference ? `/${reference}` : ''}` } const generateNuclioLink = pathname => { diff --git a/tests/features/MLFunction.feature b/tests/features/MLFunction.feature index 3f18b88a3..fd88ee95f 100644 --- a/tests/features/MLFunction.feature +++ b/tests/features/MLFunction.feature @@ -70,7 +70,7 @@ Feature: ML Functions And click on cell with value "ML functions" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And hover "MLRun_Logo" component on "commonPagesHeader" wizard And wait load page - When click on cell with row index 1 in "name" column in "Functions_Table" table on "ML_Functions" wizard + When click on cell with row index 2 in "name" column in "Functions_Table" table on "ML_Functions" wizard Then verify "Header" element visibility on "ML_Function_Info_Pane" wizard Then verify "Updated" element visibility on "ML_Function_Info_Pane" wizard Then verify "Cross_Close_Button" element visibility on "ML_Function_Info_Pane" wizard @@ -78,6 +78,15 @@ Feature: ML Functions Then verify "Info_Pane_Tab_Selector" on "ML_Function_Info_Pane" wizard should contains "ML_Function_Info_Pane"."Tab_List" Then verify "Overview" tab is active in "Info_Pane_Tab_Selector" on "ML_Function_Info_Pane" wizard Then verify "Overview_Headers" on "ML_Function_Info_Pane" wizard should contains "ML_Function_Info_Pane"."Overview_Headers" + Then click on "Cross_Close_Button" element on "ML_Function_Info_Pane" wizard + When click on cell with row index 1 in "name" column in "Functions_Table" table on "ML_Functions" wizard + Then verify "Header" element visibility on "ML_Function_Info_Pane" wizard + Then verify "Updated" element visibility on "ML_Function_Info_Pane" wizard + Then verify "Cross_Close_Button" element visibility on "ML_Function_Info_Pane" wizard + Then verify "Info_Pane_Tab_Selector" element visibility on "ML_Function_Info_Pane" wizard + Then verify "Info_Pane_Tab_Selector" on "ML_Function_Info_Pane" wizard should contains "ML_Function_Info_Pane"."Tab_List_Application_type" + Then verify "Overview" tab is active in "Info_Pane_Tab_Selector" on "ML_Function_Info_Pane" wizard + Then verify "Overview_Headers" on "ML_Function_Info_Pane" wizard should contains "ML_Function_Info_Pane"."Overview_Headers_Application_type" @MLF @passive @@ -939,6 +948,7 @@ Feature: ML Functions And click on "Continue_Button" element on "Create_ML_Function_Popup" wizard When collapse "General_Accordion" on "New_Function" wizard When collapse "Code_Accordion" on "New_Function" wizard + When collapse "Environment_Variables_Accordion" on "New_Function" wizard When select "Manual" option in "New_Function_Volume_Mount_Dropdown" dropdown on "Resources_Accordion" on "New_Function" wizard When add new volume rows to "Volume_Paths_Table" table in "Resources_Accordion" on "New_Function" wizard using nontable inputs | Volume_Paths_Table_Type_Dropdown | Volume_Paths_Table_Volume_Name_Input | Volume_Paths_Table_Path_Input | Volume_Paths_Table_Container_Input | Volume_Paths_Table_Access_Key_Input | Volume_Paths_Table_Resource_Path_Input | Add_New_Row_Button | Delete_New_Row_Button | @@ -1086,7 +1096,7 @@ Feature: ML Functions And click on cell with value "ML functions" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And hover "MLRun_Logo" component on "commonPagesHeader" wizard And wait load page - When click on cell with row index 2 in "name" column in "Functions_Table" table on "ML_Functions" wizard + When click on cell with row index 3 in "name" column in "Functions_Table" table on "ML_Functions" wizard Then verify "Action_Menu" element visibility on "ML_Function_Info_Pane" wizard Then verify "Action_Menu" dropdown element on "ML_Function_Info_Pane" wizard should contains "ML_Functions_Tab"."Common_Action_Menu_Options" diff --git a/tests/features/artifacts.feature b/tests/features/artifacts.feature index 1e0e7ff91..2b68f9704 100644 --- a/tests/features/artifacts.feature +++ b/tests/features/artifacts.feature @@ -26,14 +26,14 @@ Feature: Files Page And wait load page Then verify "Table_Name_Filter_Input" element visibility on "Files" wizard Then click on "Table_FilterBy_Button" element on "Files" wizard - Then verify "Table_Label_Filter_Input" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard Then verify "Table_Refresh_Button" element visibility on "Files" wizard Then verify "Files_Table" element visibility on "Files" wizard Then verify "Register_File_Button" element visibility on "Files" wizard Then "Register_File_Button" element on "Files" should contains "Register artifact" value - Then verify "Table_Tree_Filter_Dropdown" dropdown element on "Artifacts_FilterBy_Popup" wizard should contains "Dropdown_Options"."Tag_Filer_Options" + Then verify "Table_Tree_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Tag_Filer_Options" @MLA @passive @@ -73,18 +73,18 @@ Feature: Files Page And select "tab" with "Artifacts" value in breadcrumbs menu And wait load page Then click on "Table_FilterBy_Button" element on "Files" wizard - Then type value "owner" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "owner" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then value in "labels" column with "dropdowns" in "Files_Table" on "Files" wizard should contains "owner" Then click on "Table_FilterBy_Button" element on "Files" wizard - Then type value "v3io_user=admin" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "v3io_user=admin" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then value in "labels" column with "dropdowns" in "Files_Table" on "Files" wizard should contains "v3io_user=admin" Then click on "Table_FilterBy_Button" element on "Files" wizard - Then type value "v3io_user=123" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "v3io_user=123" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard Then "No_Data_Message" component on "commonPagesHeader" should contains "No_Data_Message"."No_Files_data" @@ -100,25 +100,25 @@ Feature: Files Page And click on cell with value "Artifacts" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Files" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard Then check "expand_btn" not presented in "Files_Table" on "Files" wizard - Then uncheck "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then uncheck "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Files" wizard - Then "Show_Iterations_Checkbox" element should be unchecked on "Artifacts_FilterBy_Popup" wizard + Then "Show_Iterations_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then check "expand_btn" visibility in "Files_Table" on "Files" wizard with 0 offset Then click on cell with row index 1 in "expand_btn" column in "Files_Table" table on "Files" wizard And wait load page Then click on cell with row index 1 in "name" column in "Files_Table" table on "Files" wizard Then verify "Header" element visibility on "Files_Info_Pane" wizard Then click on "Table_FilterBy_Button" element on "Files" wizard - Then check "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then check "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then verify "Header" element not exists on "Files_Info_Pane" wizard Then click on "Table_FilterBy_Button" element on "Files" wizard - Then "Show_Iterations_Checkbox" element should be checked on "Artifacts_FilterBy_Popup" wizard + Then "Show_Iterations_Checkbox" element should be checked on "FilterBy_Popup" wizard Then check "expand_btn" not presented in "Files_Table" on "Files" wizard @MLA @@ -315,8 +315,8 @@ Feature: Files Page Then click on "Apply_Changes_Button" element on "Files_Info_Pane" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Files" wizard - Then select "v1" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "v1" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page When click on cell with value "test-file" in "name" column in "Files_Table" table on "Files" wizard Then verify "Info_Pane_Tab_Selector" element visibility on "Files_Info_Pane" wizard @@ -400,8 +400,8 @@ Feature: Files Page And wait load page Then verify "Table_FilterBy_Button" element visibility on "Files" wizard Then click on "Table_FilterBy_Button" element on "Files" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then verify action menu on "Files" wizard in "Files_Table" table with "survival-curves_km-survival" value in "name" column should contains "Common_Lists"."Action_Menu_List_Expanded" Then verify that in action menu on "Files" wizard in "Files_Table" table with "survival-curves_km-survival" value in "name" column "Delete" option is disabled @@ -475,8 +475,8 @@ Feature: Files Page Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard Then click on "Cross_Cancel_Button" element on "View_YAML" wizard Then click on "Table_FilterBy_Button" element on "Files" wizard - Then uncheck "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then uncheck "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on cell with row index 1 in "expand_btn" column in "Files_Table" table on "Files" wizard Then select "View YAML" option in action menu on "Files" wizard in "Files_Table" table at row with "latest" value in "name_expand_btn" column @@ -636,8 +636,8 @@ Feature: Files Page And wait load page Then verify "Table_FilterBy_Button" element visibility on "Files" wizard Then click on "Table_FilterBy_Button" element on "Files" wizard - Then select "newTag" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "newTag" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then check "newTag" value in "tag" column in "Files_Table" table on "Files" wizard @@ -662,8 +662,8 @@ Feature: Files Page Then click on "Apply_Changes_Button" element on "Files_Info_Pane" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Files" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page When click on cell with row index 1 in "name" column in "Files_Table" table on "Files" wizard And wait load page @@ -680,8 +680,8 @@ Feature: Files Page And click on cell with value "Artifacts" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Files" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on cell with row index 3 in "name" column in "Files_Table" table on "Files" wizard Then save to context "name" column on 3 row from "Files_Table" table on "Files" wizard diff --git a/tests/features/common-tools/common-consts.js b/tests/features/common-tools/common-consts.js index b9b0d1ceb..d9c3567da 100644 --- a/tests/features/common-tools/common-consts.js +++ b/tests/features/common-tools/common-consts.js @@ -136,6 +136,7 @@ module.exports = { }, ML_Function_Info_Pane: { Tab_List: ['Overview', 'Code', 'Build Log'], + Tab_List_Application_type: ['Overview', 'Build Log'], Overview_Headers: [ 'Name:', 'Kind:', @@ -147,6 +148,21 @@ module.exports = { 'Updated:', 'Default handler:', 'Description:' + ], + Overview_Headers_Application_type: [ + 'Name:', + 'Kind:', + 'Code entry point:', + 'Internal URL:', + 'Image:', + 'Application image:', + 'Version tag:', + 'Hash:', + 'Internal port:', + 'Code origin:', + 'Updated:', + 'Default handler:', + 'Description:' ] }, Files_Info_Pane: { @@ -336,8 +352,8 @@ module.exports = { 'Key must be unique', Projects_Labels_Warning_Key: '[Name] Valid characters : a–z, A–Z, 0–9, –, _, .\n[Name] Must begin and end with: a–z, A–Z, 0–9\n[Name] Max length - 63 characters\n' + '[Prefix] Valid characters: a–z, 0–9, –, .\n[Prefix] Must begin and end with: a–z, 0–9\n[Prefix] Max length - 253 characters\n' + - '[Prefix] Must not start with \'kubernetes.io\', \'k8s.io\'\nKey must be unique', - Projects_Labels_Warning_Value: '[Value] Must begin and end with : a–z, A–Z, 0–9\n[Value] Max length - 63 characters\n[Value] Valid characters: a–z, A–Z, 0–9, –, _, .', + '[Prefix] Must not start with \'kubernetes.io\', \'k8s.io\'\nSystem-defined labels cannot be modified.\nKey must be unique', + Projects_Labels_Warning_Value: '[Value] Must begin and end with: a–z, A–Z, 0–9\n[Value] Length – max: 63\n[Value] Valid characters: a–z, A–Z, 0–9, –, _, .', Labels_Warning_Value: 'Valid characters: a–z, A–Z, 0–9, –, _, .\nMust begin and end with: a–z, A–Z, 0–9\nLength – max: 56', Feature_Set_Name_Hint: 'Valid characters: a–z, A–Z, 0–9, –, _, .\nMust begin and end with: a–z, A–Z, 0–9\nLength – max: 56\n' + @@ -444,6 +460,16 @@ module.exports = { How_To_Create: 'See how to create a serving function in https://docs.mlrun.org/en/stable/serving/built-in-model-serving.html and https://docs.mlrun.org/en/stable/tutorials/03-model-serving.html' }, + Jobs_Monitoring: { + Tab_List: ['Jobs', 'Workflows', 'Scheduled'] + // Job_Action_Menu_Options: ['Batch re-run', 'Monitoring', 'View YAML', 'Delete'], + // Job_Overview_Action_Menu_Options: ['View YAML', 'Batch re-run', 'Delete'], + // Running_Job_Action_Menu_Options: ['Monitoring', 'Abort', 'View YAML'], + // Workflows_Action_Menu_Options: ['View YAML'], + // Workflows_Info_Pane_Action_Menu_Options: ['Batch re-run', 'Monitoring', 'View YAML', 'Delete'], + // Pending_Job_Action_Menu_Options: ['Batch re-run', 'Monitoring', 'Abort', 'View YAML'], + // Schedule_Action_Menu_Options: ['Run now', 'Edit', 'Delete', 'View YAML'] + }, Jobs_And_Workflows: { Tab_List: ['Monitor Jobs', 'Monitor Workflows', 'Schedule'], Job_Action_Menu_Options: ['Batch re-run', 'Monitoring', 'View YAML', 'Delete'], @@ -497,6 +523,10 @@ module.exports = { Dropdown_Options: { Tag_Filer_Options: ['All', 'latest'], Status_Filter_Options: ['All', 'Completed', 'Running', 'Pending', 'Error', 'Aborted'], + Jobs_Status_Filter_Options: ['All', 'Aborted', 'Aborting', 'Completed', 'Error', 'Running', 'Pending'], + Workflows_Status_Filter_Options: ['All', 'Error', 'Failed', 'Running', 'Completed'], + Jobs_Type_Filter_Options: ['All', 'Local', 'Dask', 'Databricks', 'Handler', 'Job', 'Horovod', 'Spark'], + Scheduled_Type_Filter_Options: ['All', 'Jobs', 'Workflows'], Group_By_Filter_Options: ['None', 'Name'], Start_Time_Filter_Options: [ 'Any time', @@ -507,6 +537,24 @@ module.exports = { 'Past year', 'Custom range' ], + Date_Picker_Filter_Options: [ + 'Any time', + 'Past hour', + 'Past 24 hours', + 'Past week', + 'Past month', + 'Past year', + 'Custom range' + ], + Scheduled_Date_Picker_Filter_Options: [ + 'Any time', + 'Next hour', + 'Next 24 hours', + 'Next week', + 'Next month', + 'Next year', + 'Custom range' + ], Parameters_Table_Type_Options: ['str', 'int', 'float', 'bool', 'list', 'map'], Parameter_Table_Simple_Hyper_Options: ['Simple', 'Hyper'], Turning_Strategy_Options: ['List', 'Grid', 'Random'], diff --git a/tests/features/common/components/breadcrumbs.component.js b/tests/features/common/components/breadcrumbs.component.js index 382ce801d..744c5354f 100644 --- a/tests/features/common/components/breadcrumbs.component.js +++ b/tests/features/common/components/breadcrumbs.component.js @@ -25,6 +25,7 @@ module.exports = { projectsPageLabel: By.css('.breadcrumbs__item:nth-of-type(1)'), projectLabel: By.css('.breadcrumbs__item:nth-of-type(3)'), tabLabel: By.css('.breadcrumbs__item:nth-of-type(5)'), + crossLabel: By.css('.breadcrumbs__item:nth-of-type(3)'), project: { open_button: By.css('.breadcrumbs__item:nth-of-type(2) button'), options: By.css('a.breadcrumbs__dropdown-item'), @@ -46,5 +47,12 @@ module.exports = { option: function(index) { return By.css(`.breadcrumbs__dropdown-item:nth-of-type(${index})`) } + }, + crossTab: { + open_button: By.css('.breadcrumbs__item:nth-of-type(2) button'), + options: By.css('a.breadcrumbs__dropdown-item'), + option: function(index) { + return By.css(`.breadcrumbs__dropdown-item:nth-of-type(${index})`) + } } } diff --git a/tests/features/common/page-objects.js b/tests/features/common/page-objects.js index c2c690a9d..76ddac576 100644 --- a/tests/features/common/page-objects.js +++ b/tests/features/common/page-objects.js @@ -25,6 +25,7 @@ import infoPane from './page-objects/info-pane.po' import interactivePopup from './page-objects/interactive-popup.po' import sidePanel from './page-objects/side-panel.po' import jobsAndWorkflows from './page-objects/jobs-and-workflows.po' +import jobsMonitoring from './page-objects/jobs-monitoring.po' import Functions from './page-objects/ml-functions.po' import projectsSettings from './page-objects/project-settings.po' import files from './page-objects/files.po' @@ -36,7 +37,6 @@ module.exports = { Analysis_Info_Pane: infoPane['analysisInfoPane'], Artifact_Preview_Popup: interactivePopup['artifactPreviewPopup'], Artifacts_Info_Pane: infoPane['artifactsInfoPane'], - Artifacts_FilterBy_Popup: interactivePopup['artifactsFilterByPopup'], Change_Project_Owner_Popup: interactivePopup['changeProjectOwnerPopup'], Common_Popup: interactivePopup['commonPopup'], Consumer_Groups: project['consumerGroups'], @@ -61,8 +61,12 @@ module.exports = { Features_Info_Pane: infoPane['featuresInfoPane'], Files: files['filesTab'], Files_Info_Pane: infoPane['filesInfoPane'], + FilterBy_Popup: interactivePopup['filterByPopup'], Inputs_Info_Pane: infoPane['inputsInfoPane'], Jobs_Monitor_Tab: jobsAndWorkflows['JobsMonitorTab'], + Jobs_Monitoring_Jobs_Tab: jobsMonitoring['crossJobsMonitorTab'], + Jobs_Monitoring_Workflows_Tab: jobsMonitoring['crossWorkflowsMonitorTab'], + Jobs_Monitoring_Scheduled_Tab: jobsMonitoring['crossScheduledMonitorTab'], Jobs_Monitor_Tab_Info_Pane: infoPane['jobsMonitorTabInfoPane'], ML_Function_Info_Pane: infoPane['mlFunctionInfoPane'], ML_Functions: Functions['mlFunctions'], diff --git a/tests/features/common/page-objects/interactive-popup.po.js b/tests/features/common/page-objects/interactive-popup.po.js index f4d61c023..333a6abf1 100644 --- a/tests/features/common/page-objects/interactive-popup.po.js +++ b/tests/features/common/page-objects/interactive-popup.po.js @@ -649,7 +649,16 @@ const commonRunSaveButton = By.css('.modal__content [data-testid="run-btn"]') const commonLabelFilterInput = inputGroup( generateInputGroup( - '#overlay_container .form-field__wrapper', + '[data-testid="labels-form-field-input"]', + true, + false, + true + ) +) + +const commonProjectFilterInput = inputGroup( + generateInputGroup( + '[data-testid="project-form-field-input"]', true, false, true @@ -1461,10 +1470,26 @@ module.exports = { No_Button: By.css('.pop-up-dialog .pop-up-dialog__btn_cancel'), Discard_Button: commonConfirmButton }, - artifactsFilterByPopup: { - Title: By.css('#overlay_container .artifacts-filters__wrapper h3'), + filterByPopup: { + Title: By.css('[data-testid="pop-up-dialog"] h3'), Table_Label_Filter_Input: commonLabelFilterInput, + Table_Project_Filter_Input: commonProjectFilterInput, Table_Tree_Filter_Dropdown: commonTableTreeFilterDropdown, + Status_Filter_Element: By.css('[data-testid="state-form-field-select"]'), + Status_Filter_Dropdown: dropdownComponent( + generateDropdownGroup( + '[data-testid="state-form-field-select"]', + '[data-testid="select-header"]', + '[data-testid="select-body"] label' + ) + ), + Type_Filter_Dropdown: dropdownComponent( + generateDropdownGroup( + '[data-testid="type-form-field-select"]', + '[data-testid="select-header"]', + '[data-testid="select-option"] [data-testid="tooltip-wrapper"]' + ) + ), Show_Iterations_Checkbox: checkboxComponent({ root: '#overlay_container .form-field-checkbox input', elements: { @@ -1473,9 +1498,97 @@ module.exports = { icon: '' } }), + Status_All_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(1)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Aborting_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(3)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Jobs_Running_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(6)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Workflows_Running_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(4)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Pending_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(7)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Aborted_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(2)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Jobs_Error_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(5)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Workflows_Error_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(2)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Failed_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(3)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Jobs_Completed_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(4)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), + Status_Workflows_Completed_Checkbox: checkboxComponent({ + root: '[data-testid="select-checkbox"]:nth-of-type(5)', + elements: { + checkbox: 'input', + name: 'label', + icon: '' + } + }), Checkbox_Label: By.css('#overlay_container .form-field-checkbox label'), - Clear_Button: By.css('#overlay_container .btn-tertiary'), - Apply_Button: By.css('#overlay_container .btn-secondary') + Clear_Button: By.css('[data-testid="filter-clear-btn"]'), + Apply_Button: By.css('[data-testid="filter-apply-btn"]') }, downloadsPopUp: { Download_Pop_Up: By.css('[data-testid="download-container"]'), diff --git a/tests/features/common/page-objects/jobs-monitoring.po.js b/tests/features/common/page-objects/jobs-monitoring.po.js new file mode 100644 index 000000000..9fda90e98 --- /dev/null +++ b/tests/features/common/page-objects/jobs-monitoring.po.js @@ -0,0 +1,125 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import { By } from 'selenium-webdriver' +import commonTable from '../components/table.component' +import dropdownComponent from '../components/dropdown.component' +import { + generateDropdownGroup, + generateInputGroup +} from '../../common-tools/common-tools' +import inputGroup from '../components/input-group.component' + + +const tabSelector = { + root: '.content .content-menu', + header: {}, + body: { + root: '.content-menu__list', + row: { + root: '.content-menu__item', + fields: { + key: 'a' + } + } + } +} + +const overallTable = { + root: '.table__content', + header: { + root: '.table-head', + sorters: { + name: '.table-head__item:nth-of-type(1) .data-ellipsis', + type: '.table-head__item:nth-of-type(2) .data-ellipsis', + duration: '.table-head__item:nth-of-type(3) .data-ellipsis', + owner: '.table-head__item:nth-of-type(4) .data-ellipsis', + labels: '.table-head__item:nth-of-type(5) .data-ellipsis', + parameters: '.table-head__item:nth-of-type(6) .data-ellipsis', + results: '.table-head__item:nth-of-type(7) .data-ellipsis' + } + }, + body: { + root: '.table-body', + row: { + root: '.table-row', + fields: { + name: '.table-body__cell:nth-of-type(1) a .link', + datetime: + '.table-body__cell:nth-of-type(1) a .date-uid-row .link-subtext:nth-of-type(1)', + uid: + '.table-body__cell:nth-of-type(1) a .date-uid-row .link-subtext:nth-of-type(2)', + duration: '.table-body__cell:nth-of-type(3) .data-ellipsis', + owner: '.table-body__cell:nth-of-type(4) .data-ellipsis' + } + } + } +} + +const commonDatePickerFilter = dropdownComponent( + generateDropdownGroup( + '[data-testid="date-picker-container"]', + '[data-testid="date-picker-input"]', + '.date-picker__pop-up .select__item', + '.data-ellipsis .data-ellipsis', + false + ) +) + +module.exports = { + crossJobsMonitorTab: { + Cross_Jobs_Tab_Selector: commonTable(tabSelector), + Table_FilterBy_Button: By.css('[data-testid="filter-menu-btn-tooltip-wrapper"]'), + Search_By_Name_Filter_Input: inputGroup( + generateInputGroup( + '[data-testid="name-form-field-input"]', + true, + false + ) + ), + Date_Picker_Filter_Dropdown: commonDatePickerFilter, + Jobs_Table: commonTable(overallTable) + }, + crossWorkflowsMonitorTab: { + Cross_Jobs_Tab_Selector: commonTable(tabSelector), + Table_FilterBy_Button: By.css('[data-testid="filter-menu-btn-tooltip-wrapper"]'), + Search_By_Name_Filter_Input: inputGroup( + generateInputGroup( + '[data-testid="name-form-field-input"]', + true, + false + ) + ), + Date_Picker_Filter_Dropdown: commonDatePickerFilter, + Workflows_Table: commonTable(overallTable) + }, + crossScheduledMonitorTab: { + Cross_Jobs_Tab_Selector: commonTable(tabSelector), + Table_FilterBy_Button: By.css('[data-testid="filter-menu-btn-tooltip-wrapper"]'), + Search_By_Name_Filter_Input: inputGroup( + generateInputGroup( + '[data-testid="name-form-field-input"]', + true, + false + ) + ), + Date_Picker_Filter_Dropdown: commonDatePickerFilter, + Scheduled_Table: commonTable(overallTable) + } +} diff --git a/tests/features/common/page-objects/side-panel.po.js b/tests/features/common/page-objects/side-panel.po.js index 14876ed1d..fa67d2e2f 100644 --- a/tests/features/common/page-objects/side-panel.po.js +++ b/tests/features/common/page-objects/side-panel.po.js @@ -801,6 +801,7 @@ module.exports = { ), Apply_Combobox_Button: By.css('.data-source .url-path-actions .round-icon-cp:nth-of-type(1) button'), Discard_Combobox_Button: By.css('.data-source .url-path-actions .round-icon-cp:nth-of-type(2) button'), + Edit_Button: By.css('.url-path-preview__actions .round-icon-cp'), Kind_Dropdown: dropdownComponent( generateDropdownGroup( '.feature-set-panel .accordion__container:nth-of-type(1) .panel-section__body .select', diff --git a/tests/features/datasets.feature b/tests/features/datasets.feature index 7aaf7765b..6a061673a 100644 --- a/tests/features/datasets.feature +++ b/tests/features/datasets.feature @@ -32,15 +32,15 @@ Feature: Datasets Page Then verify "Table_Name_Filter_Input" element visibility on "Datasets" wizard Then verify "Table_FilterBy_Button" element visibility on "Datasets" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then "Title" element on "Artifacts_FilterBy_Popup" should contains "Filter by" value - Then verify "Table_Label_Filter_Input" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" dropdown element on "Artifacts_FilterBy_Popup" wizard should contains "Dropdown_Options"."Tag_Filer_Options" - Then click on "Title" element on "Artifacts_FilterBy_Popup" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard - Then "Checkbox_Label" element on "Artifacts_FilterBy_Popup" should contains "Show best iteration only" value - Then verify "Clear_Button" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Apply_Button" element visibility on "Artifacts_FilterBy_Popup" wizard + Then "Title" element on "FilterBy_Popup" should contains "Filter by" value + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Tag_Filer_Options" + Then click on "Title" element on "FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard + Then "Checkbox_Label" element on "FilterBy_Popup" should contains "Show best iteration only" value + Then verify "Clear_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Apply_Button" element visibility on "FilterBy_Popup" wizard Then verify "Table_Refresh_Button" element visibility on "Datasets" wizard Then verify "Datasets_Table" element visibility on "Datasets" wizard @@ -61,26 +61,26 @@ Feature: Datasets Page And click on cell with value "Datasets" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard - Then "Show_Iterations_Checkbox" element should be checked on "Artifacts_FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard + Then "Show_Iterations_Checkbox" element should be checked on "FilterBy_Popup" wizard Then check "expand_btn" not presented in "Datasets_Table" on "Datasets" wizard - Then uncheck "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then uncheck "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then "Show_Iterations_Checkbox" element should be unchecked on "Artifacts_FilterBy_Popup" wizard + Then "Show_Iterations_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then check "expand_btn" visibility in "Datasets_Table" on "Datasets" wizard with 0 offset Then click on cell with row index 1 in "expand_btn" column in "Datasets_Table" table on "Datasets" wizard And wait load page Then click on cell with row index 2 in "name" column in "Datasets_Table" table on "Datasets" wizard Then verify "Header" element visibility on "Datasets_Info_Pane" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then check "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then check "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then verify "Header" element not exists on "Datasets_Info_Pane" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then "Show_Iterations_Checkbox" element should be checked on "Artifacts_FilterBy_Popup" wizard + Then "Show_Iterations_Checkbox" element should be checked on "FilterBy_Popup" wizard Then check "expand_btn" not presented in "Datasets_Table" on "Datasets" wizard @MLD @@ -158,8 +158,8 @@ Feature: Datasets Page Then click on "Apply_Changes_Button" element on "Datasets_Info_Pane" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then select "v2" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "v2" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page When click on cell with value "test-dataset" in "name" column in "Datasets_Table" table on "Datasets" wizard Then verify "Info_Pane_Tab_Selector" element visibility on "Datasets_Info_Pane" wizard @@ -315,18 +315,18 @@ Feature: Datasets Page And select "tab" with "Datasets" value in breadcrumbs menu And wait load page Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then type value "owner" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "owner" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then value in "labels" column with "dropdowns" in "Datasets_Table" on "Datasets" wizard should contains "owner" Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then type value "v3io_user=admin" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "v3io_user=admin" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then value in "labels" column with "dropdowns" in "Datasets_Table" on "Datasets" wizard should contains "v3io_user=admin" Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then type value "v3io_user=123" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "v3io_user=123" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard Then "No_Data_Message" component on "commonPagesHeader" should contains "No_Data_Message"."No_Datasets_data" @@ -360,8 +360,8 @@ Feature: Datasets Page Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard Then click on "Cross_Cancel_Button" element on "View_YAML" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then uncheck "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then uncheck "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on cell with row index 1 in "expand_btn" column in "Datasets_Table" table on "Datasets" wizard Then select "View YAML" option in action menu on "Datasets" wizard in "Datasets_Table" table at row with "latest" value in "name_expand_btn" column @@ -393,8 +393,8 @@ Feature: Datasets Page And wait load page Then verify "Table_FilterBy_Button" element visibility on "Datasets" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then verify action menu on "Datasets" wizard in "Datasets_Table" table with "test-regressor_cox-test-summary" value in "name" column should contains "Common_Lists"."Action_Menu_List_Expanded" Then verify that in action menu on "Datasets" wizard in "Datasets_Table" table with "test-regressor_cox-test-summary" value in "name" column "Delete" option is disabled @@ -549,8 +549,8 @@ Feature: Datasets Page And wait load page Then verify "Table_FilterBy_Button" element visibility on "Datasets" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then select "newTag" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "newTag" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then check "newTag" value in "tag" column in "Datasets_Table" table on "Datasets" wizard @@ -575,8 +575,8 @@ Feature: Datasets Page Then click on "Apply_Changes_Button" element on "Datasets_Info_Pane" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page When click on cell with row index 1 in "name" column in "Datasets_Table" table on "Datasets" wizard And wait load page @@ -593,8 +593,8 @@ Feature: Datasets Page And click on cell with value "Datasets" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on cell with row index 2 in "name" column in "Datasets_Table" table on "Datasets" wizard Then save to context "name" column on 2 row from "Datasets_Table" table on "Datasets" wizard @@ -1165,8 +1165,8 @@ Feature: Datasets Page Then click on "Cross_Close_Button" element on "Datasets_Info_Pane" wizard Then verify that 9 row elements are displayed in "Datasets_Table" on "Datasets" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then check "expand_btn" visibility in "Datasets_Table" on "Datasets" wizard with 0 offset Then verify that 9 row elements are displayed in "Datasets_Table" on "Datasets" wizard @@ -1187,9 +1187,7 @@ Feature: Datasets Page @MLD @inProgress - @FAILED_TODO - @smoke - #TODO: Bug ML-6022 - [Artifacts, Functions] selected item is missing from the list of visible items + @smoke # Run this test case only on full screen Scenario: MLD027 - Verify dataset elements visibility on Datasets Table with high number of rows * create "new_dataset_10" Dataset with "set_10" tag in "churn-project-admin" project with code 200 @@ -1216,10 +1214,10 @@ Feature: Datasets Page And wait load page And hover "MLRun_Logo" component on "commonPagesHeader" wizard And wait load page - Then verify that 15 row elements are displayed in "Datasets_Table" on "Datasets" wizard + Then verify that 16 row elements are displayed in "Datasets_Table" on "Datasets" wizard Then check "new_dataset_10" value in "name" column in "Datasets_Table" table on "Datasets" wizard Then check "new_dataset_24" value in "name" column in "Datasets_Table" table on "Datasets" wizard - Then check "test-regressor_cox-test-summary" value not in "name" column in "Datasets_Table" table on "Datasets" wizard + Then check "test-regressor_cox-test-summary" value in "name" column in "Datasets_Table" table on "Datasets" wizard Then check "survival-curves_coxhazard-summary" value not in "name" column in "Datasets_Table" table on "Datasets" wizard Then check "iris_gen_iris_dataset" value not in "name" column in "Datasets_Table" table on "Datasets" wizard Then check "data_clean_cleaned-data" value not in "name" column in "Datasets_Table" table on "Datasets" wizard @@ -1259,8 +1257,8 @@ Feature: Datasets Page And wait load page Then verify "Table_FilterBy_Button" element visibility on "Datasets" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then verify that 15 row elements are displayed in "Datasets_Table" on "Datasets" wizard Then check "new_dataset_24" value in "name" column in "Datasets_Table" table on "Datasets" wizard diff --git a/tests/features/featureStore.feature b/tests/features/featureStore.feature index 62161cf64..bda85a78b 100644 --- a/tests/features/featureStore.feature +++ b/tests/features/featureStore.feature @@ -388,7 +388,7 @@ Feature: Feature Store Page Then type value "type=featureSet" to "Table_Label_Filter_Input" field on "Feature_Store_Feature_Sets_Tab" wizard Then click on "Table_Refresh_Button" element on "Feature_Store_Feature_Sets_Tab" wizard And wait load page - Then value in "labels" column with "dropdowns" in "Feature_Sets_Table" on "Feature_Store_Feature_Sets_Tab" wizard should contains "type=featureSet" in "Overlay" + Then value in "labels" column with "text" in "Feature_Sets_Table" on "Feature_Store_Feature_Sets_Tab" wizard should contains "type=featureSet" Then type value "v3io_user=123" to "Table_Label_Filter_Input" field on "Feature_Store_Feature_Sets_Tab" wizard Then click on "Table_Refresh_Button" element on "Feature_Store_Feature_Sets_Tab" wizard And wait load page @@ -473,7 +473,7 @@ Feature: Feature Store Page Then type value "type=featureVector" to "Table_Label_Filter_Input" field on "Feature_Store_Features_Vectors_Tab" wizard Then click on "Table_Refresh_Button" element on "Feature_Store_Features_Tab" wizard And wait load page - Then value in "labels" column with "dropdowns" in "Feature_Vectors_Table" on "Feature_Store_Features_Vectors_Tab" wizard should contains "type=featureVector" in "Overlay" + Then value in "labels" column with "text" in "Feature_Vectors_Table" on "Feature_Store_Features_Vectors_Tab" wizard should contains "type=featureVector" Then type value "v3io_user=123" to "Table_Label_Filter_Input" field on "Feature_Store_Features_Vectors_Tab" wizard Then click on "Table_Refresh_Button" element on "Feature_Store_Features_Vectors_Tab" wizard And wait load page @@ -980,8 +980,8 @@ Feature: Feature Store Page And verify "Feature_Store_Tab_Selector" on "Feature_Store_Feature_Sets_Tab" wizard should contains "Feature_Store"."Tab_List" And verify "Feature Sets" tab is active in "Feature_Store_Tab_Selector" on "Feature_Store_Feature_Sets_Tab" wizard And click on "Create_Set_Button" element on "Feature_Store_Feature_Sets_Tab" wizard - Then verify "Save_Button" element on "New_Feature_Set" wizard is disabled - Then verify "Save_And_Ingest_Button" element on "New_Feature_Set" wizard is disabled + Then verify "Save_Button" element on "New_Feature_Set" wizard is enabled + Then verify "Save_And_Ingest_Button" element on "New_Feature_Set" wizard is enabled Then type value "demo_feature_set" to "Feature_Set_Name_Input" field on "New_Feature_Set" wizard Then type value "latest" to "Version_Input" field on "New_Feature_Set" wizard Then check "Description_Input" textarea counter on "New_Feature_Set" wizard diff --git a/tests/features/jobsAndWorkflows.feature b/tests/features/jobsAndWorkflows.feature index 9b52aa702..6302f1db1 100644 --- a/tests/features/jobsAndWorkflows.feature +++ b/tests/features/jobsAndWorkflows.feature @@ -330,41 +330,6 @@ Feature: Jobs and workflows Then verify from "11/07/2021 18:00" to "11/08/2021 18:00" filter band in "Start_Time_Filter_Dropdown" filter dropdown on "Jobs_Monitor_Tab" wizard Then value in "datetime" column in "Jobs_Monitor_Table" on "Jobs_Monitor_Tab" wizard should be from "11/07/2021 18:00" to "11/08/2021 18:00" - @oldJobWizard - Scenario: Prior implementation - verify mandatory elements starttime on Jobs Monitor tab - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - Then verify "Search_Input" element visibility on "Create_Job" wizard - Then verify "Select_Function_From_Dropdown" element visibility in "Select_Functions_From_Accordion" on "Create_Job" wizard - Then verify "Collapse_Button" element visibility in "Function_Templates_Accordion" on "Create_Job" wizard - - @oldJobWizard - Scenario: Prior implementation - verify mandatory elements starttime on Jobs Monitor tab - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When type searchable fragment "test" into "Search_Input" on "Create_Job" wizard - Then searchable case "sensitive" fragment "test" should be in every suggested option into "Search_Input" on "Create_Job" wizard - Then value in "name" column with "text" in "Selected_Functions_Templates" in "Select_Functions_From_Accordion" on "Create_Job" wizard should contains "test" - When expand each row in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - Then subtable column "templates_list" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard should contains "test" in "name" column - @MLJW @passive @smoke @@ -620,817 +585,6 @@ Feature: Jobs and workflows And wait load page Then check "erann-test" value not in "name" column in "Schedule_Monitor_Table" table on "Schedule_Monitor_Tab" wizard - @oldJobWizard - Scenario: Prior implementation - verify mandatory elements on Create New Jobs side panel except accordions - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - Then click on "Name_Edit_Button" element on "New_JobTemplate_Edit" wizard - Then type value " " to "Job_Name_Input" field on "New_JobTemplate_Edit" wizard - Then verify "Job_Name_Input" on "New_JobTemplate_Edit" wizard should display options "Input_Hint"."Jobs_Name_Hint" - Then verify "Job_Name_Input" options rules on "New_JobTemplate_Edit" wizard - Then type value "demo_Job_00" to "Job_Name_Input" field on "New_JobTemplate_Edit" wizard - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Access_Key_Checkbox" element visibility on "New_JobTemplate_Edit" wizard - Then uncheck "Access_Key_Checkbox" element on "New_JobTemplate_Edit" wizard - Then verify "Access_Key_Input" element visibility on "New_JobTemplate_Edit" wizard - Then type value " " to "Access_Key_Input" field on "New_JobTemplate_Edit" wizard - Then verify "Access_Key_Input" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Invalid" - Then type value "" to "Access_Key_Input" field on "New_JobTemplate_Edit" wizard - Then verify "Access_Key_Input" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Schedule_For_Later_Button" element visibility on "New_JobTemplate_Edit" wizard - Then "Schedule_For_Later_Button" element on "New_JobTemplate_Edit" should contains "Schedule for later" value - Then verify "Run_Now_Button" element visibility on "New_JobTemplate_Edit" wizard - Then "Run_Now_Button" element on "New_JobTemplate_Edit" should contains "Run now" value - - @oldJobWizard - Scenario: Prior implementation - verify mandatory elements in Data Inputs Accordion on Create New Jobs side panel - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - Then verify "Default_Input_Path_Input" element visibility in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Default_Artifact_Path_Input" element visibility in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - - @oldJobWizard - Scenario: Prior implementation - verify behaviour of Combobox element on Create New Jobs wizard on Data Inputs Accordion - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - Then click on "Add_Input_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then verify options in "URL_Combobox" combobox in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard should contains "Create_New_Job"."Combobox_Options" - When select "MLRun store" option in "URL_Combobox" combobox on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - When select "Artifacts" option in "URL_Combobox" combobox suggestion on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - When type searchable fragment "c" into "URL_Combobox" combobox input in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then searchable fragment "c" should be in every suggested option into "URL_Combobox" combobox input in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then select "Current project" option in "URL_Combobox" combobox suggestion on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - When type searchable fragment "clean" into "URL_Combobox" combobox input in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then searchable fragment "clean" should be in every suggested option into "URL_Combobox" combobox input in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When type value " " to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Accordion_Header" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "URL_Combobox" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Jobs_MLRun_Store_Path_Hint" - When type value "artifacts/default" to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then searchable fragment "default" should be in every suggested option into "URL_Combobox" combobox input in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When type value "artifacts/churn-project-admin/raw-data" to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then searchable fragment "raw-data" should be in every suggested option into "URL_Combobox" combobox input in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then select "V3IO" option in "URL_Combobox" combobox on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - Then type value "" to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "URL_Combobox" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."V3IO_Path_Hint" - Then select "S3" option in "URL_Combobox" combobox on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - Then verify "URL_Combobox" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."S3_Path_Hint" - Then select "Azure storage" option in "URL_Combobox" combobox on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - Then verify "URL_Combobox" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Azure_Storage_Path_Hint" - Then select "Google storage" option in "URL_Combobox" combobox on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - Then verify "URL_Combobox" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."S3_Path_Hint" - - @oldJobWizard - Scenario: Prior implementation - verify behaviour of Data Inputs Table in Data Inputs Accordion on create New JobTemplate edit wizard - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - Then click on "Add_Input_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "name1" to "Data_Inputs_Table_Name_Input" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then select "V3IO" option in "URL_Combobox" combobox on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - Then type value "container-name/file" to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Add_Row_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Add_Input_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "name1" to "Data_Inputs_Table_Name_Input" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Data_Inputs_Table_Name_Input" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Name_Already_Exists" - Then type value "name2" to "Data_Inputs_Table_Name_Input" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then select "MLRun store" option in "URL_Combobox" combobox on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - Then type value "artifacts/my-project/my-artifact" to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Add_Row_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Add_Input_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "name3" to "Data_Inputs_Table_Name_Input" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then select "S3" option in "URL_Combobox" combobox on "Data_Inputs_Accordion" accordion on "New_JobTemplate_Edit" wizard - Then type value "bucket/path" to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Add_Row_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then verify values in "Data_Source_Input_Sources_Table" table in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - | input_name_label | path_label | - | name1 | v3io:///container-name/file | - | name2 | store://artifacts/my-project/my-artifact | - | name3 | s3://bucket/path | - When click on "delete_btn" in "Data_Source_Input_Sources_Table" table in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | input_name_label | path_label | - | name1 | v3io:///container-name/file | - Then verify values in "Data_Source_Input_Sources_Table" table in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - | input_name_label | path_label | - | name2 | store://artifacts/my-project/my-artifact | - | name3 | s3://bucket/path | - When click on "input_name_label" in "Data_Source_Input_Sources_Table" table in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | input_name_label | - | name2 | - Then type value "edited_name2" to "Edit_Data_Inputs_Table_Name_Input" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "artifacts/my-project/edited-artifact" to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Apply_Edit_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When click on "input_name_label" in "Data_Source_Input_Sources_Table" table in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | input_name_label | - | name3 | - Then type value "edited_name3" to "Edit_Data_Inputs_Table_Name_Input" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "artifacts/my-project/edited-artifact3" to "URL_Combobox" field on "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Apply_Edit_Button" element in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then verify values in "Data_Source_Input_Sources_Table" table in "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - | input_name_label | path_label | - | edited_name2 | store://artifacts/my-project/edited-artifact | - | edited_name3 | s3://artifacts/my-project/edited-artifact3 | - - @oldJobWizard - Scenario: Prior implementation - verify mandatory elements in Parameters Accordion on Create New Jobs side panel - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - And wait load page - Then verify "Job_Predefined_Parameters_Table" element visibility in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Job_Custom_Parameters_Table" element visibility in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Parameters_Additional_Settings_Input" element visibility in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Result_Input" element visibility in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Turning_Strategy_Dropdown" element visibility in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then type value " " to "Parameters_Additional_Settings_Input" field on "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Turning_Strategy_Dropdown" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Turning_Strategy_Options" - Then verify "Criteria_Dropdown" element visibility in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Criteria_Dropdown" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Criteria_Dropdown_Options" - - @oldJobWizard - Scenario: Prior implementation - verify behaviour of Parameters Table in Resources Accordion on create New JobTemplate edit wizard - Given open url - And wait load page - And click on row root with value "default" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Job_Custom_Parameters_Table" element visibility in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When scroll and hover "Add_New_Row_Button" component in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When click on "Add_New_Row_Button" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Parameters_Table_Type_Dropdown" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Parameters_Table_Type_Options" - Then verify "Parameter_Table_Simple_Hyper_Dropdown" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Parameter_Table_Simple_Hyper_Options" - Then click on "Discard_New_Row_Button" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Job_Custom_Parameters_Table" table in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Parameters_Table_Name_Input | Parameters_Table_Type_Dropdown | Parameter_Table_Simple_Hyper_Dropdown | Parameters_Table_Value_Input | Apply_New_Row_Button | - | name1 | str | Simple | value1 | yes | - | name2 | int | Hyper | value2 | yes | - | name3 | map | Simple | value3 | yes | - | name4 | bool | Hyper | value4 | yes | - | name5 | str | Hyper | value5 | yes | - | name6 | float | Simple | value6 | yes | - #| name7 | map | Hyper | value7 | yes | - | name8 | list | Simple | value8 | yes | - Then verify values in "Job_Custom_Parameters_Table" table in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - | name | type | simple_hyper | values | - | name1 | str | Simple | value1 | - | name2 | int | Hyper | value2 | - | name3 | map | Simple | value3 | - | name4 | bool | Hyper | value4 | - | name5 | str | Hyper | value5 | - | name6 | float | Simple | value6 | - #| name7 | map | Hyper | value7 | - | name8 | list | Simple | value8 | - When click on "delete_btn" in "Job_Custom_Parameters_Table" table in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | name | - | name2 | - | name4 | - | name5 | - | name8 | - Then verify values in "Job_Custom_Parameters_Table" table in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - | name | type | simple_hyper | values | - | name1 | str | Simple | value1 | - | name3 | map | Simple | value3 | - | name6 | float | Simple | value6 | - #| name7 | map | Hyper | value7 | - When click on "name" in "Job_Custom_Parameters_Table" table in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | name | - | name1 | - Then type value "edited_name1" to "Edit_Parameters_Table_Name_Input" field on "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then select "Hyper" option in "Edit_Parameter_Table_Simple_Hyper_Dropdown" dropdown on "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "editedValue1" to "Edit_Parameters_Table_Value_Input" field on "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Apply_Edit_Button" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When click on "name" in "Job_Custom_Parameters_Table" table in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | name | - | name6 | - Then type value "edited_name6" to "Edit_Parameters_Table_Name_Input" field on "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then select "Simple" option in "Edit_Parameter_Table_Simple_Hyper_Dropdown" dropdown on "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "editedValue6" to "Edit_Parameters_Table_Value_Input" field on "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Apply_Edit_Button" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then verify values in "Job_Custom_Parameters_Table" table in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - | name | type | simple_hyper | values | - | edited_name1 | str | Hyper | editedValue1 | - | name3 | map | Simple | value3 | - #| name6 | float | Simple | value6 | - | edited_name6 | float | Simple | editedValue6 | - - @oldJobWizard - Scenario: Prior implementation - verify behaviour of Volume Paths Table in Resources Accordion on create New JobTemplate edit wizard - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Volume_Paths_Table_Type_Dropdown | Volume_Paths_Table_Volume_Name_Input | Volume_Paths_Table_Path_Input | Volume_Paths_Table_Container_Input | Volume_Paths_Table_Access_Key_Input | Volume_Paths_Table_Resource_Path_Input | Add_New_Row_Button | - | V3IO | | | | | | yes | - Then verify "Volume_Paths_Table_Volume_Name_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Access_Key_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."Mount_Path_Hint" - Then verify "Volume_Paths_Table_Container_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."Data_Container_Hint" - Then verify "Volume_Paths_Table_Access_Key_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."DataAccess_Key_Hint" - Then verify "Volume_Paths_Table_Resource_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."Relative_Directory_Path_Hint" - When click on "Delete_New_Row_Button" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Volume_Paths_Table_Type_Dropdown | Volume_Paths_Table_Volume_Name_Input | Volume_Paths_Table_Path_Input | Volume_Paths_Table_Config_Map_Input | Add_New_Row_Button | - | Config Map | | | | yes | - Then verify "Volume_Paths_Table_Volume_Name_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Config_Map_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."Mount_Path_Hint" - When click on "Delete_New_Row_Button" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Volume_Paths_Table_Type_Dropdown | Volume_Paths_Table_Volume_Name_Input | Volume_Paths_Table_Path_Input | Volume_Paths_Table_Secret_Name_Input | Add_New_Row_Button | - | Secret | | | | yes | - Then verify "Volume_Paths_Table_Volume_Name_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Config_Map_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."Mount_Path_Hint" - When click on "Delete_New_Row_Button" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Volume_Paths_Table_Type_Dropdown | Volume_Paths_Table_Volume_Name_Input | Volume_Paths_Table_Path_Input | Volume_Paths_Table_Claime_Name_Input | Add_New_Row_Button | - | PVC | | | | yes | - Then verify "Volume_Paths_Table_Volume_Name_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Claime_Name_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."Mount_Path_Hint" - When click on "Delete_New_Row_Button" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Volume_Paths_Table_Type_Dropdown | Volume_Paths_Table_Volume_Name_Input | Volume_Paths_Table_Path_Input | Volume_Paths_Table_Container_Input | Volume_Paths_Table_Access_Key_Input | Volume_Paths_Table_Resource_Path_Input | Add_New_Row_Button | Delete_New_Row_Button | - | V3IO | Volume_Name_1 | /path/to/happines1 | Container_Input_1 | Access_Key_1 | /resource/path_1 | yes | | - | V3IO | Volume_Name_2 | /path/to/happines2 | Container_Input_2 | Access_Key_2 | /resource/path_2 | | yes | - | V3IO | Volume_Name_3 | /path/to/happines3 | Container_Input_3 | Access_Key_3 | /resource/path_3 | yes | | - | V3IO | Volume_Name_4 | /path/to/happines4 | Container_Input_4 | Access_Key_4 | /resource/path_4 | | yes | - | V3IO | Volume_Name_5 | /path/to/happines5 | Container_Input_5 | Access_Key_5 | /resource/path_5 | yes | | - | V3IO | Volume_Name_6 | /path/to/happines6 | Container_Input_6 | Access_Key_6 | /resource/path_6 | | yes | - | V3IO | Volume_Name_7 | /path/to/happines7 | Container_Input_7 | Access_Key_7 | /resource/path_7 | yes | | - | V3IO | Volume_Name_8 | /path/to/happines8 | Container_Input_8 | Access_Key_8 | /resource/path_8 | | yes | - | V3IO | Volume_Name_9 | /path/to/happines9 | Container_Input_9 | Access_Key_9 | /resource/path_9 | | yes | - | V3IO | Volume_Name_0 | /path/to/happines0 | Container_Input_0 | Access_Key_0 | /resource/path_0 | yes | | - Then verify values in "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | volume_name | path | - | Volume_Name_1 | /path/to/happines1 | - | Volume_Name_3 | /path/to/happines3 | - | Volume_Name_5 | /path/to/happines5 | - | Volume_Name_7 | /path/to/happines7 | - | Volume_Name_0 | /path/to/happines0 | - When click on "Remove" in action menu in "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | volume_name | - | Volume_Name_0 | - | Volume_Name_3 | - Then verify values in "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | volume_name | path | - | Volume_Name_1 | /path/to/happines1 | - | Volume_Name_5 | /path/to/happines5 | - | Volume_Name_7 | /path/to/happines7 | - When click on "Edit" in action menu in "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | volume_name | - | Volume_Name_1 | - Then type value "Edited_Name_1" to "Edit_Volume_Name_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "/newPath/to/happines1" to "Edit_Volume_Path_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Apply_Edit_Button" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When click on "Edit" in action menu in "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | volume_name | - | Volume_Name_5 | - Then type value "Edited_Name_5" to "Edit_Volume_Name_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "/newPath/to/happines5" to "Edit_Volume_Path_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Apply_Edit_Button" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify values in "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | volume_name | path | - | Edited_Name_1 | /newPath/to/happines1 | - | Edited_Name_5 | /newPath/to/happines5 | - | Volume_Name_7 | /path/to/happines7 | - - @oldJobWizard - Scenario: Prior implementation - verify behaviour of Node Selector Table in Resources Accordion on create New JobTemplate edit wizard - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Resources_Node_Selector_Table" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When add rows to "Resources_Node_Selector_Table" key-value table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | key_input | value_input | - | key1 | value1 | - | key2 | value2 | - | key3 | value3 | - | key4 | value4 | - Then verify values in "Resources_Node_Selector_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | key | value | - | key1 | value1 | - | key2 | value2 | - | key3 | value3 | - | key4 | value4 | - When click on "delete_btn" in "Resources_Node_Selector_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | key | - | key3 | - | key1 | - Then verify values in "Resources_Node_Selector_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | key | value | - | key2 | value2 | - | key4 | value4 | - Then edit 1 row in "Resources_Node_Selector_Table" key-value table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | key_input | value_input | - | edited | edited | - Then edit 2 row in "Resources_Node_Selector_Table" key-value table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | key_input | value_input | - | edited | edited | - Then verify values in "Resources_Node_Selector_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - | key | value | - | key2edited | value2edited | - | key4edited | value4edited | - - @oldJobWizard - Scenario: Prior implementation - verify mandatory elements in Resources Accordion on Create New Jobs side panel - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Resources_Accordion" on "New_JobTemplate_Edit" wizard - And wait load page - Then verify "Pods_Priority_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Pods_Priority_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Pods_Priority" - Then verify "Pods_Toleration_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Pods_Toleration is deleted from implementation - Then verify "Pods_Toleration_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "Prevent" - Then verify "Pods_Toleration_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Pods_Toleration" - Then select "Allow" option in "Pods_Toleration_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Pods_Toleration_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "Allow" - Then select "Constrain" option in "Pods_Toleration_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Pods_Toleration_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "Constrain" - Then verify "Volumes_Subheader" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Volumes_Subheader" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Label_Hint"."New_Job_Volumes" - Then verify "Volume_Paths_Table" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Request_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Request_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Memory_Unit_Options" - Then verify "Memory_Limit_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Memory_Unit_Options" - Then verify "Memory_Request_Number_Input" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "0" to "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Limit_Number_Warning" - Then type value "1" to "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "1025" to "Memory_Request_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Limit_Number_Warning" - Then verify "Memory_Request_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Request_Number_Warning" - Then type value "2" to "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "KB" option in "Memory_Limit_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Limit_Number_Warning" - Then verify "Memory_Request_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Request_Number_Warning" - Then select "KB" option in "Memory_Request_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "" to "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then type value "2" to "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "GB" option in "Memory_Request_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Limit_Number_Warning" - Then verify "Memory_Request_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Request_Number_Warning" - Then verify "CPU_Request_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Request_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."CPU_Unit_Options" - Then verify "CPU_Limit_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Limit_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."CPU_Unit_Options" - Then select "millicpu" option in "CPU_Limit_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "millicpu" option in "CPU_Request_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "0" to "CPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Limit_Number_Warning" - Then type value "1" to "CPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "1025" to "CPU_Request_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Limit_Number_Warning" - Then verify "CPU_Request_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Request_Number_Warning" - Then type value "0" to "GPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "GPU_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."GPU_Minimum_Value_Warning" - Then verify "Memory_Request_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Request_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Memory_Unit_Options" - Then verify "Memory_Request_Number_Input" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "1" to "Memory_Request_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then increase value on 15 points in "Memory_Request_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then decrease value on 15 points in "Memory_Request_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Memory_Unit_Options" - Then verify "Memory_Limit_Number_Input" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "2" to "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then increase value on 15 points in "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then decrease value on 15 points in "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Request_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Request_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."CPU_Unit_Options" - Then verify "CPU_Request_Number_Input" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "3" to "CPU_Request_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then increase value on 15 points in "CPU_Request_Number_Input" field with "millicpu" on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then decrease value on 15 points in "CPU_Request_Number_Input" field with "millicpu" on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "cpu" option in "CPU_Request_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Request_Number_Input" input should contains "0.003" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then increase value on 8 points in "CPU_Request_Number_Input" field with "cpu" on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then decrease value on 8 points in "CPU_Request_Number_Input" field with "cpu" on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Limit_Dropdown" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Limit_Dropdown" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."CPU_Unit_Options" - Then verify "CPU_Limit_Number_Input" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "4" to "CPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then increase value on 15 points in "CPU_Limit_Number_Input" field with "millicpu" on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then decrease value on 15 points in "CPU_Limit_Number_Input" field with "millicpu" on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "cpu" option in "CPU_Limit_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Limit_Number_Input" input should contains "0.004" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then increase value on 8 points in "CPU_Request_Number_Input" field with "cpu" on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then decrease value on 8 points in "CPU_Request_Number_Input" field with "cpu" on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "GPU_Limit_Number_Input" element visibility in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "5" to "GPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then increase value on 15 points in "GPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then decrease value on 15 points in "GPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - - @oldJobWizard - Scenario: Prior implementation - verify mandatory elements in Advanced Accordion on Create New Jobs side panel - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Advanced_Environment_Variables_Table" element visibility in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - - @oldJobWizard - Scenario: Prior implementation - verify mandatory elements in Advanced Accordion on Create New Jobs side panel in Demo mode - Given open url - And wait load page - And turn on demo mode - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Advanced_Environment_Variables_Demo_Table" element visibility in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Advanced_Secrets_Table" element visibility in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - - @oldJobWizard - Scenario: Prior implementation - verify Advanced Environment Variables Table in Advanced Accordion on Create New Jobs side panel - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - And click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - And expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Advanced_Environment_Variables_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Environment_Variables_Name_Input | Environment_Variables_Value_Input | Add_Row_Button | Discard_Row_Button | - | name0 | value0 | yes | | - | name1 | value1 | | yes | - | name2 | value2 | yes | | - | name3 | value3 | | yes | - | name4 | value4 | yes | | - | name5 | value5 | yes | | - | name6 | value6 | | yes | - | name7 | value7 | | yes | - | name8 | value8 | yes | | - | name9 | value9 | | yes | - Then verify values in "Advanced_Environment_Variables_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | name | value | - | name0 | value0 | - | name2 | value2 | - | name4 | value4 | - | name5 | value5 | - | name8 | value8 | - When click on "delete_btn" in "Advanced_Environment_Variables_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | name | - | name8 | - | name4 | - | name2 | - Then verify values in "Advanced_Environment_Variables_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | name | value | - | name0 | value0 | - | name5 | value5 | - Then edit 1 row in "Advanced_Environment_Variables_Table" key-value table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | name_input | value_input | - | edited | edited | - Then edit 2 row in "Advanced_Environment_Variables_Table" key-value table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | name_input | value_input | - | edited | edited | - Then verify values in "Advanced_Environment_Variables_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | name | value | - | name0edited | value0edited | - | name5edited | value5edited | - - @oldJobWizard - Scenario: Prior implementation - verify Advanced Environment Variables Table in Advanced Accordion on Create New Jobs side panel - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And turn on demo mode - And wait load page - And click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - And expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Environment_Variables_Demo_Name_Input | Environment_Variables_Type_Dropdown | Environment_Variables_Demo_Value_Input | Add_Row_Button | - | | Value | | yes | - Then verify "Environment_Variables_Demo_Name_Input" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Environment_Variables_Demo_Value_Input" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - When click on "Demo_Discard_Row_Button" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Environment_Variables_Demo_Name_Input | Environment_Variables_Type_Dropdown | Environment_Variables_Secret_Name_Input | Environment_Variables_Secret_Key_Input | Add_Row_Button | - | | Secret | | @#$ | yes | - Then verify "Environment_Variables_Demo_Name_Input" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Environment_Variables_Secret_Name_Input" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Require" - Then verify "Environment_Variables_Secret_Name_Input" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."SECRET_INPUT_HINT" - Then verify "Environment_Variables_Secret_Key_Input" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Input_Field_Invalid" - Then verify "Environment_Variables_Secret_Key_Input" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard should display hint "Input_Hint"."VALUE_INPUT_HINT" - When click on "Demo_Discard_Row_Button" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Environment_Variables_Demo_Name_Input | Environment_Variables_Type_Dropdown | Environment_Variables_Demo_Value_Input | Add_Row_Button | Demo_Discard_Row_Button | - | name0 | Value | value0 | yes | | - | name1 | Value | value1 | | yes | - | name2 | Value | value2 | yes | | - | name3 | Value | value3 | | yes | - | name4 | Value | value4 | yes | | - | name5 | Value | value5 | yes | | - | name6 | Value | value6 | | yes | - | name7 | Value | value7 | | yes | - | name8 | Value | value8 | yes | | - | name9 | Value | value9 | | yes | - When add new volume rows to "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Environment_Variables_Demo_Name_Input | Environment_Variables_Type_Dropdown | Environment_Variables_Secret_Name_Input | Environment_Variables_Secret_Key_Input | Add_Row_Button | Demo_Discard_Row_Button | - | name0 | Secret | value0 | key0 | | yes | - | name1 | Secret | value1 | key1 | yes | | - | name2 | Secret | value2 | key2 | | yes | - | name3 | Secret | value3 | key3 | yes | | - | name4 | Secret | value4 | key4 | | yes | - | name5 | Secret | value5 | key5 | | yes | - | name6 | Secret | value6 | key6 | yes | | - | name7 | Secret | value7 | key7 | yes | | - | name8 | Secret | value8 | key8 | | yes | - | name9 | Secret | value9 | key9 | yes | | - Then verify values in "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | name | type | value | - | name0 | value | value0 | - | name2 | value | value2 | - | name4 | value | value4 | - | name5 | value | value5 | - | name8 | value | value8 | - | name1 | secret | value1:key1 | - | name3 | secret | value3:key3 | - | name6 | secret | value6:key6 | - | name7 | secret | value7:key7 | - | name9 | secret | value9:key9 | - When click on "Remove" in action menu in "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | name | - | name2 | - | name4 | - | name8 | - | name1 | - | name9 | - Then verify values in "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | name | type | value | - | name0 | value | value0 | - | name5 | value | value5 | - | name3 | secret | value3:key3 | - | name6 | secret | value6:key6 | - When click on "Edit" in action menu in "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | name | - | name5 | - Then type value "Edited_Name_5" to "Edit_Environment_Variables_Demo_Name_Input" field on "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "Edited_Value_5" to "Edit_Environment_Variables_Demo_Value_Input" field on "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Apply_Edit_Button" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - When click on "Edit" in action menu in "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | name | - | name0 | - Then type value "Edited_Name_0" to "Edit_Environment_Variables_Demo_Name_Input" field on "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then select "Secret" option in "Edit_Environment_Variables_Type_Dropdown" dropdown on "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "editedValue0" to "Edit_Environment_Variables_Secret_Name_Input" field on "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "editedKey0" to "Edit_Environment_Variables_Secret_Key_Input" field on "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then click on "Apply_Edit_Button" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - Then verify values in "Advanced_Environment_Variables_Demo_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | name | type | value | - | Edited_Name_0 | secret | editedValue0:editedKey0 | - | Edited_Name_5 | value | Edited_Value_5 | - | name3 | secret | value3:key3 | - | name6 | secret | value6:key6 | - When add rows to "Advanced_Secrets_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | value_input | - | value1 | - | value2 | - | value3 | - Then verify values in "Advanced_Secrets_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | kind | value | - | file | value1 | - | file | value2 | - | file | value3 | - When click on "delete_btn" in "Advanced_Secrets_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard with offset "false" - | value | - | value3 | - | value1 | - Then verify values in "Advanced_Secrets_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | kind | value | - | file | value2 | - Then edit 1 row in "Advanced_Secrets_Table" key-value table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | value_input | - | edited | - Then verify values in "Advanced_Secrets_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - | kind | value | - | file | value2edited | - - @oldJobWizard - Scenario: Prior implementation - verify non-unique value input hint on Create New Jobs side panel - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - And click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - And expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Job_Custom_Parameters_Table" table in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Parameters_Table_Name_Input | Parameters_Table_Type_Dropdown | Parameter_Table_Simple_Hyper_Dropdown | Parameters_Table_Value_Input | Apply_New_Row_Button | - | name1 | str | Simple | value1 | yes | - | name1 | int | Hyper | value2 | yes | - Then verify "Parameters_Table_Name_Input" element in "Parameters_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Name_Already_Exists" - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Volume_Paths_Table" table in "Resources_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Volume_Paths_Table_Type_Dropdown | Volume_Paths_Table_Volume_Name_Input | Volume_Paths_Table_Path_Input | Volume_Paths_Table_Container_Input | Volume_Paths_Table_Access_Key_Input | Volume_Paths_Table_Resource_Path_Input | Add_New_Row_Button | Delete_New_Row_Button | - | V3IO | Volume_Name_1 | /path/to/happines1 | Container_Input_1 | Access_Key_1 | /resource/path_1 | yes | | - | V3IO | Volume_Name_1 | /path/to/happines1 | Container_Input_1 | Access_Key_1 | /resource/path_1 | yes | | - Then verify "Volume_Paths_Table_Volume_Name_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Name_Already_Exists" - Then verify "Volume_Paths_Table_Path_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Volumes_Path_Already_Exists" - When collapse "Resources_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Advanced_Accordion" on "New_JobTemplate_Edit" wizard - When add new volume rows to "Advanced_Environment_Variables_Table" table in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard using nontable inputs - | Environment_Variables_Name_Input | Environment_Variables_Value_Input | Add_Row_Button | Discard_Row_Button | - | name0 | value0 | yes | | - | name0 | value0 | yes | | - Then verify "Environment_Variables_Name_Input" element in "Advanced_Accordion" on "New_JobTemplate_Edit" wizard should display warning "Input_Hint"."Name_Already_Exists" - - @oldJobWizard - Scenario: Prior implementation - run New Job from template - * set tear-down property "project" created with "automation-test-name-1100" value - * create "automation-test-name-1100" MLRun Project with code 201 - Given open url - And wait load page - And click on row root with value "automation-test-name-1100" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - And click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - And expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - Then click on "Name_Edit_Button" element on "New_JobTemplate_Edit" wizard - Then type value "demo_job_00" to "Job_Name_Input" field on "New_JobTemplate_Edit" wizard - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "High" option in "Pods_Priority_Dropdown" dropdown on "Resources_Accordion" on "New_Function" wizard - When scroll and hover "Run_Now_Button" component on "New_JobTemplate_Edit" wizard - Then click on "Run_Now_Button" element on "New_JobTemplate_Edit" wizard - Then check "demo_job_00" value in "name" column in "Jobs_Monitor_Table" table on "Jobs_Monitor_Tab" wizard - Then select "Re-run" option in action menu on "Jobs_Monitor_Tab" wizard in "Jobs_Monitor_Table" table at row with "demo_job_00" value in "name" column - And wait load page - When collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - When collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - When expand "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Pods_Priority_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "High" - Then select "Low" option in "Pods_Priority_Dropdown" dropdown on "Resources_Accordion" on "New_Function" wizard - Then verify "Pods_Priority_Dropdown" dropdown in "Resources_Accordion" on "New_Function" wizard selected option value "Low" - Then select "Medium" option in "Pods_Priority_Dropdown" dropdown on "Resources_Accordion" on "New_Function" wizard - Then verify "Pods_Priority_Dropdown" dropdown in "Resources_Accordion" on "New_Function" wizard selected option value "Medium" - @MLJW @passive @smoke @@ -2003,113 +1157,6 @@ Feature: Jobs and workflows And wait load page Then compare current browser URL with test "href" context value - @oldJobWizard - Scenario: Check messages when create a new Schedule - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And wait load page - And click on "Batch_Run_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - Then verify "Title" element visibility on "Modal_Wizard_Form" wizard - Then "Title" element on "Modal_Wizard_Form" should contains "Batch Run" value - And click on row root with value "clean-data" in "name" column in "Functions_Table" table on "Modal_Wizard_Form" wizard - Then "Function_Title" element on "Modal_Wizard_Form" should contains "clean-data" value - Then verify "Schedule_For_Later_Button" element visibility on "Modal_Wizard_Form" wizard - Then verify "Schedule_For_Later_Button" element on "Modal_Wizard_Form" wizard is enabled - Then click on "Schedule_For_Later_Button" element on "Modal_Wizard_Form" wizard - Then verify "Schedule_Button" element visibility in "Schedule_For_Later" on "New_JobTemplate_Edit" wizard - Then verify "Schedule_Days_Dropdown" element visibility in "Schedule_For_Later" on "New_JobTemplate_Edit" wizard - Then verify "Schedule_Time_Dropdown" element visibility in "Schedule_For_Later" on "New_JobTemplate_Edit" wizard - Then verify "Schedule_Days_Dropdown" element in "Schedule_For_Later" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Schedule_Variants" - Then verify "Schedule_Time_Dropdown" element in "Schedule_For_Later" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Schedule_Minutes_Variants" - Then select "Hourly" option in "Schedule_Days_Dropdown" dropdown on "Schedule_For_Later" on "New_JobTemplate_Edit" wizard - Then verify "Schedule_Time_Dropdown" element in "Schedule_For_Later" on "New_JobTemplate_Edit" wizard should contains "Dropdown_Options"."Schedule_Hours_Variants" - * set tear-down property "schedule" created in "default" project with "aggregate" value - Then click on "Schedule_Button" element in "Schedule_For_Later" on "New_JobTemplate_Edit" wizard - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - And expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And select "aggregate" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - Then click on "Schedule_For_Later_Button" element on "New_JobTemplate_Edit" wizard - Then click on "Schedule_Button" element in "Schedule_For_Later" on "New_JobTemplate_Edit" wizard - Then "Error_Message" component on "New_JobTemplate_Edit" should contains "Error_Messages"."Already_Scheduled" - - @oldJobWizard - Scenario: Verify behaviour of Method changing on Create New Job panel - Given open url - And wait load page - And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard - And wait load page - And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard - And click on cell with value "Jobs and workflows" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard - And hover "MLRun_Logo" component on "commonPagesHeader" wizard - And wait load page - Then click on "New_Job_Button" element on "Jobs_Monitor_Tab" wizard - And wait load page - When expand row with "Data Preparation" at "name" in "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - When select "feature-selection" in subcolumn "name" at "templates_list" column in "Data Preparation" row by "name" at "Functions_Templates_Table" in "Function_Templates_Accordion" on "Create_Job" wizard - And wait load page - Then collapse "Data_Inputs_Accordion" on "New_JobTemplate_Edit" wizard - Then collapse "Parameters_Accordion" on "New_JobTemplate_Edit" wizard - Then expand "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "High" option in "Pods_Priority_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "Allow" option in "Pods_Toleration_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "5" to "Memory_Request_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "10" to "Memory_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "millicpu" option in "CPU_Limit_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "millicpu" option in "CPU_Request_Dropdown" dropdown on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "100" to "CPU_Request_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "1000" to "CPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then type value "10" to "GPU_Limit_Number_Input" field on "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then select "plot_stat" option in "Job_Method_Dropdown" dropdown on "New_JobTemplate_Edit" wizard - Then verify "Pods_Priority_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "Medium" - Then verify "Pods_Toleration_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "Prevent" - Then verify "Memory_Request_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is disabled - Then verify "Memory_Request_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is disabled - Then verify "Memory_Limit_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Request_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is disabled - Then verify "CPU_Request_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is disabled - Then verify "CPU_Limit_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "GPU_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is disabled - Then verify "GPU_Limit_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Schedule_For_Later_Button" element on "New_JobTemplate_Edit" wizard is disabled - Then verify "Run_Now_Button" element on "New_JobTemplate_Edit" wizard is disabled - Then click on "Job_Method_Cancel" element on "New_JobTemplate_Edit" wizard - Then verify "Pods_Priority_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "High" - Then verify "Pods_Toleration_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "Allow" - Then verify "Memory_Request_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is enabled - Then verify "Memory_Request_Number_Input" input should contains "5" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is enabled - Then verify "Memory_Limit_Number_Input" input should contains "10" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Request_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is enabled - Then verify "CPU_Request_Number_Input" input should contains "100" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is enabled - Then verify "CPU_Limit_Number_Input" input should contains "1000" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "GPU_Limit_Number_Input" element in "Resources_Accordion" on "New_JobTemplate_Edit" wizard is enabled - Then verify "GPU_Limit_Number_Input" input should contains "10" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Schedule_For_Later_Button" element on "New_JobTemplate_Edit" wizard is enabled - Then verify "Run_Now_Button" element on "New_JobTemplate_Edit" wizard is enabled - Then select "plot_stat" option in "Job_Method_Dropdown" dropdown on "New_JobTemplate_Edit" wizard - Then click on "Job_Method_Apply" element on "New_JobTemplate_Edit" wizard - Then verify "Pods_Priority_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "Medium" - Then verify "Pods_Toleration_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "Prevent" - Then verify "CPU_Limit_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "cpu" - Then verify "CPU_Request_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "cpu" - Then verify "Memory_Request_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "MiB" - Then verify "Memory_Limit_Dropdown" dropdown in "Resources_Accordion" on "New_JobTemplate_Edit" wizard selected option value "MiB" - Then verify "Memory_Request_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "Memory_Limit_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Request_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "CPU_Limit_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - Then verify "GPU_Limit_Number_Input" input should contains "" value in "Resources_Accordion" on "New_JobTemplate_Edit" wizard - @MLJW @smoke Scenario: MLJW085 - Check broken link redirection on Monitor Jobs and Schedules screens diff --git a/tests/features/jobsMonitoring.feature b/tests/features/jobsMonitoring.feature new file mode 100644 index 000000000..f6b82a6c1 --- /dev/null +++ b/tests/features/jobsMonitoring.feature @@ -0,0 +1,264 @@ +Feature: Jobs Monitoring Page + + Testcases that verifies functionality on MLRun Jobs Monitoring Page + + @MLJM + @smoke + Scenario: MLJM001 - Check components on Jobs tab of Jobs monitoring page + Given open url + And wait load page + Then verify "Monitoring_Container" element visibility in "Projects_Monitoring_Container" on "Projects" wizard + Then "Total_Counter_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "6" value + When click on "See_All_Link" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/jobs-monitoring/jobs" + Then verify breadcrumbs "cross" label should be equal "Jobs monitoring" value + Then verify breadcrumbs "projectsPage" label should be equal "Projects" value + Then verify "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard should contains "Jobs_Monitoring"."Tab_List" + Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 6 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Search_By_Name_Filter_Input" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" + Then verify "Date_Picker_Filter_Dropdown" dropdown element on "Jobs_Monitoring_Jobs_Tab" wizard should contains "Dropdown_Options"."Date_Picker_Filter_Options" + Then verify "Table_FilterBy_Button" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then "Title" element on "FilterBy_Popup" should contains "Filter by" value + Then verify "Table_Project_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Status_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then verify "Status_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Jobs_Status_Filter_Options" + And wait load page + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then "Status_All_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then verify "Type_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then verify "Type_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Jobs_Type_Filter_Options" + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Clear_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Apply_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Clear_Button" element on "FilterBy_Popup" wizard is disabled + Then verify "Apply_Button" element on "FilterBy_Popup" wizard is disabled + Then navigate back + And wait load page + Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard + Then "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "3" value + When click on "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "3 items selected" + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + And wait load page + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then "Status_All_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Aborting_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then "Status_Jobs_Running_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then "Status_Pending_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 3 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard + Then "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "2" value + When click on "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Aborted, Error" + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + And wait load page + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then "Status_All_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Aborting_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Jobs_Running_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Pending_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Aborted_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then "Status_Jobs_Error_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 2 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard + Then "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "1" value + When click on "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Completed" + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + And wait load page + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then "Status_All_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Aborting_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Jobs_Running_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Pending_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Aborted_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Jobs_Error_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Jobs_Completed_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 1 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + + @MLJM + @smoke + Scenario: MLJM002 - Check components on Workflows tab of Jobs monitoring page + Given open url + And wait load page + Then verify "Monitoring_Container" element visibility in "Projects_Monitoring_Container" on "Projects" wizard + Then verify "Monitoring_Workflows_Box" element visibility in "Projects_Monitoring_Container" on "Projects" wizard + Then verify "Total_Counter_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard + Then "Total_Counter_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "2" value + When click on "See_All_Link" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/jobs-monitoring/workflows" + Then verify breadcrumbs "cross" label should be equal "Jobs monitoring" value + Then verify breadcrumbs "projectsPage" label should be equal "Projects" value + Then verify "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard should contains "Jobs_Monitoring"."Tab_List" + Then verify "Workflows" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify that 2 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Search_By_Name_Filter_Input" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" + Then verify "Date_Picker_Filter_Dropdown" dropdown element on "Jobs_Monitoring_Workflows_Tab" wizard should contains "Dropdown_Options"."Date_Picker_Filter_Options" + Then verify "Table_FilterBy_Button" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then "Title" element on "FilterBy_Popup" should contains "Filter by" value + Then verify "Table_Project_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Status_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then verify "Status_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Workflows_Status_Filter_Options" + And wait load page + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then "Status_All_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Clear_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Apply_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Clear_Button" element on "FilterBy_Popup" wizard is disabled + Then verify "Apply_Button" element on "FilterBy_Popup" wizard is disabled + Then navigate back + And wait load page + Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard + Then "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "0" value + When click on "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify "Workflows" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Running" + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then "Status_All_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Workflows_Running_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then "Status_Workflows_Error_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Failed_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Workflows_Completed_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then verify that 0 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard + Then "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value + When click on "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify "Workflows" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Error, Failed" + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then "Status_All_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Workflows_Error_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then "Status_Failed_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then "Status_Workflows_Running_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Workflows_Completed_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify that 1 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard + Then "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value + When click on "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify "Workflows" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Completed" + Then click on "Status_Filter_Element" element on "FilterBy_Popup" wizard + Then "Status_All_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Workflows_Error_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Failed_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Workflows_Running_Checkbox" element should be unchecked on "FilterBy_Popup" wizard + Then "Status_Workflows_Completed_Checkbox" element should be checked on "FilterBy_Popup" wizard + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify that 1 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard + + @MLJM + @smoke + Scenario: MLJM003 - Check components on Scheduled tab of Jobs monitoring page + Given open url + And wait load page + Then verify "Monitoring_Container" element visibility in "Projects_Monitoring_Container" on "Projects" wizard + Then verify "Monitoring_Scheduled_Box" element visibility in "Projects_Monitoring_Container" on "Projects" wizard + Then verify "Total_Job_Counter_Number" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard + Then "Total_Job_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "7" value + Then verify "Jobs_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard + When click on "Jobs_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/jobs-monitoring/scheduled" + Then verify breadcrumbs "cross" label should be equal "Jobs monitoring" value + Then verify breadcrumbs "projectsPage" label should be equal "Projects" value + Then verify "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard should contains "Jobs_Monitoring"."Tab_List" + Then verify "Scheduled" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify that 7 row elements are displayed in "Scheduled_Table" on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Search_By_Name_Filter_Input" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Scheduled_Tab" wizard selected option value "Next 24 hours" + Then verify "Date_Picker_Filter_Dropdown" dropdown element on "Jobs_Monitoring_Scheduled_Tab" wizard should contains "Dropdown_Options"."Scheduled_Date_Picker_Filter_Options" + Then verify "Table_FilterBy_Button" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Scheduled_Tab" wizard + Then "Title" element on "FilterBy_Popup" should contains "Filter by" value + Then verify "Table_Project_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Type_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Jobs" + Then verify "Type_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Scheduled_Type_Filter_Options" + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Clear_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Apply_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Clear_Button" element on "FilterBy_Popup" wizard is enabled + Then verify "Apply_Button" element on "FilterBy_Popup" wizard is disabled + Then navigate back + And wait load page + Then verify "Total_Workflows_Counter_Number" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard + Then "Total_Workflows_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "1" value + Then verify "Workflows_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard + When click on "Workflows_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard + Then verify redirection to "projects/jobs-monitoring/scheduled" + Then verify breadcrumbs "cross" label should be equal "Jobs monitoring" value + Then verify breadcrumbs "projectsPage" label should be equal "Projects" value + Then verify "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard should contains "Jobs_Monitoring"."Tab_List" + Then verify "Scheduled" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify that 1 row elements are displayed in "Scheduled_Table" on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Search_By_Name_Filter_Input" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Scheduled_Tab" wizard selected option value "Next 24 hours" + Then verify "Date_Picker_Filter_Dropdown" dropdown element on "Jobs_Monitoring_Scheduled_Tab" wizard should contains "Dropdown_Options"."Scheduled_Date_Picker_Filter_Options" + Then verify "Table_FilterBy_Button" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Scheduled_Tab" wizard + Then "Title" element on "FilterBy_Popup" should contains "Filter by" value + Then verify "Table_Project_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Type_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Workflows" + Then verify "Type_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Scheduled_Type_Filter_Options" + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Clear_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Apply_Button" element visibility on "FilterBy_Popup" wizard + Then verify "Clear_Button" element on "FilterBy_Popup" wizard is enabled + Then verify "Apply_Button" element on "FilterBy_Popup" wizard is disabled + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page diff --git a/tests/features/models.feature b/tests/features/models.feature index 7c03929a6..cb9508150 100644 --- a/tests/features/models.feature +++ b/tests/features/models.feature @@ -28,10 +28,10 @@ Feature: Models Page Then verify "Models_Tab_Selector" on "Models" wizard should contains "Models"."Tab_List" Then verify "Table_Name_Filter_Input" element visibility on "Models" wizard Then click on "Table_FilterBy_Button" element on "Models" wizard - Then verify "Table_Label_Filter_Input" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" dropdown element on "Artifacts_FilterBy_Popup" wizard should contains "Dropdown_Options"."Tag_Filer_Options" - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Tag_Filer_Options" + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard Then verify "Table_Refresh_Button" element visibility on "Models" wizard Then verify "Models_Table" element visibility on "Models" wizard Then verify "Register_Model_Button" element visibility on "Models" wizard @@ -124,25 +124,25 @@ Feature: Models Page And click on cell with value "Models" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Models" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard Then check "expand_btn" not presented in "Models_Table" on "Models" wizard - Then uncheck "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then uncheck "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Models" wizard - Then "Show_Iterations_Checkbox" element should be unchecked on "Artifacts_FilterBy_Popup" wizard + Then "Show_Iterations_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then check "expand_btn" visibility in "Models_Table" on "Models" wizard with 0 offset Then click on cell with row index 1 in "expand_btn" column in "Models_Table" table on "Models" wizard And wait load page Then click on cell with row index 1 in "name" column in "Models_Table" table on "Models" wizard Then verify "Header" element visibility on "Models_Info_Pane" wizard Then click on "Table_FilterBy_Button" element on "Models" wizard - Then check "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then check "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then verify "Header" element not exists on "Models_Info_Pane" wizard Then click on "Table_FilterBy_Button" element on "Models" wizard - Then "Show_Iterations_Checkbox" element should be checked on "Artifacts_FilterBy_Popup" wizard + Then "Show_Iterations_Checkbox" element should be checked on "FilterBy_Popup" wizard @MLM @passive @@ -180,18 +180,18 @@ Feature: Models Page And click on cell with value "Models" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Models" wizard - Then type value "my-key" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "my-key" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then value in "labels" column with "dropdowns" in "Models_Table" on "Models" wizard should contains "my-key" in "Overlay" Then click on "Table_FilterBy_Button" element on "Models" wizard - Then type value "my-key=my-value" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "my-key=my-value" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then value in "labels" column with "dropdowns" in "Models_Table" on "Models" wizard should contains "my-key=my-value" in "Overlay" Then click on "Table_FilterBy_Button" element on "Models" wizard - Then type value "MY-KEY" to "Table_Label_Filter_Input" field on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then type value "MY-KEY" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard Then "No_Data_Message" component on "commonPagesHeader" should contains "No_Data_Message"."No_Models_data" @@ -392,8 +392,8 @@ Feature: Models Page Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard Then click on "Cross_Cancel_Button" element on "View_YAML" wizard Then click on "Table_FilterBy_Button" element on "Models" wizard - Then uncheck "Show_Iterations_Checkbox" element on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then uncheck "Show_Iterations_Checkbox" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on cell with row index 1 in "expand_btn" column in "Models_Table" table on "Models" wizard Then select "View YAML" option in action menu on "Models" wizard in "Models_Table" table at row with "latest" value in "name_expand_btn" column @@ -498,8 +498,8 @@ Feature: Models Page And wait load page Then verify "Table_FilterBy_Button" element visibility on "Models" wizard Then click on "Table_FilterBy_Button" element on "Models" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then verify action menu on "Models" wizard in "Models_Table" table with "current-state_model" value in "name" column should contains "Common_Lists"."Action_Menu_List_Expanded" Then verify that in action menu on "Models" wizard in "Models_Table" table with "current-state_model" value in "name" column "Delete" option is disabled @@ -678,8 +678,8 @@ Feature: Models Page And click on cell with value "Models" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Models" wizard - Then select "v1" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "v1" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page When click on cell with value "test-model" in "name" column in "Models_Table" table on "Models" wizard Then verify "Info_Pane_Tab_Selector" element visibility on "Models_Info_Pane" wizard @@ -1047,8 +1047,8 @@ Feature: Models Page And wait load page Then verify "Table_FilterBy_Button" element visibility on "Models" wizard Then click on "Table_FilterBy_Button" element on "Models" wizard - Then select "newTag" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "newTag" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then check "newTag" value in "tag" column in "Models_Table" table on "Models" wizard @@ -1073,8 +1073,8 @@ Feature: Models Page Then click on "Apply_Changes_Button" element on "Models_Info_Pane" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Models" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page When click on cell with row index 1 in "name" column in "Models_Table" table on "Models" wizard And wait load page @@ -1091,8 +1091,8 @@ Feature: Models Page And click on cell with value "Models" in "link" column in "General_Info_Quick_Links" table on "commonPagesHeader" wizard And wait load page Then click on "Table_FilterBy_Button" element on "Models" wizard - Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "Artifacts_FilterBy_Popup" wizard - Then click on "Apply_Button" element on "Artifacts_FilterBy_Popup" wizard + Then select "All" option in "Table_Tree_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then click on cell with row index 3 in "name" column in "Models_Table" table on "Models" wizard Then save to context "name" column on 3 row from "Models_Table" table on "Models" wizard diff --git a/tests/features/projectMonitoring.feature b/tests/features/projectMonitoring.feature index be47fe36e..bb4f4e2da 100644 --- a/tests/features/projectMonitoring.feature +++ b/tests/features/projectMonitoring.feature @@ -536,9 +536,9 @@ Feature: MLRun Project Page And wait load page Then verify "Table_Name_Filter_Input" element visibility on "Models" wizard Then click on "Table_FilterBy_Button" element on "Models" wizard - Then verify "Table_Label_Filter_Input" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard Then verify "Table_Refresh_Button" element visibility on "Models" wizard Then verify "Models_Table" element visibility on "Models" wizard And turn on demo mode @@ -580,9 +580,9 @@ Feature: MLRun Project Page And wait load page Then verify "Table_Name_Filter_Input" element visibility on "Files" wizard Then click on "Table_FilterBy_Button" element on "Files" wizard - Then verify "Table_Label_Filter_Input" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard Then verify "Table_Refresh_Button" element visibility on "Files" wizard Then verify "Files_Table" element visibility on "Files" wizard Then verify "Register_File_Button" element visibility on "Files" wizard diff --git a/tests/features/projectsPage.feature b/tests/features/projectsPage.feature index 6e136580f..15c3ccb5a 100644 --- a/tests/features/projectsPage.feature +++ b/tests/features/projectsPage.feature @@ -1,4 +1,4 @@ -Feature: MLRun Projects Page +Feature: Projects Page Testcases that verifies functionality on MLRun Projects Page @@ -295,13 +295,13 @@ Feature: MLRun Projects Page Then "Monitoring_Container_Title" element in "Projects_Monitoring_Container" on "Projects" should contains "Monitoring" value Then verify "Monitoring_Container_Running_Status" element visibility in "Projects_Monitoring_Container" on "Projects" wizard Then verify "Monitoring_Container_Running_Icon" element visibility in "Projects_Monitoring_Container" on "Projects" wizard - Then "Monitoring_Container_Running_Status" element in "Projects_Monitoring_Container" on "Projects" should contains "Running" value + Then "Monitoring_Container_Running_Status" element in "Projects_Monitoring_Container" on "Projects" should contains "In Process" value Then verify "Monitoring_Container_Failed_Status" element visibility in "Projects_Monitoring_Container" on "Projects" wizard Then verify "Monitoring_Container_Failed_Icon" element visibility in "Projects_Monitoring_Container" on "Projects" wizard Then "Monitoring_Container_Failed_Status" element in "Projects_Monitoring_Container" on "Projects" should contains "Failed" value Then verify "Monitoring_Container_Completed_Status" element visibility in "Projects_Monitoring_Container" on "Projects" wizard Then verify "Monitoring_Container_Completed_Icon" element visibility in "Projects_Monitoring_Container" on "Projects" wizard - Then "Monitoring_Container_Completed_Status" element in "Projects_Monitoring_Container" on "Projects" should contains "Completed" value + Then "Monitoring_Container_Completed_Status" element in "Projects_Monitoring_Container" on "Projects" should contains "Succeeded" value Then verify "Monitoring_Jobs_Box" element visibility in "Projects_Monitoring_Container" on "Projects" wizard Then verify "Monitoring_Workflows_Box" element visibility in "Projects_Monitoring_Container" on "Projects" wizard Then verify "Monitoring_Scheduled_Box" element visibility in "Projects_Monitoring_Container" on "Projects" wizard @@ -343,16 +343,16 @@ Feature: MLRun Projects Page Then verify "Total_Counter_Title" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then "Total_Counter_Title" element in "Monitoring_Jobs_Box" on "Projects" should contains "Jobs" value Then verify "Total_Counter_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Total_Counter_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "18" value + Then "Total_Counter_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "6" value Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then verify "Counter_Running_Status_Icon" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "17" value + Then "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "3" value Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then verify "Counter_Failed_Status_Icon" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "1" value + Then "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "2" value Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then verify "Counter_Completed_Status_Icon" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "0" value + Then "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "1" value Then verify "See_All_Link" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then "See_All_Link" element in "Monitoring_Jobs_Box" on "Projects" should contains "See all" value When click on "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard @@ -395,16 +395,16 @@ Feature: MLRun Projects Page Then verify "Total_Counter_Title" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then "Total_Counter_Title" element in "Monitoring_Workflows_Box" on "Projects" should contains "Workflows" value Then verify "Total_Counter_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Total_Counter_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "0" value + Then "Total_Counter_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "2" value Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then verify "Counter_Running_Status_Icon" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "0" value Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then verify "Counter_Failed_Status_Icon" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "0" value + Then "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then verify "Counter_Completed_Status_Icon" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "0" value + Then "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value Then verify "See_All_Link" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then "See_All_Link" element in "Monitoring_Workflows_Box" on "Projects" should contains "See all" value When click on "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard @@ -450,7 +450,7 @@ Feature: MLRun Projects Page Then verify "Total_Job_Counter_Number" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard Then "Total_Job_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "7" value Then verify "Total_Workflows_Counter_Number" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard - Then "Total_Workflows_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "0" value + Then "Total_Workflows_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "1" value Then verify "Jobs_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard Then "Jobs_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" should contains "See all" value Then verify "Workflows_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard diff --git a/tests/features/quickActions.feature b/tests/features/quickActions.feature index c5c39a4b2..81ea2100c 100644 --- a/tests/features/quickActions.feature +++ b/tests/features/quickActions.feature @@ -1,6 +1,6 @@ -Feature: MLRun Project Home Page +Feature: Quick actions Page - Testcases that verifies functionality on MLRun Project Home Page + Testcases that verifies functionality on MLRun Quick actions Page @MLPH @smoke @@ -129,6 +129,7 @@ Feature: MLRun Project Home Page And wait load page Then verify breadcrumbs "tab" label should be equal "Models" value Then click on cell with value "new-model" in "name" column in "Models_Table" table on "Models" wizard + And wait load page Then "Header" element on "Models_Info_Pane" should contains "new-model" value Then check "new-model" value in "key" column in "Overview_Table" table on "Models_Info_Pane" wizard Then check "latest" value in "tag" column in "Overview_Table" table on "Models_Info_Pane" wizard @@ -156,10 +157,14 @@ Feature: MLRun Project Home Page Then verify "Labels_Table" element visibility on "New_Feature_Set" wizard Then verify "Accordion_Header" element visibility in "Data_Source_Accordion" on "New_Feature_Set" wizard Then verify "Collapse_Button" element visibility in "Data_Source_Accordion" on "New_Feature_Set" wizard - Then verify "URL_Combobox" element visibility in "Data_Source_Accordion" on "New_Feature_Set" wizard Then verify "Attributes_Input" element visibility in "Data_Source_Accordion" on "New_Feature_Set" wizard Then type value " " to "Attributes_Input" field on "Data_Source_Accordion" on "New_Feature_Set" wizard Then verify "Attributes_Input" element in "Data_Source_Accordion" on "New_Feature_Set" wizard should display warning "Input_Hint"."Input_Field_Invalid" + When click on "Edit_Button" element in "Data_Source_Accordion" on "New_Feature_Set" wizard + Then verify "URL_Combobox" element visibility in "Data_Source_Accordion" on "New_Feature_Set" wizard + Then select "V3IO" option in "URL_Combobox" combobox on "Data_Source_Accordion" accordion on "New_Feature_Set" wizard + When type value "target/path" to "URL_Combobox" field on "Data_Source_Accordion" on "New_Feature_Set" wizard + Then click on "Apply_Combobox_Button" element in "Data_Source_Accordion" on "New_Feature_Set" wizard When collapse "Data_Source_Accordion" on "New_Feature_Set" wizard Then verify "Data_Source_Accordion" is collapsed on "New_Feature_Set" wizard Then verify "Accordion_Header" element visibility in "Schema_Accordion" on "New_Feature_Set" wizard @@ -231,6 +236,7 @@ Feature: MLRun Project Home Page And wait load page Then verify breadcrumbs "tab" label should be equal "Datasets" value Then click on cell with value "dataset" in "name" column in "Datasets_Table" table on "Datasets" wizard + And wait load page Then "Header" element on "Datasets_Info_Pane" should contains "dataset" value Then check "dataset" value in "key" column in "Overview_Table" table on "Datasets_Info_Pane" wizard Then check "latest" value in "tag" column in "Overview_Table" table on "Datasets_Info_Pane" wizard @@ -239,8 +245,6 @@ Feature: MLRun Project Home Page @MLPH @passive @smoke - @FAILED_TODO - #TODO: bug - ML-6663 [ML Functions, Quick actions] 'Create New Function' pop-up doesn't appear Scenario: MLPH005 - Check all mandatory components on Create ML Function on Project Home Page * set tear-down property "project" created with "automation-test-1003" value * create "automation-test-1003" MLRun Project with code 201 @@ -427,6 +431,7 @@ Feature: MLRun Project Home Page And wait load page Then verify breadcrumbs "tab" label should be equal "Artifacts" value Then click on cell with value "artifact" in "name" column in "Files_Table" table on "Files" wizard + And wait load page Then "Header" element on "Files_Info_Pane" should contains "artifact" value Then check "artifact" value in "key" column in "Overview_Table" table on "Files_Info_Pane" wizard Then check "latest" value in "tag" column in "Overview_Table" table on "Files_Info_Pane" wizard @@ -506,9 +511,9 @@ Feature: MLRun Project Home Page Then verify breadcrumbs "tab" label should be equal "Artifacts" value Then verify "Table_Name_Filter_Input" element visibility on "Files" wizard Then click on "Table_FilterBy_Button" element on "Files" wizard - Then verify "Table_Label_Filter_Input" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard Then verify "Table_Refresh_Button" element visibility on "Files" wizard Then verify "Files_Table" element visibility on "Files" wizard Then verify "Register_File_Button" element visibility on "Files" wizard @@ -534,9 +539,9 @@ Feature: MLRun Project Home Page Then "Register_Dataset_Button" element on "Datasets" should contains "Register dataset" value Then verify "Table_Name_Filter_Input" element visibility on "Datasets" wizard Then click on "Table_FilterBy_Button" element on "Datasets" wizard - Then verify "Table_Label_Filter_Input" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" dropdown element on "Artifacts_FilterBy_Popup" wizard should contains "Dropdown_Options"."Tag_Filer_Options" + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" dropdown element on "FilterBy_Popup" wizard should contains "Dropdown_Options"."Tag_Filer_Options" Then verify "Table_Refresh_Button" element visibility on "Datasets" wizard @MLPH @@ -625,9 +630,9 @@ Feature: MLRun Project Home Page Then verify "Models_Tab_Selector" on "Models" wizard should contains "Models"."Tab_List" Then verify "Table_Name_Filter_Input" element visibility on "Models" wizard Then click on "Table_FilterBy_Button" element on "Models" wizard - Then verify "Table_Label_Filter_Input" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Table_Tree_Filter_Dropdown" element visibility on "Artifacts_FilterBy_Popup" wizard - Then verify "Show_Iterations_Checkbox" element visibility on "Artifacts_FilterBy_Popup" wizard + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then verify "Table_Tree_Filter_Dropdown" element visibility on "FilterBy_Popup" wizard + Then verify "Show_Iterations_Checkbox" element visibility on "FilterBy_Popup" wizard Then verify "Table_Refresh_Button" element visibility on "Models" wizard Then verify "Models_Table" element visibility on "Models" wizard Then verify "Train_Model_Button" element visibility on "Models" wizard diff --git a/tests/features/support/world.js b/tests/features/support/world.js index a92b09845..2a0595130 100644 --- a/tests/features/support/world.js +++ b/tests/features/support/world.js @@ -43,12 +43,14 @@ class CustomWorld extends World { if (browser === 'chrome') { browseConfigs = new chrome.Options() if (headless) { - browseConfigs.addArguments('headless') - browseConfigs.addArguments('no-sandbox') - browseConfigs.addArguments('disable-gpu') - } - browseConfigs.addArguments('start-maximized') - browseConfigs.excludeSwitches('disable-popup-blocking', 'enable-automation') + browseConfigs = new chrome.Options() + .addArguments('headless') + .addArguments('no-sandbox') + .addArguments('start-maximized') + .addArguments('disable-gpu') + } else browseConfigs = new chrome.Options() + .addArguments('start-maximized') + .excludeSwitches('disable-popup-blocking', 'enable-automation') } if (browser === 'firefox') { diff --git a/tests/mockServer/data/artifacts.json b/tests/mockServer/data/artifacts.json index 86e06b1d7..f519a054c 100644 --- a/tests/mockServer/data/artifacts.json +++ b/tests/mockServer/data/artifacts.json @@ -10276,7 +10276,7 @@ "kind": "dataset", "iter": 0, "tree": "eaae138e-439a-47fa-93c6-ba0fe1dc3b79", - "target_path": "/User/demos/customer-churn-prediction/data/pipeline/eaae138e-439a-47fa-93c6-ba0fe1dc3b79/coxhazard-summary.csv", + "target_path": "v3io:///User/demos/customer-churn-prediction/data/pipeline/eaae138e-439a-47fa-93c6-ba0fe1dc3b79/coxhazard-summary.csv", "format": "csv", "size": 3357, "db_key": "survival-curves_coxhazard-summary", diff --git a/tests/mockServer/data/frontendSpec.json b/tests/mockServer/data/frontendSpec.json index 044aae043..b10a72d24 100644 --- a/tests/mockServer/data/frontendSpec.json +++ b/tests/mockServer/data/frontendSpec.json @@ -32,13 +32,13 @@ "mpijob": "mlrun/mlrun", "application": "python:3.9-slim" }, - "function_deployment_target_image_template": "docker-registry.default-tenant.app.vmdev36.lab.iguazeng.com:80/mlrun/func-{project}-{name}:{tag}", + "function_deployment_target_image_template": "docker-registry.default-tenant.app.vmdev63.lab.iguazeng.com:80/mlrun/func-{project}-{name}:{tag}", "function_deployment_target_image_name_prefix_template": "func-{project}-{name}", "function_deployment_target_image_registries_to_enforce_prefix": [ ".mlrun/", - "docker-registry.default-tenant.app.vmdev36.lab.iguazeng.com:80/mlrun/" + "docker-registry.default-tenant.app.vmdev63.lab.iguazeng.com:80/mlrun/" ], - "function_deployment_mlrun_requirement": "mlrun[complete]==1.7.0-rc18", + "function_deployment_mlrun_requirement": "mlrun[complete]==1.7.0-rc27", "auto_mount_type": "auto", "auto_mount_params": {}, "default_artifact_path": "v3io:///projects/{{run.project}}/artifacts", @@ -102,15 +102,15 @@ "mlrun/username", "mlrun/username_domain", "mlrun/task-name", + "mlrun/resource_name", + "mlrun/created", "host", "job-type", "kind", "component", - "resource_name", - "mlrun-created", "owner", "v3io_user", "workflow", "feature-vector" ] -} +} \ No newline at end of file diff --git a/tests/mockServer/dateSynchronization.js b/tests/mockServer/dateSynchronization.js index e6df06a2b..68ee7f597 100644 --- a/tests/mockServer/dateSynchronization.js +++ b/tests/mockServer/dateSynchronization.js @@ -21,20 +21,25 @@ such restriction. function formatDate(date) { return date.toISOString() } +// Current time +const now = new Date() -export function updateRuns(runs) { - // Current time - const now = new Date() +// Date picker 'Past hour' (get past 45 min) +const oneHourAgo = new Date(now.getTime() - (45 * 60 * 1000)) - // Date picker 'Past hour' (get past 45 min) - const oneHourAgo = new Date(now.getTime() - (45 * 60 * 1000)) +// Date picker 'Past 24h' (get past 20h) +const oneDayAgo = new Date(now.getTime() - (20 * 60 * 60 * 1000)) - // Date picker 'Past 24h' (get past 20h) - const oneDayAgo = new Date(now.getTime() - (20 * 60 * 60 * 1000)) +// Date picker 'Next 24h' (get past 20h) +const next24Hours = new Date(now.getTime() + (24 * 60 * 60 * 1000)) +// Date picker 'Next hour' (get past 20h) +const nextHour = new Date(now.getTime() + (60 * 60 * 1000)) + +function updateRuns(runs) { // update start_time та last_update runs.runs = runs.runs.map(run => { - if (['cf842616c89347c7bb7bca2c9e840a21', '76f48c8165da473bb4356ef7b196343f','f5751299ee21476e897dfd90d94c49c4', 'dad0fdf93bd949589f6b20f79fa47798', '59f8e3d6f3db4dd8ab890c4bf84a0a23' ] + if (['cf842616c89347c7bb7bca2c9e840a21', '76f48c8165da473bb4356ef7b196343f','f5751299ee21476e897dfd90d94c49c4', 'dad0fdf93bd949589f6b20f79fa47798', '9723e5a30b0e43b0b7cfda098445c446'] .includes(run.metadata.uid)) { return { ...run, status: { ...run.status, start_time: formatDate(oneDayAgo), last_update: formatDate(oneDayAgo) } @@ -48,3 +53,54 @@ export function updateRuns(runs) { } }) } + +function updatePipelines(pipelines) { + // update finished_at + for (const project in pipelines) { + pipelines[project].runs = pipelines[project].runs.map(run => { + if (['5a5db6e3-7cdd-4d33-971c-d2310b02387c', '1f8b29a5-cdab-4b84-aad7-7f9bc20daf0b'].includes(run.id)) { + + return { ...run, finished_at: formatDate(oneDayAgo) } + } else if (['1d3a8b0833b74f55b008537b1e19ea57'].includes(run.id)) { + + return { ...run, finished_at: formatDate(oneHourAgo) } + } else { + return run + } + }) + } +} + +function updatePipelineIDs(pipelineIDs) { + // update created_at та finished_at + pipelineIDs.forEach(pipeline => { + if (['5a5db6e3-7cdd-4d33-971c-d2310b02387c', '1f8b29a5-cdab-4b84-aad7-7f9bc20daf0b'].includes(pipeline.run.id)) { + + pipeline.run = { ...pipeline.run, created_at: formatDate(oneDayAgo), finished_at: formatDate(oneDayAgo) } + } else if (['1d3a8b0833b74f55b008537b1e19ea57'].includes(pipeline.run.id)) { + + pipeline.run = { ...pipeline.run, created_at: formatDate(oneHourAgo), finished_at: formatDate(oneHourAgo) } + } + }) + +} + +function updateSchedules(schedules) { + // update next_run_time + schedules.schedules = schedules.schedules.map(schedule => { + if (['clean-data', 'clean-data-test', 'aggregate-test', 'erann-test', 'prep-data', 'sklearn-classifier'].includes(schedule.name)) { + + return { ...schedule, next_run_time: formatDate(next24Hours) } + } else if (['main3'].includes(schedule.name)) { + + return { ...schedule, next_run_time: formatDate(next24Hours) } + } else if (['tf2-serving'].includes(schedule.name)) { + + return { ...schedule, next_run_time: formatDate(nextHour) } + } else { + return schedule + } + }) +} + +export { updateRuns, updatePipelines, updatePipelineIDs, updateSchedules } \ No newline at end of file diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index acba47377..09c9a053a 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -71,10 +71,13 @@ import iguazioProjectsRelations from './data/iguazioProjectsRelations.json' import nuclioFunctions from './data/nuclioFunctions.json' import nuclioAPIGateways from './data/nuclioAPIGateways.json' import nuclioStreams from './data/nuclioStreams.json' -import { updateRuns } from './dateSynchronization.js' +import { updateRuns, updatePipelines, updatePipelineIDs, updateSchedules } from './dateSynchronization.js' //updating values in files with synthetic data updateRuns(runs) +updatePipelines(pipelines) +updatePipelineIDs(pipelineIDs) +updateSchedules(schedules) // Here we are configuring express to use body-parser as middle-ware. const app = express() @@ -1242,28 +1245,38 @@ function deleteProjectsFeatureVectors (req, res) { function getPipelines (req, res) { //get pipelines for Projects Monitoring page if (req.params['project'] === '*') { - let collectedMonitoringPipelines = pipelineIDs.map(pipeline => { - return pipeline.run - }) - if (JSON.parse(req.query.filter).predicates.length >= 1) { - if (JSON.parse(req.query.filter).predicates[0]) { - collectedMonitoringPipelines = collectedMonitoringPipelines.filter( - pipeline => - Date.parse(pipeline.created_at) >= - Date.parse(JSON.parse(req.query.filter).predicates[0].timestamp_value) //start time from - ) - } - if (JSON.parse(req.query.filter).predicates[1]) { - collectedMonitoringPipelines = collectedMonitoringPipelines.filter( - pipeline => - Date.parse(pipeline.created_at) <= - Date.parse(JSON.parse(req.query.filter).predicates[1].timestamp_value) //start time to - ) - } + const pipelinesRun = pipelineIDs.map(pipeline => pipeline.run) + const filter = JSON.parse(req.query.filter) + const predicates = filter.predicates + + if (!predicates.length) { + res.send({ + runs: pipelinesRun, + total_size: pipelinesRun.length, + next_page_token: null + }) } - res.send({ - runs: collectedMonitoringPipelines, + let queryTimestampValue, queryStateValue + + if (predicates.length === 1) { + queryTimestampValue = predicates[0].timestamp_value + queryStateValue = predicates[0].string_values ? predicates[0].string_values.values : null + } else { + queryTimestampValue = predicates[1].timestamp_value + queryStateValue = predicates[0].string_values.values + } + + const collectedMonitoringPipelines = pipelinesRun.filter(pipeline => { + const pipelineCreatedAt = new Date(pipeline.created_at) + const timestampMatch = !queryTimestampValue || pipelineCreatedAt >= new Date(queryTimestampValue) + const stateMatch = queryStateValue ? Array.isArray(queryStateValue) ? queryStateValue.includes(pipeline.status) : pipeline.status === queryStateValue : true + + return timestampMatch && stateMatch + }) + + res.send({ + runs: collectedMonitoringPipelines, total_size: collectedMonitoringPipelines.length, next_page_token: null }) @@ -1299,13 +1312,12 @@ function getPipeline (req, res) { function getFuncs (req, res) { const dt = parseInt(Date.now()) - const collectedFuncsByPrjTime = funcs.funcs .filter(func => func.metadata.project === req.query.project) .filter(func => Date.parse(func.metadata.updated) > dt) - let collectedFuncs = [] const newArray = cloneDeep(funcs.funcs) + if (collectedFuncsByPrjTime.length) { collectedFuncs = newArray.filter(func => func.metadata.project === req.query.project) @@ -1317,14 +1329,11 @@ function getFuncs (req, res) { } else if (req.query['hash_key']) { collectedFuncs = funcs.funcs.filter(func => func.metadata.hash === req.query.hash_key) } else { - collectedFuncs = funcs.funcs + funcs.funcs .filter(func => func.metadata.project === req.params['project']) .filter(func => func.metadata.tag === 'latest') .filter(func => func.status?.state === 'deploying') - - collectedFuncs.forEach(func => { - func.status.state = 'ready' - }) + .forEach(func => (func.status.state = 'ready')) collectedFuncs = funcs.funcs.filter(func => func.metadata.project === req.params['project']) } @@ -1339,6 +1348,28 @@ function getFuncs (req, res) { }) } + if (req.query['since']) { + collectedFuncs = collectedFuncs.filter(func => { + return new Date(func.metadata.updated) > new Date(req.query['since']) + }) + } + + if (req.query['until']) { + collectedFuncs = collectedFuncs.filter(func => { + return new Date(func.metadata.updated) < new Date(req.query['until']) + }) + } + + if (req.query['tag']) { + collectedFuncs = collectedFuncs.filter(func => { + if (req.query['tag'] === '*') { + return Boolean(func.metadata.tag) + } else { + return func.metadata.tag === req.query['tag'] + } + }) + } + if (req.query['format'] === 'minimal') { collectedFuncs = collectedFuncs.map(func => { const specFields = [ @@ -1353,10 +1384,7 @@ function getFuncs (req, res) { 'priority_class_name' ].map(fieldName => `spec.${fieldName}`) - return pick( - func, - ['kind', 'metadata', 'status', ...specFields] - ) + return pick(func, ['kind', 'metadata', 'status', ...specFields]) }) } @@ -1442,7 +1470,7 @@ function deleteFunc (req, res) { } } -function getNuclioLogs (req, res) { +function getNuclioLogs(req, res) { sendLogsData( { project: req.params.project, @@ -1454,7 +1482,7 @@ function getNuclioLogs (req, res) { ) } -function getBuildStatus (req, res) { +function getBuildStatus(req, res) { sendLogsData( { project: req.query.name, @@ -1466,7 +1494,7 @@ function getBuildStatus (req, res) { ) } -function sendLogsData (data, res) { +function sendLogsData(data, res) { const dt = parseInt(Date.now()) const collectedFunc = funcs.funcs @@ -1785,15 +1813,22 @@ function deleteTags (req, res) { res.send() } -function getArtifact (req, res) { +function getArtifact(req, res) { let resData let requestedArtifact = artifacts.artifacts.find( artifact => - (artifact.metadata?.project === req.params.project || artifact.project === req.params.project) && + (artifact.metadata?.project === req.params.project || + artifact.project === req.params.project) && (artifact.spec?.db_key === req.params.key || artifact?.db_key === req.params.key) && - (isNil(req.query.iter) || +req.query.iter === artifact?.iter || +req.query.iter === artifact.metadata?.iter) && - (isNil(req.query.tag) || artifact.metadata?.tag === req.query.tag || artifact?.tag === req.query.tag) && - (isNil(req.query.tree) || artifact.metadata?.tree === req.query.tree || artifact?.tree === req.query.tree) + (isNil(req.query.iter) || + +req.query.iter === artifact?.iter || + +req.query.iter === artifact.metadata?.iter) && + (isNil(req.query.tag) || + artifact.metadata?.tag === req.query.tag || + artifact?.tag === req.query.tag) && + (isNil(req.query.tree) || + artifact.metadata?.tree === req.query.tree || + artifact?.tree === req.query.tree) ) if (requestedArtifact) { @@ -1810,7 +1845,7 @@ function getArtifact (req, res) { res.send(resData) } -function postArtifact (req, res) { +function postArtifact(req, res) { const currentDate = new Date() const artifactTag = req.body.metadata.tag || 'latest' const tagObject = artifactTags.find( From 5c85e305f24ca8c95758f544564154f2700a6865 Mon Sep 17 00:00:00 2001 From: Pini Shahmurov Date: Wed, 14 Aug 2024 12:51:22 +0300 Subject: [PATCH 53/54] Fix [UI] duplication in DetailsMetrics component. (#2663) --- .../DetailsMetrics/DetailsMetrics.js | 11 -- .../DetailsMetrics/DetailsMetrics.scss | 1 - .../FunctionsPanel/FunctionsPanel.js | 2 +- tests/features/support/world.js | 5 +- tests/mockServer/mock.js | 164 +++++++++--------- 5 files changed, 86 insertions(+), 97 deletions(-) diff --git a/src/components/DetailsMetrics/DetailsMetrics.js b/src/components/DetailsMetrics/DetailsMetrics.js index 0a470f680..f718c0913 100644 --- a/src/components/DetailsMetrics/DetailsMetrics.js +++ b/src/components/DetailsMetrics/DetailsMetrics.js @@ -81,17 +81,6 @@ const DetailsMetrics = ({ selectedItem }) => { ) }, [generatedMetrics.length]) - const chooseMetricsDataCard = useMemo(() => { - return ( - generatedMetrics.length === 1 && ( - - -
    Choose metrics to view endpoint’s data
    -
    - ) - ) - }, [generatedMetrics.length]) - const expandInvocationCard = useCallback( (isUnpinAction = false) => { const invocationBodyCard = invocationBodyCardRef.current diff --git a/src/components/DetailsMetrics/DetailsMetrics.scss b/src/components/DetailsMetrics/DetailsMetrics.scss index 2a79f03c5..6fe8efedb 100644 --- a/src/components/DetailsMetrics/DetailsMetrics.scss +++ b/src/components/DetailsMetrics/DetailsMetrics.scss @@ -287,7 +287,6 @@ $stickyHeaderHeight: 55px; margin-right: 15px; visibility: visible; opacity: 1; - margin-right: 15px; transition: flex 0.2s, visibility 0s, diff --git a/src/components/FunctionsPanel/FunctionsPanel.js b/src/components/FunctionsPanel/FunctionsPanel.js index e0e6e1d44..031832178 100644 --- a/src/components/FunctionsPanel/FunctionsPanel.js +++ b/src/components/FunctionsPanel/FunctionsPanel.js @@ -81,7 +81,7 @@ const FunctionsPanel = ({ (defaultData?.build?.image || defaultData?.build?.base_image || defaultData?.build?.commands?.length > 0) && - defaultData.image?.length === 0 + defaultData.image?.length === 0 ? NEW_IMAGE : '' ) diff --git a/tests/features/support/world.js b/tests/features/support/world.js index 2a0595130..9cf37cdcd 100644 --- a/tests/features/support/world.js +++ b/tests/features/support/world.js @@ -41,16 +41,17 @@ class CustomWorld extends World { let browseConfigs if (browser === 'chrome') { - browseConfigs = new chrome.Options() if (headless) { browseConfigs = new chrome.Options() .addArguments('headless') .addArguments('no-sandbox') .addArguments('start-maximized') .addArguments('disable-gpu') - } else browseConfigs = new chrome.Options() + } else { + browseConfigs = new chrome.Options() .addArguments('start-maximized') .excludeSwitches('disable-popup-blocking', 'enable-automation') + } } if (browser === 'firefox') { diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index 09c9a053a..1041c20ea 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -173,7 +173,7 @@ const iguazioApiUrl = '/platform-api.default-tenant.app.vmdev36.lab.iguazeng.com const port = 30000 // Support function -function createTask (projectName, config) { +function createTask(projectName, config) { const newTask = cloneDeep(backgroundTaskTemplate) const now = new Date().toISOString() @@ -248,11 +248,11 @@ function createTask (projectName, config) { return newTask } -function generateHash (txt) { +function generateHash(txt) { return crypto.createHash('sha1').update(JSON.stringify(txt)).digest('hex') } -function getGraphById (targetId) { +function getGraphById(targetId) { let foundGraph = null find(pipelineIDs, item => { @@ -262,7 +262,7 @@ function getGraphById (targetId) { return foundGraph } -function makeUID (length) { +function makeUID(length) { let result = '' const characters = 'abcdef0123456789' const charactersLength = characters.length @@ -274,7 +274,7 @@ function makeUID (length) { return result } -function deleteProjectHandler (req, res, omitResponse) { +function deleteProjectHandler(req, res, omitResponse) { //todo: Improve this handler according to the real roles of deleting. Add 412 response (if project has resources) const collectedProject = projects.projects.filter( @@ -302,31 +302,31 @@ function deleteProjectHandler (req, res, omitResponse) { } // Request Handlers -function getFrontendSpec (req, res) { +function getFrontendSpec(req, res) { res.send(frontendSpec) } -function getProjectTask (req, res) { +function getProjectTask(req, res) { res.send(get(projectBackgroundTasks, [req.params.project, req.params.taskId], {})) } -function getProjectTasks (req, res) { +function getProjectTasks(req, res) { res.send({ background_tasks: Object.values(get(projectBackgroundTasks, req.params.project, [])) }) } -function getTask (req, res) { +function getTask(req, res) { res.send(get(backgroundTasks, req.params.taskId, {})) } -function getTasks (req, res) { +function getTasks(req, res) { res.send({ background_tasks: Object.values(backgroundTasks) ?? [] }) } -function getFeatureSet (req, res) { +function getFeatureSet(req, res) { let collectedFeatureSets = featureSets.feature_sets.filter( featureSet => featureSet.metadata.project === req.params['project'] ) @@ -362,7 +362,7 @@ function getFeatureSet (req, res) { res.send({ feature_sets: collectedFeatureSets }) } -function createProjectsFeatureSet (req, res) { +function createProjectsFeatureSet(req, res) { const currentDate = new Date() let featureSet = req.body featureSet.metadata['project'] = req.params['project'] @@ -374,7 +374,7 @@ function createProjectsFeatureSet (req, res) { res.send(featureSet) } -function deleteFeatureSet (req, res) { +function deleteFeatureSet(req, res) { const collecledFeatureSet = featureSets.feature_sets .filter(featureSet => featureSet.metadata.project === req.params.project) .filter(featureSet => featureSet.metadata.name === req.params.featureSet) @@ -388,11 +388,11 @@ function deleteFeatureSet (req, res) { res.send() } -function getProject (req, res) { +function getProject(req, res) { res.send(projects.projects.find(project => project.metadata.name === req.params['project'])) } -function getProjects (req, res) { +function getProjects(req, res) { let data = projects switch (req.query['format']) { @@ -409,7 +409,7 @@ function getProjects (req, res) { res.send(data) } -function createNewProject (req, res) { +function createNewProject(req, res) { const currentDate = new Date() let data = {} const collectedProjects = projects.projects.filter( @@ -438,11 +438,11 @@ function createNewProject (req, res) { res.send(data) } -function deleteProject (req, res) { +function deleteProject(req, res) { deleteProjectHandler(req, res) } -function deleteProjectV2 (req, res) { +function deleteProjectV2(req, res) { const taskFunc = () => { return new Promise(resolve => { setTimeout( @@ -465,7 +465,7 @@ function deleteProjectV2 (req, res) { res.send(task) } -function patchProject (req, res) { +function patchProject(req, res) { const project = projects.projects.find(project => project.metadata.name === req.params['project']) switch (req.body.spec['desired_state']) { @@ -492,7 +492,7 @@ function patchProject (req, res) { res.send(project) } -function putProject (req, res) { +function putProject(req, res) { for (const i in projects.projects) { if (projects.projects[i].metadata.name === req.body.metadata.name) { projects.projects[i] = req.body @@ -502,27 +502,27 @@ function putProject (req, res) { res.send(projects.projects.find(project => project.metadata.name === req.params['project'])) } -function getSecretKeys (req, res) { +function getSecretKeys(req, res) { res.send(secretKeys[req.params['project']]) } -function postSecretKeys (req, res) { +function postSecretKeys(req, res) { secretKeys[req.params['project']].secret_keys.push(Object.keys(req.body.secrets)[0]) res.statusCode = 201 res.send('') } -function deleteSecretKeys (req, res) { +function deleteSecretKeys(req, res) { secretKeys[req.params['project']].secret_keys = secretKeys[ req.params['project'] - ].secret_keys.filter(item => item !== req.query.secret) + ].secret_keys.filter(item => item !== req.query.secret) res.statusCode = 204 res.send('') } -function getProjectsSummaries (req, res) { +function getProjectsSummaries(req, res) { const currentDate = new Date() const last24Hours = new Date(currentDate.getTime() - 24 * 60 * 60 * 1000) const next24Hours = new Date(currentDate.getTime() + 24 * 60 * 60 * 1000) @@ -600,14 +600,14 @@ function getProjectsSummaries (req, res) { res.send(projectsSummary) } -function getFunctionItem (req, res) { +function getFunctionItem(req, res) { const funcName = req.params.uid === 'batch_inference_v2' ? 'batch-inference-v2' : req.params.uid const hubItem = itemsCatalog.catalog.find(item => item.metadata.name === funcName) res.send(hubItem) } -function getFunctionObject (req, res) { +function getFunctionObject(req, res) { const urlParams = req.query.url const urlArray = urlParams.split('/') const funcYAMLPath = `./tests/mockServer/data/mlrun/functions/${urlArray[6]}/${urlArray[6]}.yaml` @@ -616,7 +616,7 @@ function getFunctionObject (req, res) { res.send(funcObject) } -function getProjectSummary (req, res) { +function getProjectSummary(req, res) { const collectedProject = projectsSummary.project_summaries.find( item => item.name === req.params['project'] ) @@ -624,7 +624,7 @@ function getProjectSummary (req, res) { res.send(collectedProject) } -function getRuns (req, res) { +function getRuns(req, res) { //get runs for Projects Monitoring page if (req.params['project'] === '*') { const { start_time_from, state } = req.query @@ -691,7 +691,7 @@ function getRuns (req, res) { res.send({ runs: collectedRuns }) } -function getRun (req, res) { +function getRun(req, res) { const run_prj_uid = runs.runs.find( item => item.metadata.project === req.params['project'] && item.metadata.uid === req.params['uid'] @@ -700,7 +700,7 @@ function getRun (req, res) { res.send({ data: run_prj_uid }) } -function patchRun (req, res) { +function patchRun(req, res) { const collectedRun = runs.runs .filter(run => run.metadata.project === req.params.project) .filter(run => run.metadata.uid === req.params.uid) @@ -710,7 +710,7 @@ function patchRun (req, res) { res.send() } -function abortRun (req, res) { +function abortRun(req, res) { const currentRun = runs.runs.find(run => run.metadata.uid === req.params.uid) currentRun.status.state = 'aborting' @@ -745,7 +745,7 @@ function abortRun (req, res) { res.send(task) } -function deleteRun (req, res) { +function deleteRun(req, res) { const collectedRun = runs.runs.find( run => run.metadata.project === req.params.project && run.metadata.uid === req.params.uid ) @@ -760,7 +760,7 @@ function deleteRun (req, res) { res.send() } -function deleteRuns (req, res) { +function deleteRuns(req, res) { const collectedRuns = runs.runs.filter( run => run.metadata.project === req.params.project && run.metadata.name === req.query.name ) @@ -772,18 +772,18 @@ function deleteRuns (req, res) { res.send() } -function getFunctionCatalog (req, res) { +function getFunctionCatalog(req, res) { res.send(itemsCatalog) } -function getFunctionTemplate (req, res) { +function getFunctionTemplate(req, res) { const funcYAMLPath = `./tests/mockServer/data/mlrun/functions/${req.params.function}/${req.params.function}.yaml` const funcObject = fs.readFileSync(funcYAMLPath, 'utf8') res.send(funcObject) } -function getProjectsSchedules (req, res) { +function getProjectsSchedules(req, res) { //get schedules for Projects Monitoring page if (req.params['project'] === '*') { let collectedSchedules = schedules.schedules @@ -813,13 +813,13 @@ function getProjectsSchedules (req, res) { res.send({ schedules: collectedSchedules }) } -function getProjectsSchedule (req, res) { +function getProjectsSchedule(req, res) { const collectedSchedule = schedules.schedules.find(item => item.name === req.params['schedule']) res.send(collectedSchedule) } -function invokeSchedule (req, res) { +function invokeSchedule(req, res) { const currentDate = new Date() const runUID = makeUID(32) const { project: runProject, name: runName, labels } = req.body.task.metadata @@ -935,7 +935,7 @@ function invokeSchedule (req, res) { res.send(respTemplate) } -function getProjectsFeaturesEntities (req, res) { +function getProjectsFeaturesEntities(req, res) { const artifact = req.path.substring(req.path.lastIndexOf('/') + 1) let collectedArtifacts = [] let collectedFeatureSetDigests = [] @@ -1049,7 +1049,7 @@ function getProjectsFeaturesEntities (req, res) { res.send(result) } -function getProjectsFeatureArtifactTags (req, res) { +function getProjectsFeatureArtifactTags(req, res) { let featureArtifactTags = [] if (req.params.featureArtifact === 'feature-vectors') { @@ -1070,13 +1070,13 @@ function getProjectsFeatureArtifactTags (req, res) { res.send({ tags: featureArtifactTags }) } -function getProjectsArtifactTags (req, res) { +function getProjectsArtifactTags(req, res) { let artifactTag = artifactTags.find(aTag => aTag.project === req.params['project']) res.send(artifactTag) } -function getArtifacts (req, res) { +function getArtifacts(req, res) { const categories = { dataset: ['dataset'], model: ['model'], @@ -1145,7 +1145,7 @@ function getArtifacts (req, res) { res.send({ artifacts: collectedArtifacts }) } -function getProjectsFeatureSets (req, res) { +function getProjectsFeatureSets(req, res) { const featureArtifactTags = featureSets.feature_sets .filter(artifact => artifact.metadata.project === req.params.project) .filter(artifact => artifact.metadata.name === req.params.name) @@ -1154,7 +1154,7 @@ function getProjectsFeatureSets (req, res) { res.send(featureArtifactTags[0]) } -function patchProjectsFeatureSets (req, res) { +function patchProjectsFeatureSets(req, res) { const featureArtifactTags = featureSets.feature_sets .filter(artifact => artifact.metadata.project === req.params.project) .filter(artifact => artifact.metadata.name === req.params.name) @@ -1166,7 +1166,7 @@ function patchProjectsFeatureSets (req, res) { res.send(featureArtifactTags[0]) } -function postProjectsFeatureVectors (req, res) { +function postProjectsFeatureVectors(req, res) { const collectedFV = featureVectors.feature_vectors.filter( item => item.metadata.name === req.body.metadata.name ) @@ -1192,7 +1192,7 @@ function postProjectsFeatureVectors (req, res) { } } -function putProjectsFeatureVectors (req, res) { +function putProjectsFeatureVectors(req, res) { const collectedFV = featureVectors.feature_vectors .filter(item => item.metadata.project === req.body.metadata.project) .filter(item => item.metadata.name === req.body.metadata.name) @@ -1203,7 +1203,7 @@ function putProjectsFeatureVectors (req, res) { res.send(req.body) } -function patchProjectsFeatureVectors (req, res) { +function patchProjectsFeatureVectors(req, res) { const currentDate = new Date() const collectedFV = featureVectors.feature_vectors @@ -1225,7 +1225,7 @@ function patchProjectsFeatureVectors (req, res) { res.send('') } -function deleteProjectsFeatureVectors (req, res) { +function deleteProjectsFeatureVectors(req, res) { const collectedFV = featureVectors.feature_vectors .filter(item => item.metadata.project === req.params.project) .filter(item => item.metadata.name === req.params.name) @@ -1242,7 +1242,7 @@ function deleteProjectsFeatureVectors (req, res) { res.send('') } -function getPipelines (req, res) { +function getPipelines(req, res) { //get pipelines for Projects Monitoring page if (req.params['project'] === '*') { const pipelinesRun = pipelineIDs.map(pipeline => pipeline.run) @@ -1304,13 +1304,13 @@ function getPipelines (req, res) { res.send(collectedPipelines) } -function getPipeline (req, res) { +function getPipeline(req, res) { const collectedPipeline = pipelineIDs.find(item => item.run.id === req.params.pipelineID) res.send(collectedPipeline) } -function getFuncs (req, res) { +function getFuncs(req, res) { const dt = parseInt(Date.now()) const collectedFuncsByPrjTime = funcs.funcs .filter(func => func.metadata.project === req.query.project) @@ -1391,7 +1391,7 @@ function getFuncs (req, res) { res.send({ funcs: collectedFuncs }) } -function getFunc (req, res) { +function getFunc(req, res) { const collectedFunc = funcs.funcs .filter(func => func.metadata.project === req.params['project']) .filter(func => func.metadata.name === req.params['func']) @@ -1412,7 +1412,7 @@ function getFunc (req, res) { res.send(respBody) } -function postFunc (req, res) { +function postFunc(req, res) { const hashPwd = generateHash(req.body) const dt0 = parseInt(Date.now()) @@ -1427,7 +1427,7 @@ function postFunc (req, res) { res.send({ hash_key: hashPwd }) } -function deleteFunc (req, res) { +function deleteFunc(req, res) { const collectedFunc = funcs.funcs .filter(func => func.metadata.project === req.params.project) .filter(func => func.metadata.name === req.params.func) @@ -1520,7 +1520,7 @@ function sendLogsData(data, res) { res.send(logText) } -function deployMLFunction (req, res) { +function deployMLFunction(req, res) { const respBody = { data: cloneDeep(req.body.function) } respBody.data.metadata.categories = [] delete respBody.data.spec.secret_sources @@ -1573,14 +1573,14 @@ function deployMLFunction (req, res) { setTimeout(() => res.send(respBody), 1050) } -function getFile (req, res) { +function getFile(req, res) { const dataRoot = mockHome + '/data/' const filePath = dataRoot + req.query['path'].split('://')[1] res.sendFile(filePath) } -function deleteSchedule (req, res) { +function deleteSchedule(req, res) { const collectedSchedule = schedules.schedules .filter(schedule => schedule.project === req.params.project) .filter(schedule => schedule.name === req.params.schedule) @@ -1595,16 +1595,16 @@ function deleteSchedule (req, res) { res.send() } -function getLog (req, res) { +function getLog(req, res) { const collectedLog = logs.find(log => log.uid === req.params['uid']) res.send(collectedLog.log) } -function getRuntimeResources (req, res) { +function getRuntimeResources(req, res) { res.send({}) } -function postSubmitJob (req, res) { +function postSubmitJob(req, res) { const currentDate = new Date() let respTemplate = { @@ -1744,7 +1744,7 @@ function postSubmitJob (req, res) { res.send(respTemplate) } -function putTags (req, res) { +function putTags(req, res) { const tagName = req.params.tag const projectName = req.params.project const tagObject = artifactTags.find( @@ -1785,7 +1785,7 @@ function putTags (req, res) { }) } -function deleteTags (req, res) { +function deleteTags(req, res) { const collectedArtifacts = artifacts.artifacts.filter(artifact => { const artifactMetaData = artifact.metadata ?? artifact const artifactSpecData = artifact.spec ?? artifact @@ -1906,7 +1906,7 @@ function postArtifact(req, res) { res.send() } -function putArtifact (req, res) { +function putArtifact(req, res) { const collectedArtifacts = artifacts.artifacts.filter(artifact => { const artifactMetaData = artifact.metadata ?? artifact const artifactSpecData = artifact.spec ?? artifact @@ -1930,7 +1930,7 @@ function putArtifact (req, res) { res.send(collectedArtifacts) } -function deleteArtifact (req, res) { +function deleteArtifact(req, res) { const collectedArtifacts = artifacts.artifacts.filter(artifact => { const artifactMetaData = artifact.metadata ?? artifact const artifactSpecData = artifact.spec ?? artifact @@ -1952,7 +1952,7 @@ function deleteArtifact (req, res) { res.send({}) } -function getModelEndpoints (req, res) { +function getModelEndpoints(req, res) { let collectedEndpoints = modelEndpoints.endpoints .filter(endpoint => endpoint.metadata.project === req.params.project) .map(endpoint => ({ @@ -1976,7 +1976,7 @@ function getModelEndpoints (req, res) { res.send({ endpoints: collectedEndpoints }) } -function getModelEndpoint (req, res) { +function getModelEndpoint(req, res) { const endpoint = modelEndpoints.endpoints.find( item => item.metadata.project === req.params.project && item.metadata.uid === req.params.uid ) @@ -1984,7 +1984,7 @@ function getModelEndpoint (req, res) { res.send(endpoint) } -function getMetrics (req, res) { +function getMetrics(req, res) { let metricsOptions = metricsData.metrics.find( item => item.project === req.params.project && item.modelEndpointUID === req.params.uid @@ -1997,7 +1997,7 @@ function getMetrics (req, res) { res.send(metricsOptions) } -function getMetricsValues (req, res) { +function getMetricsValues(req, res) { const start = req.query.start || new Date() - 86400000 // past 24 hours const end = req.query.end || new Date() const names = req.query.name @@ -2052,16 +2052,16 @@ function getMetricsValues (req, res) { res.send(metricsValues) } -function getNuclioFunctions (req, res) { +function getNuclioFunctions(req, res) { res.send(nuclioFunctions) } -function getNuclioAPIGateways (req, res) { +function getNuclioAPIGateways(req, res) { res.send(nuclioAPIGateways) } // Iguazio -function getIguazioProjects (req, res) { +function getIguazioProjects(req, res) { let resultTemplate = cloneDeep(iguazioProjects) let filteredProject = {} @@ -2104,15 +2104,15 @@ function getIguazioProjects (req, res) { res.send(resultTemplate) } -function getIguazioAuthorization (req, res) { +function getIguazioAuthorization(req, res) { res.send({ data: [], meta: { ctx: 11661436569072727632 } }) } -function getIguazioSelf (req, res) { +function getIguazioSelf(req, res) { res.send(iguazioSelf) } -function getIguazioProject (req, res) { +function getIguazioProject(req, res) { let filteredProject = iguazioProjects.data.find(item => item.id === req.params.id) let filteredAuthRoles = [] @@ -2177,7 +2177,7 @@ function getIguazioProject (req, res) { }) } -function putIguazioProject (req, res) { +function putIguazioProject(req, res) { const prevOwner = req.params.id const newOwner = req.body.data.relationships.owner.data.id const filteredProject = iguazioProjects.data.find(item => item.id === req.params.id) @@ -2207,7 +2207,7 @@ function putIguazioProject (req, res) { }) } -function postProjectMembers (req, res) { +function postProjectMembers(req, res) { const projectId = req.body.data.attributes.metadata.project_ids[0] const items = req.body.data.attributes.requests const projectRelations = cloneDeep(iguazioProjectsRelations[projectId]) @@ -2249,19 +2249,19 @@ function postProjectMembers (req, res) { }) } -function getIguazioUserGrops (req, res) { +function getIguazioUserGrops(req, res) { res.send(iguazioUserGrops) } -function getIguazioUsers (req, res) { +function getIguazioUsers(req, res) { res.send(iguazioUsers) } -function getNuclioStreams (req, res) { +function getNuclioStreams(req, res) { res.send(nuclioStreams[req.headers['x-nuclio-project-name']]) } -function getNuclioShardLags (req, res) { +function getNuclioShardLags(req, res) { res.send({ [`${req.body.containerName}${req.body.streamPath}`]: { [req.body.consumerGroup]: { @@ -2280,7 +2280,7 @@ function getNuclioShardLags (req, res) { }) } -function getIguazioJob (req, res) { +function getIguazioJob(req, res) { res.send({ data: { attributes: { From 3af1bc0e42758f81d1c6a09569ee3f2f67b7993a Mon Sep 17 00:00:00 2001 From: pinis-gini-apps Date: Mon, 9 Sep 2024 20:56:38 +0300 Subject: [PATCH 54/54] fix some duplicated line after the merge --- tests/features/step-definitions/steps.js | 29 ++++++++++-------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/tests/features/step-definitions/steps.js b/tests/features/step-definitions/steps.js index 4b183b62d..e7dba8054 100644 --- a/tests/features/step-definitions/steps.js +++ b/tests/features/step-definitions/steps.js @@ -202,14 +202,13 @@ Then('wait load page', async function () { await this.driver.sleep(DRIVER_SLEEP || 500) }) -Then('navigate forward', async function () { -Then('wait for {int} seconds', async function(seconds) { +Then('wait for {int} seconds', async function (seconds) { const milliseconds = seconds * 1000 await new Promise(resolve => setTimeout(resolve, milliseconds)) }) -Then('navigate forward', async function() { +Then('navigate forward', async function () { await navigateForward(this.driver) await waitPageLoad(this.driver, pageObjects['commonPagesHeader']['loader']) await this.driver.sleep(DRIVER_SLEEP || 500) @@ -341,7 +340,10 @@ Then( Then( 'verify checkbox {string} element in {string} on {string} wizard is enabled', async function (elementName, accordionName, wizardName) { - await verifyCheckboxEnabled(this.driver, pageObjects[wizardName][accordionName][elementName].root) + await verifyCheckboxEnabled( + this.driver, + pageObjects[wizardName][accordionName][elementName].root + ) } ) @@ -447,11 +449,6 @@ Then( const result = Number.parseInt(txt) + value await incrementValue(this.driver, pageObjects[wizard][accordion][inputField], value) - await incrementValue( - this.driver, - pageObjects[wizard][accordion][inputField], - value - ) await verifyTypedValue( this.driver, pageObjects[wizard][accordion][inputField], @@ -526,8 +523,7 @@ Then( if (unit === 'cpu') { return result.toFixed(3) - } - else if (unit !== 'cpu' && result < 1) { + } else if (unit !== 'cpu' && result < 1) { result = 1 } @@ -881,7 +877,7 @@ Then( Then( 'verify {string} in {string} on {string} wizard should display {string}.{string}', - async function(inputField, accordion, wizard, constStorage, constValue) { + async function (inputField, accordion, wizard, constStorage, constValue) { await checkHintTextWithHover( this.driver, pageObjects[wizard][accordion][inputField], @@ -1135,7 +1131,6 @@ Then( Then('check that {string} file is existed on {string} directory', async function (file, filePath) { const path = await generatePath(file, filePath) - await this.driver.sleep(150) await this.driver.sleep(DRIVER_SLEEP || 150) await determineFileAccess(path, file) await this.driver.sleep(DRIVER_SLEEP || 150) @@ -1214,10 +1209,10 @@ Then( } ) Then( - 'verify {string} input should contains {string} placeholder value on {string} wizard', - async function (component, value, wizard) { - await verifyPlaceholder(this.driver, pageObjects[wizard][component], value) - } + 'verify {string} input should contains {string} placeholder value on {string} wizard', + async function (component, value, wizard) { + await verifyPlaceholder(this.driver, pageObjects[wizard][component], value) + } ) Then(