@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 +25 -1
- package/Dockerfile +1 -1
- package/Jenkinsfile +153 -135
- package/Makefile +35 -2
- package/README.md +2 -0
- package/cypress.config.js +2 -2
- package/docs/volto-tableau.gif +0 -0
- package/package.json +1 -1
- package/src/Blocks/EmbedTableauVisualization/View.jsx +6 -8
- package/src/Blocks/EmbedTableauVisualization/View.test.jsx +1 -1
- package/src/Tableau/Tableau.jsx +31 -25
- package/src/Utils/{Download/Download.jsx → Download.jsx} +31 -38
- package/src/Utils/index.js +2 -0
- package/src/Views/VisualizationView.jsx +4 -0
- package/src/Widgets/VisualizationViewWidget.jsx +6 -1
- package/src/Widgets/VisualizationViewWidget.test.jsx +33 -0
- package/src/Widgets/VisualizationWidget.jsx +16 -4
- package/src/Widgets/VisualizationWidget.test.jsx +45 -0
- package/src/Widgets/schema.js +2 -1
- package/src/helpers.js +0 -24
- package/src/Utils/Download/Download.test.jsx +0 -22
- package/src/Utils/FigureNote/FigureNote.jsx +0 -43
- package/src/Utils/FigureNote/FigureNote.test.jsx +0 -25
- package/src/Utils/MoreInfoLink/MoreInfoLink.jsx +0 -22
- package/src/Utils/MoreInfoLink/MoreInfoLink.test.jsx +0 -24
- package/src/Utils/Share/Share.jsx +0 -69
- package/src/Utils/Sources/Sources.jsx +0 -67
- /package/src/Utils/{JsonCodeSnippet/JsonCodeSnippet.jsx → JsonCodeSnippet.jsx} +0 -0
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
|
-
### [
|
|
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
package/Jenkinsfile
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
pipeline {
|
|
2
|
-
agent
|
|
2
|
+
agent {
|
|
3
|
+
node { label 'docker-host' }
|
|
4
|
+
}
|
|
3
5
|
|
|
4
6
|
environment {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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('
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
if (check_result == 0) {
|
|
48
|
+
env.SKIP_TESTS = 'yes'
|
|
49
|
+
}
|
|
57
50
|
}
|
|
58
|
-
|
|
59
|
-
)
|
|
51
|
+
}
|
|
60
52
|
}
|
|
61
53
|
}
|
|
62
54
|
|
|
63
|
-
stage('
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
sh '''
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+

|
|
24
|
+
|
|
23
25
|
## Getting started
|
|
24
26
|
|
|
25
27
|
### Try volto-tableau with Docker
|
package/cypress.config.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -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
|
|
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-
|
|
32
|
+
<div className="embed-tableau">
|
|
35
33
|
<PrivacyProtection
|
|
36
34
|
{...props}
|
|
37
35
|
data={{ ...data, url: tableau_visualization?.url }}
|
package/src/Tableau/Tableau.jsx
CHANGED
|
@@ -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
|
|
19
|
-
|
|
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
|
|
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
|
-
|
|
422
|
-
<div className="visualization-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
<
|
|
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
|
-
|
|
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 [
|
|
6
|
+
const [open, setOpen] = React.useState(false);
|
|
7
7
|
const popupRef = React.useRef();
|
|
8
8
|
|
|
9
9
|
return (
|
|
10
10
|
<Popup
|
|
11
|
-
popper={{ id: '
|
|
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
|
-
|
|
16
|
+
setOpen(false);
|
|
23
17
|
}}
|
|
24
18
|
onOpen={() => {
|
|
25
|
-
|
|
19
|
+
setOpen(true);
|
|
26
20
|
}}
|
|
27
21
|
ref={popupRef}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
</>
|
|
88
|
+
}
|
|
89
|
+
/>
|
|
97
90
|
);
|
|
98
91
|
};
|
|
99
92
|
|
|
@@ -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
|
|
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={
|
|
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
|
|
79
|
-
|
|
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
|
|
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
|
+
});
|
package/src/Widgets/schema.js
CHANGED
|
@@ -38,7 +38,8 @@ const staticParameters = {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const breakpointUrlSchema = (config) => {
|
|
41
|
-
const 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;
|
|
File without changes
|