Skip to content

Commit

Permalink
Include entity create 2022.1 forms in 2024.1 upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
ktuite committed Oct 29, 2024
1 parent 5eef273 commit b313748
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 3 deletions.
4 changes: 3 additions & 1 deletion lib/data/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -641,9 +641,11 @@ const _updateEntityVersion = (xml, oldVersion, newVersion) => new Promise((pass,
// If there are any problems with updating the XML, this will just
// return the unaltered XML which will then be a clue for the worker
// to not change anything about the Form.
// 2022.1 -> 2024.1 forms only have version changed and suffix added.
// 2023.1 -> 2024.1 forms (for updating) also have branchId and trunkVersion added.
const updateEntityForm = (xml, oldVersion, newVersion, suffix) =>
_updateEntityVersion(xml, oldVersion, newVersion)
.then(_addBranchIdAndTrunkVersion)
.then(x => (oldVersion === '2023.1.0' ? _addBranchIdAndTrunkVersion(x) : x))
.then(x => addVersionSuffix(x, suffix))
.catch(() => xml);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 ODK Central Developers
// See the NOTICE file at the top-level directory of this distribution and at
// https://github.com/getodk/central-backend/blob/master/NOTICE.
// This file is part of ODK Central. It is subject to the license terms in
// the LICENSE file found in the top-level directory of this distribution and at
// https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central,
// including this file, may be copied, modified, propagated, or distributed
// except according to the terms contained in the LICENSE file.

// The previous migration only added this event for forms with an
// upgrade action, which should have been entity spec version 2023.1.0.
// We also need to flag 2022.1.0 forms with the create action.
// To avoid flagging forms that do both create + update twice, this
// migration captures the complement set of forms.
// Basically, every existing form should be flagged, but I didn't want
// to change an old migration.

const up = (db) => db.raw(`
INSERT INTO audits ("action", "acteeId", "loggedAt", "details")
SELECT 'upgrade.process.form.entities_version', forms."acteeId", clock_timestamp(),
'{"upgrade": "As part of upgrading Central to v2024.3, this form is being updated to the latest entities-version spec."}'
FROM forms
JOIN form_defs fd ON forms."id" = fd."formId"
JOIN dataset_form_defs dfd ON fd."id" = dfd."formDefId"
JOIN projects ON projects."id" = forms."projectId"
WHERE NOT dfd."actions" @> '["update"]'
AND forms."deletedAt" IS NULL
AND projects."deletedAt" IS NULL
GROUP BY forms."acteeId";
`);

const down = () => {};

module.exports = { up, down };
15 changes: 13 additions & 2 deletions lib/worker/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,21 @@ const updateDraftSet = pushDraftToEnketo;
const updatePublish = pushFormToEnketo;

const _upgradeEntityVersion = async (form) => {
const xml = await updateEntityForm(form.xml, '2023.1.0', '2024.1.0', '[upgrade]');
// If the XML doesnt change (not the version in question, or a parsing error), don't return the new partial Form
// We need to upgrade both 2022.1 and 2023.1 forms to 2024, and we are not sure which it is
// without parsing the form.
// Try one upgrade and then the other.

// Attempt the 2023.1 upgrade first:
let xml = await updateEntityForm(form.xml, '2023.1.0', '2024.1.0', '[upgrade]');

// If the XML doesnt change (not the version in question, or a parsing error), try the 2022.1 upgrade:
if (xml === form.xml)
xml = await updateEntityForm(form.xml, '2022.1.0', '2024.1.0', '[upgrade]');

// If the XML still has not changed, don't return a partial.
if (xml === form.xml)
return null;

const partial = await Form.fromXml(xml);
return partial.withAux('xls', { xlsBlobId: form.def.xlsBlobId });
};
Expand Down
66 changes: 66 additions & 0 deletions test/integration/other/form-entities-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ const upgradedUpdateEntity = `<?xml version="1.0"?>
</h:head>
</h:html>`;

const upgradedSimpleEntity = `<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:entities="http://www.opendatakit.org/xforms">
<h:head>
<model entities:entities-version="2024.1.0">
<instance>
<data id="simpleEntity" orx:version="1.0[upgrade]">
<name/>
<age/>
<hometown/>
<meta>
<entity dataset="people" id="" create="">
<label/>
</entity>
</meta>
</data>
</instance>
<bind nodeset="/data/name" type="string" entities:saveto="first_name"/>
<bind nodeset="/data/age" type="int" entities:saveto="age"/>
<bind nodeset="/data/hometown" type="string"/>
</model>
</h:head>
</h:html>`;

describe('Update / migrate entities-version within form', () => {
describe('upgrading a 2023.1.0 update form', () => {
it('should upgrade a form with only a published version', testService(async (service, container) => {
Expand Down Expand Up @@ -469,6 +492,49 @@ describe('Update / migrate entities-version within form', () => {
}));
});

describe('upgrading a 2022.1.0 create form', () => {
it('should upgrade a form with a draft version and a published version', testService(async (service, container) => {
const { Forms, Audits } = container;
const asAlice = await service.login('alice');

// Upload a form and publish it
await asAlice.post('/v1/projects/1/forms?publish=true')
.send(testData.forms.simpleEntity)
.set('Content-Type', 'application/xml')
.expect(200);

// Convert the published form to a draft
await asAlice.post('/v1/projects/1/forms/simpleEntity/draft');

const { acteeId } = await Forms.getByProjectAndXmlFormId(1, 'simpleEntity').then(o => o.get());
await Audits.log(null, 'upgrade.process.form.entities_version', { acteeId });

// Run form upgrade
await exhaust(container);

// The version on the draft does change even though it is updated in place
await asAlice.get('/v1/projects/1/forms/simpleEntity/draft')
.then(({ body }) => {
body.version.should.equal('1.0[upgrade]');
});

await asAlice.get('/v1/projects/1/forms/simpleEntity/versions')
.then(({ body }) => {
body.length.should.equal(2);
body[0].version.should.equal('1.0[upgrade]');
body[1].version.should.equal('1.0');
});

// The published form XML is updated
await asAlice.get('/v1/projects/1/forms/simpleEntity.xml')
.then(({ text }) => text.should.equal(upgradedSimpleEntity));

// The draft XML is updated
await asAlice.get('/v1/projects/1/forms/simpleEntity/draft.xml')
.then(({ text }) => text.should.equal(upgradedSimpleEntity));
}));
});

describe('audit logging and errors', () => {
it('should log events about the upgrade for a published form', testService(async (service, container) => {
const { Forms, Audits } = container;
Expand Down
39 changes: 39 additions & 0 deletions test/unit/data/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -2116,14 +2116,53 @@ describe('form schema', () => {
</h:html>`);
}));

it('should change version 2022->2024', (async () => {
const result = await updateEntityForm(testData.forms.simpleEntity, '2022.1.0', '2024.1.0', '[upgrade]');
// entities-version has been updated
// version has suffix
// trunkVersion and branchId are NOT added
result.should.equal(`<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:entities="http://www.opendatakit.org/xforms">
<h:head>
<model entities:entities-version="2024.1.0">
<instance>
<data id="simpleEntity" orx:version="1.0[upgrade]">
<name/>
<age/>
<hometown/>
<meta>
<entity dataset="people" id="" create="">
<label/>
</entity>
</meta>
</data>
</instance>
<bind nodeset="/data/name" type="string" entities:saveto="first_name"/>
<bind nodeset="/data/age" type="int" entities:saveto="age"/>
<bind nodeset="/data/hometown" type="string"/>
</model>
</h:head>
</h:html>`);
}));

// updateEntityForm takes the old version to replace as an argument
// these tests show it will not change a 2022.1 (create-only) form when 2023.1 is provided
it('should not alter a version 2022.1.0 form when the old version to replace is 2023.1.0', (async () => {
const result = await updateEntityForm(testData.forms.simpleEntity, '2023.1.0', '2024.1.0', '[upgrade]');
result.should.equal(testData.forms.simpleEntity);
}));

it('should not alter a version 2024.1.0 form when the old version to replace is 2023.1.0', (async () => {
const result = await updateEntityForm(testData.forms.offlineEntity, '2023.1.0', '2024.1.0', '[upgrade]');
result.should.equal(testData.forms.offlineEntity);
}));

// these tests show it will not change a 2023.1 (update) form when 2022.1 is provided
it('should not alter a version 2023.1.0 form when the old version to replace is 2022.1.0', (async () => {
const result = await updateEntityForm(testData.forms.updateEntity, '2022.1.0', '2024.1.0', '[upgrade]');
result.should.equal(testData.forms.updateEntity);
}));

});
});

0 comments on commit b313748

Please sign in to comment.