diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js index 90d6bfd23b989bbff133a63d65e1e68d99f4b3bb..4c74b319fe51fc3aea924885b74edd82b3c5bf03 100644 --- a/src/core/js/caosdb.js +++ b/src/core/js/caosdb.js @@ -1074,13 +1074,17 @@ function createFileXML(name, id, parents, * @param {(Document|XMLDocumentFragment)} xmls The xml documents. * @return {Document} A new xml document. */ -function wrapXML(root, xmls) { +function wrapXML(root, xmls, start_with_root=undefined) { xmls = caosdb_utils.assert_array(xmls, "param `xmls`", true); caosdb_utils.assert_string(root, "param `root`"); var doc = _createDocument(root); for (var i = 0; i < xmls.length; i++) { - doc.firstElementChild.appendChild(xmls[i].firstElementChild); + if (start_with_root === undefined || start_with_root != true){ + doc.firstElementChild.appendChild(xmls[i].firstElementChild); + }else { + doc.firstElementChild.appendChild(xmls[i]); + } } return doc; @@ -1164,6 +1168,37 @@ async function update(xml) { return await transaction.updateEntitiesXml(wrapped); } + +/** + * Restore an old version of an entity using an xml representation. + * First, the old version is retrieved and the current version is set to the + * old one. + * @param versionid The version id (e.g. 123@abbabbaeff23322) of the version of + * the entity which shall be restored. + */ +async function restore_old_version(versionid){ + // retrieve entity + console.log("Restore ") + console.log(versionid) + var ent = await transaction.retrieveEntityById(versionid); + if (ent === undefined){ + throw new Error(`Entity with version id ${versionid} could not be retrieved.`); + } + // remove unwanted tags (Version and Permissions) + ent.getElementsByTagName("Version")[0].remove(); + var permissions = ent.getElementsByTagName("Permissions"); + for (let i = permissions.length-1; i >=0 ; i--) { + permissions[i].remove(); + } + // use XML to update entity/restore old version + reps = await transaction.updateEntitiesXml( + wrapXML("Request", [ent], start_with_root=true)) + if (reps.getElementsByTagName("Error").length>0) { + throw new Error(`Could not restore the Entity to the version ${versionid}.`); + } + +} + /** * Insert an entity in xml representation. * diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js index efa28c9b39921df3e02a25ec95d4601f752fa288..a4a124f02e6965989d1e712b1c63a94222a4b4f9 100644 --- a/src/core/js/webcaosdb.js +++ b/src/core/js/webcaosdb.js @@ -1035,6 +1035,7 @@ var version_history = new function () { .retrieve_history(entity_id_version); sparse.replaceWith(history_table); version_history.init_export_history_buttons(entity); + version_history.init_restore_version_buttons(entity); }); } } @@ -1072,7 +1073,7 @@ var version_history = new function () { for (let version_info of $(entity) .find(".caosdb-f-entity-version-info")) { $(version_info).find(".caosdb-f-entity-version-export-history-btn") - .click(async () => { + .click(() => { const html_table = $(version_info).find("table")[0]; const history_tsv = this.get_history_tsv(html_table); version_history._download_tsv(history_tsv); @@ -1080,6 +1081,47 @@ var version_history = new function () { } } + /** + * 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_restore_version_buttons = function (entity) { + entity = entity || $(".caosdb-entity-panel"); + + if (hasEntityPermission(entity, "UPDATE:")){ + for (let version_info of $(entity) + .find(".caosdb-f-entity-version-info")) { + console.log(version_info) + $(version_info).find(".caosdb-f-entity-version-restore-btn") + .toggleClass("d-none", false) + .click(async (eve) => { + const versionid = eve.delegateTarget.getAttribute("data-version-id") + try { + await restore_old_version(versionid); + window.location.reload(); + } catch (e) { + console.log(e); + $(version_info).find(".modal-body").prepend( + $(`<div class="alert alert-danger + alert-dismissible " role="alert"> + <button class="btn-close" data-bs-dismiss="alert" aria-label="close"></button> + Restore failed! + <p>${e.message}</p> + </div>`)); + + } + }); + } + } + } + this._download_tsv = function (tsv_link) { window.location.href = tsv_link; } @@ -1088,6 +1130,7 @@ var version_history = new function () { this.init = function () { this.init_load_history_buttons(); this.init_export_history_buttons(); + this.init_restore_version_buttons(); } } @@ -1927,4 +1970,4 @@ class _CaosDBModules { var caosdb_modules = new _CaosDBModules() -$(document).ready(initOnDocumentReady); \ No newline at end of file +$(document).ready(initOnDocumentReady); diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl index 92f08ea70645a8282f97f364c9fc4143f37afd6a..d8ba00a9810e9861bfba741055ea113c7f2b88bc 100644 --- a/src/core/xsl/entity.xsl +++ b/src/core/xsl/entity.xsl @@ -644,11 +644,14 @@ <div class="modal-body"> <table class="table table-hover"> <thead> - <tr><th><div class="export-data">Entity ID</div></th> + <tr> + <th></th> + <th class="invisible"><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> <th class="invisible"><div class="export-data">URI</div></th> + <th></th> </tr></thead> <tbody> <xsl:apply-templates mode="entity-version-modal-successor" select="Successor"> @@ -664,6 +667,13 @@ <xsl:value-of select="@username"/>@<xsl:value-of select="@realm"/> </td> <td class="invisible"><div class="export-data"><xsl:value-of select="concat($entitypath, $entityId, '@', @id)"/></div></td> + <td> + <xsl:if test="not(@head='true')"> + <button type="button" class="caosdb-f-entity-version-restore-btn btn btn-secondary d-none" title="Restore this version of the entity."> + <xsl:attribute name="data-version-id"><xsl:value-of select="$entityId"/>@<xsl:value-of select="@id"/></xsl:attribute> + <i class="bi-arrow-counterclockwise"></i></button> + </xsl:if> + </td> </tr> <xsl:apply-templates mode="entity-version-modal-predecessor" select="Predecessor"> <xsl:with-param name="entityId" select="$entityId"/> @@ -672,7 +682,7 @@ </table> </div> <div class="modal-footer"> - <button type="button" class="caosdb-f-entity-version-export-history-btn btn btn-secondary">Export history</button> + <button type="button" class="caosdb-f-entity-version-export-history-btn btn btn-secondary" title="Export this history table as a CSV file.">Export history</button> </div> </xsl:template> @@ -745,6 +755,14 @@ <xsl:value-of select="@username"/>@<xsl:value-of select="@realm"/> </td> <td class="invisible"><div class="export-data"><xsl:value-of select="concat($entitypath, $entityId, '@', @id)"/></div></td> + <td> + <!-- include button if it is not head, i.e. Predecessors are always old and Successors if they do have a Successor Member --> + <xsl:if test="(name()='Predecessor' or Successor)"> + <button type="button" class="caosdb-f-entity-version-restore-btn btn btn-secondary d-none" title="Restore this version of the entity."> + <xsl:attribute name="data-version-id"><xsl:value-of select="$entityId"/>@<xsl:value-of select="@id"/></xsl:attribute> + <i class="bi-arrow-counterclockwise"></i></button> + </xsl:if> + </td> </tr> </xsl:template>