diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 07f72610a0165bb0c221671c922a0ce9232bfbe3..548f86457c3216f5eed7b75b4b81d7f048351248 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,9 +21,10 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. variables: - CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/caosdb-webui/testenv - # When using dind, it's wise to use the overlayfs driver for - # improved performance. + DEPLOY_REF: dev + CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/caosdb-webui/testenv + # When using dind, it's wise to use the overlayfs driver for + # improved performance. image: $CI_REGISTRY_IMAGE:latest @@ -66,10 +67,11 @@ trigger_build: - echo $TOKEN - /usr/bin/curl -X POST -F token=$DEPLOY_TRIGGER_TOKEN + -F "variables[F_BRANCH]=$CI_COMMIT_REF_NAME" -F "variables[WEBUI]=$CI_COMMIT_REF_NAME" -F "variables[TriggerdBy]=WEBUI" -F "variables[TriggerdByHash]=$CI_COMMIT_SHORT_SHA" - -F ref=dev https://gitlab.indiscale.com/api/v4/projects/14/trigger/pipeline + -F ref=$DEPLOY_REF https://gitlab.indiscale.com/api/v4/projects/14/trigger/pipeline # Build a docker image in which tests for this repository can run build-testenv: diff --git a/CHANGELOG.md b/CHANGELOG.md index 645616eb054a3099dafd1f18717d621d05a66246..323abc3f602827cfc8967cd639e14f1528eb9f50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added (for new features, dependecies etc.) +* basic support for entity versioning: + * entity.xsl generates a versioning button which opens a versioning info modal. + * `preview` also works for versioned references + * `edit_mode` prevents the user from editing old versions of an entity. + ### Changed (for changes in existing functionality) - added a layout argument to the create_plot function of ext_bottom_line diff --git a/misc/versioning_test_data.py b/misc/versioning_test_data.py new file mode 100755 index 0000000000000000000000000000000000000000..eaa83e46f61ea2f20263b487e4bb42c37678c94f --- /dev/null +++ b/misc/versioning_test_data.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header + +""" Insert test data for manually testing the versioning feature in the +webinterface. +""" +# pylint: disable=no-member + +import caosdb + +# clean +old = caosdb.execute_query("FIND Test*") +if len(old) > 0: + old.delete() + +# data model + +rt = caosdb.RecordType("TestRT") +rt.insert() + + +# test data +## record with several versions +rec1 = caosdb.Record("TestRecord1-firstVersion").add_parent("TestRT") +rec1.description = "This is the first version." +rec1.insert() + +rec1.name = "TestRecord1-secondVersion" +rec1.description = "This is the second version." +rec1.update() +if rec1.version: + ref = str(rec1.id) + "@" + rec1.version.id +else: + ref = rec1.id + +rec1.name = "TestRecord1-thirdVersion" +rec1.description = "This is the third version." +rec1.update() + +rec2 = caosdb.Record("TestRecord2").add_parent("TestRT") +rec2.description = ("This record references the TestRecord1 in the second " + "version where the name and the description of the record " + "should indicate the referenced version.") +rec2.add_property("TestRT", ref) +rec2.insert() + +rec3 = caosdb.Record("TestRecord3").add_parent("TestRT") +rec3.description = ("This record references the TestRecord1 without " + "specifying a version. Therefore the latest (at least the " + "third version) is being openend when you click on the " + "reference link.") +rec3.add_property("TestRT", rec1.id) +rec3.insert() + +rec4 = caosdb.Record("TestRecord4").add_parent("TestRT") +rec4.description = ("This record has a list of references to several versions " + "of TestRecord1. The first references the record without " + "specifying the version, the other each reference a " + "different version of that record.") +if rec1.version: + rec4.add_property("TestRT", datatype=caosdb.LIST("TestRT"), + value=[rec1.id, + str(rec1.id) + "@HEAD", + str(rec1.id) + "@HEAD~1", + str(rec1.id) + "@HEAD~2"]) +else: + rec4.add_property("TestRT", datatype=caosdb.LIST("TestRT"), + value=[rec1.id, + str(rec1.id), + str(rec1.id), + str(rec1.id)]) +rec4.insert() diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css index a7a17cd18018c2f5a3de84d5099011a40ef91ca6..ea7ca2a83970aa7d55b5fa58c1b58a6449e17933 100644 --- a/src/core/css/webcaosdb.css +++ b/src/core/css/webcaosdb.css @@ -27,6 +27,17 @@ body { flex-direction: column; } +button.caosdb-v-entity-version-button { + height: 15px; +} + +.caosdb-v-entity-header-buttons-list > * { + margin: 0; + margin-left: 8px; + padding: 0; + vertical-align: middle; +} + .caosdb-v-main-col { flex-grow: 1; max-width: 90vw; diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js index ac2b1c7574cd2b43d05ee56fa24d4b10732ec5bb..1c1318fe840702d979e64616fe380abb874f958d 100644 --- a/src/core/js/caosdb.js +++ b/src/core/js/caosdb.js @@ -245,6 +245,35 @@ function getEntityID(element) { throw new Error("id is ambigous for this element!"); } + +/** + * Get the version string of an entity. + * + * @param {HTMLElement} an Entity in HTML representation + * (div.caosdb-entity-panel) + * @return {string} the version + */ +var getEntityVersion = function (entity) { + return entity.getAttribute("data-version-id"); +} + + +/** + * Get the id and, if present, the version of an entity. + * + * @param {HTMLElement} an Entity in HTML representation + * (div.caosdb-entity-panel) + * @return {string} <id>[@<version>] + */ +var getEntityIdVersion = function (entity) { + const id = getEntityID(entity); + const version = getEntityVersion(entity); + if (version) { + return `${id}@${version}`; + } + return id; +} + /** * Take a date and a time and format it into a CaosDB compatible representation. * @param date A date diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js index b68d8ca4d9d5d309f3374c3717fe001806241173..cc935bd3c0ba75a300453c69d5a8caeed28af17a 100644 --- a/src/core/js/edit_mode.js +++ b/src/core/js/edit_mode.js @@ -1168,6 +1168,10 @@ var edit_mode = new function() { const state = app.state; $('.caosdb-entity-panel').each(function(index) { let entity = this; + if ($(entity).is("[data-version-successor]")) { + // not the latest version -> ignore + return; + } // remove listeners from all entities edit_mode.unset_entity_dropable(entity, edit_mode.dragover, edit_mode.dragleave, edit_mode.parent_drop_listener, edit_mode.property_drop_listener); @@ -1202,6 +1206,10 @@ var edit_mode = new function() { edit_mode.enable_new_buttons(); $('.caosdb-entity-panel').each(function(index) { let entity = this; + if ($(entity).is("[data-version-successor]")) { + // not the latest version -> ignore + return; + } if (typeof getEntityID(entity) == "undefined" || getEntityID(entity) == '') { // no id -> insert edit_mode.init_actions_panels(entity); diff --git a/src/core/js/preview.js b/src/core/js/preview.js index 9af9bb0366097f6d1017a46ff077874ab34bf773..f74fd13ffd2fe373543c025866e91cfe6b6fd9eb 100644 --- a/src/core/js/preview.js +++ b/src/core/js/preview.js @@ -30,9 +30,25 @@ var preview = new function() { return props; } + // import from global name space. + this.getEntityID = getEntityID; + this.getEntityVersion = getEntityVersion; + this.getEntityIdVersion = getEntityIdVersion; + + /** + * Get the id and, if present, the version of an entity from a link or a + * displayed reference. + * + * @param {HTMLElement} a link to an entity. + * @return {string} <id>[@<version>] + */ + this.getEntityRef = function (link) { + return link.getElementsByClassName("caosdb-id")[0].textContent; + } + /** * Initialize the preview feature for all reference properties which belong to certain entity. - * + * * @param {HTMLElement} entity * @return {HTMLElement[]} The initialized properties. */ @@ -113,7 +129,7 @@ var preview = new function() { app.onEnterWaiting = function(e) { executeFailSave(function() { preview.addWaitingNotification(ref_property_elem, preview.createWaitingNotification()); - let entityIds = preview.getEntityIds(refLinksContainer); + let entityIds = preview.getAllEntityRefs(refLinksContainer); preview.retrievePreviewEntities(entityIds).then(entities => { app.receivePreview(entities); }, err => { @@ -391,9 +407,9 @@ var preview = new function() { let selectorButtons = preview.getSelectorButtons(nav); selectorButtons.each((index, button) => { let slide_id = button.getAttribute("data-slide-to"); - let entity_id = getEntityId(button); - let entity = preview.getEntityById(entities, entity_id); - if (entity == null) throw new Error("Entity with ID " + entity_id + " could not be found!"); + let entity_id_version = preview.getEntityRef(button); + let entity = preview.getEntityByIdVersion(entities, entity_id_version); + if (entity == null) throw new Error("Entity with ID " + entity_id_version + " could not be found!"); inner.children[slide_id].appendChild(preview.preparePreviewEntity(entity)); }); @@ -443,8 +459,8 @@ var preview = new function() { * */ this.createSinglePreview = function(entities, refLinksContainer) { - let entityId = getEntityId(preview.getReferenceLinks(refLinksContainer)[0]); - let entity = preview.preparePreviewEntity(preview.getEntityById(entities, entityId)); + const entityRef = preview.getEntityRef(preview.getReferenceLinks(refLinksContainer)[0]); + const entity = preview.preparePreviewEntity(preview.getEntityByIdVersion(entities, entityRef)); return entity; } @@ -456,15 +472,25 @@ var preview = new function() { * @return {HTMLElement} The prepared entity. */ this.preparePreviewEntity = function(entity) { + // move version modal into body because otherwise it would be displayed + // inside the caroussel. That would make sense but there is simply not + // enough space. + $(entity).find(".caosdb-f-entity-version-info").appendTo(document.body); + + + // make backref button smaller + $(entity).find(".caosdb-backref-link > .hidden-xs").hide(); + var preparedEntity = entity.cloneNode(true); // header is clickable: - let href = connection.getBasePath() + transaction.generateEntitiesUri([getEntityId(entity)]); + let href = connection.getBasePath() + transaction.generateEntitiesUri([preview.getEntityRef(entity)]); let link = $('<a title="Load this entity in a new window." href="' + href + '" class="label caosdb-id caosdb-id-button" target="_blank"></a>'); let entityIdElem = $(preparedEntity).find('.label.caosdb-id'); link.insertAfter(entityIdElem); link.append(entityIdElem.text() + " "); link.append('<span class="glyphicon glyphicon-new-window"/>'); + // TODO this link is not visible due to webcaosdb.css (caosdb-id) entityIdElem.remove(); return preparedEntity; @@ -529,22 +555,34 @@ var preview = new function() { } /** - * Get the entity with a certain ID from an array of entities. Returns null if no such entity - * is in the array. + * Get the entity with a certain ID and Version (if applicable) from an + * array of entities. Returns null if no such entity is in the array. + * * @param {HTMLElement[]} entities - * @param {Number} entity_id - * @return {HTMLElement} The entity with id=entity_id or null. + * @param {String} entity_id_version + * @return {HTMLElement} Matching entity or null. */ - this.getEntityById = function(entities, entity_id) { + this.getEntityByIdVersion = function(entities, entity_id_version) { if (entities == null) { throw new Error("entities must not be null"); } - if (entity_id == null || isNaN(entity_id)) { - throw new Error("entity_id is to be a number"); + if (entity_id_version == null) { + throw new Error("entity_id_version must not be null"); + } + + // if the entity_id_version contains an "@" it is actually a reference + // to a versioned entity. Otherwise, it is just an id an thus only the + // id has to be matched. + const is_versioned = entity_id_version.indexOf("@") !== -1; + var matches; + if (is_versioned) { + matches = (e) => preview.getEntityIdVersion(e) === entity_id_version; + } else { + matches = (e) => preview.getEntityID(e) === entity_id_version; } for (let i = 0; i < entities.length; i++) { - let e = entities[i]; - if (getEntityId(e) === entity_id) { + const e = entities[i]; + if (matches(e)) { return e; } } @@ -694,26 +732,27 @@ var preview = new function() { * @param {HTMLElement} refLinksContainer * @return {String[]} An array of entity ids. */ - this.getEntityIds = function(refLinksContainer) { + this.getAllEntityRefs = function(refLinksContainer) { if (refLinksContainer == null) { throw new Error("parameter refLinksContainer must not be null."); } - let entityIds = []; - preview.getReferenceLinks(refLinksContainer).each((index, link) => { - entityIds.push(getEntityId(link)); - }); - return entityIds; + let entityRefs = []; + for (let link of preview.getReferenceLinks(refLinksContainer)) { + entityRefs.push(preview.getEntityRef(link)); + }; + return entityRefs; } /** * Get an array of all reference links. * * @param {HTMLElement} refLinksContainer - The original reference links. - * @return {jQuery} A collection of links. + * @return {HTMLElement[]} A collection of links. */ this.getReferenceLinks = function(refLinksContainer) { - return $(refLinksContainer).find('a').addBack('a').has('.caosdb-id'); + return $(refLinksContainer) + .find('a').addBack('a').has('.caosdb-id').toArray(); } }; diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl index c43b2e8b7c891a6b7f7e17a74b5f42bcefcb4ed3..816d826c0a4d282f6b81f841aad7baa81cdebbbd 100644 --- a/src/core/xsl/entity.xsl +++ b/src/core/xsl/entity.xsl @@ -46,9 +46,9 @@ <xsl:attribute name="href"> <xsl:value-of select="concat($entitypath, '?P=0L10&query=FIND+Entity+which+references+', current())"/> </xsl:attribute> - <span class="glyphicon glyphicon-share-alt flipped-horiz-icon"/> References + <span class="glyphicon glyphicon-share-alt flipped-horiz-icon"/> + <span class="hidden-xs"> References</span> </a> - <span class="spacer"/> </xsl:template> <!-- special entity properties like type, checksum, path... --> <xsl:template match="@datatype" mode="entity-heading-attributes-datatype"> @@ -87,11 +87,16 @@ </p> </xsl:template> <xsl:template match="*" mode="entity-action-panel"> - <div class="caosdb-entity-actions-panel text-right btn-group-xs"></div> + <div class="caosdb-entity-actions-panel text-right btn-group-xs"> + <xsl:apply-templates select="Version/Successor" mode="entity-action-panel-version"> + <xsl:with-param name="entityId" select="@id"/> + </xsl:apply-templates> + </div> </xsl:template> <!-- Main entry for ENTITIES --> <xsl:template match="Property|Record|RecordType|File" mode="entities"> <div class="panel panel-default caosdb-entity-panel"> + <xsl:apply-templates select="Version" mode="entity-version-marker"/> <xsl:attribute name="id"> <xsl:value-of select="@id"/> </xsl:attribute> @@ -138,17 +143,22 @@ </h5> </div> <div class="col-sm-4 text-right"> - <h5> + <h5 class="caosdb-v-entity-header-buttons-list"> <!-- Button for expanding/collapsing the comments section--> - <span class="caosdb-clickable glyphicon glyphicon-comment" data-toggle="collapse" title="Comments" style="margin-right: 10px;"> + <span class="caosdb-clickable glyphicon glyphicon-comment" data-toggle="collapse" title="Toggle the comments section at the bottom of this entity."> <xsl:attribute name="data-target"> <xsl:value-of select="concat('#', 'comment_', $entityid)"/> </xsl:attribute> </span> + <span> <xsl:apply-templates mode="backreference-link" select="@id"/> + </span> <span class="label caosdb-id caosdb-id-button hidden"> <xsl:value-of select="@id"/> </span> + <xsl:apply-templates mode="entity-heading-attributes-version" select="Version"> + <xsl:with-param name="entityId" select="@id"/> + </xsl:apply-templates> </h5> </div> </div> @@ -357,7 +367,7 @@ <xsl:when test="contains(concat('<',@datatype),'<LIST<')"> <!-- list --> <xsl:choose> - <xsl:when test="translate(normalize-space(text()),'0123456789','')='' and not(contains('+LIST<INTEGER>+LIST<DOUBLE>+LIST<TEXT>+LIST<BOOLEAN>+LIST<DATETIME>+',concat('+',@datatype,'+')))"> + <xsl:when test="not(contains('+LIST<INTEGER>+LIST<DOUBLE>+LIST<TEXT>+LIST<BOOLEAN>+LIST<DATETIME>+',concat('+',@datatype,'+')))"> <xsl:apply-templates mode="property-reference-value-list" select="."/> </xsl:when> <xsl:otherwise> @@ -372,7 +382,7 @@ <xsl:value-of select="text()"/> </xsl:with-param> <xsl:with-param name="reference"> - <xsl:value-of select="translate(normalize-space(text()),'0123456789','')='' and not(contains('+INTEGER+DOUBLE+TEXT+BOOLEAN+DATETIME+',concat('+',@datatype,'+')))"/> + <xsl:value-of select="not(contains('+INTEGER+DOUBLE+TEXT+BOOLEAN+DATETIME+',concat('+',@datatype,'+')))"/> </xsl:with-param> <xsl:with-param name="boolean"> <xsl:value-of select="@datatype='BOOLEAN'"/> @@ -458,4 +468,115 @@ </li> </ul> </xsl:template> + <!--VERSIONING--> + <xsl:template match="Version" mode="entity-heading-attributes-version"> + <xsl:param name="entityId"/> + <xsl:param name="versionModalId">version-modal-<xsl:value-of select="generate-id()"/></xsl:param> + <!-- the clock button which opens the window with the versioning info --> + <button title="Versioning Info" type="button" data-toggle="modal"> + <xsl:attribute name="data-target">#<xsl:value-of select="$versionModalId"/></xsl:attribute> + <xsl:attribute name="class"> + caosdb-f-entity-version-button caosdb-v-entity-version-button btn + <xsl:if test="Successor"> + <!-- indicate old version by color and symbol --> + <xsl:value-of select="' text-danger'"/> + </xsl:if> + </xsl:attribute> + <span class="glyphicon glyphicon-time"/> + </button> + <!-- the following div.modal is the window that pops up when the user clicks on the clock button --> + <div class="caosdb-f-entity-version-info modal fade" tabindex="-1" role="dialog"> + <xsl:attribute name="id"><xsl:value-of select="$versionModalId"/></xsl:attribute> + <div class="modal-dialog modal-lg" role="document"> + <div class="modal-content text-left"> + <div> + <xsl:attribute name="class"> + modal-header + <xsl:if test="Successor"> + <!-- indicate old version by color --> + <xsl:value-of select="' bg-danger'"/> + </xsl:if> + </xsl:attribute> + <button type="button" class="close" data-dismiss="modal" aria-label="Close" title="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title">Version Info</h4> + <p class="caosdb-entity-heading-attr"> + <em class="caosdb-entity-heading-attr-name"> + This is + <xsl:if test="Successor"><b>not</b></xsl:if> + the latest version of this entity. + </em> + </p> + </div> + <div class="modal-body"> + <xsl:apply-templates mode="entity-version-modal-head" select="Successor"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> + <xsl:apply-templates mode="entity-version-modal-successor" select="Successor"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> + <p class="caosdb-entity-heading-attr"> + <em class="caosdb-entity-heading-attr-name">This version:</em> + <xsl:value-of select="@id"/> (<xsl:value-of select="@date"/>) + </p> + <xsl:apply-templates mode="entity-version-modal-predecessor" select="Predecessor"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> + </div> + </div> + </div> + </div> + </xsl:template> + <xsl:template match="Predecessor" mode="entity-version-modal-predecessor"> + <!-- content of the versioning window --> + <xsl:param name="entityId"/> + <p class="caosdb-entity-heading-attr"> + <em class="caosdb-entity-heading-attr-name">Previous version:</em> + <a><xsl:attribute name="href"><xsl:value-of select="$entityId"/>@<xsl:value-of select="@id"/></xsl:attribute> + <xsl:value-of select="@id"/> (<xsl:value-of select="@date"/>) + </a> + </p> + </xsl:template> + <xsl:template match="Successor" mode="entity-version-modal-head"> + <!-- content of the versioning window --> + <xsl:param name="entityId"/> + <p class="caosdb-entity-heading-attr"> + <em class="caosdb-entity-heading-attr-name">Newest version:</em> + <a><xsl:attribute name="href"><xsl:value-of select="$entityId"/>@HEAD</xsl:attribute> + <xsl:value-of select="$entityId"/>@HEAD + </a> + </p> + </xsl:template> + <xsl:template match="Successor" mode="entity-version-modal-successor"> + <!-- content of the versioning window --> + <xsl:param name="entityId"/> + <p class="caosdb-entity-heading-attr"> + <em class="caosdb-entity-heading-attr-name">Next version:</em> + <a><xsl:attribute name="href"><xsl:value-of select="$entityId"/>@<xsl:value-of select="@id"/></xsl:attribute> + <xsl:value-of select="@id"/> (<xsl:value-of select="@date"/>) + </a> + </p> + </xsl:template> + <xsl:template match="Version/Successor" mode="entity-action-panel-version"> + <!-- clickable warning message in the entity actions panel when there exists a newer version --> + <xsl:param name="entityId"/> + <a class="caosdb-f-entity-version-old-warning alert-warning btn btn-link" title="Go to the latest version of this entity."> + <xsl:attribute name="href"><xsl:value-of select="$entityId"/>@HEAD</xsl:attribute> + <strong>Warning</strong> A newer version exists! + </a> + </xsl:template> + <xsl:template match="Version" mode="entity-version-marker"> + <!-- content of the data-version-id attribute --> + <xsl:attribute name="data-version-id"> + <xsl:value-of select="@id"/> + </xsl:attribute> + <xsl:apply-templates select="Successor" mode="entity-version-marker"/> + </xsl:template> + <xsl:template match="Successor" mode="entity-version-marker"> + <!-- content of the data-version-successor attribute + This data-attribute marks entities which have a newer version. + --> + <xsl:attribute name="data-version-successor"> + <xsl:value-of select="@id"/> + </xsl:attribute> + </xsl:template> </xsl:stylesheet> diff --git a/test/core/js/modules/caosdb.js.js b/test/core/js/modules/caosdb.js.js index 35a1834083d0b849a62b5f9552a3b7f1259c56c4..250d0f756fea34ad4c30e6377c61294c3d179513 100644 --- a/test/core/js/modules/caosdb.js.js +++ b/test/core/js/modules/caosdb.js.js @@ -167,6 +167,17 @@ QUnit.test("getProperties", function(assert) { assert.equal(ps[0].datatype, "TEXT"); }); +QUnit.test("getEntityIdVersion", function(assert) { + // without version + var html = $('<div data-entity-id="1234"/>')[0]; + assert.equal(getEntityIdVersion(html), "1234", "id extracted"); + + // with version + html = $('<div data-entity-id="1234" data-version-id="abcd"/>')[0]; + assert.equal(getEntityIdVersion(html), "1234@abcd", "<id>@<version> extracted"); + +}); + /** * @author Alexander Schlemmer * Test whether parents are retrieved correctly. diff --git a/test/core/js/modules/entity.xsl.js b/test/core/js/modules/entity.xsl.js index e9e50955b33e44a9f39ae014eba8aa7bde032067..c607ee28bf7888f94e3086abf8e033e1532d0d09 100644 --- a/test/core/js/modules/entity.xsl.js +++ b/test/core/js/modules/entity.xsl.js @@ -180,6 +180,90 @@ QUnit.test("single-value template with reference property.", function(assert) { assert.equal($(link).find('.caosdb-id').length, 1, 'has caosdb-id span'); }) +QUnit.test("old version warning", function(assert) { + // with successor tag + var xmlstr = '<Record id="2345"><Version id="abcd1234"><Successor id="bcde2345"/></Version></Record>'; + var xml = str2xml(xmlstr); + var html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find(".caosdb-entity-panel .caosdb-f-entity-version-old-warning").length, 1, "warning present"); + + // with version tag, without successor + xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>'; + xml = str2xml(xmlstr); + html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find(".caosdb-f-entity-version-old-warning").length, 0, "warning not present"); + + // without version tag + xmlstr = '<Record id="2345"></Record>'; + xml = str2xml(xmlstr); + html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find(".caosdb-f-entity-version-old-warning").length, 0, "warning not present"); +}); + +QUnit.test("version button", function(assert) { + // with version tag + var xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>'; + var xml = str2xml(xmlstr); + var html = applyTemplates(xml, this.entityXSL, "entities", "*"); + + assert.equal($(html).find("div.caosdb-entity-panel button.caosdb-f-entity-version-button").length, 1, "button present"); + + // without version tag + xmlstr = '<Record id="2345"></Record>'; + xml = str2xml(xmlstr); + html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find(".caosdb-f-entity-version-button").length, 0, "button not present"); +}); + +QUnit.test("version info modal", function(assert) { + // with version tag + var xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>'; + var xml = str2xml(xmlstr); + var html = applyTemplates(xml, this.entityXSL, "entities", "*"); + + assert.equal($(html).find("div.caosdb-entity-panel div.caosdb-f-entity-version-info").length, 1, "info present"); + + // without version tag + xmlstr = '<Record id="2345"></Record>'; + xml = str2xml(xmlstr); + html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find(".caosdb-f-entity-version-info").length, 0, "info not present"); +}); + +QUnit.test("data-version-id attribute", function(assert) { + // with version tag + var xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>'; + var xml = str2xml(xmlstr); + var html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find("div.caosdb-entity-panel[data-version-id='abcd1234']").length, 1, "data-version-id attribute present"); + + // without version tag + xmlstr = '<Record id="2345"></Record>'; + xml = str2xml(xmlstr); + html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find("div.caosdb-entity-panel[data-version-id]").length, 0, "data-version-id attribute not present"); +}); + +QUnit.test("data-version-successor attribute", function(assert) { + // with successor tag + var xmlstr = '<Record id="2345"><Version id="abcd1234"><Successor id="bcde2345"/></Version></Record>'; + var xml = str2xml(xmlstr); + var html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find("div.caosdb-entity-panel[data-version-successor='bcde2345']").length, 1, "data-version-successor attribute present"); + + // with version tag, without successor + xmlstr = '<Record id="2345"><Version id="abcd1234"/></Record>'; + xml = str2xml(xmlstr); + html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find("div.caosdb-entity-panel[data-version-successor]").length, 0, "data-version-successor attribute not present"); + + // without version tag + xmlstr = '<Record id="2345"></Record>'; + xml = str2xml(xmlstr); + html = applyTemplates(xml, this.entityXSL, "entities", "*"); + assert.equal($(html).find("div.caosdb-entity-panel[data-version-successor]").length, 0, "data-version-successor attribute not present"); +}); + /* MISC FUNCTIONS */ function applyTemplates(xml, xsl, mode, select = "*") { let entryRule = '<xsl:template priority="9" match="/"><xsl:apply-templates select="' + select + '" mode="' + mode + '"/></xsl:template>'; diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js index 3b615e11e4bd96c26ecc0d8e1cb9d8220a4cba8e..1f8db45c07fef49fbd1e90be803d48fc8c96c7d2 100644 --- a/test/core/js/modules/webcaosdb.js.js +++ b/test/core/js/modules/webcaosdb.js.js @@ -164,7 +164,6 @@ QUnit.test("get", function(assert) { }); }); - /* MODULE transformation */ QUnit.module("webcaosdb.js - transformation", { before: function(assert) { @@ -782,34 +781,29 @@ QUnit.test("getActiveSlideItemIndex", function(assert) { assert.equal(2, preview.getActiveSlideItemIndex(okElem2)); }); -QUnit.test("getEntityById", function(assert) { - assert.ok(preview.getEntityById, "function available"); +QUnit.test("getEntityByIdVersion", function(assert) { + assert.ok(preview.getEntityByIdVersion, "function available"); let e1 = $('<div><div class="caosdb-id">1</div></div>')[0]; let e2 = $('<div><div class="caosdb-id">2</div></div>')[0]; - let e3 = $('<div><div class="caosdb-id">3</div><div><div class="caosdb-id">1</div></div></div>')[0]; - let es = [e1, e2, e3]; + let es = [e1, e2]; assert.throws(() => { - preview.getEntityById() + preview.getEntityByIdVersion() }, "no param throws."); assert.throws(() => { - preview.getEntityById(null, 1) + preview.getEntityByIdVersion(null, 1) }, "null first param throws."); assert.throws(() => { - preview.getEntityById("asdf", 1) + preview.getEntityByIdVersion("asdf", 1) }, "string first param throws."); assert.throws(() => { - preview.getEntityById(es, null) + preview.getEntityByIdVersion(es, null) }, "null second param throws."); - assert.throws(() => { - preview.getEntityById(es, "asdf") - }, "string second param throws."); - assert.equal(e1, preview.getEntityById(es, 1), "find 1"); - assert.equal(e2, preview.getEntityById(es, 2), "find 2"); - assert.equal(e3, preview.getEntityById(es, 3), "find 3"); - assert.equal(null, preview.getEntityById(es, 4), "find 4 -> null"); + assert.equal(e1, preview.getEntityByIdVersion(es, "1"), "find 1"); + assert.equal(e2, preview.getEntityByIdVersion(es, "2"), "find 2"); + assert.equal(null, preview.getEntityByIdVersion(es, "3"), "find 3 -> null"); }); QUnit.test("createEmptyInner", function(assert) { @@ -870,7 +864,7 @@ QUnit.test("createCarouselNav", function(assert) { let refLinks = $('<div class="caosdb-value-list"><a><span class="caosdb-id">1234</span></a><a><span class="caosdb-id">2345</span></a><a><span class="caosdb-id">3456</span></a><a><span class="caosdb-id">4567</span></a></div>')[0]; let e1 = $('<div><div class="caosdb-id">1234</div></div>')[0]; let e2 = $('<div><div class="caosdb-id">2345</div></div>')[0]; - let e3 = $('<div><div class="caosdb-id">3456</div><div><div class="caosdb-id">1234</div></div></div>')[0]; + let e3 = $('<div><div class="caosdb-id">3456</div></div>')[0]; let e4 = $('<div><div class="caosdb-id">4567</div></div>')[0]; let entities = [e1, e3, e4, e2]; let carousel = preview.createPreviewCarousel(entities, refLinks); @@ -1040,8 +1034,41 @@ QUnit.test("preparePreviewEntity", function(assert){ assert.equal($(prepared).find('a.caosdb-id')[0].href, connection.getBasePath() + "Entity/1234", "link is correct."); }); -QUnit.test("getEntitiyIds", function(assert) { - assert.ok(preview.getEntityIds, 'function available'); +QUnit.test("getEntityRef", function(assert) { + assert.ok(preview.getEntityRef, 'function available'); + + var html = $('<div><div class="caosdb-id">sdfg</div></div>')[0]; + assert.equal(preview.getEntityRef(html), "sdfg", "id extracted"); + + html = $('<div><div class="caosdb-id"></div></div>')[0]; + assert.equal(preview.getEntityRef(html), "", "empty string extracted"); + + html = $('<div></div>')[0]; + assert.throws(()=>{preview.getEntityRef(html);}, "missing .caosdb-id throws"); +}); + + +QUnit.test("getAllEntityRefs", function(assert) { + assert.ok(preview.getAllEntityRefs, 'function available'); + assert.throws(preview.getAllEntityRefs, "null param throws"); + + // overwrite called methods + const oldGetReferenceLinks = preview.getReferenceLinks; + preview.getReferenceLinks = function(links) { + assert.propEqual(links, ["bla"], "array is passed to getReferenceLinks"); + return links; + } + const oldGetEntityRef = preview.getEntityRef; + preview.getEntityRef = function(link) { + assert.equal(link, "bla", "array elements are passed to getEntityRef"); + return "asdf"; + } + + assert.propEqual(preview.getAllEntityRefs(["bla"]), ["asdf"], "returns array with refs"); + + + // cleanup + preview.getReferenceLinks = oldGetReferenceLinks; }); QUnit.test("retrievePreviewEntities", function(assert) {