Skip to content

Commit a81b7d8

Browse files
authored
Move roles along with person (#82)
And disable automatic codemeta generation while importing, to avoid getting transient errors during the import
1 parent c8b3910 commit a81b7d8

File tree

7 files changed

+373
-152
lines changed

7 files changed

+373
-152
lines changed

cypress/integration/persons.js

Lines changed: 132 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (C) 2020 The Software Heritage developers
2+
* Copyright (C) 2020-2025 The Software Heritage developers
33
* See the AUTHORS file at the top-level directory of this distribution
44
* License: GNU Affero General Public License version 3, or any later version
55
* See top-level LICENSE file for more information
@@ -395,31 +395,62 @@ describe('Author order change', function() {
395395
});
396396
});
397397

398+
it('moves roles with person', function() {
399+
cy.get('#name').type('My Test Software');
400+
401+
cy.get('#author_add').click();
402+
cy.get('#author_1_givenName').type('Jane');
403+
404+
cy.get('#author_add').click();
405+
cy.get('#author_2_givenName').type('John');
406+
407+
cy.get('#author_1_role_add').click();
408+
cy.get('#author_1_roleName_0').type('Developer');
409+
cy.get('#author_1_roleName_0').should('have.value', 'Developer');
410+
411+
// Move author 1 to the right (swap with author 2)
412+
cy.get('#author_1_moveToRight').click();
413+
414+
// After the swap, Jane (and her role) should be at author_2
415+
cy.get('#author_2_givenName').should('have.value', 'Jane');
416+
cy.get('#author_2_roleName_0').should('have.value', 'Developer');
417+
418+
// John should now be at author_1 and should not have the role
419+
cy.get('#author_1_givenName').should('have.value', 'John');
420+
cy.get('#author_1_roleName_0').should('not.exist');
421+
});
422+
398423
it('wraps around to the right', function() {
399424
cy.get('#name').type('My Test Software');
400425

401426
cy.get('#author_add').click();
402427
cy.get('#author_add').click();
403428
cy.get('#author_add').click();
404-
cy.get('#author_1_givenName').type('Jane');
429+
cy.get('#author_add').click();
430+
cy.get('#author_1_givenName').type('One');
405431
cy.get('#author_1_affiliation').type('Example Org');
406-
cy.get('#author_2_givenName').type('John');
407-
cy.get('#author_2_familyName').type('Doe');
408-
cy.get('#author_3_givenName').type('Alex');
432+
cy.get('#author_2_givenName').type('Two');
433+
cy.get('#author_2_familyName').type('Too');
434+
cy.get('#author_3_givenName').type('Three');
435+
cy.get('#author_4_givenName').type('Four');
409436

410437
cy.get('#author_1_moveToLeft').click()
411438

412-
cy.get('#author_1_givenName').should('have.value', 'Alex');
413-
cy.get('#author_1_familyName').should('have.value', '');
439+
cy.get('#author_1_givenName').should('have.value', 'Two');
440+
cy.get('#author_1_familyName').should('have.value', 'Too');
414441
cy.get('#author_1_affiliation').should('have.value', '');
415442

416-
cy.get('#author_2_givenName').should('have.value', 'John');
417-
cy.get('#author_2_familyName').should('have.value', 'Doe');
443+
cy.get('#author_2_givenName').should('have.value', 'Three');
444+
cy.get('#author_2_familyName').should('have.value', '');
418445
cy.get('#author_2_affiliation').should('have.value', '');
419446

420-
cy.get('#author_3_givenName').should('have.value', 'Jane');
447+
cy.get('#author_3_givenName').should('have.value', 'Four');
421448
cy.get('#author_3_familyName').should('have.value', '');
422-
cy.get('#author_3_affiliation').should('have.value', 'Example Org');
449+
cy.get('#author_3_affiliation').should('have.value', '');
450+
451+
cy.get('#author_4_givenName').should('have.value', 'One');
452+
cy.get('#author_4_familyName').should('have.value', '');
453+
cy.get('#author_4_affiliation').should('have.value', 'Example Org');
423454
});
424455

425456
it('wraps around to the left', function() {
@@ -428,25 +459,31 @@ describe('Author order change', function() {
428459
cy.get('#author_add').click();
429460
cy.get('#author_add').click();
430461
cy.get('#author_add').click();
431-
cy.get('#author_1_givenName').type('Jane');
462+
cy.get('#author_add').click();
463+
cy.get('#author_1_givenName').type('One');
432464
cy.get('#author_1_affiliation').type('Example Org');
433-
cy.get('#author_2_givenName').type('John');
434-
cy.get('#author_2_familyName').type('Doe');
435-
cy.get('#author_3_givenName').type('Alex');
465+
cy.get('#author_2_givenName').type('Two');
466+
cy.get('#author_2_familyName').type('Too');
467+
cy.get('#author_3_givenName').type('Three');
468+
cy.get('#author_4_givenName').type('Four');
436469

437-
cy.get('#author_3_moveToRight').click()
470+
cy.get('#author_4_moveToRight').click()
438471

439-
cy.get('#author_1_givenName').should('have.value', 'Alex');
472+
cy.get('#author_1_givenName').should('have.value', 'Four');
440473
cy.get('#author_1_familyName').should('have.value', '');
441474
cy.get('#author_1_affiliation').should('have.value', '');
442475

443-
cy.get('#author_2_givenName').should('have.value', 'John');
444-
cy.get('#author_2_familyName').should('have.value', 'Doe');
445-
cy.get('#author_2_affiliation').should('have.value', '');
476+
cy.get('#author_2_givenName').should('have.value', 'One');
477+
cy.get('#author_2_familyName').should('have.value', '');
478+
cy.get('#author_2_affiliation').should('have.value', 'Example Org');
446479

447-
cy.get('#author_3_givenName').should('have.value', 'Jane');
448-
cy.get('#author_3_familyName').should('have.value', '');
449-
cy.get('#author_3_affiliation').should('have.value', 'Example Org');
480+
cy.get('#author_3_givenName').should('have.value', 'Two');
481+
cy.get('#author_3_familyName').should('have.value', 'Too');
482+
cy.get('#author_3_affiliation').should('have.value', '');
483+
484+
cy.get('#author_4_givenName').should('have.value', 'Three');
485+
cy.get('#author_4_familyName').should('have.value', '');
486+
cy.get('#author_4_affiliation').should('have.value', '');
450487
});
451488
});
452489

@@ -889,6 +926,78 @@ describe('Multiple authors', function () {
889926
cy.get('#author_1_endDate_0').should('have.value', '2024-04-03');
890927
cy.get('#author_2_givenName').should('have.value', 'Joe');
891928
});
929+
930+
it('can remove the first one and reindexes remaining ones', function() {
931+
cy.get('#name').type('My Test Software');
932+
933+
cy.get('#author_add').click();
934+
cy.get('#author_add').click();
935+
cy.get('#author_nb').should('have.value', '2');
936+
937+
cy.get('#author_1_givenName').type('Alice');
938+
cy.get('#author_2_givenName').type('Bob');
939+
940+
cy.get('#author_1_remove').click();
941+
942+
cy.get('#author_nb').should('have.value', '1');
943+
cy.get('#author_1_givenName').should('have.value', 'Bob');
944+
945+
cy.get('#generateCodemetaV2').click();
946+
cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
947+
.should('deep.equal', {
948+
"@context": "https://doi.org/10.5063/schema/codemeta-2.0",
949+
"type": "SoftwareSourceCode",
950+
"name": "My Test Software",
951+
"author": [
952+
{
953+
"id": "_:author_1",
954+
"type": "Person",
955+
"givenName": "Bob"
956+
}
957+
],
958+
});
959+
});
960+
961+
it('can remove a middle one and reindexes remaining ones', function() {
962+
cy.get('#name').type('My Test Software');
963+
964+
cy.get('#author_add').click();
965+
cy.get('#author_add').click();
966+
cy.get('#author_add').click();
967+
cy.get('#author_nb').should('have.value', '3');
968+
969+
cy.get('#author_1_givenName').type('Alice');
970+
cy.get('#author_2_givenName').type('Bob');
971+
cy.get('#author_3_givenName').type('Carol');
972+
973+
cy.get('#author_2_remove').click();
974+
975+
cy.get('#author_nb').should('have.value', '2');
976+
cy.get('#author_1_givenName').should('have.value', 'Alice');
977+
cy.get('#author_2_givenName').should('have.value', 'Carol');
978+
979+
cy.get('#generateCodemetaV2').click();
980+
cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
981+
.should('deep.equal', {
982+
"@context": "https://doi.org/10.5063/schema/codemeta-2.0",
983+
"type": "SoftwareSourceCode",
984+
"name": "My Test Software",
985+
"author": [
986+
{
987+
"id": "_:author_1",
988+
"type": "Person",
989+
"givenName": "Alice"
990+
},
991+
{
992+
"id": "_:author_2",
993+
"type": "Person",
994+
"givenName": "Carol"
995+
}
996+
],
997+
});
998+
});
999+
1000+
8921001
});
8931002

8941003
describe('Contributors', function () {

index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ <h1>CodeMeta Generator v3.0</h1>
372372
<input type="button" id="author_remove" value="Remove last"
373373
onclick="removePerson('author');" />
374374
</div>
375+
<!-- author_list div is to be filled as the user adds authors -->
376+
<div id="author_list"></div>
375377
</fieldset>
376378

377379
<fieldset class="persons" id="contributor_container">
@@ -386,6 +388,8 @@ <h1>CodeMeta Generator v3.0</h1>
386388
<input type="button" id="contributor_remove" value="Remove last"
387389
onclick="removePerson('contributor');" />
388390
</div>
391+
<!-- contributor_list div is to be filled as the user adds contributors -->
392+
<div id="contributor_list"></div>
389393
</fieldset>
390394
</div>
391395

js/codemeta_generation.js

Lines changed: 66 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
/**
2-
* Copyright (C) 2019-2020 The Software Heritage developers
2+
* Copyright (C) 2019-2025 The Software Heritage developers
33
* See the AUTHORS file at the top-level directory of this distribution
44
* License: GNU Affero General Public License version 3, or any later version
55
* See top-level LICENSE file for more information
66
*/
77

88
"use strict";
99

10+
let codemetaGenerationEnabled = true;
11+
1012
const CODEMETA_CONTEXTS = {
1113
"2.0": {
1214
path: "./data/contexts/codemeta-2.0.jsonld",
@@ -291,6 +293,12 @@ async function buildExpandedDocWithAllContexts() {
291293

292294
// v2.0 is still default version for generation, for now
293295
async function generateCodemeta(codemetaVersion = "2.0") {
296+
if (!codemetaGenerationEnabled) {
297+
// Avoid regenerating a document while we are importing it.
298+
// This avoid resetting the input in case there is an error in it.
299+
return false;
300+
}
301+
294302
var inputForm = document.querySelector('#inputForm');
295303
var codemetaText, errorHTML;
296304

@@ -427,68 +435,73 @@ async function recompactDocWithAllContexts(doc) {
427435
}
428436

429437
async function importCodemeta() {
430-
var inputForm = document.querySelector('#inputForm');
431-
var doc = parseAndValidateCodemeta(false);
438+
// Don't wipe the codemeta text (if any) in case of error
439+
codemetaGenerationEnabled = false;
432440

433-
// Re-compact document with all contexts
434-
// to allow importing property from any context
435-
doc = await recompactDocWithAllContexts(doc);
441+
try {
442+
var doc = parseAndValidateCodemeta(false);
443+
// Re-compact document with all contexts
444+
// to allow importing property from any context
445+
doc = await recompactDocWithAllContexts(doc);
436446

437-
resetForm();
447+
resetForm();
438448

439-
if (doc['license'] !== undefined) {
440-
if (typeof doc['license'] === 'string') {
441-
doc['license'] = [doc['license']];
449+
if (doc['license'] !== undefined) {
450+
if (typeof doc['license'] === 'string') {
451+
doc['license'] = [doc['license']];
452+
}
453+
454+
doc['license'].forEach(l => {
455+
if (l.indexOf(SPDX_PREFIX) !== 0) { return; }
456+
let licenseId = l.substring(SPDX_PREFIX.length);
457+
insertLicenseElement(licenseId);
458+
});
442459
}
443460

444-
doc['license'].forEach(l => {
445-
if (l.indexOf(SPDX_PREFIX) !== 0) { return; }
446-
let licenseId = l.substring(SPDX_PREFIX.length);
447-
insertLicenseElement(licenseId);
461+
directCodemetaFields.forEach(function (item, index) {
462+
setIfDefined('#' + item, doc[item]);
448463
});
449-
}
450-
451-
directCodemetaFields.forEach(function (item, index) {
452-
setIfDefined('#' + item, doc[item]);
453-
});
454-
importShortOrg('#funder', doc["funder"]);
455-
importReview(doc["review"]);
456-
457-
// Import simple fields by joining on their separator
458-
splittedCodemetaFields.forEach(function (item, index) {
459-
const id = item[0];
460-
const separator = item[1];
461-
const serializer = item[2];
462-
const deserializer = item[3];
463-
let value = doc[id];
464-
if (value !== undefined) {
465-
if (Array.isArray(value)) {
466-
if (deserializer !== undefined) {
467-
value = value.map((item) => deserializer(id, item));
468-
}
469-
value = value.join(separator);
470-
} else {
471-
if (deserializer !== undefined) {
472-
value = deserializer(id, value);
464+
importShortOrg('#funder', doc["funder"]);
465+
importReview(doc["review"]);
466+
467+
// Import simple fields by joining on their separator
468+
splittedCodemetaFields.forEach(function (item, index) {
469+
const id = item[0];
470+
const separator = item[1];
471+
const deserializer = item[3];
472+
let value = doc[id];
473+
if (value !== undefined) {
474+
if (Array.isArray(value)) {
475+
if (deserializer !== undefined) {
476+
value = value.map((item) => deserializer(id, item));
477+
}
478+
value = value.join(separator);
479+
} else {
480+
if (deserializer !== undefined) {
481+
value = deserializer(id, value);
482+
}
473483
}
484+
setIfDefined('#' + id, value);
474485
}
475-
setIfDefined('#' + id, value);
476-
}
477-
});
478-
479-
for (const [key, items] of Object.entries(crossCodemetaFields)) {
480-
let value = "";
481-
items.forEach(item => {
482-
value = doc[item] || value;
483486
});
484-
setIfDefined(`#${key}`, value);
485-
}
486487

487-
importPersons('author', 'Author', doc['author']);
488-
if (doc['contributor']) {
489-
// If only one contributor, it is compacted to an object
490-
const contributors = Array.isArray(doc['contributor'])? doc['contributor'] : [doc['contributor']];
491-
importPersons('contributor', 'Contributor', contributors);
488+
for (const [key, items] of Object.entries(crossCodemetaFields)) {
489+
let value = "";
490+
items.forEach(item => {
491+
value = doc[item] || value;
492+
});
493+
setIfDefined(`#${key}`, value);
494+
}
495+
496+
importPersons('author', 'Author', doc['author']);
497+
if (doc['contributor']) {
498+
// If only one contributor, it is compacted to an object
499+
const contributors = Array.isArray(doc['contributor']) ? doc['contributor'] : [doc['contributor']];
500+
importPersons('contributor', 'Contributor', contributors);
501+
}
502+
} finally {
503+
// Re-enable codemeta generation
504+
codemetaGenerationEnabled = true;
492505
}
493506
}
494507

0 commit comments

Comments
 (0)