diff --git a/CHANGELOG.md b/CHANGELOG.md index d35b665700386249fc2df5b9ae770f5130dc7992..82822d009a19b0414c82af31ab0c75df033da098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 with the full version history of the first entity on the page. * `BUILD_MODULE_SHOW_ID_IN_LABEL` build variable with which the showing of entity ids together with their names if it is enabled (disabled by default). +* Introduced `caosdb-f-form-required-marker` and `caosdb-f-form-required-label` + css classes for the markers of required fields in CaosDB form elements. ### Changed (for changes in existing functionality) @@ -25,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed saving of text properties that were changed in the Source-Editing mode of the WYSIWYG editor. +* [#266](https://gitlab.indiscale.com/caosdb/src/caosdb-webui/-/issues/266) + Fixed an issue whereby missing property descriptions in the edit mode would + lead to wrongly detected entity updates in the server ### Security (in case of vulnerabilities) diff --git a/misc/entity_state_test_data.py b/misc/entity_state_test_data.py index 400b73749011834fb5390be2e00eebdde1a91062..ff06452cb33045e876e1f17a94862737e77a8207 100755 --- a/misc/entity_state_test_data.py +++ b/misc/entity_state_test_data.py @@ -3,7 +3,7 @@ import sys import caosdb as db -_PASSWORD = "password1A!" +_PASSWORD = "Password1!" def teardown(): @@ -49,6 +49,8 @@ def setup_users(): "Grant", "STATE:TRANSITION:Edit"), db.administration.PermissionRule( "Grant", "STATE:TRANSITION:Start Review"), + db.administration.PermissionRule( + "Grant", "STATE:ASSIGN:Publish Life-cycle"), ]) db.administration._set_permissions( @@ -180,9 +182,17 @@ def setup_test_data(): # any record of this type will have the unpublished state rt = db.RecordType("TestRT") rt.state = db.State(model="Publish Life-cycle", name="Unpublished") + rt.acl = db.ACL() + rt.acl.grant(role="normal", permission="RETRIEVE:ENTITY") + rt.acl.grant(role="normal", permission="USE:AS_PARENT") rt.insert() - db.Property("TestProperty", datatype=db.TEXT).insert() + prop = db.Property("TestProperty", datatype=db.TEXT) + prop.acl = db.ACL() + prop.acl.grant(role="normal", permission="RETRIEVE:ENTITY") + prop.acl.grant(role="normal", permission="USE:AS_PROPERTY") + prop.insert() + rec = db.Record().add_parent("TestRT") rec.description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." rec.add_property("TestProperty", "TestValue") diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css index 8fe10a153887a0c051846734a5a40cc195666f5b..e8b7b42086dd41b0c599fb93b5ac0c1977abfd06 100644 --- a/src/core/css/webcaosdb.css +++ b/src/core/css/webcaosdb.css @@ -779,3 +779,15 @@ details p { font-size: 0.875rem; color: #5E6762; } + +.caosdb-f-form-wrapper .caosdb-f-form-required-marker { + font-size: 10px; + color: red; + margin-right: 4px; + font-weight: 100; +} + +.caosdb-f-form-elements-footer .caosdb-f-form-required-label { + margin-right: 4px; + font-size: 11px; +} diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js index 7c3c4300f971eae16d30748e6787843334708baa..a2c1f55ce49e409b0153c888197002868b90a637 100644 --- a/src/core/js/caosdb.js +++ b/src/core/js/caosdb.js @@ -550,10 +550,14 @@ function getPropertyFromElement(propertyelement, names = undefined) { let dtel = propertyelement.getElementsByClassName("caosdb-property-datatype")[0]; let idel = propertyelement.getElementsByClassName("caosdb-property-id")[0]; let unitel = valel.getElementsByClassName("caosdb-unit")[0]; + const descel = propertyelement.getElementsByClassName("caosdb-property-description")[0]; property.html = propertyelement; // name property.name = getPropertyName(propertyelement); + // description + property.description = descel ? descel.textContent : undefined; + // id if (idel === undefined) { @@ -623,20 +627,11 @@ function getPropertyFromElement(propertyelement, names = undefined) { if (property.list) { // list datatypes let listel; - if (property.reference) { - // list of referernces - listel = findElementByConditions(valel, x => x.classList.contains("caosdb-f-reference-value"), - x => x.classList.contains("caosdb-preview-container")); - for (var j = 0; j < listel.length; j++) { - property.value.push(getIDfromHREF(listel[j])); - } - } else { - // list of anything but references - listel = findElementByConditions(valel, x => x.classList.contains("list-inline-item"), - x => x.classList.contains("caosdb-preview-container")); - for (var j = 0; j < listel.length; j++) { - property.value.push(listel[j].textContent); - } + // list of anything but references + listel = findElementByConditions(valel, x => x.classList.contains("caosdb-f-property-single-raw-value"), + x => x.classList.contains("caosdb-preview-container")); + for (var j = 0; j < listel.length; j++) { + property.value.push(listel[j].textContent); } } else if (property.reference && valel.getElementsByTagName("a")[0]) { // reference datatypes @@ -888,13 +883,16 @@ function getProperty(element, property_name, case_sensitive = true) { * @param parentElement The element which is to recieve the attributes. * @param parent The object possibly containing an id and or a name. */ -function setNameID(parentElement, parent) { +function _setDescriptionNameID(parentElement, parent) { if (typeof parent.id !== 'undefined' && parent.id !== '') { parentElement.setAttribute("id", parent.id); } if (typeof parent.name !== 'undefined' && parent.name !== '') { parentElement.setAttribute("name", parent.name); } + if (typeof parent.description !== 'undefined' && parent.description !== '') { + parentElement.setAttribute("description", parent.description); + } } /** @@ -906,7 +904,7 @@ function setNameID(parentElement, parent) { */ function appendParent(doc, element, parent) { var parentElement = document.createElementNS(undefined, "Parent"); - setNameID(parentElement, parent); + _setDescriptionNameID(parentElement, parent); element.appendChild(parentElement); } @@ -933,7 +931,7 @@ function appendValueNode(doc, element, name, value) { */ function appendProperty(doc, element, property, append_datatype = false) { var propertyElement = document.createElementNS(undefined, "Property"); - setNameID(propertyElement, property); + _setDescriptionNameID(propertyElement, property); if (append_datatype && typeof property.datatype !== "undefined") { propertyElement.setAttribute("datatype", property.datatype); } @@ -995,19 +993,16 @@ function createEntityXML(role, name, id, properties, parents, var doc = _createDocument(role); var nelnode = doc.children[0]; - setNameID(nelnode, { + _setDescriptionNameID(nelnode, { name: name, - id: id + id: id, + description: description, }); if (typeof datatype !== 'undefined' && datatype.length > 0) { $(nelnode).attr("datatype", datatype); } - if (typeof description !== 'undefined' && description.length > 0) { - $(nelnode).attr("description", description); - } - if (typeof unit !== 'undefined' && unit.length > 0) { $(nelnode).attr("unit", unit); } diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js index 193235a2f8a799c07ccc893742d5df9a7d0fa7d1..6815acd791213c6b239a693c3c64667965c369ed 100644 --- a/src/core/js/form_elements.js +++ b/src/core/js/form_elements.js @@ -1232,7 +1232,7 @@ var form_elements = new function () { .css({ "margin": "20px", }).append(this.make_required_marker()) - .append('<span style="margin-right: 4px; font-size: 11px">required field</span>')[0]; + .append('<span class="caosdb-f-form-required-label">required field</span>')[0]; } this.make_error_message = function (message) { @@ -1502,14 +1502,7 @@ var form_elements = new function () { * @returns {HTMLElement} span element. */ this.make_required_marker = function () { - // TODO create class and move to css file - return $('<span>*</span>') - .css({ - "font-size": "10px", - "color": "red", - "margin-right": "4px", - "font-weight": "100", - })[0]; + return $('<span class="caosdb-f-form-required-marker">*</span>')[0]; } diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl index ab7979367d2f77204a1e0c2e8acd9d5810649911..82de9e416c2e28f21cd3f386cc6e04419284dc5f 100644 --- a/src/core/xsl/entity.xsl +++ b/src/core/xsl/entity.xsl @@ -273,7 +273,7 @@ </xsl:attribute> <hr class="caosdb-subproperty-divider"/> <dl class="row caosdb-v-entity-property-attributes"> - <xsl:apply-templates mode="property-attributes" select="@description"/> + <xsl:apply-templates mode="property-attributes-desc" select="@description"/> <xsl:apply-templates mode="property-attributes-id" select="@id"/> <xsl:apply-templates mode="property-attributes-type" select="@datatype"/> <xsl:apply-templates mode="property-attributes" select="@*[not(contains('+cuid+id+name+description+datatype+',concat('+',name(),'+')))]"/> @@ -477,6 +477,12 @@ <xsl:value-of select="."/> </dd> </xsl:template> + <xsl:template match="@description" mode="property-attributes-desc"> + <dt class="col-6 col-md-4 mb-0">description</dt> + <dd class="col-6 col-md-8 mb-0 caosdb-property-description"> + <xsl:value-of select="."/> + </dd> + </xsl:template> <xsl:template match="@id" mode="property-attributes-id"> <dt class="col-6 col-md-4 mb-0">id</dt> <dd class="col-6 col-md-8 mb-0"> diff --git a/test/core/js/modules/caosdb.js.js b/test/core/js/modules/caosdb.js.js index 8df5e2f9c2b933cd7b678286295961f2c73d7113..49b26b61d3448cbc0c9b8dd50b536e282d134dec 100644 --- a/test/core/js/modules/caosdb.js.js +++ b/test/core/js/modules/caosdb.js.js @@ -19,7 +19,15 @@ QUnit.module("caosdb.js", { }, before: function(assert) { - var done = assert.async(3); + var done = assert.async(4); + + // load entity.xsl + var qunit_obj = this; + _retrieveEntityXSL().then(function(xsl) { + qunit_obj.entityXSL = xsl + done(); + }); + this.setTestDocument("x", done, ` <Response> <Record name="nameofrecord"> @@ -82,7 +90,7 @@ QUnit.module("caosdb.js", { </Record> </Response>`); - + // Test document for unset references this.setTestDocument("unsetReferencesTest", done, ` <Response> @@ -566,3 +574,61 @@ QUnit.test("unset_file_attributes", function(assert) { undefined); assert.equal(xml2str(res3), "<File id=\"103\" name=\"test\" path=\"testfile.txt\" checksum=\"blablabla\" size=\"0\"/>"); }); + +QUnit.test("getPropertyFromElement", async function(assert) { + var data = await $.ajax({ + cache: true, + dataType: 'xml', + url: "xml/test_case_list_of_myrecordtype.xml", + }); + console.log(this.entityXSL); + var xsl = injectTemplate(this.entityXSL, '<xsl:template match="/"><ul><xsl:apply-templates select="Property" mode="entity-body"/></ul></xsl:template>'); + var params = { + entitypath: "/entitypath/" + }; + var ret = xslt(data, xsl, params); + assert.ok(ret); + assert.propEqual(getPropertyFromElement(ret.firstElementChild), { + "datatype": "LIST<MyRecordType>", + "description": undefined, + "html": {}, + "id": "149315", + "list": true, + "listDatatype": "MyRecordType", + "name": "MyRecordType", + "reference": true, + "unit": undefined, + "value": [ + "167510", + "", + "167546", + "167574", + "167625", + "167515", + "167441", + "167596", + "167249", + "167632", + "167593", + "167321", + "167536", + "167389", + "167612", + "167585", + "167228", + "167211", + "167414", + "167282", + "167409", + "167637", + "167487", + "167328", + "167572", + "167245", + "167615", + "167301", + "167466" + ] + }); + +}); diff --git a/test/core/js/modules/entity.xsl.js b/test/core/js/modules/entity.xsl.js index e5ff1e8700b8349cddbdda0a0b52ffef47e4e75f..04ec35a6fc14cbad81c731908e28f788999c3563 100644 --- a/test/core/js/modules/entity.xsl.js +++ b/test/core/js/modules/entity.xsl.js @@ -128,12 +128,13 @@ QUnit.test("Entities have a caosdb-annotation-section", function(assert) { QUnit.test("LIST Property", function(assert) { var done = assert.async(); var entityXSL = this.entityXSL; - assert.expect(2); + assert.expect(4); $.ajax({ cache: true, dataType: 'xml', url: "xml/test_case_list_of_myrecordtype.xml", }).done(function(data, textStatus, jdXHR) { + console.log(entityXSL); var xsl = injectTemplate(entityXSL, '<xsl:template match="/"><xsl:apply-templates select="Property" mode="property-value"/></xsl:template>'); var params = { entitypath: "/entitypath/" @@ -141,6 +142,8 @@ QUnit.test("LIST Property", function(assert) { var ret = xslt(data, xsl, params); assert.ok(ret); assert.equal(ret.firstChild.className, "caosdb-value-list", "property value contains a list.") + assert.equal($(ret.firstChild).find(".caosdb-f-property-single-raw-value").length, 29, "29 values in the list"); + assert.equal($(ret.firstChild).find(".caosdb-f-reference-value").length, 28, "28 reference values in the list"); }).always(function() { done(); }); @@ -292,7 +295,7 @@ QUnit.test("version full history", function (assert) { }); QUnit.test("Transforming abstract properties", function (assert) { - var xmlstr = `<Property id="3842" name="reftotestrt" datatype="TestRT"> + var xmlstr = `<Property id="3842" description="bla" name="reftotestrt" datatype="TestRT"> <Version id="04ad505da057603a9177a1fcf6c9efd5f3690fe4" date="2020-11-23T10:38:02.936+0100" /> </Property>`; var xml = str2xml(xmlstr); @@ -302,6 +305,7 @@ QUnit.test("Transforming abstract properties", function (assert) { "datatype": "TestRT", "html": {}, "id": "3842", + "description": "bla", "list": false, "name": "reftotestrt", "reference": true, diff --git a/test/core/xml/test_case_list_of_myrecordtype.xml b/test/core/xml/test_case_list_of_myrecordtype.xml index b67c857e22a983774b0ae1ba7e88d9ccd2515de8..63587832758049c6de22055225c9dbb9acb7ff48 100644 --- a/test/core/xml/test_case_list_of_myrecordtype.xml +++ b/test/core/xml/test_case_list_of_myrecordtype.xml @@ -24,6 +24,7 @@ <Property id="149315" name="MyRecordType" datatype="LIST<MyRecordType>" importance="FIX"> <Value>167510</Value> + <Value></Value> <Value>167546</Value> <Value>167574</Value> <Value>167625</Value>