cht-conf 3.9.1 → 3.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [3.9.5](https://github.com/medic/cht-conf/compare/v3.9.4...v3.9.5) (2022-01-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **#458:** return non-zero code when exiting with failure ([516bff0](https://github.com/medic/cht-conf/commit/516bff0eabec2d3c13a16a994212280d77358c7e)), closes [#458](https://github.com/medic/cht-conf/issues/458)
7
+
8
+ ## [3.9.4](https://github.com/medic/cht-conf/compare/v3.9.3...v3.9.4) (2022-01-05)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **#460:** log useful error messages when api connection fails ([#461](https://github.com/medic/cht-conf/issues/461)) ([095a55c](https://github.com/medic/cht-conf/commit/095a55c9254c6872696cbee2fe42f244fc1fc4a8)), closes [#460](https://github.com/medic/cht-conf/issues/460) [#460](https://github.com/medic/cht-conf/issues/460)
14
+
15
+ ## [3.9.3](https://github.com/medic/cht-conf/compare/v3.9.2...v3.9.3) (2021-12-16)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **#455:** allow empty translation constants ([ac170d7](https://github.com/medic/cht-conf/commit/ac170d7cce13a6a500d89884d4ca63801f06a969)), closes [#455](https://github.com/medic/cht-conf/issues/455)
21
+
22
+ ## [3.9.2](https://github.com/medic/cht-conf/compare/v3.9.1...v3.9.2) (2021-12-08)
23
+
24
+
25
+ ### Performance Improvements
26
+
27
+ * **#432:** refactor the moving of reports to use a recursive function to improve performance ([c17b43b](https://github.com/medic/cht-conf/commit/c17b43b4d89b29b813db4be6e0f2d2c0a1e6c94d)), closes [#432](https://github.com/medic/cht-conf/issues/432)
28
+
1
29
  ## [3.9.1](https://github.com/medic/cht-conf/compare/v3.9.0...v3.9.1) (2021-11-16)
2
30
 
3
31
 
package/README.md CHANGED
@@ -282,7 +282,7 @@ To develop a new action or improve an existing one, check the ["Actions" doc](sr
282
282
 
283
283
  ## Testing
284
284
 
285
- Execute `npm test` to run static analysis checks and the test suite.
285
+ Execute `npm test` to run static analysis checks and the test suite. Requires Docker to run integration tests against a CouchDB instance.
286
286
 
287
287
  ## Executing your local branch
288
288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cht-conf",
3
- "version": "3.9.1",
3
+ "version": "3.9.5",
4
4
  "description": "Configure CHT deployments",
5
5
  "main": "./src/lib/main.js",
6
6
  "engines": {
@@ -9,7 +9,9 @@
9
9
  "scripts": {
10
10
  "clean": "rm -rf ./build/",
11
11
  "eslint": "eslint src/**/*.js test/*.js test/**/*.js",
12
- "test": "npm run eslint && npm run clean && mkdir -p build/test && cp -r test/data build/test/data && cd build/test && nyc --reporter=html mocha --forbid-only ../../test/**/*.spec.js && cd ../..",
12
+ "docker-start-couchdb": "npm run docker-stop-couchdb && docker run -d -p 6984:5984 --rm --name cht-conf-couchdb couchdb:2.3.1 && sh test/scripts/wait_for_response_code.sh 6984 200 CouchDB",
13
+ "docker-stop-couchdb": "docker stop cht-conf-couchdb || true",
14
+ "test": "npm run eslint && npm run docker-start-couchdb && npm run clean && mkdir -p build/test && cp -r test/data build/test/data && cd build/test && nyc --reporter=html mocha --forbid-only ../../test/**/*.spec.js && cd ../.. && npm run docker-stop-couchdb",
13
15
  "semantic-release": "semantic-release"
14
16
  },
15
17
  "bin": {
@@ -34,7 +36,7 @@
34
36
  "homepage": "https://github.com/medic/cht-conf#readme",
35
37
  "dependencies": {
36
38
  "@hapi/joi": "^16.1.8",
37
- "@medic/translation-checker": "1.0.0",
39
+ "@medic/translation-checker": "^1.0.1",
38
40
  "canonical-json": "0.0.4",
39
41
  "csv-parse": "^4.16.0",
40
42
  "dom-compare": "^0.6.0",
package/src/bin/index.js CHANGED
@@ -12,7 +12,7 @@ const main = require('../lib/main');
12
12
  }
13
13
  catch (e) {
14
14
  error(e);
15
- returnCode = -1;
15
+ returnCode = 1;
16
16
  }
17
17
  finally {
18
18
  process.exit(returnCode);
@@ -18,7 +18,7 @@ module.exports = {
18
18
  warn('Changes untracked or to be committed:\n' + status);
19
19
  if (!userPrompt.keyInYN('Are you sure you want to continue?')) {
20
20
  error('User failed to confirm action.');
21
- process.exit(-1);
21
+ process.exit(1);
22
22
  }
23
23
  }
24
24
  info('Fetching git upstream...');
@@ -27,7 +27,7 @@ module.exports = {
27
27
  warn(syncStatus);
28
28
  if (!userPrompt.keyInYN('Are you sure you want to continue?')) {
29
29
  error('User failed to confirm action.');
30
- process.exit(-1);
30
+ process.exit(1);
31
31
  }
32
32
  }
33
33
  }
@@ -10,6 +10,7 @@ const pouch = require('../lib/db');
10
10
  const { warn, trace, info, error } = require('../lib/log');
11
11
 
12
12
  const HIERARCHY_ROOT = 'root';
13
+ const BATCH_SIZE = 10000;
13
14
 
14
15
  module.exports = {
15
16
  requiresInstance: true,
@@ -49,19 +50,15 @@ const updateLineagesAndStage = async (options, db) => {
49
50
  trace(`Considering primary contact updates to ${ancestors.length} ancestor(s) of contact ${prettyPrintDocument(contactDoc)}.`);
50
51
  const updatedAncestors = replaceLineageInAncestors(descendantsAndSelf, ancestors);
51
52
 
52
- const reportsCreatedByDescendants = await fetch.reportsCreatedBy(db, descendantsAndSelf.map(descendant => descendant._id));
53
- trace(`${reportsCreatedByDescendants.length} report(s) created by these affected contact(s) will update`);
54
- const updatedReports = replaceLineageInReports(reportsCreatedByDescendants, replacementLineage, contactId);
53
+ minifyLineageAndWriteToDisk([...updatedDescendants, ...updatedAncestors], options);
55
54
 
56
- [...updatedDescendants, ...updatedReports, ...updatedAncestors].forEach(updatedDoc => {
57
- lineageManipulation.minifyLineagesInDoc(updatedDoc);
58
- writeDocumentToDisk(options, updatedDoc);
59
- });
55
+ const movedReportsCount = await moveReports(db, descendantsAndSelf, options, replacementLineage, contactId);
56
+ trace(`${movedReportsCount} report(s) created by these affected contact(s) will be updated`);
60
57
 
61
58
  affectedContactCount += updatedDescendants.length + updatedAncestors.length;
62
- affectedReportCount += updatedReports.length;
59
+ affectedReportCount += movedReportsCount;
63
60
 
64
- info(`Staged updates to ${prettyPrintDocument(contactDoc)}. ${updatedDescendants.length} contact(s) and ${updatedReports.length} report(s).`);
61
+ info(`Staged updates to ${prettyPrintDocument(contactDoc)}. ${updatedDescendants.length} contact(s) and ${movedReportsCount} report(s).`);
65
62
  }
66
63
 
67
64
  info(`Staged changes to lineage information for ${affectedContactCount} contact(s) and ${affectedReportCount} report(s).`);
@@ -129,7 +126,7 @@ const prepareDocumentDirectory = ({ docDirectoryPath, force }) => {
129
126
  fs.deleteFilesInFolder(docDirectoryPath);
130
127
  } else {
131
128
  error('User failed to confirm action.');
132
- process.exit(-1);
129
+ process.exit(1);
133
130
  }
134
131
  }
135
132
  };
@@ -155,6 +152,31 @@ ${bold('OPTIONS')}
155
152
  `);
156
153
  };
157
154
 
155
+ const moveReports = async (db, descendantsAndSelf, writeOptions, replacementLineage, contactId) => {
156
+ const contactIds = descendantsAndSelf.map(contact => contact._id);
157
+
158
+ let skip = 0;
159
+ let reportDocsBatch;
160
+ do {
161
+ info(`Processing ${skip} to ${skip + BATCH_SIZE} report docs`);
162
+ reportDocsBatch = await fetch.reportsCreatedBy(db, contactIds, skip);
163
+
164
+ const updatedReports = replaceLineageInReports(reportDocsBatch, replacementLineage, contactId);
165
+ minifyLineageAndWriteToDisk(updatedReports, writeOptions);
166
+
167
+ skip += reportDocsBatch.length;
168
+ } while (reportDocsBatch.length >= BATCH_SIZE);
169
+
170
+ return skip;
171
+ };
172
+
173
+ const minifyLineageAndWriteToDisk = (docs, parsedArgs) => {
174
+ docs.forEach(doc => {
175
+ lineageManipulation.minifyLineagesInDoc(doc);
176
+ writeDocumentToDisk(parsedArgs, doc);
177
+ });
178
+ };
179
+
158
180
  const writeDocumentToDisk = ({ docDirectoryPath }, doc) => {
159
181
  const destinationPath = path.join(docDirectoryPath, `${doc._id}.doc.json`);
160
182
  if (fs.exists(destinationPath)) {
@@ -214,10 +236,12 @@ const fetch = {
214
236
  .filter(doc => doc && doc.type !== 'tombstone');
215
237
  },
216
238
 
217
- reportsCreatedBy: async (db, contactIds) => {
239
+ reportsCreatedBy: async (db, contactIds, skip) => {
218
240
  const reports = await db.query('medic-client/reports_by_freetext', {
219
241
  keys: contactIds.map(id => [`contact:${id}`]),
220
242
  include_docs: true,
243
+ limit: BATCH_SIZE,
244
+ skip: skip,
221
245
  });
222
246
 
223
247
  return reports.rows.map(row => row.doc);
@@ -66,7 +66,7 @@ const execute = async () => {
66
66
  errMsg += '\nYou can use messages-ex.properties to add placeholders missing from the reference context.';
67
67
  }
68
68
  log.error(errMsg);
69
- return process.exit(-1);
69
+ return process.exit(1);
70
70
  }
71
71
  } else {
72
72
  throw err;
@@ -78,7 +78,7 @@ const execute = async () => {
78
78
  const languageCode = id.substring('messages-'.length);
79
79
  if (!isLanguageCodeValid(languageCode)) {
80
80
  log.error(`The language code '${languageCode}' is not valid. It must begin with a letter(a–z, A-Z), followed by any number of hyphens, underscores, letters, or numbers.`);
81
- return process.exit(-1);
81
+ return process.exit(1);
82
82
  }
83
83
 
84
84
  let languageName = iso639.getName(languageCode);
package/src/lib/api.js CHANGED
@@ -99,8 +99,19 @@ const api = {
99
99
  await request.get(url);
100
100
  return true;
101
101
  } catch (err) {
102
- log.error(`Failed to get a response from ${url}. Maybe you entered the wrong URL, `
103
- + 'wrong port or the instance is not started? Please check and try again.');
102
+ if (err.statusCode === 401) {
103
+ log.error(`Authentication failed connecting to ${url}. `
104
+ + 'Check the supplied username and password and try again.');
105
+ } else if (err.statusCode === 403) {
106
+ log.error(`Insufficient permissions connecting to ${url}. `
107
+ + 'You need to use admin permissions to execute this command.');
108
+ } else if (err.statusCode) {
109
+ log.error(`Received error code ${err.statusCode} connecting to ${url}. `
110
+ + 'Check the server and and try again.');
111
+ } else {
112
+ log.error(`Failed to get a response from ${url}. Maybe you entered the wrong URL, `
113
+ + 'wrong port or the instance is not started. Please check and try again.');
114
+ }
104
115
  return false;
105
116
  }
106
117
  },
package/src/lib/main.js CHANGED
@@ -57,7 +57,7 @@ module.exports = async (argv, env) => {
57
57
 
58
58
  if(argv.length <= 2) {
59
59
  usage(0);
60
- return -1;
60
+ return 1;
61
61
  }
62
62
 
63
63
  const cmdArgs = require('minimist')(argv.slice(2), {
@@ -94,7 +94,7 @@ module.exports = async (argv, env) => {
94
94
 
95
95
  if (cmdArgs.archive && !cmdArgs.destination) {
96
96
  error('--destination=<path to save files> is required with --archive.');
97
- return -1;
97
+ return 1;
98
98
  }
99
99
 
100
100
  if (cmdArgs['accept-self-signed-certs']) {
@@ -131,7 +131,7 @@ module.exports = async (argv, env) => {
131
131
  const unsupported = actions.filter(a => !supportedActions.includes(a));
132
132
  if(unsupported.length) {
133
133
  error(`Unsupported action(s): ${unsupported.join(' ')}`);
134
- return -1;
134
+ return 1;
135
135
  }
136
136
 
137
137
  if (cmdArgs['skip-git-check']) {
@@ -164,7 +164,7 @@ module.exports = async (argv, env) => {
164
164
  apiUrl = getApiUrl(cmdArgs, env);
165
165
  if (!apiUrl) {
166
166
  error('Failed to obtain a url to the API');
167
- return -1;
167
+ return 1;
168
168
  }
169
169
  }
170
170
 
@@ -184,7 +184,7 @@ module.exports = async (argv, env) => {
184
184
  );
185
185
 
186
186
  if (apiUrl && !await api().available()) {
187
- return false;
187
+ return 1;
188
188
  }
189
189
 
190
190
  const productionUrlMatch = environment.instanceUrl && environment.instanceUrl.match(/^https:\/\/(?:[^@]*@)?(.*)\.(app|dev)\.medicmobile\.org(?:$|\/)/);
@@ -194,7 +194,7 @@ module.exports = async (argv, env) => {
194
194
  `against non-matching instance: \x1b[31m${redactBasicAuth(environment.instanceUrl)}\x1b[33m`);
195
195
  if(!userPrompt.keyInYN()) {
196
196
  error('User failed to confirm action.');
197
- return false;
197
+ return 1;
198
198
  }
199
199
  }
200
200
 
@@ -218,6 +218,8 @@ module.exports = async (argv, env) => {
218
218
  if (actions.length > 1) {
219
219
  await info('All actions completed.');
220
220
  }
221
+
222
+ return 0;
221
223
  };
222
224
 
223
225
  // Exists for generic mocking purposes