Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #1 from zotya/master

Data persistence for sparql results & quick preview for queries
  • Loading branch information...
commit cf8755a0c1160c1c0a2d0f736aa8f0a789ce6ca0 2 parents ef2e3b8 + 9957afe
@avoinea avoinea authored
View
8 docs/HISTORY.txt
@@ -3,6 +3,14 @@ Changelog
2.4-dev - (unreleased)
----------------------
+* Upgrade step: Within "Plone > Site setup > Add-ons" click on
+ upgrade button available for eea.sparql.
+* Feature: Added quick preview for sparql queries
+ [szabozo0 refs #9608]
+* Feature: Data persistence and versioning
+ - Added versioning for sparql results
+ - Possibility to select static & live queries
+ [szabozo0 refs #9424]
2.3 - (2013-01-18)
------------------
View
16 eea/sparql/browser/configure.zcml
@@ -151,4 +151,20 @@
permission="cmf.ModifyPortalContent"
/>
+ <browser:page
+ name="sparql.quick_preview"
+ for="*"
+ class=".sparql.QuickPreview"
+ attribute="preview"
+ permission="zope2.View"
+ />
+
+ <browser:page
+ name="sparql.related_items"
+ for="*"
+ class=".sparql.Sparql"
+ attribute="relatedItems"
+ permission="zope2.View"
+ />
+
</configure>
View
71 eea/sparql/browser/sparql.py
@@ -6,6 +6,10 @@
from Products.ZSPARQLMethod.Method import interpolate_query_html
from Products.ZSPARQLMethod.Method import map_arg_values
from Products.ZSPARQLMethod.Method import parse_arg_spec
+from Products.ZSPARQLMethod.Method import interpolate_query
+from Products.ZSPARQLMethod.Method import run_with_timeout
+from Products.ZSPARQLMethod.Method import query_and_get_result
+
from eea.sparql.converter.sparql2json import sparql2json
from eea.sparql.converter.sparql2json import sortProperties
from eea.versions import versions
@@ -14,6 +18,7 @@
import json
import urllib2
import contextlib
+import cgi
logger = logging.getLogger('eea.sparql')
@@ -163,11 +168,10 @@ def sparql2csv(self, dialect='excel'):
header = '%s:%s' % (col, data['properties'][col]['valueType'])
row.append(header)
writter.writerow(row)
-
for item in data['items']:
row = []
for col in headers:
- row.append(unicode(item[col]))
+ row.append(unicode(item[col]).encode('utf'))
writter.writerow(row)
return ''
@@ -240,6 +244,11 @@ def isDavizInstalled(self):
return has_daviz
+ def relatedItems(self):
+ """ Items what are back related to this query
+ """
+ return json.dumps([[x.title, x.absolute_url()]
+ for x in self.context.getBRefs()])
class SparqlBookmarksFolder(Sparql):
"""SparqlBookmarksFolder view"""
@@ -312,3 +321,61 @@ def __call__(self):
except Exception, err:
logger.exception(err)
return "Sync done"
+
+class QuickPreview(BrowserView):
+ """ Quick Preview For Query
+ """
+
+ def preview(self):
+ """preview"""
+ tmp_query = self.request.get("sparql_query", "")
+ tmp_arg_spec = self.request.get("arg_spec", "")
+ tmp_endpoint = self.request.get("endpoint", "")
+ tmp_timeout = int(self.request.get("timeout", "0"))
+
+ arg_spec = parse_arg_spec(tmp_arg_spec)
+ missing, arg_values = map_arg_values(arg_spec, self.request.form)
+ error = None
+ if missing:
+ error = ""
+ for missing_arg in missing:
+ error = error + "<div>Argument '%s' missing</div>" % missing_arg
+ else:
+ result = {}
+ data = []
+ error = None
+ cooked_query = interpolate_query(tmp_query, arg_values)
+ args = (tmp_endpoint, cooked_query)
+ try:
+ result, error = {}, None
+ result = run_with_timeout(tmp_timeout,
+ query_and_get_result,
+ *args)
+ data = result.get('result')
+ error = error or result.get('exception')
+ except Exception:
+ import traceback
+ error = traceback.format_exc()
+
+ if error:
+ return "<blockquote class='sparql-error'> %s </blockquote>" % error
+
+ result = []
+ result.append(u"<table class='sparql-results'>")
+ result.append(u"<thead>")
+ result.append(u"<tr>")
+ for var_name in data.get('var_names', []):
+ result.append(u"<th> %s </th>" %var_name)
+ result.append(u"</tr>")
+ result.append(u"</thead>")
+ result.append(u"<tbody>")
+ for row in data.get('rows', []):
+ result.append(u"<tr class='row_0'>")
+ for value in row:
+ result.append(u"<td> %s </td>" %cgi.escape(value.n3()))
+ result.append(u"</tr>")
+ result.append(u"</tbody>")
+ result.append(u"</table>")
+ return "\n".join(result)
+
+
View
3  eea/sparql/browser/view.pt
@@ -36,7 +36,8 @@
</div>
<h3 i18n:translate="">Dataset preview</h3>
-
+ <h4 tal:condition="context/sparql_static">(Static Query)</h4>
+ <h4 tal:condition="not:context/sparql_static">(Live Query)</h4>
<div tal:define="data options/data">
<div class="eea-sparql-table" style="overflow: hidden">
<metal:table use-macro="here/@@sparql.preview/macros/table" />
View
11 eea/sparql/cache/browser.py
@@ -24,11 +24,14 @@ class CacheView(BrowserView):
""" Caching for sparql query results
"""
def __call__(self):
- if not "submit" in self.request.form:
- return self.index()
+ if "invalidate_cache" in self.request.form:
+ event.notify(ObjectModifiedEvent(self.context))
+ IStatusMessage(self.request).addStatusMessage("Cache invalidated")
+ if "invalidate_last_working_results" in self.request.form:
+ self.context.invalidateWorkingResult()
+ message = "Last working results invalidated"
+ IStatusMessage(self.request).addStatusMessage(message)
- event.notify(ObjectModifiedEvent(self.context))
- IStatusMessage(self.request).addStatusMessage("Cache invalidated")
return self.index()
def purgeRelatedItems(obj, evt):
View
5 eea/sparql/cache/caching.pt
@@ -12,7 +12,10 @@
<div metal:fill-slot="main" >
<form method="POST" action="" tal:attributes="action string:${context/absolute_url}/@@caching">
- <input type="submit" name="submit" value="Invalidate cache"
+ <input type="submit" name="invalidate_cache" value="Invalidate cache"
+ i18n:attributes="value" />
+ <br/>
+ <input type="submit" name="invalidate_last_working_results" value="Invalidate last working result"
i18n:attributes="value" />
</form>
View
1  eea/sparql/configure.zcml
@@ -6,6 +6,7 @@
<include file="profiles.zcml" />
<include file="permissions.zcml" />
+ <include file="skins.zcml" />
<include package=".browser" />
<include package=".cache" />
View
111 eea/sparql/content/sparql.py
@@ -9,13 +9,14 @@
from Products.Archetypes.atapi import IntegerField
from Products.Archetypes.atapi import SelectionWidget
from Products.Archetypes.atapi import Schema
-from Products.Archetypes.atapi import StringField, StringWidget
+from Products.Archetypes.atapi import StringField, StringWidget, \
+ BooleanWidget, BooleanField
from Products.Archetypes.atapi import TextField, TextAreaWidget
from Products.ZSPARQLMethod.Method import ZSPARQLMethod, \
interpolate_query, \
run_with_timeout, \
- query_and_get_result, \
parse_arg_spec, \
+ query_and_get_result, \
map_arg_values
from AccessControl.Permissions import view
from eea.sparql.cache import ramcache, cacheSparqlKey
@@ -26,6 +27,14 @@
from zope.interface import implements
+from Products.CMFCore.utils import getToolByName
+from Products.CMFEditions.interfaces.IModifier import FileTooLargeToVersionError
+
+from AccessControl import SpecialUsers
+from AccessControl import getSecurityManager
+from AccessControl.SecurityManagement import newSecurityManager, \
+ setSecurityManager
+
SparqlBaseSchema = atapi.Schema((
StringField(
name='endpoint_url',
@@ -59,10 +68,31 @@
allowable_content_types = ('text/plain',),
widget=TextAreaWidget(
+ macro="sparql_textfield_with_preview",
+ helper_js=("sparql_textfield_with_preview.js",),
+ helper_css=("sparql_textfield_with_preview.css",),
label="Query",
),
required=1
),
+ BooleanField(
+ name='sparql_static',
+ widget=BooleanWidget(
+ label='Static query',
+ description='The data will be fetched only once',
+ ),
+ default=False,
+ required=0
+ ),
+ TextField(
+ name='sparql_results',
+ widget=TextAreaWidget(
+ label="Results",
+ visible={'edit': 'invisible', 'view': 'invisible' }
+ ),
+ required=0,
+
+ ),
))
SparqlSchema = getattr(base.ATCTContent, 'schema', Schema(())).copy() + \
@@ -77,6 +107,8 @@
SparqlBaseSchema.copy()
SparqlBookmarksFolderSchema['sparql_query'].widget.description = \
'The query should return label, bookmark url, query'
+SparqlBookmarksFolderSchema['sparql_static'].widget.visible['edit'] = \
+ 'invisible'
class Sparql(base.ATCTContent, ZSPARQLMethod):
"""Sparql"""
@@ -127,25 +159,86 @@ def setTimeout(self, value):
except Exception:
self.timeout = 10
+ security.declareProtected(view, 'invalidateWorkingResult')
+ def invalidateWorkingResult(self):
+ """ invalidate working results"""
+ self.cached_result = {}
+ self.setSparql_results("")
+ pr = getToolByName(self, 'portal_repository')
+ comment = "Invalidated last working result"
+ comment = comment.encode('utf')
+ try:
+ pr.save(obj=self, comment=comment)
+ except FileTooLargeToVersionError:
+ commands = view.getCommandSet('plone')
+ commands.issuePortalMessage(
+ """Changes Saved. Versioning for this file
+ has been disabled because it is too large.""",
+ msgtype="warn")
+
security.declareProtected(view, 'execute')
def execute(self, **arg_values):
"""
Override execute from ZSPARQLMethod in order to have a default timeout
"""
+ cached_result = getattr(self, 'cached_result', {})
cooked_query = interpolate_query(self.query, arg_values)
- cache_key = {'query': cooked_query}
- result = self.ZCacheable_get(keywords=cache_key)
- if result is None:
+ force_requery = False
+
+ if not self.getSparql_static():
+ force_requery = True
+
+ if cached_result == {}:
+ force_requery = True
+
+ if force_requery:
args = (self.endpoint_url, cooked_query)
- result = run_with_timeout(
+ new_result = run_with_timeout(
max(getattr(self, 'timeout', 10), 10),
query_and_get_result,
*args)
- self.ZCacheable_set(result, keywords=cache_key)
-
- return result
+ force_save = False
+
+ if new_result.get("result", {}) != {}:
+ if new_result != cached_result:
+ if len(new_result.get("result", {}).get("rows", {})) > 0:
+ force_save = True
+ else:
+ if len(cached_result.get('result', {}).\
+ get('rows', {})) == 0:
+ force_save = True
+
+ if force_save:
+ self.cached_result = new_result
+ new_sparql_results = u""
+ for row in self.cached_result.get('result', {}).get('rows', {}):
+ for val in row:
+ new_sparql_results = new_sparql_results + \
+ unicode(val) + " | "
+ new_sparql_results = new_sparql_results[0:-3] + "\n"
+ self.setSparql_results(new_sparql_results)
+ pr = getToolByName(self, 'portal_repository')
+ if self.portal_type in pr.getVersionableContentTypes():
+ comment = "Result changed"
+ comment = comment.encode('utf')
+
+ oldSecurityManager = getSecurityManager()
+ newSecurityManager(self.REQUEST, SpecialUsers.system)
+ try:
+ pr.save(obj=self, comment=comment)
+ except FileTooLargeToVersionError:
+ commands = view.getCommandSet('plone')
+ commands.issuePortalMessage(
+ """Changes Saved. Versioning for this file
+ has been disabled because it is too large.""",
+ msgtype="warn")
+ setSecurityManager(oldSecurityManager)
+
+ if new_result.get('exception', None):
+ self.cached_result['exception'] = new_result['exception']
+ return self.cached_result
class SparqlBookmarksFolder(ATFolder, Sparql):
"""Sparql Bookmarks Folder"""
View
8 eea/sparql/profiles/default/diff_tool.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<object>
+ <difftypes>
+ <type portal_type="Sparql">
+ <field name="any" difftype="Compound Diff for AT types"/>
+ </type>
+ </difftypes>
+</object>
View
2  eea/sparql/profiles/default/metadata.xml
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<metadata>
- <version>1001</version>
+ <version>2400</version>
<dependencies>
<dependency>profile-eea.versions:default</dependency>
</dependencies>
View
9 eea/sparql/profiles/default/repositorytool.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<repositorytool>
+ <policymap>
+ <type name="Sparql">
+ <policy name="at_edit_autoversion"/>
+ <policy name="version_on_revert"/>
+ </type>
+ </policymap>
+</repositorytool>
View
10 eea/sparql/profiles/default/skins.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+ <object name="sparql_templates"
+ meta_type="Filesystem Directory View"
+ directory="eea.sparql:skins/sparql_templates"/>
+
+ <skin-path name="*">
+ <layer insert-after="custom" name="sparql_templates"/>
+ </skin-path>
+</object>
View
7 eea/sparql/skins.zcml
@@ -0,0 +1,7 @@
+<configure
+ xmlns:cmf="http://namespaces.zope.org/cmf"
+ i18n_domain="eea">
+
+ <cmf:registerDirectory name="sparql_templates" />
+
+</configure>
View
28 eea/sparql/skins/sparql_templates/sparql_textfield_with_preview.css
@@ -0,0 +1,28 @@
+.sparql-preview-div {
+ background-color:#DDD;
+ float:left;
+}
+
+.sparql-preview-label {
+ font-size:14px;
+}
+
+.sparql-readonly-field {
+ background-color:#DDD;
+}
+
+.sparql-preview-loading {
+ margin:auto;
+ width:100%;
+ top:40%;
+ text-align:center;
+ position:fixed;
+ z-index:9999;
+ font-size: 22px;
+ font-weight: bold;
+}
+.sparql-preview-loading > div {
+ background-color:gray;
+ width:300px;
+ margin:auto;
+}
View
95 eea/sparql/skins/sparql_templates/sparql_textfield_with_preview.js
@@ -0,0 +1,95 @@
+function preview_sparql(){
+ var ajax_data = {
+ "endpoint" : jQuery("#endpoint_url").attr("value"),
+ "timeout" : jQuery("#timeout").attr("value"),
+ "arg_spec" : jQuery("#arg_spec").attr("value"),
+ "sparql_query" : jQuery("#sparql_query").attr("value")
+ };
+ var preview_arguments = jQuery(".sparql-preview-arguments").attr("value");
+ var args_list = preview_arguments.split("&");
+ jQuery.each(args_list, function(idx, arg){
+ ajax_data[arg.split("=")[0]] = arg.split("=")[1];
+ });
+
+ var loading_msg = jQuery("<div class='sparql-preview-loading'><div>Executing query...</div></div>");
+ jQuery(loading_msg).appendTo("body");
+ jQuery.ajax({
+ url:portal_url + "/sparql.quick_preview",
+ type:"POST",
+ data: ajax_data,
+ success:function(data){
+ jQuery(".sparql-preview-loading").remove();
+ var sparql_preview = jQuery("<div class='sparql_preview'></div>");
+ jQuery(data).appendTo(sparql_preview);
+ sparql_preview.dialog({
+ title:"Preview for " + jQuery("#title").attr("value"),
+ modal:true,
+ width:'auto',
+ create: function() {
+ $(this).css("maxHeight", 600);
+ $(this).css("maxWidth", 800);
+ }
+ });
+ }
+ });
+}
+
+function sparql_setstatic(){
+ if (jQuery("#sparql_static").attr("checked")){
+ jQuery("#endpoint_url").attr("readonly", true);
+ jQuery("#timeout").attr("disabled", true);
+ jQuery("#arg_spec").attr("readonly", true);
+ jQuery("#sparql_query").attr("readonly", true);
+
+ jQuery("#endpoint_url").addClass("sparql-readonly-field");
+ jQuery("#arg_spec").addClass("sparql-readonly-field");
+ jQuery("#sparql_query").addClass("sparql-readonly-field");
+ }
+ else{
+ jQuery("#endpoint_url").attr("readonly", false);
+ jQuery("#timeout").attr("disabled", false);
+ jQuery("#arg_spec").attr("readonly", false);
+ jQuery("#sparql_query").attr("readonly", false);
+ jQuery(".sparql-readonly-field").removeClass("sparql-readonly-field");
+ }
+}
+
+function check_relations(){
+ if (window.location.href.indexOf("portal_factory") !== -1){
+ return;
+ }
+ jQuery.ajax({
+ url:absolute_url + "/sparql.related_items",
+ type:"GET",
+ success:function(data){
+ var back_rels = JSON.parse(data);
+ if (back_rels.length !== 0){
+ var warningMessage = jQuery(
+ '<dl class="portalMessage">'+
+ '<dt>Warning</dt>'+
+ '<div style="clear:both"></div'+
+ '<dd>' +
+ 'The result of this query is used by:' +
+ '<ul class="sparql-back-relations"></ul>' +
+ 'Modifying the query may cause problems in them.' +
+ '</dd>'+
+ '</dl>');
+ jQuery("#sparql-base-edit").prepend(warningMessage);
+ jQuery.each(back_rels, function(idx, rel){
+ var rel_msg = jQuery(
+ '<li><a href="'+rel[1]+'">'+rel[0]+'</a></li>'
+ );
+ rel_msg.appendTo(".sparql-back-relations");
+ });
+ }
+ }
+ });
+
+// jQuery("<div>XXX</div>").after(".documentFirstHeading");
+}
+jQuery(document).ready(function($) {
+ jQuery(".sparql-query-results-preview").click(preview_sparql);
+ jQuery("#sparql_static").click(sparql_setstatic);
+ sparql_setstatic();
+ check_relations();
+});
View
159 eea/sparql/skins/sparql_templates/sparql_textfield_with_preview.pt
@@ -0,0 +1,159 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ i18n:domain="plone">
+
+ <head><title></title></head>
+
+ <body>
+
+ <!-- TextArea Widgets -->
+
+ <metal:view_macro define-macro="view"
+ tal:define="kssClassesView context/@@kss_field_decorator_view;
+ getKssClasses nocall:kssClassesView/getKssClassesInlineEditable;">
+ <span metal:define-macro="textarea-field-view"
+ tal:define="kss_class python:getKssClasses(fieldName,
+ templateId='widgets/textarea', macro='textarea-field-view');
+ uid context/UID|nothing;"
+ tal:attributes="class kss_class;
+ id string:parent-fieldname-$fieldName-$uid">
+ <span metal:define-slot="inside"
+ tal:replace="accessor">textarea</span>
+ </span>
+ </metal:view_macro>
+
+ <metal:define define-macro="area_edit">
+ <tal:define
+ define="base python:hasattr(value, 'isUnit');
+ binary python:base and value.isBinary() or context.isBinary(fieldName);
+ content python: not not base and value.getRaw() or value;
+ content python: not binary and content or '';
+ content_length python:len(unicode(content, 'utf-8'));
+ append_only widget/append_only|nothing;
+ maxlength widget/maxlength|nothing;
+ tcname string:textCounter_${fieldName};
+ keypress string:textCounter(this, '${tcname}', ${maxlength});">
+
+ <textarea
+ class="blurrable firstToFocus"
+ tal:attributes="name fieldName;
+ id fieldName;
+ cols widget/cols;
+ rows widget/rows;
+ placeholder widget/placeholder|nothing;
+ onkeydown python:test(maxlength, keypress, None);
+ onkeyup python:test(maxlength, keypress, None);"
+ tal:define="content python:not append_only and content or '';"
+ tal:content="content">content</textarea>
+
+ <div tal:condition="maxlength"
+ i18n:translate="label_characters_remaining">
+ <input readonly="readonly"
+ type="text"
+ name=""
+ maxlength="4"
+ size="4"
+ value=""
+ i18n:name="count"
+ tal:define="remaining python:(int(maxlength) - content_length) + content.count('\n')"
+ tal:attributes="name tcname;
+ value remaining;
+ id string:maxlength_${fieldName};" />
+ characters remaining
+ </div>
+
+ <fieldset tal:condition="append_only">
+ <legend i18n:translate=""
+ tal:define="label widget/label"
+ tal:content="string:HISTORY: ${label}">
+ label
+ </legend>
+ <span i18n:translate=""
+ tal:condition="python:(content_length &lt; 333)"
+ tal:content="accessor">content</span>
+ <textarea readonly="readonly" i18n:translate=""
+ tal:condition="python:(content_length &gt;= 333)"
+ tal:content="content"
+ tal:attributes="cols widget/cols;
+ rows widget/rows;">
+ content
+ </textarea>
+ </fieldset>
+
+ </tal:define>
+ </metal:define>
+
+ <metal:define define-macro="area_format">
+ <tal:define
+ define="field_text_format string:${fieldName}_text_format;
+ contentType python:request.get(field_text_format, context.getContentType(fieldName));
+ get_act python:getattr(field, 'getAllowedContentTypes', False);
+ allowable_ct python:get_act and get_act(here) or ('text/plain',);
+ mimetypes python:[t for t in allowable_ct if t.startswith('text/')];
+ contenttype python:hasattr(field, 'getContentType') and field.getContentType(here) or ''">
+
+ <tal:condition condition="python:len(mimetypes) > 1">
+ <div style="text-align: right; margin-right: 0.75em;">
+ <label i18n:translate="label_format">Format</label>
+ <select tal:define="textareaview context/@@at_textarea_widget;
+ selection python:textareaview.getSelected(mimetypes, contenttype)"
+ tal:attributes="id string:${fieldName}_text_format;
+ name string:${fieldName}_text_format;">
+ <option selected="selected"
+ value=""
+ tal:attributes="value contentType"
+ tal:condition="python:contenttype not in mimetypes">
+ (no change)
+ </option>
+ <option tal:repeat="item mimetypes"
+ tal:content="python:textareaview.lookupMime(item)"
+ tal:attributes="value item;
+ selected python:item in selection and 'selected' or None;"
+ />
+ </select>
+ </div>
+ </tal:condition>
+ <tal:condition condition="python:len(mimetypes) == 1">
+ <input type="hidden"
+ name=""
+ value=""
+ tal:attributes="name field_text_format;
+ value python:mimetypes[0]"
+ />
+ </tal:condition>
+ </tal:define>
+ </metal:define>
+
+ <metal:define define-macro="edit">
+ <metal:use use-macro="field_macro | context/widgets/field/macros/edit">
+ <metal:fill fill-slot="widget_body">
+ <metal:block use-macro="context/widgets/textarea/macros/area_edit" />
+ <metal:block use-macro="context/widgets/textarea/macros/area_format" />
+ </metal:fill>
+ </metal:use>
+ <div class="sparql-preview-div field">
+ <script type="text/javascript" tal:content="string: var portal_url = '${context/portal_url}'; var absolute_url = '${context/absolute_url}'">
+ </script>
+
+ <label class="sparql-preview-label">Quick Preview</label><br/>
+ <label class="sparql-preview-arguments-label">Arguments for preview</label><br/>
+ <span class="sparql-preview-arguments-helper formHelper">Arguments for preview should look like: arg1=value1&amp;arg2=value2</span><br/>
+ <input class="sparql-preview-arguments" type="text"> <br/>
+ <input class="sparql-query-results-preview" type="button" value="Preview Query Results"/>
+ </div>
+ </metal:define>
+
+ <metal:define define-macro="search">
+ <metal:use use-macro="context/widgets/field/macros/edit">
+ <metal:fill fill-slot="widget_body">
+
+ <metal:block use-macro="context/widgets/textarea/macros/area_edit" />
+
+ </metal:fill>
+ </metal:use>
+ </metal:define>
+ </body>
+
+</html>
View
12 eea/sparql/upgrades/configure.zcml
@@ -14,4 +14,16 @@
</genericsetup:upgradeSteps>
+ <genericsetup:upgradeSteps
+ source="1001"
+ destination="2400"
+ profile="eea.sparql:default">
+
+ <genericsetup:upgradeDepends
+ title="Import all steps from eea.sparql profile"
+ import_profile="eea.sparql:default"
+ />
+
+ </genericsetup:upgradeSteps>
+
</configure>
Please sign in to comment.
Something went wrong with that request. Please try again.