diff --git a/Jenkinsfile b/Jenkinsfile
index eee82b2..3a15cb6 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -149,7 +149,7 @@ pipeline {
def nodeJS = tool 'NodeJS11';
withSonarQubeEnv('Sonarqube') {
sh '''sed -i "s|/plone/instance/src/$GIT_NAME|$(pwd)|g" coverage.xml'''
- sh "export PATH=$PATH:${scannerHome}/bin:${nodeJS}/bin; sonar-scanner -Dsonar.python.xunit.skipDetails=true -Dsonar.python.xunit.reportPath=xunit-reports/*.xml -Dsonar.python.coverage.reportPath=coverage.xml -Dsonar.sources=./eea -Dsonar.projectKey=$GIT_NAME-$BRANCH_NAME -Dsonar.projectVersion=$BRANCH_NAME-$BUILD_NUMBER"
+ sh "export PATH=$PATH:${scannerHome}/bin:${nodeJS}/bin; sonar-scanner -Dsonar.python.xunit.skipDetails=true -Dsonar.python.xunit.reportPath=xunit-reports/*.xml -Dsonar.python.coverage.reportPaths=coverage.xml -Dsonar.sources=./eea -Dsonar.projectKey=$GIT_NAME-$BRANCH_NAME -Dsonar.projectVersion=$BRANCH_NAME-$BUILD_NUMBER"
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'''
}
}
diff --git a/docs/HISTORY.txt b/docs/HISTORY.txt
index f70a70b..de9b3fa 100644
--- a/docs/HISTORY.txt
+++ b/docs/HISTORY.txt
@@ -1,6 +1,15 @@
Changelog
=========
+13.0 - (2021-06-16)
+---------------------------
+* Feature: Added GET RestAPI endpoint for Daviz Charts @charts
+ [avoinea refs #126277]
+* Feature: Added GET RestAPI endpoint for Data Table @table
+ [avoinea refs #133973]
+* Feature: Added GET RestAPI endpoint for IDataProvenance @provenances
+ [iulianpetchesi refs #123935]
+
12.7 - (2020-12-16)
---------------------------
* Change: load google charts version 49 in order to avoid warning
diff --git a/eea/app/visualization/common.zcml b/eea/app/visualization/common.zcml
index 7f9b19c..fb3c7ca 100644
--- a/eea/app/visualization/common.zcml
+++ b/eea/app/visualization/common.zcml
@@ -11,6 +11,7 @@
+
diff --git a/eea/app/visualization/restapi/__init__.py b/eea/app/visualization/restapi/__init__.py
new file mode 100644
index 0000000..637fa62
--- /dev/null
+++ b/eea/app/visualization/restapi/__init__.py
@@ -0,0 +1,2 @@
+""" RestAPI
+"""
\ No newline at end of file
diff --git a/eea/app/visualization/restapi/charts/__init__.py b/eea/app/visualization/restapi/charts/__init__.py
new file mode 100644
index 0000000..637fa62
--- /dev/null
+++ b/eea/app/visualization/restapi/charts/__init__.py
@@ -0,0 +1,2 @@
+""" RestAPI
+"""
\ No newline at end of file
diff --git a/eea/app/visualization/restapi/charts/configure.zcml b/eea/app/visualization/restapi/charts/configure.zcml
new file mode 100644
index 0000000..b917911
--- /dev/null
+++ b/eea/app/visualization/restapi/charts/configure.zcml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/eea/app/visualization/restapi/charts/get.py b/eea/app/visualization/restapi/charts/get.py
new file mode 100644
index 0000000..4393e96
--- /dev/null
+++ b/eea/app/visualization/restapi/charts/get.py
@@ -0,0 +1,52 @@
+""" Charts
+"""
+from plone.restapi.interfaces import IExpandableElement
+from plone.restapi.serializer.converters import json_compatible
+from plone.restapi.services import Service
+from Products.CMFPlone.interfaces import IPloneSiteRoot
+from zope.component import adapter, queryMultiAdapter
+from zope.interface import implementer
+from zope.interface import Interface
+from eea.app.visualization.interfaces import IVisualizationEnabled
+
+@implementer(IExpandableElement)
+@adapter(IVisualizationEnabled, Interface)
+class Charts(object):
+ """ Charts
+ """
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self, expand=False):
+ result = {"charts": {"@id": "{}/@charts".format(
+ self.context.absolute_url())}}
+
+ if not expand:
+ return result
+
+ if IPloneSiteRoot.providedBy(self.context):
+ return result
+
+ view = queryMultiAdapter((
+ self.context, self.request),
+ name='daviz-view.html'
+ )
+
+ if not view:
+ return result
+
+ result["charts"]["items"] = []
+ for tab in view.tabs:
+ result["charts"]["items"].append(json_compatible(tab))
+ return result
+
+
+class ChartsGet(Service):
+ """Get charts information"""
+
+ def reply(self):
+ """ Reply
+ """
+ info = Charts(self.context, self.request)
+ return info(expand=True)["charts"]
diff --git a/eea/app/visualization/restapi/configure.zcml b/eea/app/visualization/restapi/configure.zcml
new file mode 100644
index 0000000..88dcf3f
--- /dev/null
+++ b/eea/app/visualization/restapi/configure.zcml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/eea/app/visualization/restapi/data/__init__.py b/eea/app/visualization/restapi/data/__init__.py
new file mode 100644
index 0000000..637fa62
--- /dev/null
+++ b/eea/app/visualization/restapi/data/__init__.py
@@ -0,0 +1,2 @@
+""" RestAPI
+"""
\ No newline at end of file
diff --git a/eea/app/visualization/restapi/data/configure.zcml b/eea/app/visualization/restapi/data/configure.zcml
new file mode 100644
index 0000000..4418193
--- /dev/null
+++ b/eea/app/visualization/restapi/data/configure.zcml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/eea/app/visualization/restapi/data/provenance/__init__.py b/eea/app/visualization/restapi/data/provenance/__init__.py
new file mode 100644
index 0000000..637fa62
--- /dev/null
+++ b/eea/app/visualization/restapi/data/provenance/__init__.py
@@ -0,0 +1,2 @@
+""" RestAPI
+"""
\ No newline at end of file
diff --git a/eea/app/visualization/restapi/data/provenance/configure.zcml b/eea/app/visualization/restapi/data/provenance/configure.zcml
new file mode 100644
index 0000000..3742c49
--- /dev/null
+++ b/eea/app/visualization/restapi/data/provenance/configure.zcml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eea/app/visualization/restapi/data/provenance/get.py b/eea/app/visualization/restapi/data/provenance/get.py
new file mode 100644
index 0000000..c785747
--- /dev/null
+++ b/eea/app/visualization/restapi/data/provenance/get.py
@@ -0,0 +1,68 @@
+""" RestAPI GET enpoints
+"""
+from zope.publisher.interfaces import IPublishTraverse
+from zope.interface import implementer
+from zope.interface import Interface
+from zope.component import adapter, queryAdapter
+from plone.restapi.services import Service
+from plone.restapi.serializer.converters import json_compatible
+from plone.restapi.interfaces import IExpandableElement
+from eea.app.visualization.interfaces import IDataProvenance
+from eea.app.visualization.interfaces import IMultiDataProvenance
+from eea.app.visualization.interfaces import IVisualizationEnabled
+from Products.CMFPlone.interfaces import IPloneSiteRoot
+
+
+@implementer(IExpandableElement)
+@adapter(IVisualizationEnabled, Interface)
+class DataProvenance(object):
+ """ Get data provenances
+ """
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self, expand=False):
+ result = {"provenances": {
+ "@id": "{}/@provenances".format(self.context.absolute_url()),
+ }}
+
+ if not expand:
+ return result
+
+ if IPloneSiteRoot.providedBy(self.context):
+ return result
+
+ result['provenances']['items'] = []
+
+ # Get IMultiDataProvenance
+ multi = queryAdapter(self.context, IMultiDataProvenance)
+ if multi:
+ provenances = json_compatible(multi.provenances)
+ result['provenances']['items'].extend(provenances)
+
+ source = queryAdapter(self.context, IDataProvenance)
+ if (getattr(source, 'link', None) and
+ getattr(source, 'title', None) and
+ getattr(source, 'owner', None)):
+ provenance = {
+ "title": json_compatible(source.title),
+ "owner": json_compatible(source.owner),
+ "link": json_compatible(source.link)
+ }
+
+ if getattr(source, "copyrights", None):
+ provenance['copyrights'] = json_compatible(source.copyrights)
+
+ result['provenances']['items'].append(provenance)
+ return result
+
+
+@implementer(IPublishTraverse)
+class Get(Service):
+ """GET"""
+
+ def reply(self):
+ """Reply"""
+ info = DataProvenance(self.context, self.request)
+ return info(expand=True)["provenances"]
diff --git a/eea/app/visualization/restapi/data/provenance/post.py b/eea/app/visualization/restapi/data/provenance/post.py
new file mode 100644
index 0000000..8a0e24e
--- /dev/null
+++ b/eea/app/visualization/restapi/data/provenance/post.py
@@ -0,0 +1,126 @@
+""" RestAPI enpoint POST
+"""
+from zope.publisher.interfaces import IPublishTraverse
+from zope.interface import implementer, alsoProvides
+from zope.interface import Interface
+from zope.component import adapter, queryAdapter
+from plone.restapi.services import Service
+from plone.restapi.interfaces import IExpandableElement
+from plone.restapi.deserializer import json_body
+from eea.app.visualization.interfaces import IDataProvenance, IMultiDataProvenance
+from Products.CMFPlone.interfaces import IPloneSiteRoot
+
+
+import plone.protect.interfaces
+
+
+@implementer(IExpandableElement)
+@adapter(Interface, Interface)
+class DataProvenance(object):
+ """ Set DataProvenance
+ """
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self, data=[]):
+ if IPloneSiteRoot.providedBy(self.context):
+ self.request.response.setStatus(400)
+ return dict(
+ error=dict(
+ type="BadRequest",
+ message="Tried to set data provenances on site root.",
+ )
+ )
+
+ source = queryAdapter(self.context, IDataProvenance)
+ multi = queryAdapter(self.context, IMultiDataProvenance)
+ if not source and not multi:
+ self.request.response.setStatus(400)
+ return dict(
+ error=dict(
+ type="BadRequest",
+ message="Can't adapt IDataProvenance/IMultiDataProvenance to context.",
+ )
+ )
+
+ multi_provenances = []
+ provenances = data.get('provenances', [])
+ if len(provenances) < 1:
+ self.request.response.setStatus(400)
+ return dict(
+ error=dict(
+ type="BadRequest",
+ message="No data provenances provided.",
+ )
+ )
+
+ # differentiate between multi and normal provenances
+ if not len(provenances) == 1:
+ if not multi:
+ multi_provenances = []
+ if not source:
+ multi_provenances = provenances
+ provenances = []
+ if multi and source:
+ multi_provenances = [prov for prov in provenances if prov.get('multi', True) != 'False']
+ provenances = [prov for prov in provenances if prov not in multi_provenances]
+
+ # if more than one non multi provenance is given, save the last one
+ if len(provenances) > 1:
+ provenances = [provenances[-1]]
+
+ # True if len == 1
+ if len(provenances) == 1:
+ data = provenances[0]
+ source.title = data["title"]
+ source.owner = data["owner"]
+ source.link = data["link"]
+
+ if "copyrights" in data and hasattr(source, "copyrights"):
+ copyrights = data['copyrights']
+
+ if len(copyrights) > 2 and isinstance(copyrights, list):
+ self.request.response.setStatus(400)
+ return dict(
+ error=dict(
+ type="BadRequest",
+ message="Copyrights must be a list with <= 2 items or string.",
+ )
+ )
+
+ if isinstance(copyrights, (str, unicode)):
+ source.copyrights = copyrights
+ else:
+ source.copyrights = tuple(copyrights)
+ elif "copyrights" in data:
+ self.request.response.setStatus(400)
+ return dict(
+ error=dict(
+ type="BadRequest",
+ message="Can't set copyrights, not a blob object.",
+ )
+ )
+
+ # multi provenances
+ if len(multi_provenances) > 0:
+ multi.provenances = multi_provenances
+
+ self.request.response.setStatus(200)
+ return dict(message="Successfully set data provenance")
+
+
+@implementer(IPublishTraverse)
+class Post(Service):
+ """POST"""
+
+ def reply(self):
+ """Reply"""
+ data = json_body(self.request)
+
+ # Disable CSRF protection
+ if "IDisableCSRFProtection" in dir(plone.protect.interfaces):
+ alsoProvides(self.request, plone.protect.interfaces.IDisableCSRFProtection)
+
+ post = DataProvenance(self.context, self.request)
+ return post(data)
\ No newline at end of file
diff --git a/eea/app/visualization/restapi/data/table/__init__.py b/eea/app/visualization/restapi/data/table/__init__.py
new file mode 100644
index 0000000..637fa62
--- /dev/null
+++ b/eea/app/visualization/restapi/data/table/__init__.py
@@ -0,0 +1,2 @@
+""" RestAPI
+"""
\ No newline at end of file
diff --git a/eea/app/visualization/restapi/data/table/configure.zcml b/eea/app/visualization/restapi/data/table/configure.zcml
new file mode 100644
index 0000000..b88da2b
--- /dev/null
+++ b/eea/app/visualization/restapi/data/table/configure.zcml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/eea/app/visualization/restapi/data/table/get.py b/eea/app/visualization/restapi/data/table/get.py
new file mode 100644
index 0000000..d420a06
--- /dev/null
+++ b/eea/app/visualization/restapi/data/table/get.py
@@ -0,0 +1,53 @@
+""" RestAPI GET enpoints
+"""
+from zope.publisher.interfaces import IPublishTraverse
+from zope.interface import implementer
+from zope.interface import Interface
+from zope.component import adapter, queryMultiAdapter
+from plone.restapi.services import Service
+from plone.restapi.serializer.converters import json_compatible
+from plone.restapi.interfaces import IExpandableElement
+from Products.CMFPlone.interfaces import IPloneSiteRoot
+from eea.app.visualization.interfaces import IVisualizationEnabled
+
+
+@implementer(IExpandableElement)
+@adapter(IVisualizationEnabled, Interface)
+class DataTable(object):
+ """ Get data table
+ """
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self, expand=False):
+ result = {"table": {
+ "@id": "{}/@table".format(self.context.absolute_url()),
+ }}
+
+ if not expand:
+ return result
+
+ if IPloneSiteRoot.providedBy(self.context):
+ return result
+
+ view = queryMultiAdapter((
+ self.context, self.request),
+ name='download.table'
+ )
+
+ if not view:
+ return result
+
+ result['table'].update(json_compatible(view.data))
+ return result
+
+
+@implementer(IPublishTraverse)
+class Get(Service):
+ """GET"""
+
+ def reply(self):
+ """Reply"""
+ info = DataTable(self.context, self.request)
+ return info(expand=True)["table"]
diff --git a/eea/app/visualization/version.txt b/eea/app/visualization/version.txt
index 6754387..f075061 100644
--- a/eea/app/visualization/version.txt
+++ b/eea/app/visualization/version.txt
@@ -1 +1 @@
-12.7
+13.0