diff --git a/CHANGELOG.md b/CHANGELOG.md index 67446d22180ea9ef38664aee86e288ab13becdc8..c2802f2a004cb839bffcd816537bbb7a24769699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added (for new features, dependecies etc.) + +- The versioning model has a new styling and can show and tsv-export the full + version history now. - Module `ext_bookmarks` which allows users to bookmark entities, store them in a link or export them to TSV. - table previews in the bottom line module diff --git a/misc/versioning_test_data.py b/misc/versioning_test_data.py index eaa83e46f61ea2f20263b487e4bb42c37678c94f..5ec7073aeaffc894916ee8a6c4cfdc82bc25a4f1 100755 --- a/misc/versioning_test_data.py +++ b/misc/versioning_test_data.py @@ -91,3 +91,8 @@ else: str(rec1.id), str(rec1.id)]) rec4.insert() + +for i in range(4,11): + rec1.name = f"TestRecord1-{i}thVersion" + rec1.description = f"This is the {i}th version." + rec1.update() diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css index 57399e5787c28a6ecdf0c5fe7ce505b3d23f90d5..69a700376423a44bcb28a9920f1f3d15ef9a3b90 100644 --- a/src/core/css/webcaosdb.css +++ b/src/core/css/webcaosdb.css @@ -27,6 +27,35 @@ body { flex-direction: column; } + +div.export-data { + display: none; +} + +tr:not(:hover) .caosdb-v-entity-version-hint-cur { + color: #DDD; +} + +tr:hover .caosdb-v-entity-version-hint { + color: unset; +} + +.caosdb-v-entity-version-hint { + color: #DDD; +} + +tbody:not(:hover) tr .caosdb-v-entity-version-hint-cur { + color: unset; +} + +.caosdb-v-entity-version-no-related { + color: #DDD; +} + +.caosdb-v-entity-version-no-related:hover { + color: unset; +} + #top-navbar>ul>li>a { margin: 8px 0px; padding: 6px 12px; diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js index 7fe4e9441822bf6c706a09c95aa65f0a2fef06a8..76d8a904e4806c0cdadf37ff453b13f452f376ff 100644 --- a/src/core/js/caosdb.js +++ b/src/core/js/caosdb.js @@ -286,6 +286,32 @@ function input2caosdbDate(date, time) { return date + "T" + time; } +/** + * Return true if the current user has the given permission for the given + * entity. + * + * @param {HTMLElement} entity + * @return {boolean} + */ +var hasEntityPermission = function (entity, permission) { + if (userHasRole("administration")) { + // administration is a special role. It has * permissions. + return true; + } + const permissions = getAllEntityPermissions(entity); + return permissions.indexOf(permission.toUpperCase()) > -1; +} + +/** + * Get all permissions the current user has for this entity. + * @param {HTMLElement} entity + * @return {string[]} array of permissions. + */ +var getAllEntityPermissions = function (entity) { + const permissions = $(entity).find("[data-permission]").toArray().map(x => x.getAttribute("data-permission")); + return permissions; +} + /** * Take a datetime from caosdb and return a date and a time * suitable for html inputs. diff --git a/src/core/js/ext_bookmarks.js b/src/core/js/ext_bookmarks.js index 07de3014825de6c64040e98e3da143d4ad5b8228..569d19a09ff72a4f6174bba36a83bf0a3ca18be6 100644 --- a/src/core/js/ext_bookmarks.js +++ b/src/core/js/ext_bookmarks.js @@ -217,6 +217,8 @@ var ext_bookmarks = function ($, logger, config) { * Generate the TSV data for the export callback with all current * bookmarks. * + * TODO merge with caosdb_utils.create_tsv_table. + * * @param {string[]} bookmarks - array of ids. */ const get_export_table = async function (bookmarks, preamble, tab, newline) { diff --git a/src/core/js/ext_xls_download.js b/src/core/js/ext_xls_download.js index fbedd0c6cd10c75c3d0b2914de1a1eef9cb7b1f0..03fba88b0920dc94b17caccf4b4ed365a9c64e15 100644 --- a/src/core/js/ext_xls_download.js +++ b/src/core/js/ext_xls_download.js @@ -113,6 +113,8 @@ var caosdb_table_export = new function () { /** * Convert all entities to an encoded tsv string with the given columns. * + * TODO merge with caosdb_utils.create_tsv_table. + * * @param {HTMLElement[]} entities - entities which are converted to rows * of the tsv string. * @param {string[]} columns - array of property names. diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js index ac4d908bca32e0f8bf0f0645a6f4d5bc9d628ada..509abee58bd66bd938788527f4f0e459ceb36e6d 100644 --- a/src/core/js/webcaosdb.js +++ b/src/core/js/webcaosdb.js @@ -327,6 +327,30 @@ this.caosdb_utils = new function () { } throw new TypeError(name + " is expected to be an array, was " + typeof obj); } + + /** + * Create a tsv table as string. + * + * The data must be appropriately encoded (e.g. urlencoded). + * + * With `tab=","` it is also possible to create csv tables. + * + * @param {string[][]} data - An array of rows which contain arrays of + * cells. + * @param {string} [preamble="data:text/csv;charset=utf-8,"] - a prefix for + * the the resulting string. The default is suitable for creating + * downloadable href attributes of links. + * @param {string} [tab="%09"] - the cell separator. + * @param {string} [newline="%0A"] - the row separator. + * @return {string} a tsv table as a string. + */ + this.create_tsv_table = function(data, preamble, tab, newline) { + preamble = ((typeof preamble == 'undefined') ? "data:text/csv;charset=utf-8,": preamble); + tab = tab || "%09"; + newline = newline || "%0A"; + const rows = data.map(x => x.join(tab)) + return `${preamble}${rows.join(newline)}`; + } } /** @@ -651,7 +675,7 @@ this.transaction = new function () { */ this.retrieveEntitiesById = async function _rEBIs(entityIds) { const response = await connection.get(this.generateEntitiesUri(entityIds)); - return $(response).find('Response [id]').toArray(); + return $(response).find('Response > [id]').toArray(); } /** Sends a PUT request with an xml representation of entities and @@ -964,6 +988,117 @@ this.transaction = new function () { } } +/** + * This module provides the functionality to load the full version history (for + * privileged users) and export it to tsv. + */ +var version_history = new function () { + + this._get = connection.get; + /** + * Retrieve the version history of an entity and return a table with the + * history. + * + * @param {string} entity - the entity id with or without version id. + * @return {HTMLElement} A table with the version history. + */ + this.retrieve_history = async function(entity) { + const xml = this._get(transaction + .generateEntitiesUri([entity]) + "?H"); + const html = (await transformation.transformEntities(xml))[0]; + const history_table = $(html).find(".caosdb-f-entity-version-history"); + return history_table[0]; + } + + /** + * Initalize the buttons for loading the version history. + * + * The buttons are visible when the entity has only the normal version info + * attached and the current user has the permissions to retrieve the + * version history. + * + * The buttons trigger the retrieval of the version history and append the + * version history to the version info modal. + */ + this.init_load_history_buttons = function () { + for (let entity of $(".caosdb-entity-panel")) { + const is_permitted = hasEntityPermission(entity, "RETRIEVE:HISTORY"); + if (!is_permitted) { + continue; + } + const entity_id_version = getEntityIdVersion(entity); + const version_info = $(entity) + .find(".caosdb-f-entity-version-info"); + const button = $(version_info) + .find(".caosdb-f-entity-version-load-history-btn"); + button.show(); + button + .click(async () => { + button.prop("disabled", true); + const wait = createWaitingNotification("Retrieving full history. Please wait."); + const sparse = $(version_info) + .find(".caosdb-f-entity-version-history"); + sparse.find(".modal-body *").replaceWith(wait); + + const history_table = await version_history + .retrieve_history(entity_id_version); + sparse.replaceWith(history_table); + version_history.init_export_history_buttons(entity); + }); + } + } + + /** + * Transform the HTML table with the version history to tsv. + * + * @param {HTMLElement} history_table - the HTML representation of the + * version history. + * @return {string} the version history as downloadable tsv string, + * suitable for the href attribute of a link or window.location. + */ + this.get_history_tsv = function (history_table) { + const rows = []; + for (let row of $(history_table).find("tr")) { + const cells = $(row).find(".export-data").toArray().map(x => x.textContent); + rows.push(cells); + } + return caosdb_utils.create_tsv_table(rows); + } + + /** + * Initialize the export buttons of `entity`. + * + * The buttons are only visible when the version history is visible and + * trigger a download of a tsv file which contains the version history. + * + * The buttons trigger the download of a tsv file with the version history. + * + * @param {HTMLElement} [entity] - if undefined, the export buttons of all + * page entities are initialized. + */ + this.init_export_history_buttons = function (entity) { + entity = entity || $(".caosdb-entity-panel"); + for (let version_info of $(entity) + .find(".caosdb-f-entity-version-info")) { + $(version_info).find(".caosdb-f-entity-version-export-history-btn") + .click(async () => { + const html_table = $(version_info).find("table")[0]; + const history_tsv = this.get_history_tsv(html_table); + version_history._download_tsv(history_tsv); + }); + } + } + + this._download_tsv = function(tsv_link) { + window.location.href = tsv_link; + } + + + this.init = function () { + this.init_load_history_buttons(); + this.init_export_history_buttons(); + } +} var paging = new function () { @@ -1602,6 +1737,7 @@ function initOnDocumentReady() { } caosdb_modules.init(); navbar.init(); + version_history.init(); } diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl index 826961b9e3f748daf73017715348fa3682bcbb1a..f9216f0b7b9e56d40f2ea2761712eef9b11d04f7 100644 --- a/src/core/xsl/entity.xsl +++ b/src/core/xsl/entity.xsl @@ -103,6 +103,7 @@ <xsl:attribute name="data-entity-id"> <xsl:value-of select="@id"/> </xsl:attribute> + <xsl:apply-templates mode="entity-permissions" select="Permissions"/> <!-- A page-unique ID for this entity --> <xsl:variable name="entityid" select="concat('entity_',generate-id())"/> <div class="panel-heading caosdb-entity-panel-heading"> @@ -524,78 +525,197 @@ </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> + <xsl:attribute name="data-entity-versioned-id"><xsl:value-of select="concat($entityId, '@', @id)"/></xsl:attribute> <div class="modal-dialog modal-lg" role="document"> <div class="modal-content text-left"> + <!-- modal-header start --> <div> <xsl:attribute name="class"> modal-header - <xsl:if test="Successor"> + <xsl:if test="not(@head='true')"> <!-- 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"> + <p class="caosdb-entity-heading-attr"> <em class="caosdb-entity-heading-attr-name"> This is - <xsl:if test="Successor"><b>not</b></xsl:if> + <xsl:if test="not(@head='true')"><b>not</b></xsl:if> the latest version of this entity. + <xsl:apply-templates mode="entity-version-modal-head" select="Successor"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> </em> </p> </div> - <div class="modal-body"> - <xsl:apply-templates mode="entity-version-modal-head" select="Successor"> + <!-- modal-header end --> + <div class="caosdb-f-entity-version-history"> + <!-- modal-body and modal-footer are added by this template --> + <xsl:apply-templates select="." mode="entity-version-history-table"> <xsl:with-param name="entityId" select="$entityId"/> </xsl:apply-templates> - <xsl:apply-templates mode="entity-version-modal-successor" select="Successor"> + </div> + </div> + </div> + </div> + </xsl:template> + + <xsl:template match="Version[@completeHistory='true']" mode="entity-version-history-table"> + <!-- contains the table of the full version history --> + <xsl:param name="entityId"/> + <div class="modal-body"> + <table class="table table-hover"> + <thead> + <tr><div class="export-data">Entity ID</div><th/> + <th class="export-data">Version ID</th> + <th class="export-data">Date</th> + <th class="export-data">User</th> + <div class="export-data">URI</div> + </tr></thead> + <tbody> + <xsl:apply-templates mode="entity-version-modal-successor" select="Successor"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> + <tr> + <div class="export-data"><xsl:value-of select="$entityId"/></div> + <td class="caosdb-v-entity-version-hint caosdb-v-entity-version-hint-cur">This Version</td> + <td><xsl:apply-templates select="@id" mode="entity-version-id"/> + </td><td> + <xsl:apply-templates select="@date" mode="entity-version-date"/> + </td><td class="export-data"> + <xsl:value-of select="@username"/>@<xsl:value-of select="@realm"/> + </td> + <div class="export-data"><xsl:value-of select="concat($entitypath, $entityId, '@', @id)"/></div> + </tr> + <xsl:apply-templates mode="entity-version-modal-predecessor" select="Predecessor"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> + </tbody> + </table> + </div> + <div class="modal-footer"> + <button type="button" class="caosdb-f-entity-version-export-history-btn btn btn-default">Export history</button> + </div> + </xsl:template> + + <xsl:template match="Version[not(@completeHistory='true')]" mode="entity-version-history-table"> + <!-- contains the table of the simple version info (not the full history)--> + <xsl:param name="entityId"/> + <div class="modal-body"> + <table class="table"> + <thead><tr><th>Previous Version</th><th>This Version</th><th>Next Version</th></tr></thead> + <tbody> + <tr> + <td> + <xsl:if test="not(Predecessor)"> + <div class="caosdb-v-entity-version-no-related">No predecessor</div> + </xsl:if> + <xsl:apply-templates select="Predecessor/@id" mode="entity-version-link-to-other-version"> <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"> + </td> + <td> + <xsl:apply-templates select="@id" mode="entity-version-id"/> + </td> + <td> + <xsl:if test="not(Successor)"> + <div class="caosdb-v-entity-version-no-related">No successor</div> + </xsl:if> + <xsl:apply-templates select="Successor/@id" mode="entity-version-link-to-other-version"> <xsl:with-param name="entityId" select="$entityId"/> </xsl:apply-templates> - </div> - </div> - </div> + </td> + </tr> + </tbody> + </table> + </div> + <div class="modal-footer"> + <button type="button" style="display: none" class="caosdb-f-entity-version-load-history-btn btn btn-default">Load full history</button> </div> </xsl:template> + + <xsl:template match="@id" mode="entity-version-id"> + <!-- a versions'id (abbreviated) --> + <xsl:attribute name="title">Full Version ID: <xsl:value-of select="."/></xsl:attribute> + <xsl:value-of select="substring(.,1,8)"/> + <div class="export-data"><xsl:value-of select="."/></div> + </xsl:template> + + <xsl:template match="@date" mode="entity-version-date"> + <!-- a version's date (abbreviated)--> + <xsl:attribute name="title"><xsl:value-of select="."/></xsl:attribute> + <xsl:value-of select="substring(.,0,11)"/> + <xsl:value-of select="' '"/> + <xsl:value-of select="substring(.,12,8)"/> + <div class="export-data"><xsl:value-of select="."/></div> + </xsl:template> + + <xsl:template match="Predecessor|Successor" mode="entity-version-modal-single-history-item"> + <!-- a single row of the version history table --> + <xsl:param name="entityId"/> + <xsl:param name="hint"/> + <tr> + <div class="export-data"><xsl:value-of select="$entityId"/></div> + <td class="caosdb-v-entity-version-hint"><xsl:value-of select="$hint"/></td> + <td> + <xsl:apply-templates select="@id" mode="entity-version-link-to-other-version"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> + </td><td> + <xsl:apply-templates select="@date" mode="entity-version-date"/> + </td><td class="export-data"> + <xsl:value-of select="@username"/>@<xsl:value-of select="@realm"/> + </td> + <div class="export-data"><xsl:value-of select="concat($entitypath, $entityId, '@', @id)"/></div> + </tr> + </xsl:template> + + <xsl:template match="@id" mode="entity-version-link-to-other-version"> + <!-- link to other version (used by both version tables)--> + <xsl:param name="entityId"/> + <a><xsl:attribute name="href"><xsl:value-of select="$entityId"/>@<xsl:value-of select="."/></xsl:attribute> + <xsl:apply-templates select="." mode="entity-version-id"/></a> + </xsl:template> + <xsl:template match="Predecessor" mode="entity-version-modal-predecessor"> - <!-- content of the versioning window --> + <!-- content of the versioning info (not the full history) --> <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:apply-templates mode="entity-version-modal-single-history-item" select="."> + <xsl:with-param name="entityId" select="$entityId"/> + <xsl:with-param name="hint" select="'Older Version'"/> + </xsl:apply-templates> + <xsl:apply-templates mode="entity-version-modal-predecessor" select="Predecessor"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> </xsl:template> + <xsl:template match="Successor" mode="entity-version-modal-head"> - <!-- content of the versioning window --> + <!-- content of the versioning modal's header (if a newer version exists) --> <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> + View the newest version here: + <a><xsl:attribute name="href"><xsl:value-of select="$entityId"/>@HEAD</xsl:attribute> + <xsl:value-of select="$entityId"/>@HEAD + </a> </xsl:template> + <xsl:template match="Successor" mode="entity-version-modal-successor"> - <!-- content of the versioning window --> + <!-- content of the versioning info (not the full history) --> <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:apply-templates mode="entity-version-modal-successor" select="Successor"> + <xsl:with-param name="entityId" select="$entityId"/> + </xsl:apply-templates> + <xsl:apply-templates mode="entity-version-modal-single-history-item" select="."> + <xsl:with-param name="entityId" select="$entityId"/> + <xsl:with-param name="hint" select="'Newer Version'"/> + </xsl:apply-templates> </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"/> @@ -604,13 +724,15 @@ <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: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. @@ -619,4 +741,17 @@ <xsl:value-of select="@id"/> </xsl:attribute> </xsl:template> + + <!-- PERMISSIONS --> + <xsl:template match="Permissions" mode="entity-permissions"> + <div style="display: none"> + <xsl:apply-templates select="Permission" mode="entity-permissions"/> + </div> + </xsl:template> + + <xsl:template match="Permission" mode="entity-permissions"> + <div> + <xsl:attribute name="data-permission"><xsl:value-of select="@name"/></xsl:attribute> + </div> + </xsl:template> </xsl:stylesheet> diff --git a/test/core/js/modules/entity.xsl.js b/test/core/js/modules/entity.xsl.js index 91a6c18c2f0bee58c272461fe4b4fe595508110c..59a2f8a6f9a03cf4990fe11bfd751d437a3ffc3e 100644 --- a/test/core/js/modules/entity.xsl.js +++ b/test/core/js/modules/entity.xsl.js @@ -264,6 +264,48 @@ QUnit.test("data-version-successor attribute", function(assert) { assert.equal($(html).find("div.caosdb-entity-panel[data-version-successor]").length, 0, "data-version-successor attribute not present"); }); +QUnit.test("version full history", function (assert) { + var xmlstr = ` + <Response username="user1" realm="Realm1" srid="31ce8ea1-6c9b-4a82-82ec-9f6f3edd2622" timestamp="1606225647516" baseuri="https://localhost:10443" count="1"> + <Record id="3373" name="TestRecord1-10thVersion" description="This is the 10th version."> + <Permissions> + <Permission name="RETRIEVE:HISTORY" /> + </Permissions> + <Version id="vid6" username="user1" realm="Realm1" date="date6" completeHistory="true"> + <Predecessor id="vid5" username="user1" realm="Realm1" date="date5"> + <Predecessor id="vid4" username="user1" realm="Realm1" date="date4"> + <Predecessor id="vid3" username="user1" realm="Realm1" date="date3"> + <Predecessor id="vid2" username="user1" realm="Realm1" date="date2"> + <Predecessor id="vid1" username="user1" realm="Realm1" date="date1" /> + </Predecessor> + </Predecessor> + </Predecessor> + </Predecessor> + <Successor id="vid7" username="user1" realm="Realm1" date="date7"> + <Successor id="vid8" username="user1" realm="Realm1" date="date8"> + <Successor id="vid9" username="user1" realm="Realm1" date="date9"> + <Successor id="vid10" username="user1" realm="Realm1" date="date10" /> + </Successor> + </Successor> + </Successor> + </Version> + <Parent id="3372" name="TestRT" /> + </Record> +</Response> +`; + var xml = str2xml(xmlstr); + var html = applyTemplates(xml, this.entityXSL, "entities", "*"); + var version_info = $(html).find(".caosdb-f-entity-version-info"); + var table_elem = $(version_info).find("table"); + + var TAB = "%09", NEWL = "%0A", usr = "user1@Realm1", + path = "/entitypath/3373"; + var export_table = `data:text/csv;charset=utf-8,Entity ID${TAB}Version ID${TAB}Date${TAB}User${TAB}URI${NEWL}3373${TAB}vid10${TAB}date10${TAB}${usr}${TAB}${path}@vid10${NEWL}3373${TAB}vid9${TAB}date9${TAB}${usr}${TAB}${path}@vid9${NEWL}3373${TAB}vid8${TAB}date8${TAB}${usr}${TAB}${path}@vid8${NEWL}3373${TAB}vid7${TAB}date7${TAB}${usr}${TAB}${path}@vid7${NEWL}3373${TAB}vid6${TAB}date6${TAB}${usr}${TAB}${path}@vid6${NEWL}3373${TAB}vid5${TAB}date5${TAB}${usr}${TAB}${path}@vid5${NEWL}3373${TAB}vid4${TAB}date4${TAB}${usr}${TAB}${path}@vid4${NEWL}3373${TAB}vid3${TAB}date3${TAB}${usr}${TAB}${path}@vid3${NEWL}3373${TAB}vid2${TAB}date2${TAB}${usr}${TAB}${path}@vid2${NEWL}3373${TAB}vid1${TAB}date1${TAB}${usr}${TAB}${path}@vid1` + assert.equal(version_info.length, 1); + assert.equal(table_elem.length, 1); + assert.equal(version_history.get_history_tsv(table_elem[0]), export_table); +}); + QUnit.test("Transforming abstract properties", function (assert) { var xmlstr = `<Property id="3842" name="reftotestrt" datatype="TestRT"> <Version id="04ad505da057603a9177a1fcf6c9efd5f3690fe4" date="2020-11-23T10:38:02.936+0100" /> diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js index 5b43d2c1dcd2be0a9cac710b5749d13c631c76ed..7b3f7abf404f261668c18690df337b73794dd8bd 100644 --- a/test/core/js/modules/webcaosdb.js.js +++ b/test/core/js/modules/webcaosdb.js.js @@ -957,10 +957,6 @@ QUnit.test("createCarouselNav", function(assert) { let original_get = connection.get; ref_property_elem.find('div').append(refLinks); - const sleep = function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - QUnit.test("initProperty", async function(assert) { var done = assert.async(2); assert.ok(preview.initProperty, "function available"); @@ -1939,3 +1935,80 @@ QUnit.test("toolbox example", function(assert) { assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Tools"]').length, 1, "one 'Tools' toolbox"); assert.equal($('.caosdb-f-navbar-toolbox[data-toolbox-name="Tools"] button').length, 3, "three 'Tools' buttons"); }); + +QUnit.module("webcaosdb.js - version_history", { + before: function(assert) { + connection._init(); + }, + after: function(assert) { + connection._init(); + }, +}); + +QUnit.test("available", function (assert) { + assert.equal(typeof version_history.init, "function"); + assert.equal(typeof version_history.get_history_tsv, "function"); + assert.equal(typeof version_history.init_export_history_buttons, "function"); + assert.equal(typeof version_history.init_load_history_buttons, "function"); + assert.equal(typeof version_history.retrieve_history, "function"); +}) + +QUnit.test("init_load_history_buttons and init_load_history_buttons", async function (assert) { + var xml_str = `<Response username="user1" realm="Realm1" srid="bc2f8f6b-71d6-49ca-890c-eebea3e38e18" timestamp="1606253365632" baseuri="https://localhost:10443" count="1"> + <UserInfo username="user1" realm="Realm1"> + <Roles> + <Role>role1</Role> + </Roles> + </UserInfo> + <Record id="8610" name="TestRecord1-6thVersion" description="This is the 6th version."> + <Permissions> + <Permission name="RETRIEVE:HISTORY" /> + </Permissions> + <Version id="efa5ac7126c722b3f43284e150d070d6deac0ba6"> + <Predecessor id="f09114b227d88f23d4e23645ae471d688b1e82f7" /> + <Successor id="5759d2bccec3662424db5bb005acea4456a299ef" /> + </Version> + <Parent id="8609" name="TestRT" /> + </Record> +</Response> +`; + var done = assert.async(2); + var xml = str2xml(xml_str); + version_history._get = async function (entity) { + assert.equal(entity, "Entity/8610@efa5ac7126c722b3f43284e150d070d6deac0ba6?H"); + done(); + $(xml).find("Version").attr("completeHistory", "true"); + return xml; + } + var html = await transformation.transformEntities(xml); + var load_button = $(html).find(".caosdb-f-entity-version-load-history-btn"); + $("body").append(html); + + assert.notOk(load_button.is(":visible"), "load_button hidden"); + load_button.click(); // nothing happens + + version_history.init_load_history_buttons(); + assert.ok(load_button.is(":visible"), "load_button is not hidden anymore"); + + // load_button triggers retrieval of history + load_button.click(); + await sleep(200); + + var gone_button = $(html).find(".caosdb-f-entity-version-load-history-btn"); + assert.equal(gone_button.length, 0, "button is gone"); + + export_button = $(html).find(".caosdb-f-entity-version-export-history-btn"); + assert.ok(export_button.is(":visible"), "export_button is visible"); + + version_history._download_tsv = function (tsv) { + assert.equal(tsv.indexOf("data:text/csv;charset=utf-8,Entity ID%09"), 0); + done(); + } + export_button.click(); + + $(html).remove(); +}); + +const sleep = function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +}