@eeacms/volto-tableau 5.0.2 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,7 +4,31 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
- ### [5.0.2](https://github.com/eea/volto-tableau/compare/5.0.1...5.0.2) - 23 October 2023
7
+ ### [6.0.1](https://github.com/eea/volto-tableau/compare/6.0.0...6.0.1) - 10 November 2023
8
+
9
+ #### :house: Internal changes
10
+
11
+ - chore: [JENKINS] Refactor automated testing [valentinab25 - [`903b90a`](https://github.com/eea/volto-tableau/commit/903b90a2df0abc183aa68d99a89bb688de3a0c73)]
12
+
13
+ #### :hammer_and_wrench: Others
14
+
15
+ - tests: add missing imports [kreafox - [`68289f0`](https://github.com/eea/volto-tableau/commit/68289f0a0d95388751487635cfefbf992cd2f3ee)]
16
+ - tests: add missing imports [kreafox - [`fc5adc2`](https://github.com/eea/volto-tableau/commit/fc5adc2ab4d6fa56ac2b83d88a3a812dde427188)]
17
+ - tests: update volto version in Jenkinsfile [kreafox - [`3271a6c`](https://github.com/eea/volto-tableau/commit/3271a6c611d19511d5db67be9e29104c03b3f027)]
18
+ - tests: increase coverage [kreafox - [`ecb9262`](https://github.com/eea/volto-tableau/commit/ecb92625a44365faf4c39f0ff3f02ccc4ca21656)]
19
+ - Fix tableau height [kreafox - [`6194c24`](https://github.com/eea/volto-tableau/commit/6194c2405e2bd93aba9865caed76292cd55193d3)]
20
+ - Show share & download button on widget view [kreafox - [`a66e253`](https://github.com/eea/volto-tableau/commit/a66e2537d08dc7129e249c28e1daa27246c88815)]
21
+ - test: [JENKINS] Add cpu limit on cypress docker [valentinab25 - [`9d2189d`](https://github.com/eea/volto-tableau/commit/9d2189d4edb13dd449d75cd6dd9052bbed67304d)]
22
+ - test: [JENKINS] Increase shm-size to cypress docker [valentinab25 - [`f2fa3f9`](https://github.com/eea/volto-tableau/commit/f2fa3f9ccabab918586179ac16ead44e2eba8498)]
23
+ - test: [JENKINS] Improve cypress time [valentinab25 - [`5748183`](https://github.com/eea/volto-tableau/commit/5748183c27dae18a4ebbc067d659cbc24cad8f71)]
24
+ - use toolbar from volto-embed [Miu Razvan - [`1a232a1`](https://github.com/eea/volto-tableau/commit/1a232a15f2bbe05da75307800590d0a0a1c4996b)]
25
+ ## [6.0.0](https://github.com/eea/volto-tableau/compare/5.0.2...6.0.0) - 30 October 2023
26
+
27
+ #### :house: Documentation changes
28
+
29
+ - docs: add demo gif [ana-oprea - [`a9ccb11`](https://github.com/eea/volto-tableau/commit/a9ccb1194ba6bcac99649b586113fe4f7c26a7eb)]
30
+
31
+ ### [5.0.2](https://github.com/eea/volto-tableau/compare/5.0.1...5.0.2) - 24 October 2023
8
32
 
9
33
  #### :rocket: New Features
10
34
 
package/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # syntax=docker/dockerfile:1
2
2
  ARG VOLTO_VERSION
3
- FROM plone/frontend-builder:${VOLTO_VERSION}
3
+ FROM eeacms/frontend-builder:${VOLTO_VERSION}
4
4
 
5
5
  ARG ADDON_NAME
6
6
  ARG ADDON_PATH
package/Jenkinsfile CHANGED
@@ -1,16 +1,20 @@
1
1
  pipeline {
2
- agent any
2
+ agent {
3
+ node { label 'docker-host' }
4
+ }
3
5
 
4
6
  environment {
5
- GIT_NAME = "volto-tableau"
6
- NAMESPACE = "@eeacms"
7
- SONARQUBE_TAGS = "volto.eea.europa.eu,climate-energy.eea.europa.eu,forest.eea.europa.eu,biodiversity.europa.eu,water.europa.eu-freshwater,water.europa.eu-marine,industry.eea.europa.eu,demo-www.eea.europa.eu,www.eea.europa.eu-en"
8
- DEPENDENCIES = ""
9
- VOLTO = "16"
10
- }
7
+ GIT_NAME = "volto-tableau"
8
+ NAMESPACE = "@eeacms"
9
+ SONARQUBE_TAGS = "volto.eea.europa.eu,climate-energy.eea.europa.eu,forest.eea.europa.eu,biodiversity.europa.eu,water.europa.eu-freshwater,water.europa.eu-marine,industry.eea.europa.eu,demo-www.eea.europa.eu,www.eea.europa.eu-en"
10
+ DEPENDENCIES = ""
11
+ BACKEND_PROFILES = "eea.kitkat:testing"
12
+ BACKEND_ADDONS = ""
13
+ VOLTO = "16.24.0"
14
+ IMAGE_NAME = BUILD_TAG.toLowerCase()
15
+ }
11
16
 
12
17
  stages {
13
-
14
18
  stage('Release') {
15
19
  when {
16
20
  allOf {
@@ -20,52 +24,41 @@ pipeline {
20
24
  }
21
25
  steps {
22
26
  node(label: 'docker') {
23
- withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN'),string(credentialsId: 'eea-jenkins-npm-token', variable: 'NPM_TOKEN')]) {
24
- sh '''docker pull eeacms/gitflow'''
25
- sh '''docker run -i --rm --name="$BUILD_TAG-gitflow-master" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_NAME="$GIT_NAME" -e GIT_TOKEN="$GITHUB_TOKEN" -e NPM_TOKEN="$NPM_TOKEN" -e LANGUAGE=javascript eeacms/gitflow'''
27
+ withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN'), string(credentialsId: 'eea-jenkins-npm-token', variable: 'NPM_TOKEN')]) {
28
+ sh '''docker run -i --rm --pull always --name="$IMAGE_NAME-gitflow-master" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_NAME="$GIT_NAME" -e GIT_TOKEN="$GITHUB_TOKEN" -e NPM_TOKEN="$NPM_TOKEN" -e LANGUAGE=javascript eeacms/gitflow'''
26
29
  }
27
30
  }
28
31
  }
29
32
  }
30
33
 
31
- stage('Code') {
34
+ stage('Check if testing needed') {
32
35
  when {
33
36
  allOf {
34
- environment name: 'CHANGE_ID', value: ''
35
- not { changelog '.*^Automated release [0-9\\.]+$' }
36
37
  not { branch 'master' }
38
+ not { branch 'develop' }
39
+ environment name: 'CHANGE_ID', value: ''
37
40
  }
38
41
  }
39
42
  steps {
40
- parallel(
41
-
42
- "ES lint": {
43
- node(label: 'docker') {
44
- sh '''docker run -i --rm --name="$BUILD_TAG-eslint" -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e VOLTO=$VOLTO plone/volto-addon-ci eslint'''
45
- }
46
- },
47
-
48
- "Style lint": {
49
- node(label: 'docker') {
50
- sh '''docker run -i --rm --name="$BUILD_TAG-stylelint" -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e VOLTO=$VOLTO plone/volto-addon-ci stylelint'''
51
- }
52
- },
43
+ script {
44
+ withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN')]) {
45
+ check_result = sh script: '''docker run --pull always -i --rm --name="$IMAGE_NAME-gitflow-check" -e GIT_TOKEN="$GITHUB_TOKEN" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_ORG="$GIT_ORG" -e GIT_NAME="$GIT_NAME" eeacms/gitflow /check_if_testing_needed.sh''', returnStatus: true
53
46
 
54
- "Prettier": {
55
- node(label: 'docker') {
56
- sh '''docker run -i --rm --name="$BUILD_TAG-prettier" -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e VOLTO=$VOLTO plone/volto-addon-ci prettier'''
47
+ if (check_result == 0) {
48
+ env.SKIP_TESTS = 'yes'
49
+ }
57
50
  }
58
- }
59
- )
51
+ }
60
52
  }
61
53
  }
62
54
 
63
- stage('Tests') {
55
+ stage('Testing') {
64
56
  when {
65
57
  anyOf {
66
58
  allOf {
67
59
  not { environment name: 'CHANGE_ID', value: '' }
68
60
  environment name: 'CHANGE_TARGET', value: 'develop'
61
+ environment name: 'SKIP_TESTS', value: ''
69
62
  }
70
63
  allOf {
71
64
  environment name: 'CHANGE_ID', value: ''
@@ -73,26 +66,76 @@ pipeline {
73
66
  not { changelog '.*^Automated release [0-9\\.]+$' }
74
67
  branch 'master'
75
68
  }
69
+ environment name: 'SKIP_TESTS', value: ''
76
70
  }
77
71
  }
78
72
  }
79
- steps {
80
- parallel(
73
+ stages {
74
+ stage('Build test image') {
75
+ steps {
76
+ checkout scm
77
+ sh '''docker build --pull --build-arg="VOLTO_VERSION=$VOLTO" --build-arg="ADDON_NAME=$NAMESPACE/$GIT_NAME" --build-arg="ADDON_PATH=$GIT_NAME" . -t $IMAGE_NAME-frontend'''
78
+ }
79
+ }
80
+
81
+ stage('Fix code') {
82
+ when {
83
+ environment name: 'CHANGE_ID', value: ''
84
+ not { branch 'master' }
85
+ }
86
+ steps {
87
+ script {
88
+ fix_result = sh(script: '''docker run --name="$IMAGE_NAME-fix" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend ci-fix''', returnStatus: true)
89
+ sh '''docker cp $IMAGE_NAME-fix:/app/src/addons/$GIT_NAME/src .'''
90
+ sh '''docker rm -v $IMAGE_NAME-fix'''
91
+ FOUND_FIX = sh(script: '''git diff | wc -l''', returnStdout: true).trim()
81
92
 
82
- "Volto": {
83
- node(label: 'docker') {
84
- script {
85
- try {
86
- sh '''docker pull plone/volto-addon-ci'''
87
- sh '''docker run -i --name="$BUILD_TAG-volto" -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e VOLTO=$VOLTO plone/volto-addon-ci'''
88
- sh '''rm -rf xunit-reports'''
89
- sh '''mkdir -p xunit-reports'''
90
- sh '''docker cp $BUILD_TAG-volto:/opt/frontend/my-volto-project/coverage xunit-reports/'''
91
- sh '''docker cp $BUILD_TAG-volto:/opt/frontend/my-volto-project/junit.xml xunit-reports/'''
92
- sh '''docker cp $BUILD_TAG-volto:/opt/frontend/my-volto-project/unit_tests_log.txt xunit-reports/'''
93
- stash name: "xunit-reports", includes: "xunit-reports/**"
94
- archiveArtifacts artifacts: "xunit-reports/unit_tests_log.txt", fingerprint: true
95
- publishHTML (target : [
93
+ if (FOUND_FIX != '0') {
94
+ withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN')]) {
95
+ sh '''sed -i "s|url = .*|url = https://eea-jenkins:$GITHUB_TOKEN@github.com/eea/$GIT_NAME.git|" .git/config'''
96
+ }
97
+ sh '''git fetch origin $GIT_BRANCH:$GIT_BRANCH'''
98
+ sh '''git checkout $GIT_BRANCH'''
99
+ sh '''git add src/'''
100
+ sh '''git commit -m "style: Automated code fix" '''
101
+ sh '''git push --set-upstream origin $GIT_BRANCH'''
102
+ sh '''exit 1'''
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ stage('ES lint') {
109
+ steps {
110
+ sh '''docker run --rm --name="$IMAGE_NAME-eslint" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend lint'''
111
+ }
112
+ }
113
+
114
+ stage('Style lint') {
115
+ steps {
116
+ sh '''docker run --rm --name="$IMAGE_NAME-stylelint" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend stylelint'''
117
+ }
118
+ }
119
+
120
+ stage('Prettier') {
121
+ steps {
122
+ sh '''docker run --rm --name="$IMAGE_NAME-prettier" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend prettier'''
123
+ }
124
+ }
125
+
126
+ stage('Coverage Tests') {
127
+ parallel {
128
+
129
+ stage('Unit tests') {
130
+ steps {
131
+ script {
132
+ try {
133
+ sh '''docker run --name="$IMAGE_NAME-volto" --entrypoint=make --workdir=/app/src/addons/$GIT_NAME $IMAGE_NAME-frontend test-ci'''
134
+ sh '''rm -rf xunit-reports'''
135
+ sh '''mkdir -p xunit-reports'''
136
+ sh '''docker cp $IMAGE_NAME-volto:/app/coverage xunit-reports/'''
137
+ sh '''docker cp $IMAGE_NAME-volto:/app/junit.xml xunit-reports/'''
138
+ publishHTML(target : [
96
139
  allowMissing: false,
97
140
  alwaysLinkToLastBuild: true,
98
141
  keepAll: true,
@@ -105,75 +148,62 @@ pipeline {
105
148
  catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
106
149
  junit testResults: 'xunit-reports/junit.xml', allowEmptyResults: true
107
150
  }
108
- sh script: '''docker rm -v $BUILD_TAG-volto''', returnStatus: true
151
+ sh script: '''docker rm -v $IMAGE_NAME-volto''', returnStatus: true
152
+ }
109
153
  }
110
154
  }
111
155
  }
112
- }
113
- )
114
- }
115
- }
156
+
157
+ stage('Integration tests') {
158
+ steps {
159
+ script {
160
+ try {
161
+ sh '''docker run --pull always --rm -d --name="$IMAGE_NAME-plone" -e SITE="Plone" -e PROFILES="$BACKEND_PROFILES" -e ADDONS="$BACKEND_ADDONS" eeacms/plone-backend'''
162
+ sh '''timeout -s 9 3600 docker run --shm-size=2g --cpu-quota=150000 --link $IMAGE_NAME-plone:plone --entrypoint=make --name="$IMAGE_NAME-cypress" --workdir=/app/src/addons/${GIT_NAME} -e "RAZZLE_INTERNAL_API_PATH=http://plone:8080/Plone" $IMAGE_NAME-frontend cypress-ci'''
163
+ } finally {
164
+ try {
165
+ sh '''rm -rf cypress-videos cypress-results cypress-coverage cypress-screenshots'''
166
+ sh '''mkdir -p cypress-videos cypress-results cypress-coverage cypress-screenshots'''
167
+ videos = sh script: '''docker cp $IMAGE_NAME-cypress:/app/src/addons/$GIT_NAME/cypress/videos cypress-videos/''', returnStatus: true
168
+ sh '''docker cp $IMAGE_NAME-cypress:/app/src/addons/$GIT_NAME/cypress/reports cypress-results/'''
169
+ screenshots = sh script: '''docker cp $IMAGE_NAME-cypress:/app/src/addons/$GIT_NAME/cypress/screenshots cypress-screenshots''', returnStatus: true
116
170
 
117
- stage('Integration tests') {
118
- when {
119
- anyOf {
120
- allOf {
121
- not { environment name: 'CHANGE_ID', value: '' }
122
- environment name: 'CHANGE_TARGET', value: 'develop'
123
- }
124
- allOf {
125
- environment name: 'CHANGE_ID', value: ''
126
- anyOf {
127
- not { changelog '.*^Automated release [0-9\\.]+$' }
128
- branch 'master'
129
- }
130
- }
131
- }
132
- }
133
- steps {
134
- parallel(
171
+ archiveArtifacts artifacts: 'cypress-screenshots/**', fingerprint: true, allowEmptyArchive: true
135
172
 
136
- "Cypress": {
137
- node(label: 'docker') {
138
- script {
139
- try {
140
- sh '''docker pull eeacms/plone-backend; docker run --rm -d --name="$BUILD_TAG-plone" -e SITE="Plone" -e PROFILES="eea.kitkat:testing" eeacms/plone-backend'''
141
- sh '''docker pull plone/volto-addon-ci; docker run -i --name="$BUILD_TAG-cypress" --link $BUILD_TAG-plone:plone -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e DEPENDENCIES="$DEPENDENCIES" -e NODE_ENV=development -e VOLTO=$VOLTO plone/volto-addon-ci cypress'''
142
- } finally {
143
- try {
144
- sh '''rm -rf cypress-reports cypress-results cypress-coverage'''
145
- sh '''mkdir -p cypress-reports cypress-results cypress-coverage'''
146
- sh '''docker cp $BUILD_TAG-cypress:/opt/frontend/my-volto-project/src/addons/$GIT_NAME/cypress/videos cypress-reports/'''
147
- sh '''docker cp $BUILD_TAG-cypress:/opt/frontend/my-volto-project/src/addons/$GIT_NAME/cypress/reports cypress-results/'''
148
- coverage = sh script: '''docker cp $BUILD_TAG-cypress:/opt/frontend/my-volto-project/src/addons/$GIT_NAME/coverage cypress-coverage/''', returnStatus: true
149
- if ( coverage == 0 ) {
150
- publishHTML (target : [allowMissing: false,
173
+ coverage = sh script: '''docker cp $IMAGE_NAME-cypress:/app/src/addons/$GIT_NAME/coverage cypress-coverage''', returnStatus: true
174
+
175
+ if ( coverage == 0 ) {
176
+ publishHTML(target : [allowMissing: false,
151
177
  alwaysLinkToLastBuild: true,
152
178
  keepAll: true,
153
179
  reportDir: 'cypress-coverage/coverage/lcov-report',
154
180
  reportFiles: 'index.html',
155
181
  reportName: 'CypressCoverage',
156
182
  reportTitles: 'Integration Tests Code Coverage'])
157
- }
158
- sh '''touch empty_file; for ok_test in $(grep -E 'file=.*failures="0"' $(grep 'testsuites .*failures="0"' $(find cypress-results -name *.xml) empty_file | awk -F: '{print $1}') empty_file | sed 's/.* file="\\(.*\\)" time.*/\\1/' | sed 's#^cypress/integration/##g' | sed 's#^../../../node_modules/@eeacms/##g'); do rm -f cypress-reports/videos/$ok_test.mp4; rm -f cypress-reports/$ok_test.mp4; done'''
159
- archiveArtifacts artifacts: 'cypress-reports/**/*.mp4', fingerprint: true, allowEmptyArchive: true
160
- stash name: "cypress-coverage", includes: "cypress-coverage/**", allowEmpty: true
161
- }
162
- finally {
163
- catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
183
+ }
184
+ if ( videos == 0 ) {
185
+ sh '''for file in $(find cypress-results -name *.xml); do if [ $(grep -E 'failures="[1-9].*"' $file | wc -l) -eq 0 ]; then testname=$(grep -E 'file=.*failures="0"' $file | sed 's#.* file=".*\\/\\(.*\\.[jsxt]\\+\\)" time.*#\\1#' ); rm -f cypress-videos/videos/$testname.mp4; fi; done'''
186
+ archiveArtifacts artifacts: 'cypress-videos/**/*.mp4', fingerprint: true, allowEmptyArchive: true
187
+ }
188
+ } finally {
189
+ catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
164
190
  junit testResults: 'cypress-results/**/*.xml', allowEmptyResults: true
191
+ }
192
+ sh script: "docker stop $IMAGE_NAME-plone", returnStatus: true
193
+ sh script: "docker rm -v $IMAGE_NAME-plone", returnStatus: true
194
+ sh script: "docker rm -v $IMAGE_NAME-cypress", returnStatus: true
165
195
  }
166
- sh script: "docker stop $BUILD_TAG-plone", returnStatus: true
167
- sh script: "docker rm -v $BUILD_TAG-plone", returnStatus: true
168
- sh script: "docker rm -v $BUILD_TAG-cypress", returnStatus: true
169
-
170
196
  }
171
197
  }
172
198
  }
173
199
  }
174
200
  }
175
-
176
- )
201
+ }
202
+ }
203
+ post {
204
+ always {
205
+ sh script: "docker rmi $IMAGE_NAME-frontend", returnStatus: true
206
+ }
177
207
  }
178
208
  }
179
209
 
@@ -197,19 +227,14 @@ pipeline {
197
227
  }
198
228
  }
199
229
  steps {
200
- node(label: 'swarm') {
201
- script{
202
- checkout scm
203
- unstash "xunit-reports"
204
- unstash "cypress-coverage"
205
- def scannerHome = tool 'SonarQubeScanner';
206
- def nodeJS = tool 'NodeJS';
207
- withSonarQubeEnv('Sonarqube') {
208
- sh '''sed -i "s#/opt/frontend/my-volto-project/src/addons/${GIT_NAME}/##g" xunit-reports/coverage/lcov.info'''
209
- sh '''sed -i "s#src/addons/${GIT_NAME}/##g" xunit-reports/coverage/lcov.info'''
210
- sh "export PATH=${scannerHome}/bin:${nodeJS}/bin:$PATH; sonar-scanner -Dsonar.javascript.lcov.reportPaths=./xunit-reports/coverage/lcov.info,./cypress-coverage/coverage/lcov.info -Dsonar.sources=./src -Dsonar.projectKey=$GIT_NAME-$BRANCH_NAME -Dsonar.projectVersion=$BRANCH_NAME-$BUILD_NUMBER"
211
- sh '''try=2; while [ \$try -gt 0 ]; do curl -s -XPOST -u "${SONAR_AUTH_TOKEN}:" "${SONAR_HOST_URL}api/project_tags/set?project=${GIT_NAME}-${BRANCH_NAME}&tags=${SONARQUBE_TAGS},${BRANCH_NAME}" > set_tags_result; if [ \$(grep -ic error set_tags_result ) -eq 0 ]; then try=0; else cat set_tags_result; echo "... Will retry"; sleep 60; try=\$(( \$try - 1 )); fi; done'''
212
- }
230
+ script {
231
+ def scannerHome = tool 'SonarQubeScanner'
232
+ def nodeJS = tool 'NodeJS'
233
+ withSonarQubeEnv('Sonarqube') {
234
+ sh '''sed -i "s#/app/src/addons/${GIT_NAME}/##g" xunit-reports/coverage/lcov.info'''
235
+ sh '''sed -i "s#src/addons/${GIT_NAME}/##g" xunit-reports/coverage/lcov.info'''
236
+ sh "export PATH=${scannerHome}/bin:${nodeJS}/bin:$PATH; sonar-scanner -Dsonar.javascript.lcov.reportPaths=./xunit-reports/coverage/lcov.info,./cypress-coverage/coverage/lcov.info -Dsonar.sources=./src -Dsonar.projectKey=$GIT_NAME-$BRANCH_NAME -Dsonar.projectVersion=$BRANCH_NAME-$BUILD_NUMBER"
237
+ sh '''try=5; while [ \$try -gt 0 ]; do curl -s -XPOST -u "${SONAR_AUTH_TOKEN}:" "${SONAR_HOST_URL}api/project_tags/set?project=${GIT_NAME}-${BRANCH_NAME}&tags=${SONARQUBE_TAGS},${BRANCH_NAME}" > set_tags_result; if [ \$(grep -ic error set_tags_result ) -eq 0 ]; then try=0; else cat set_tags_result; echo "... Will retry"; sleep 15; try=\$(( \$try - 1 )); fi; done'''
213
238
  }
214
239
  }
215
240
  }
@@ -230,18 +255,15 @@ pipeline {
230
255
  }
231
256
  }
232
257
  steps {
233
- node(label: 'docker') {
234
- script {
235
- sh '''docker pull eeacms/gitflow'''
236
- sh '''echo "Error" > checkresult.txt'''
237
- catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
238
- sh '''set -o pipefail; docker run -i --rm --name="$BUILD_TAG-gitflow-sn" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_NAME="$GIT_NAME" eeacms/gitflow /checkSonarqubemaster.sh | grep -v "Found script" | tee checkresult.txt'''
239
- }
240
-
241
- publishChecks name: 'SonarQube', title: 'Sonarqube Code Quality Check', summary: "Quality check on the SonarQube metrics from branch develop, comparing it with the ones from master branch. No bugs are allowed",
242
- text: readFile(file: 'checkresult.txt'), conclusion: "${currentBuild.currentResult}",
243
- detailsURL: "${env.BUILD_URL}display/redirect"
258
+ script {
259
+ sh '''echo "Error" > checkresult.txt'''
260
+ catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
261
+ sh '''set -o pipefail; docker run -i --rm --pull always --name="$IMAGE_NAME-gitflow-sn" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_NAME="$GIT_NAME" eeacms/gitflow /checkSonarqubemaster.sh | grep -v "Found script" | tee checkresult.txt'''
244
262
  }
263
+
264
+ publishChecks name: 'SonarQube', title: 'Sonarqube Code Quality Check', summary: 'Quality check on the SonarQube metrics from branch develop, comparing it with the ones from master branch. No bugs are allowed',
265
+ text: readFile(file: 'checkresult.txt'), conclusion: "${currentBuild.currentResult}",
266
+ detailsURL: "${env.BUILD_URL}display/redirect"
245
267
  }
246
268
  }
247
269
  }
@@ -254,20 +276,16 @@ pipeline {
254
276
  environment name: 'CHANGE_TARGET', value: 'master'
255
277
  }
256
278
  steps {
257
- node(label: 'docker') {
258
- script {
259
- if ( env.CHANGE_BRANCH != "develop" ) {
260
- error "Pipeline aborted due to PR not made from develop branch"
261
- }
262
- withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN')]) {
263
- sh '''docker pull eeacms/gitflow'''
264
- sh '''docker run -i --rm --name="$BUILD_TAG-gitflow-pr" -e GIT_CHANGE_TARGET="$CHANGE_TARGET" -e GIT_CHANGE_BRANCH="$CHANGE_BRANCH" -e GIT_CHANGE_AUTHOR="$CHANGE_AUTHOR" -e GIT_CHANGE_TITLE="$CHANGE_TITLE" -e GIT_TOKEN="$GITHUB_TOKEN" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e GIT_ORG="$GIT_ORG" -e GIT_NAME="$GIT_NAME" -e LANGUAGE=javascript eeacms/gitflow'''
265
- }
279
+ script {
280
+ if (env.CHANGE_BRANCH != 'develop') {
281
+ error 'Pipeline aborted due to PR not made from develop branch'
282
+ }
283
+ withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN')]) {
284
+ sh '''docker run --pull always -i --rm --name="$IMAGE_NAME-gitflow-pr" -e GIT_CHANGE_TARGET="$CHANGE_TARGET" -e GIT_CHANGE_BRANCH="$CHANGE_BRANCH" -e GIT_CHANGE_AUTHOR="$CHANGE_AUTHOR" -e GIT_CHANGE_TITLE="$CHANGE_TITLE" -e GIT_TOKEN="$GITHUB_TOKEN" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e GIT_ORG="$GIT_ORG" -e GIT_NAME="$GIT_NAME" -e LANGUAGE=javascript eeacms/gitflow'''
266
285
  }
267
286
  }
268
287
  }
269
288
  }
270
-
271
289
  }
272
290
 
273
291
  post {
@@ -289,4 +307,4 @@ pipeline {
289
307
  }
290
308
  }
291
309
  }
292
- }
310
+ }
package/Makefile CHANGED
@@ -50,6 +50,11 @@ VOLTO_VERSION?=16
50
50
  ADDON_PATH="${DIR}"
51
51
  ADDON_NAME="@eeacms/${ADDON_PATH}"
52
52
  DOCKER_COMPOSE=PLONE_VERSION=${PLONE_VERSION} VOLTO_VERSION=${VOLTO_VERSION} ADDON_NAME=${ADDON_NAME} ADDON_PATH=${ADDON_PATH} docker compose
53
+ RAZZLE_INTERNAL_API_PATH?="http://localhost:8080/Plone"
54
+ RAZZLE_DEV_PROXY_API_PATH?="${RAZZLE_INTERNAL_API_PATH}"
55
+ CYPRESS_API_PATH="${RAZZLE_DEV_PROXY_API_PATH}"
56
+
57
+
53
58
 
54
59
  # Top-level targets
55
60
  .PHONY: all
@@ -77,11 +82,11 @@ shell: ## Start a shell in the frontend container
77
82
 
78
83
  .PHONY: cypress-open
79
84
  cypress-open: ## Open cypress integration tests
80
- NODE_ENV=development $(NODE_MODULES)/cypress/bin/cypress open
85
+ CYPRESS_API_PATH="${RAZZLE_DEV_PROXY_API_PATH}" NODE_ENV=development $(NODE_MODULES)/cypress/bin/cypress open
81
86
 
82
87
  .PHONY: cypress-run
83
88
  cypress-run: ## Run cypress integration tests
84
- NODE_ENV=development $(NODE_MODULES)/cypress/bin/cypress run
89
+ CYPRESS_API_PATH="${RAZZLE_DEV_PROXY_API_PATH}" NODE_ENV=development $(NODE_MODULES)/cypress/bin/cypress run --browser chromium
85
90
 
86
91
  .PHONY: test
87
92
  test: ## Run jest tests
@@ -129,3 +134,31 @@ i18n: ## i18n
129
134
  help: ## Show this help.
130
135
  @echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)"
131
136
  head -n 14 Makefile
137
+
138
+ .PHONY: ci-fix
139
+ ci-fix:
140
+ echo "Running lint-fix"
141
+ make lint-fix
142
+ echo "Running prettier-fix"
143
+ make prettier-fix
144
+ echo "Running stylelint-fix"
145
+ make stylelint-fix
146
+
147
+ .PHONY: test-ci
148
+ test-ci:
149
+ cd /app
150
+ RAZZLE_JEST_CONFIG=src/addons/${ADDON_PATH}/jest-addon.config.js CI=true yarn test src/addons/${ADDON_PATH}/src --watchAll=false --reporters=default --reporters=jest-junit --collectCoverage --coverageReporters lcov cobertura text
151
+
152
+ .PHONY: start-ci
153
+ start-ci:
154
+ cd ../..
155
+ yarn start &
156
+
157
+ .PHONY: cypress-ci
158
+ cypress-ci:
159
+ cp .coverage.babel.config.js /app/babel.config.js
160
+ make start-ci
161
+ $(NODE_MODULES)/.bin/wait-on -t 240000 http://localhost:3000
162
+ NODE_ENV=development make cypress-run
163
+
164
+
package/README.md CHANGED
@@ -20,6 +20,8 @@
20
20
 
21
21
  Registers a VisualizationView component for a content type named 'tableau_visualization'.
22
22
 
23
+ ![Tableau](https://raw.githubusercontent.com/eea/volto-tableau/master/docs/volto-tableau.gif)
24
+
23
25
  ## Getting started
24
26
 
25
27
  ### Try volto-tableau with Docker
package/cypress.config.js CHANGED
@@ -5,9 +5,9 @@ module.exports = defineConfig({
5
5
  defaultCommandTimeout: 8888,
6
6
  chromeWebSecurity: false,
7
7
  reporter: 'junit',
8
- video: true,
8
+ video: false,
9
9
  retries: {
10
- runMode: 1,
10
+ runMode: 2,
11
11
  openMode: 0,
12
12
  },
13
13
  reporterOptions: {
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-tableau",
3
- "version": "5.0.2",
3
+ "version": "6.0.1",
4
4
  "description": "@eeacms/volto-tableau: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -1,12 +1,10 @@
1
1
  import React from 'react';
2
- import { PrivacyProtection } from '@eeacms/volto-embed';
3
- import Tableau from '@eeacms/volto-tableau/Tableau/Tableau';
4
-
5
- import { flattenToAppURL } from '@plone/volto/helpers';
6
- import { getContent } from '@plone/volto/actions';
7
-
8
2
  import { connect } from 'react-redux';
9
3
  import { compose } from 'redux';
4
+ import { flattenToAppURL } from '@plone/volto/helpers';
5
+ import { getContent } from '@plone/volto/actions';
6
+ import { PrivacyProtection } from '@eeacms/volto-embed';
7
+ import Tableau from '@eeacms/volto-tableau/Tableau/Tableau';
10
8
 
11
9
  const View = (props) => {
12
10
  const data = props.data;
@@ -16,7 +14,7 @@ const View = (props) => {
16
14
  with_more_info = true,
17
15
  with_download = true,
18
16
  with_share = true,
19
- tableau_height = '700',
17
+ tableau_height,
20
18
  } = data;
21
19
  const { figure_note = [], data_provenance = {}, tableau_visualization } =
22
20
  props.tableau_visualization_data || {};
@@ -31,7 +29,7 @@ const View = (props) => {
31
29
  }, [tableau_vis_url]);
32
30
 
33
31
  return (
34
- <div className="embed-container">
32
+ <div className="embed-tableau">
35
33
  <PrivacyProtection
36
34
  {...props}
37
35
  data={{ ...data, url: tableau_visualization?.url }}
@@ -39,6 +39,6 @@ describe('View', () => {
39
39
  <View data={data} />
40
40
  </Provider>,
41
41
  );
42
- expect(container.querySelector('.embed-container')).toBeInTheDocument();
42
+ expect(container.querySelector('.embed-tableau')).toBeInTheDocument();
43
43
  });
44
44
  });
@@ -14,17 +14,21 @@ import isUndefined from 'lodash/isUndefined';
14
14
  import cx from 'classnames';
15
15
  import { Button } from 'semantic-ui-react';
16
16
  import { Toast, Icon } from '@plone/volto/components';
17
+ import {
18
+ FigureNote,
19
+ Sources,
20
+ MoreInfo,
21
+ Share,
22
+ } from '@eeacms/volto-embed/Toolbar';
17
23
  import { useTableau } from '@eeacms/volto-tableau/hooks';
18
- import JsonCodeSnippet from '@eeacms/volto-tableau/Utils/JsonCodeSnippet/JsonCodeSnippet';
19
- import FigureNote from '@eeacms/volto-tableau/Utils/FigureNote/FigureNote';
20
- import Sources from '@eeacms/volto-tableau/Utils/Sources/Sources';
21
- import MoreInfoLink from '@eeacms/volto-tableau/Utils/MoreInfoLink/MoreInfoLink';
22
- import Download from '@eeacms/volto-tableau/Utils/Download/Download';
23
- import Share from '@eeacms/volto-tableau/Utils/Share/Share';
24
+ import { JsonCodeSnippet, Download } from '@eeacms/volto-tableau/Utils';
25
+
24
26
  import { getSheetnames, getActiveSheetname, getDevice } from './helpers';
25
27
 
26
28
  import resetSVG from '@plone/volto/icons/reset.svg';
27
29
 
30
+ import '@eeacms/volto-embed/Toolbar/styles.less';
31
+
28
32
  const TableauDebug = ({ mode, data, vizState, url, version, clearData }) => {
29
33
  const { loaded, error } = vizState;
30
34
  const { filters = {}, parameters = {} } = data;
@@ -96,12 +100,12 @@ const Tableau = forwardRef((props, ref) => {
96
100
  toolbarPosition = 'Top',
97
101
  breakpointUrls = [],
98
102
  tableau_vis_url,
99
- with_note,
100
- with_sources,
101
- with_more_info,
102
- with_download,
103
- with_share,
104
- tableau_height = '700',
103
+ with_note = true,
104
+ with_sources = true,
105
+ with_more_info = true,
106
+ with_download = true,
107
+ with_share = true,
108
+ tableau_height,
105
109
  } = data;
106
110
  const device = useMemo(
107
111
  () => getDevice(breakpoints, screen.page?.width || Infinity),
@@ -412,25 +416,27 @@ const Tableau = forwardRef((props, ref) => {
412
416
  clearData={clearData}
413
417
  />
414
418
  <div
415
- style={{ height: tableau_height + 'px' }}
419
+ style={{ height: tableau_height ? tableau_height + 'px' : '100%' }}
416
420
  className={cx('tableau', `tableau-${version}`, {
417
421
  'tableau-autoscale': autoScale,
418
422
  })}
419
423
  ref={vizEl}
420
424
  />
421
- <div className="visualization-info-container">
422
- <div className="visualization-info">
423
- {with_note && loaded && <FigureNote note={figure_note || []} />}
424
- {with_sources && loaded && <Sources sources={sources} />}
425
- {with_more_info && loaded && (
426
- <MoreInfoLink contentTypeLink={tableau_vis_url} />
427
- )}
425
+ {loaded && (
426
+ <div className="visualization-toolbar">
427
+ <div className="left-col">
428
+ {with_note && <FigureNote note={figure_note || []} />}
429
+ {with_sources && <Sources sources={sources} />}
430
+ {with_more_info && <MoreInfo href={tableau_vis_url || data.url} />}
431
+ </div>
432
+ <div className="right-col">
433
+ {with_download && loaded && <Download viz={viz.current} />}
434
+ {with_share && loaded && (
435
+ <Share href={tableau_vis_url || data.url} />
436
+ )}
437
+ </div>
428
438
  </div>
429
- <div className="visualization-info">
430
- {with_download && loaded && <Download viz={viz.current} />}
431
- {with_share && loaded && <Share contentTypeLink={tableau_vis_url} />}
432
- </div>
433
- </div>
439
+ )}
434
440
  </div>
435
441
  );
436
442
  });
@@ -3,37 +3,37 @@ import { Popup } from 'semantic-ui-react';
3
3
  import cx from 'classnames';
4
4
 
5
5
  const Download = ({ viz }) => {
6
- const [expanded, setExpanded] = React.useState(false);
6
+ const [open, setOpen] = React.useState(false);
7
7
  const popupRef = React.useRef();
8
8
 
9
9
  return (
10
10
  <Popup
11
- popper={{ id: 'tableau-download-popup' }}
12
- trigger={
13
- <div className="tableau-download-container">
14
- <button className={cx('tableau-download-button', { expanded })}>
15
- Download <i class="ri-download-fill"></i>
16
- </button>
17
- </div>
18
- }
11
+ popper={{ id: 'vis-toolbar-popup', className: 'download-popup' }}
19
12
  position="bottom left"
20
13
  on="click"
14
+ open={open}
21
15
  onClose={() => {
22
- setExpanded(false);
16
+ setOpen(false);
23
17
  }}
24
18
  onOpen={() => {
25
- setExpanded(true);
19
+ setOpen(true);
26
20
  }}
27
21
  ref={popupRef}
28
- >
29
- <ul className="no-bullets">
30
- <li>
31
- Data formats
32
- <div className="visualization-wrapper">
33
- <div className="visualization-info">
34
- <div>
22
+ trigger={
23
+ <div className="tableau-download-container">
24
+ <button className={cx('trigger-button', { open })}>
25
+ <i className="ri-download-fill" />
26
+ Download
27
+ </button>
28
+ </div>
29
+ }
30
+ content={
31
+ <>
32
+ <div className="item">
33
+ <span className="label">Data formats</span>
34
+ <div className="types">
35
+ <div className="type">
35
36
  <button
36
- className="tableau-download-button tableau-format-download"
37
37
  onClick={() => {
38
38
  viz.showExportCrossTabDialog();
39
39
  popupRef.current.triggerRef.current.click();
@@ -42,9 +42,8 @@ const Download = ({ viz }) => {
42
42
  <span>CSV</span>
43
43
  </button>
44
44
  </div>
45
- <div>
45
+ <div className="type">
46
46
  <button
47
- className="tableau-download-button tableau-format-download"
48
47
  onClick={() => {
49
48
  viz.exportCrossTabToExcel();
50
49
  popupRef.current.triggerRef.current.click();
@@ -55,14 +54,11 @@ const Download = ({ viz }) => {
55
54
  </div>
56
55
  </div>
57
56
  </div>
58
- </li>
59
- <li>
60
- Image formats
61
- <div className="visualization-wrapper">
62
- <div className="visualization-info">
63
- <div>
57
+ <div className="item">
58
+ <span className="label">Image formats</span>
59
+ <div className="types">
60
+ <div className="type">
64
61
  <button
65
- className="tableau-download-button tableau-format-download"
66
62
  onClick={() => {
67
63
  viz.showExportImageDialog();
68
64
  popupRef.current.triggerRef.current.click();
@@ -73,14 +69,11 @@ const Download = ({ viz }) => {
73
69
  </div>
74
70
  </div>
75
71
  </div>
76
- </li>
77
- <li>
78
- Other formats
79
- <div className="visualization-wrapper">
80
- <div className="visualization-info">
81
- <div>
72
+ <div className="item">
73
+ <span className="label">Other formats</span>
74
+ <div className="types">
75
+ <div className="type">
82
76
  <button
83
- className="tableau-download-button tableau-format-download"
84
77
  onClick={() => {
85
78
  viz.showExportPDFDialog();
86
79
  popupRef.current.triggerRef.current.click();
@@ -91,9 +84,9 @@ const Download = ({ viz }) => {
91
84
  </div>
92
85
  </div>
93
86
  </div>
94
- </li>
95
- </ul>
96
- </Popup>
87
+ </>
88
+ }
89
+ />
97
90
  );
98
91
  };
99
92
 
@@ -0,0 +1,2 @@
1
+ export { default as Download } from './Download';
2
+ export { default as JsonCodeSnippet } from './JsonCodeSnippet';
@@ -17,6 +17,10 @@ const VisualizationView = (props) => {
17
17
  <Tableau
18
18
  data={{
19
19
  ...tableau_visualization,
20
+ with_note: false,
21
+ with_sources: false,
22
+ with_more_info: false,
23
+ with_share: false,
20
24
  with_download: true,
21
25
  }}
22
26
  breakpoints={
@@ -3,14 +3,19 @@ import config from '@plone/volto/registry';
3
3
 
4
4
  export default function VisualizationViewWidget(props) {
5
5
  const { value = {} } = props;
6
+
6
7
  return (
7
8
  <Tableau
8
9
  data={{
9
10
  ...value,
11
+ with_note: false,
12
+ with_sources: false,
13
+ with_more_info: true,
14
+ with_share: true,
10
15
  with_download: true,
11
16
  }}
12
17
  breakpoints={
13
- config.blocks.blocksConfig.embed_tableau_visualization.breakpoints
18
+ config.blocks.blocksConfig?.embed_tableau_visualization?.breakpoints
14
19
  }
15
20
  />
16
21
  );
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import '@testing-library/jest-dom/extend-expect';
4
+ import { Provider } from 'react-redux';
5
+ import configureStore from 'redux-mock-store';
6
+ import VisualizationViewWidget from './VisualizationViewWidget';
7
+
8
+ const mockStore = configureStore([]);
9
+ const store = mockStore({});
10
+
11
+ jest.mock('@plone/volto/components', () => ({
12
+ Icon: ({ children }) => <img alt="incon">{children}</img>,
13
+ Toast: ({ children }) => <p>{children}</p>,
14
+ }));
15
+
16
+ describe('VisualizationViewWidget', () => {
17
+ it('should render the component', () => {
18
+ const data = {
19
+ url: 'http://localhost:3000/tableau-ct',
20
+ with_download: true,
21
+ with_more_info: true,
22
+ with_note: true,
23
+ with_share: true,
24
+ };
25
+
26
+ const { container } = render(
27
+ <Provider store={store}>
28
+ <VisualizationViewWidget data={data} />
29
+ </Provider>,
30
+ );
31
+ expect(container.querySelector('.tableau-wrapper')).toBeInTheDocument();
32
+ });
33
+ });
@@ -72,11 +72,18 @@ const VisualizationWidget = (props) => {
72
72
  >
73
73
  <Tableau
74
74
  ref={viz}
75
- data={value}
75
+ data={{
76
+ ...(value || {}),
77
+ with_note: false,
78
+ with_sources: false,
79
+ with_more_info: false,
80
+ with_share: false,
81
+ with_download: false,
82
+ }}
76
83
  mode="edit"
77
84
  breakpoints={
78
- config.blocks.blocksConfig.embed_tableau_visualization
79
- .breakpoints
85
+ config.blocks.blocksConfig?.embed_tableau_visualization
86
+ ?.breakpoints
80
87
  }
81
88
  extraOptions={extraOptions}
82
89
  setVizState={setVizState}
@@ -116,9 +123,14 @@ const VisualizationWidget = (props) => {
116
123
  data={{
117
124
  ...props.value,
118
125
  autoScale: true,
126
+ with_note: false,
127
+ with_sources: false,
128
+ with_more_info: false,
129
+ with_share: false,
130
+ with_download: false,
119
131
  }}
120
132
  breakpoints={
121
- config.blocks.blocksConfig.embed_tableau_visualization.breakpoints
133
+ config.blocks.blocksConfig?.embed_tableau_visualization?.breakpoints
122
134
  }
123
135
  extraOptions={extraOptions}
124
136
  />
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import '@testing-library/jest-dom/extend-expect';
4
+ import { Provider } from 'react-redux';
5
+ import configureStore from 'redux-mock-store';
6
+ import VisualizationWidget from './VisualizationWidget';
7
+
8
+ const mockStore = configureStore([]);
9
+ const store = mockStore({});
10
+
11
+ jest.mock('@plone/volto/components', () => ({
12
+ FormFieldWrapper: jest.fn(({ children }) => <>{children}</>),
13
+ InlineForm: jest.fn(() => <div>Mocked InlineForm</div>),
14
+ Icon: ({ children }) => <img alt="incon">{children}</img>,
15
+ Toast: ({ children }) => <p>{children}</p>,
16
+ }));
17
+
18
+ describe('VisualizationWidget', () => {
19
+ it('should render the component', () => {
20
+ const data = {
21
+ value: {
22
+ url: 'http://localhost:3000/tableau-ct',
23
+ with_download: true,
24
+ with_more_info: true,
25
+ with_note: true,
26
+ with_share: true,
27
+ hideTabs: false,
28
+ staticParameters: [
29
+ {
30
+ '@id': '1f050748-c020-4a48-8109-e99a25bf558d',
31
+ field: 'Tableau field',
32
+ value: 'Tableau value',
33
+ },
34
+ ],
35
+ },
36
+ };
37
+
38
+ const { container } = render(
39
+ <Provider store={store}>
40
+ <VisualizationWidget {...data} />
41
+ </Provider>,
42
+ );
43
+ expect(container.querySelector('.tableau-wrapper')).toBeInTheDocument();
44
+ });
45
+ });
@@ -38,7 +38,8 @@ const staticParameters = {
38
38
  };
39
39
 
40
40
  const breakpointUrlSchema = (config) => {
41
- const breakpoints = config.blocks.blocksConfig.tableau_block.breakpoints;
41
+ const breakpoints =
42
+ config.blocks.blocksConfig?.tableau_block?.breakpoints || {};
42
43
 
43
44
  return {
44
45
  title: 'URL',
package/src/helpers.js CHANGED
@@ -1,5 +1,3 @@
1
- import React from 'react';
2
-
3
1
  export const loadTableauScript = (callback, version) => {
4
2
  if (!__CLIENT__) return;
5
3
  const source = `https://public.tableau.com/javascripts/api/tableau-${version}.min.js`;
@@ -34,25 +32,3 @@ export const loadTableauScript = (callback, version) => {
34
32
  // https://public.tableau.com/javascripts/api/tableau-2.2.2.min.js
35
33
  // https://public.tableau.com/javascripts/api/tableau-2.1.2.min.js
36
34
  // https://public.tableau.com/javascripts/api/tableau-2.0.3.min.js
37
-
38
- export const useCopyToClipboard = (text) => {
39
- const [copyStatus, setCopyStatus] = React.useState('inactive');
40
- const copy = React.useCallback(() => {
41
- navigator.clipboard.writeText(text).then(
42
- () => setCopyStatus('copied'),
43
- () => setCopyStatus('failed'),
44
- );
45
- }, [text]);
46
-
47
- React.useEffect(() => {
48
- if (copyStatus === 'inactive') {
49
- return;
50
- }
51
-
52
- const timeout = setTimeout(() => setCopyStatus('inactive'), 3000);
53
-
54
- return () => clearTimeout(timeout);
55
- }, [copyStatus]);
56
-
57
- return [copyStatus, copy];
58
- };
@@ -1,22 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
4
-
5
- import Download from './Download';
6
-
7
- window.URL.createObjectURL = jest.fn(() => 'test');
8
-
9
- jest.mock('@plone/volto/components', () => ({
10
- Icon: ({ children }) => <img alt="incon">{children}</img>,
11
- }));
12
-
13
- describe('Download', () => {
14
- const viz = {};
15
-
16
- it('should render the component', () => {
17
- const { container } = render(<Download viz={viz} />);
18
- expect(
19
- container.querySelector('.tableau-download-container'),
20
- ).toBeInTheDocument();
21
- });
22
- });
@@ -1,43 +0,0 @@
1
- import React from 'react';
2
- import cx from 'classnames';
3
- import { Popup } from 'semantic-ui-react';
4
- import {
5
- serializeNodes,
6
- serializeNodesToText,
7
- } from '@plone/volto-slate/editor/render';
8
- import { isArray } from 'lodash';
9
-
10
- export const serializeText = (note) => {
11
- if (!serializeNodesToText(note))
12
- return <p>There are no notes set for this visualization</p>;
13
- return isArray(note) ? serializeNodes(note) : note;
14
- };
15
-
16
- const FigureNote = ({ note = [] }) => {
17
- const [expanded, setExpanded] = React.useState(false);
18
-
19
- return (
20
- <div className="tableau-note-container">
21
- <Popup
22
- position="bottom left"
23
- popper={{ id: 'tableau-note-popup' }}
24
- trigger={
25
- <button className={cx('tableau-note-button', { expanded })}>
26
- Note
27
- </button>
28
- }
29
- on="click"
30
- onClose={() => {
31
- setExpanded(false);
32
- }}
33
- onOpen={() => {
34
- setExpanded(true);
35
- }}
36
- >
37
- <Popup.Content>{serializeText(note)}</Popup.Content>
38
- </Popup>
39
- </div>
40
- );
41
- };
42
-
43
- export default FigureNote;
@@ -1,25 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
4
-
5
- import FigureNote from './FigureNote';
6
-
7
- window.URL.createObjectURL = jest.fn(() => 'test');
8
-
9
- const slateEditor = require('@plone/volto-slate/editor/render');
10
- slateEditor.serializeNodes = jest.fn();
11
-
12
- jest.mock('@plone/volto-slate/editor/render', () => ({
13
- serializeNodesToText: ({ note = [] }) => note,
14
- }));
15
-
16
- describe('FigureNote', () => {
17
- const note = [];
18
-
19
- it('should render the component', () => {
20
- const { container } = render(<FigureNote note={note} />);
21
- expect(
22
- container.querySelector('.tableau-note-container'),
23
- ).toBeInTheDocument();
24
- });
25
- });
@@ -1,22 +0,0 @@
1
- import React from 'react';
2
- import cx from 'classnames';
3
- import { UniversalLink } from '@plone/volto/components';
4
-
5
- const Link = ({ children, ...props }) => {
6
- if (props.href) {
7
- return <UniversalLink {...props}>{children}</UniversalLink>;
8
- }
9
- return <span {...props}>{children}</span>;
10
- };
11
-
12
- const MoreInfoLink = ({ contentTypeLink }) => {
13
- return (
14
- <Link href={contentTypeLink}>
15
- <button className={cx('tableau-more-info-button')}>
16
- More info <i class="ri-external-link-line"></i>
17
- </button>
18
- </Link>
19
- );
20
- };
21
-
22
- export default MoreInfoLink;
@@ -1,24 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
4
-
5
- import MoreInfoLink from './MoreInfoLink';
6
-
7
- window.URL.createObjectURL = jest.fn(() => 'test');
8
-
9
- jest.mock('@plone/volto/components', () => ({
10
- UniversalLink: ({ children }) => <div>{children}</div>,
11
- }));
12
-
13
- describe('MoreInfoLink', () => {
14
- const contentTypeLink = '/tableau-content-type';
15
-
16
- it('should render the component', () => {
17
- const { container } = render(
18
- <MoreInfoLink contentTypeLink={contentTypeLink} />,
19
- );
20
- expect(
21
- container.querySelector('.tableau-more-info-button'),
22
- ).toBeInTheDocument();
23
- });
24
- });
@@ -1,69 +0,0 @@
1
- import React from 'react';
2
- import { Popup, Input, Button } from 'semantic-ui-react';
3
- import { useCopyToClipboard } from '../../helpers.js';
4
- import cx from 'classnames';
5
-
6
- const Share = ({ contentTypeLink = '' }) => {
7
- const [expanded, setExpanded] = React.useState(false);
8
- const popupRef = React.useRef();
9
-
10
- const CopyUrlButton = ({ className, url, buttonText }) => {
11
- const [copyUrlStatus, copyUrl] = useCopyToClipboard(url);
12
-
13
- if (copyUrlStatus === 'copied') {
14
- buttonText = 'Copied!';
15
- } else if (copyUrlStatus === 'failed') {
16
- buttonText = 'Copy failed. Please try again.';
17
- }
18
-
19
- return (
20
- <Button
21
- primary
22
- onClick={copyUrl}
23
- className={cx('copy-button', className)}
24
- >
25
- {buttonText}
26
- </Button>
27
- );
28
- };
29
-
30
- return (
31
- <Popup
32
- popper={{ id: 'tableau-share-popup' }}
33
- trigger={
34
- <div className="tableau-share-container">
35
- <button className={cx('tableau-share-button', { expanded })}>
36
- <span>Share</span>
37
- <i class="ri-share-fill"></i>
38
- </button>
39
- </div>
40
- }
41
- position="bottom left"
42
- on="click"
43
- onClose={() => {
44
- setExpanded(false);
45
- }}
46
- onOpen={() => {
47
- setExpanded(true);
48
- }}
49
- ref={popupRef}
50
- >
51
- <div>
52
- <span className="tableau-share-popup-text">Copy link</span>
53
- <div className="tableau-share-popup-container">
54
- <Input
55
- className="tableau-share-link"
56
- defaultValue={contentTypeLink}
57
- />
58
- <CopyUrlButton
59
- className="tableau-copy-button"
60
- url={contentTypeLink}
61
- buttonText="Copy"
62
- />
63
- </div>
64
- </div>
65
- </Popup>
66
- );
67
- };
68
-
69
- export default Share;
@@ -1,67 +0,0 @@
1
- import React from 'react';
2
- import cx from 'classnames';
3
- import { Popup } from 'semantic-ui-react';
4
- import { UniversalLink } from '@plone/volto/components';
5
-
6
- const Link = ({ children, ...props }) => {
7
- if (props.href) {
8
- return <UniversalLink {...props}>{children}</UniversalLink>;
9
- }
10
- return <span {...props}>{children}</span>;
11
- };
12
-
13
- const Source = ({ source }) => {
14
- return (
15
- <>
16
- <Link className="embed-sources-param-title" href={source.link}>
17
- {source.title}
18
- </Link>
19
- ,
20
- <span className="embed-sources-param-description">
21
- {source.organisation}
22
- </span>
23
- </>
24
- );
25
- };
26
-
27
- const SourcesWidget = ({ sources }) => {
28
- const [expanded, setExpanded] = React.useState(false);
29
-
30
- return (
31
- <div className="tableau-sources-container">
32
- <Popup
33
- content={
34
- sources?.length ? (
35
- <ol className="sources-list">
36
- {sources?.map((source, index) => {
37
- return (
38
- <li key={index}>
39
- <Source source={source} />
40
- </li>
41
- );
42
- })}
43
- </ol>
44
- ) : (
45
- <p>Data provenance is not set for this visualization.</p>
46
- )
47
- }
48
- position="bottom left"
49
- popper={{ id: 'tableau-sources-popup' }}
50
- trigger={
51
- <button className={cx('tableau-sources-button', { expanded })}>
52
- Sources
53
- </button>
54
- }
55
- on="click"
56
- onClose={() => {
57
- setExpanded(false);
58
- }}
59
- onOpen={() => {
60
- setExpanded(true);
61
- }}
62
- />
63
- </div>
64
- );
65
- };
66
-
67
- export default SourcesWidget;