Skip to content

Commit

Permalink
Merge pull request eea#20 from eea/develop
Browse files Browse the repository at this point in the history
RestAPI @provenances @charts @table
  • Loading branch information
avoinea committed Jun 16, 2021
2 parents f50511f + fd924f9 commit 2651ab0
Show file tree
Hide file tree
Showing 18 changed files with 389 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Jenkinsfile
Expand Up @@ -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'''
}
}
Expand Down
9 changes: 9 additions & 0 deletions 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
Expand Down
1 change: 1 addition & 0 deletions eea/app/visualization/common.zcml
Expand Up @@ -11,6 +11,7 @@
<include package=".data" />
<include package=".converter" />
<include package=".facets" />
<include package=".restapi" />
<include package=".storage" />
<include package=".subtypes" />
<include package=".views" />
Expand Down
2 changes: 2 additions & 0 deletions eea/app/visualization/restapi/__init__.py
@@ -0,0 +1,2 @@
""" RestAPI
"""
2 changes: 2 additions & 0 deletions eea/app/visualization/restapi/charts/__init__.py
@@ -0,0 +1,2 @@
""" RestAPI
"""
15 changes: 15 additions & 0 deletions eea/app/visualization/restapi/charts/configure.zcml
@@ -0,0 +1,15 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:plone="http://namespaces.plone.org/plone">

<plone:service
method="GET"
name="@charts"
for="eea.app.visualization.interfaces.IVisualizationEnabled"
factory=".get.ChartsGet"
permission="zope2.View"
/>

<adapter factory=".get.Charts" name="charts" />

</configure>
52 changes: 52 additions & 0 deletions 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"]
5 changes: 5 additions & 0 deletions eea/app/visualization/restapi/configure.zcml
@@ -0,0 +1,5 @@
<configure xmlns="http://namespaces.zope.org/zope">
<include package="plone.restapi" />
<include package=".charts" />
<include package=".data" />
</configure>
2 changes: 2 additions & 0 deletions eea/app/visualization/restapi/data/__init__.py
@@ -0,0 +1,2 @@
""" RestAPI
"""
4 changes: 4 additions & 0 deletions eea/app/visualization/restapi/data/configure.zcml
@@ -0,0 +1,4 @@
<configure xmlns="http://namespaces.zope.org/zope">
<include package=".table" />
<include package=".provenance" />
</configure>
2 changes: 2 additions & 0 deletions eea/app/visualization/restapi/data/provenance/__init__.py
@@ -0,0 +1,2 @@
""" RestAPI
"""
29 changes: 29 additions & 0 deletions eea/app/visualization/restapi/data/provenance/configure.zcml
@@ -0,0 +1,29 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:plone="http://namespaces.plone.org/plone">

<include package="plone.restapi" />

<!-- Data Provenance -->

<plone:service
method="GET"
name="@provenances"
for="eea.app.visualization.interfaces.IVisualizationEnabled"
factory=".get.Get"
permission="zope2.View"
/>

<adapter factory=".get.DataProvenance" name="provenances" />

<!-- TODO: #123935 - Unify IDataProvenance with IMultiDataProvenance
<plone:service
method="POST"
name="@provenances"
for="eea.app.visualization.interfaces.IVisualizationEnabled"
factory=".post.Post"
permission="eea.app.visualization.configure"
/>
-->

</configure>
68 changes: 68 additions & 0 deletions 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"]
126 changes: 126 additions & 0 deletions 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)
2 changes: 2 additions & 0 deletions eea/app/visualization/restapi/data/table/__init__.py
@@ -0,0 +1,2 @@
""" RestAPI
"""
15 changes: 15 additions & 0 deletions eea/app/visualization/restapi/data/table/configure.zcml
@@ -0,0 +1,15 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:plone="http://namespaces.plone.org/plone">

<plone:service
method="GET"
name="@table"
for="eea.app.visualization.interfaces.IVisualizationEnabled"
factory=".get.Get"
permission="zope2.View"
/>

<adapter factory=".get.DataTable" name="table" />

</configure>

0 comments on commit 2651ab0

Please sign in to comment.