-
Notifications
You must be signed in to change notification settings - Fork 72
/
20211008-01-track-select-many-options.js
100 lines (87 loc) · 3.73 KB
/
20211008-01-track-select-many-options.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// Copyright 2021 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.
const { map } = require('ramda');
const { getFormFields } = require('../../data/schema');
const { getSelectMultipleResponses } = require('../../data/submission');
const { Form } = require('../frames');
const { construct } = require('../../util/util');
const up = async (db) => {
// add select many flag, options field to fields
await db.schema.table('form_fields', (ffs) => {
ffs.boolean('selectMultiple');
});
// and add a place to put cached values
await db.schema.createTable('form_field_values', (fvs) => {
fvs.integer('formId').notNullable();
fvs.integer('submissionDefId').notNullable();
fvs.text('path').notNullable();
fvs.text('value');
fvs.index([ 'formId' ]);
fvs.index([ 'submissionDefId' ]);
fvs.index([ 'formId', 'submissionDefId', 'path' ]);
fvs.foreign('formId').references('forms.id');
fvs.foreign('submissionDefId').references('submission_defs.id');
});
// and backfill all existing fields
await new Promise((resolve, reject) => {
const work = [];
const stream = db.select('id', 'xml').from('form_defs').stream();
stream.on('data', ({ id, xml }) => {
work.push(getFormFields(xml).then((fields) => Promise.all(fields
.filter((field) => field.selectMultiple === true)
.map((field) => db
.update({ selectMultiple: true })
.into('form_fields')
.where({ formDefId: id, path: field.path })))));
});
stream.on('error', reject);
stream.on('end', () => { Promise.all(work).then(resolve); });
});
// and backfill all existing values, which requires a bunch of weird homework.
// TODO: i guess you could derive this data from the above operation instead.
// oh well for now.
const allFields = await db.select('formId', 'path')
.from('form_fields')
.where({ selectMultiple: true })
.groupBy('formId', 'path')
.then(map(construct(Form.Field)));
const fieldsByFormId = {};
for (const field of allFields) {
if (fieldsByFormId[field.formId] == null) fieldsByFormId[field.formId] = [];
fieldsByFormId[field.formId].push(field);
}
const formIds = Object.keys(fieldsByFormId);
await new Promise((resolve, reject) => {
const work = [];
const stream = db.select('formId', 'submission_defs.id', 'xml')
.from('submission_defs')
.innerJoin(db.select('id', 'formId').from('form_defs').whereIn('formId', formIds).as('fds'),
'fds.id', 'formDefId')
.stream();
stream.on('data', ({ formId, id, xml }) => {
work.push(getSelectMultipleResponses(fieldsByFormId[formId], xml)
.then((pairs) => {
const inserts = [];
for (const path of Object.keys(pairs))
for (const value of pairs[path].values())
inserts.push({ formId, submissionDefId: id, path, value });
return (inserts.length === 0) ? null : db.insert(inserts).into('form_field_values');
}));
});
stream.on('error', reject);
stream.on('end', () => { Promise.all(work).then(resolve); });
});
};
const down = async (db) => {
await db.schema.dropTable('form_field_values');
await db.schema.table('form_fields', (ffs) => {
ffs.dropColumn('selectMultiple');
});
};
module.exports = { up, down };