cht-conf 3.7.0 → 3.9.2

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.
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ npx --no-install commitlint --edit "$1"
package/.nycrc.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "branches": 75,
3
+ "lines": 85,
4
+ "functions": 83,
5
+ "statements": 85,
6
+ "check-coverage": true
7
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "plugins": [
3
+ "@semantic-release/commit-analyzer",
4
+ "@semantic-release/release-notes-generator",
5
+ "@semantic-release/npm",
6
+ "@semantic-release/github",
7
+ [
8
+ "@semantic-release/changelog",
9
+ {
10
+ "changelogFile": "CHANGELOG.md"
11
+ }
12
+ ],
13
+ [
14
+ "@semantic-release/git",
15
+ {
16
+ "assets": ["package.json", "package-lock.json", "CHANGELOG.md"],
17
+ "message": "chore(release): ${nextRelease.version} [skip ci]"
18
+ }
19
+ ]
20
+ ],
21
+ "branches": ["master"]
22
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,351 @@
1
+ ## [3.9.2](https://github.com/medic/cht-conf/compare/v3.9.1...v3.9.2) (2021-12-08)
2
+
3
+
4
+ ### Performance Improvements
5
+
6
+ * **#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)
7
+
8
+ ## [3.9.1](https://github.com/medic/cht-conf/compare/v3.9.0...v3.9.1) (2021-11-16)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **#448:** Bump Ubuntu version and Node version in container to fix docker usage. Updates to readme ([25cb955](https://github.com/medic/cht-conf/commit/25cb955377eea6c6a0a7cb3953d889b57c93884b))
14
+
15
+ # [3.9.0](https://github.com/medic/cht-conf/compare/v3.8.0...v3.9.0) (2021-11-01)
16
+
17
+
18
+ ### Features
19
+
20
+ * **#413:** fix tags so that next release is 3.9.0 ([6eea489](https://github.com/medic/cht-conf/commit/6eea489804cf0de3c11fe855d2b285e41553439d))
21
+ * **#413:** implement continuous delivery ([244203e](https://github.com/medic/cht-conf/commit/244203e341fe46e11dcb7249fd3a77dcb8d1f43c)), closes [#413](https://github.com/medic/cht-conf/issues/413)
22
+ * **#413:** provide medic-ci with write permissions to npm ([fd2522e](https://github.com/medic/cht-conf/commit/fd2522ef9c40f697dff2c71a3b638a225cf7adfe)), closes [#413](https://github.com/medic/cht-conf/issues/413)
23
+ * **#413:** publish to npm with automation token ([1cf8550](https://github.com/medic/cht-conf/commit/1cf8550cca2d404342e28d0a9086c837fa2a011e)), closes [#413](https://github.com/medic/cht-conf/issues/413)
24
+ * **#413:** use GH_ADMIN_TOKEN for release workflow ([0f1c581](https://github.com/medic/cht-conf/commit/0f1c581cc5e0990881ab6a99f6f26a040144bfbc)), closes [#413](https://github.com/medic/cht-conf/issues/413)
25
+
26
+ ## 3.8.0
27
+
28
+ ### Ability to edit a contact type
29
+
30
+ It is now possible to edit a contact type with the `edit-contacts` action. Additionally, `edit-contacts` accepts a new flag `--updateOfflineDocs`, that allows editing json docs that already exist in the directory provided in `--docDirectoryPath` instead of downloading the docs from the database.
31
+
32
+ [#408](https://github.com/medic/cht-conf/issues/408)
33
+
34
+ ### Fail early if .eslintrc file is not provided
35
+
36
+ Running the `compile-app-settings` command now fails early and provides a more descriptive error message to users.
37
+
38
+ [#333](https://github.com/medic/cht-conf/issues/333)
39
+
40
+ ### Check to make sure CHT instance is up
41
+
42
+ Commands that require an instance now fail with a more descriptive error message if the instance provided is not reachable
43
+
44
+ [#380](https://github.com/medic/cht-conf/issues/380)
45
+
46
+ ## 3.7.0
47
+
48
+ ### The repository has been renamed to `cht-conf`.
49
+
50
+ Additionally:
51
+ - the `medic-conf` cli command is now deprecated, in favor of `cht`.
52
+ - the `medic-logs` cli command is now deprecated, in favor of `cht-logs`
53
+ - the `pngout-medic` cli command is now deprecated, in favor of `pngout-cht`
54
+
55
+ ### Default "resolvedIf" function for tasks
56
+
57
+ Task definitions can now omit the `resolvedIf` function. In this case, the task is resolved by any report assigned to the contact that matches the `form` defined in the action of type `report`.
58
+ For more information, please check the [documentation](https://docs.communityhealthtoolkit.org/apps/reference/tasks/#default-resolvedif-method).
59
+
60
+ [#107](https://github.com/medic/cht-conf/issues/107)
61
+
62
+ ## 3.6.0
63
+
64
+ ### Tasks actions `modifyContent` functions now receive the event as a parameter
65
+
66
+ To support task logic that depends on knowing which event the emission is generated for, `modifyContent` now receives a fourth parameter containing the event data.
67
+ For more information, please check the [documentation](https://docs.communityhealthtoolkit.org/apps/reference/tasks/#tasksjs).
68
+
69
+ [#398](https://github.com/medic/cht-conf/issues/398)
70
+
71
+ ## 3.5.0
72
+
73
+ ### Forms are validated before being uploaded
74
+
75
+ When uploading forms to CHT Core v3.11.0 or above these are submitted for validation to ensure they will work when uploaded.
76
+
77
+ [#331](https://github.com/medic/cht-conf/issues/331)
78
+
79
+ ### Check that the code is up to date with the git repo
80
+
81
+ Warns when pushing configuration that isn't up to date with the git repo.
82
+
83
+ [#64](https://github.com/medic/cht-conf/issues/64)
84
+
85
+ ### Bug fixes
86
+
87
+ - [#358](https://github.com/medic/cht-conf/issues/358): Can't create task action with type "contact"
88
+ - [#387](https://github.com/medic/cht-conf/issues/387): Can't upload-app-settings on Node lower than 12
89
+
90
+ ## 3.4.0
91
+
92
+ ### Support for uploading branding and partners docs
93
+
94
+ Two new actions, `upload-branding` and `upload-partners`, permit uploading branding and partners images, respectively.
95
+ For more information, please check the [documentation](https://docs.communityhealthtoolkit.org/apps/reference/resources/#branding).
96
+
97
+ [#167](https://github.com/medic/cht-conf/issues/167)
98
+
99
+ ### Modular app_settings and updated folder structure
100
+
101
+ The `forms` and `schedules` sections of `app_settings` should now be configured individually, in separate files, while other configs should be added to the new `base_settings.json` file. All settings will be compiled into the `app_settings.json`, which should now no longer be edited manually.
102
+ For more information, please check the [documentation](https://docs.communityhealthtoolkit.org/apps/reference/app-settings/).
103
+
104
+ [#214](https://github.com/medic/cht-conf/issues/214)
105
+ [#68](https://github.com/medic/cht-conf/issues/68)
106
+
107
+ ### Improvements
108
+
109
+ - [#254](https://github.com/medic/cht-conf/issues/254): Add validation to the upload custom translations function
110
+ - [#269](https://github.com/medic/cht-conf/issues/269): Warn about deprecated transitions when uploading app-settings
111
+
112
+ ### Bug fixes
113
+
114
+ - [#345](https://github.com/medic/cht-conf/issues/345): Warn config overwrite falsely alerts about changes when uploading certain types of attachments
115
+ - [#164](https://github.com/medic/cht-conf/issues/164): Declarative config appliesToType is indicated as optional for fields and cards but we do require them
116
+ - [#377](https://github.com/medic/cht-conf/issues/377): Incorrect warning when specifying hidden_fields
117
+ - [#382](https://github.com/medic/cht-conf/issues/382): Contacts with hardcoded types that also have a `contact_type` property don't get the correct contact_summary fields and don't count towards target goals
118
+
119
+ ## 3.3.0
120
+
121
+ ### Support for headless execution
122
+
123
+ The `--force` argument can now be provided to make it possible to execute commands in a scripted environment by skipping any prompts. This is more dangerous as cht-conf will no longer warn about possible configuration errors, so it's recommended this option is only used when user input is not possible and you have independently verified that the configuration is correct.
124
+
125
+ [#307](https://github.com/medic/cht-conf/issues/307)
126
+
127
+ ### Privacy policies
128
+
129
+ Privacy policies can now be uploaded using cht-conf. Read [the documentation](https://docs.communityhealthtoolkit.org/apps/guides/security/privacy-policy/) for more information.
130
+
131
+ [cht-core#6538](https://github.com/medic/cht-core/issues/6538)
132
+
133
+ ### Bug fixes
134
+
135
+ - [#332](https://github.com/medic/cht-conf/issues/332): Uploading of multimedia is extremely limited
136
+ - [#273](https://github.com/medic/cht-conf/issues/273): Add configurable hierarchy support to doc references in csv-to-docs
137
+ - [#322](https://github.com/medic/cht-conf/issues/322): Missing optional appliesIf attribute on contact summary cards causes crash
138
+ - [#315](https://github.com/medic/cht-conf/issues/315): Error running csv-to-docs with null properties
139
+ - [#325](https://github.com/medic/cht-conf/issues/325): Task generation fails when given contactless reports
140
+ - [cht-core#6291](https://github.com/medic/cht-core/issues/6291): Allow specifying subject_key in properties
141
+
142
+ ## 3.2.0
143
+
144
+ ### Prompt before configuration overwrites
145
+
146
+ This new feature warns and prompts users when uploading a configuration if the configuration being overwritten is not the last known configuration. These warnings are comparable to a database write conflict. Previous versions of cht-conf would upload the new configuration overwriting whatever was there, resulting in users accidentally wiping out someone elses changes or changes made through the App Management webapp.
147
+
148
+ To detect accidental overwrites, cht-conf will generate two files - `.snapshots/remote.json` and `.snapshots/local.json`. These store the last known configuration on the server. The `remote.json` file should be commited with your configuration changes, and `local.json` should be added to the .gitignore since everyone's local environment is different. If changes are made to the configuration on the server using cht-conf or the App Management webapp without committing the `remote.json` file to your config repo, and then subsequently pulling the changes, then any attempt to upload configuration changes will be notified about the risk and prompted to either overwrite or cancel.
149
+
150
+ This feature was introduced but not announced in 3.1.0. In this release we have substantially changed the implementation, fixed some bugs, and improved the overall experience.
151
+
152
+ ### Configuration is only uploaded if something has changed
153
+
154
+ As of this release no docs on the server will be touched unless some configuration has actually changed. This means the end users will not have to download docs with no actual difference giving them a better experience with upgrades.
155
+
156
+ [#271](https://github.com/medic/cht-conf/issues/271)
157
+
158
+ ### New action to bulk-edit contacts
159
+
160
+ You can now bulk edit contacts at once by providing a CSV. For more information, read [the documentation](https://github.com/medic/cht-conf/blob/master/README.md#editing-contacts-across-the-hierarchy).
161
+
162
+ [#297](https://github.com/medic/cht-conf/issues/297)
163
+
164
+ ## 3.1.0
165
+
166
+ ### Declarative Configuration Support for Core Framework v3.8.0
167
+
168
+ The updates to tasks and targets in Core Framework v3.8.0 require additional data about the tasks and targets emitted by partner rules code. After upgrading to v3.8, the Tasks tab and Targets tab won't function until partner code is updated to send this new data. **Declarative configuration projects must deploy their configuration using cht-conf v3.1 or later for tasks and targets to function on the Core Framework v3.8.0**
169
+
170
+ It is still safe to deploy declarative configuration to any Core Framework version, including those prior to v3.8.
171
+
172
+ ### Declarative Configuration Tasks/Targets Schema Validation
173
+ The updates to tasks and targets in Core Framework v3.8.0 allow for project members to write impact queries against Postgres to understand how tasks are behaving on users' phones. In previous versions of the declarative configuration system, some identification attributes were optional or able to be reused. This becomes problematic after the Core Framework v3.8.0 release because it will lead to data integrity issues if task or target elements are reorderd or removed from within the configuration.
174
+
175
+ `cht-conf` v3.1 guarantees clearer data integrity with the following changes to the declarative configuration schema:
176
+
177
+ * Task.name is now a required unique attribute
178
+ * Task.event[n].id is now a required unique attribute when there is more than one event
179
+ * Target.id although previously required, is now required to be unique
180
+
181
+ ### Declarative Configuration Target "GroupBy"
182
+ Two new optional variables `groupBy` and `passesIfGroupCount` in the [declarative Target configuration schema](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md#targets) allows for target emissions to be counted and scored in groups. This allows targets to support more complex scenarios like the number of families receiving n household visits/family/month.
183
+
184
+ ## 3.0.5
185
+
186
+ ### Updates to purge configuration
187
+
188
+ cht-conf has changed the way it compiles purge configuration.
189
+ To configure purge, create a file `purge.js` in your project root. This file is a javascript module that should export an object containing your purge configuration:
190
+
191
+ ```
192
+ module.exports = {
193
+ text_expression: 'at 12 am on Sunday',
194
+ run_every_days: 7,
195
+ fn: (userCtx, contact, reports, messages) => {
196
+ const old = Date.now() - (1000 * 60 * 60 * 24 * 365);
197
+ return [
198
+ ...reports.filter(report => report.reported_date > old),
199
+ ...messages.filter(message => message.reported_date > old),
200
+ ].map(doc => doc._id);
201
+ },
202
+ };
203
+ ```
204
+ Storing the purge function in `purging.js` is deprecated.
205
+
206
+ ## 3.0.0
207
+
208
+ cht-conf v3.0 contains breaking changes! This release only impacts the `compile-app-settings` action, which impacts the configuration code in `tasks.js`, `targets.js`, `contact-summary.js`, and `contact-summary.templated.js`.
209
+
210
+ ### Breaking Change - Static Analysis via ESLint
211
+ cht-conf has changed the engine we use for performing static analysis from [JSHint](https://jshint.com/) to [ESLint](https://eslint.org/). This grants authors more control over the static analysis rules which are enforced on their code. We provide a set of default rules, but you can change or add rules by creating an [.eslintrc file](https://eslint.org/docs/user-guide/configuring#using-configuration-files-1). The default static analysis rules which are enforced in cht-conf 3.0 are similar to the rules which were enforced in cht-conf 2.x, but details may be slightly different.
212
+
213
+ ### Support for ECMAScript 6
214
+ In previous versions of cht-conf, configuration JavaScript was been limited to ECMAScript 5. [ECMAScript 6](http://es6-features.org/) is the next generation of the JavaScript language. To use ES6 in your JavaScript configuration code, add a `.eslintrc` file to your project directory with the following contents:
215
+
216
+ ```
217
+ {
218
+ "env": {
219
+ "node": true,
220
+ "es6": true
221
+ },
222
+ "parserOptions": {
223
+ "ecmaVersion": 6
224
+ }
225
+ }
226
+ ```
227
+
228
+ If your project is using [CHT Android](https://github.com/medic/cht-android) you must be running a version newer than v0.1.203 (March 2017) to use `ecmaVersion: 6`. ecmaVersions above 6 are not currently supported.
229
+
230
+ ### Breaking Change - Modules
231
+ As part of our effort to leverage JavaScript standards, JavaScript configuration code should now use `require('./file')` or `import lib from './file';` to share code between multiple JavaScript files. The global `extras` object is no longer present and you may see the error `'extras' is not defined` when running `compile-app-settings`. This global object was a reference to `nools-extras.js` or `contact-summary-extras.js`, so you can achieve the same behavior by adding the line `var extras = require('./nools-extras');` or `import extras from './nools-extras';` to the top of your JavaScript. This gives configuration authors the flexibility to have multiple library files, and enables the sharing of code across projects.
232
+
233
+ You may also find it helpful to leverage third-party modules like [MomentJs](https://momentjs.com/) or [lodash](https://lodash.com/). You may install them using [npm](https://docs.npmjs.com/about-npm/) packages. For example, run `npm install moment` and then add `const moment = require('moment');` in your script. Ensure that you maintain a reasonable file size and memory footprint for your scripts. We support [tree shaking](https://webpack.js.org/guides/tree-shaking/) so use it when possible.
234
+
235
+ ### Breaking Change - contact-summary.js as an ES Module
236
+ As part of our effort to leverage JavaScript standards and increase the testability of code, `contact-summary.js` should be a standard ES Module instead of using a bare return. This allows you to write standard unit tests for the file instead of needing a harness or custom wrapper.
237
+
238
+ `contact-summary.js` in cht-conf 1.x or 2.x:
239
+ ```
240
+ return {
241
+ fields: [],
242
+ cards: [],
243
+ context: {},
244
+ };
245
+ ```
246
+
247
+ `contact-summary.js` in cht-conf 3.0:
248
+ ```
249
+ module.exports = {
250
+ fields: [],
251
+ cards: [],
252
+ context: {},
253
+ };
254
+ ```
255
+
256
+ ### Breaking Change - Declarative Configuration library is now internal
257
+ The [Declarative Configuration System](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md) explicitly provides documented functions which are meant to be re-used in configuration code (eg. [Utils](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md#utils)). In cht-conf 2.x and before, it was possible to also re-use some undocumented internals of the declarative library (common examples are `now`, `isReportValid()`, and `emitTargetInstance()`). In cht-conf 3.0, these internals and hidden as was intended. Documented interfaces remain unchanged.
258
+
259
+ ### Breaking Change - Interface change in Target.emitCustom()
260
+ The Declarative Configuration [Target Schema](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md#target-schema) for `emitCustom` has changed to `function(emit, instance, contact, report)`. The default value is `(emit, instance) => emit(instance)`.
261
+
262
+ ### Debug Mode for compile-app-settings
263
+ `cht --local compile-app-settings upload-app-settings -- --debug`
264
+
265
+ The `--debug` flag will change the behavior of `compile-app-settings` such that:
266
+
267
+ 1. ESLint failures are logged as warnings instead of causing errors
268
+ 1. Webpack warnings are logged as warnings instead of causing errors
269
+ 1. Code minification is skipped to make it easier to debug your code
270
+
271
+ ## 2.2.0
272
+
273
+ cht-conf v2.2 includes:
274
+
275
+ * New action `move-contact` facilitates the moving of contacts and places within a project's hierarchy. [Documentation](https://github.com/medic/cht-conf#moving-contacts-within-the-hierarchy). [#172](https://github.com/medic/cht-conf/issues/172)
276
+ * Updates the `upload-custom-translations` action to support custom locales (for WebApp projects >v3.4.0). [#199](https://github.com/medic/cht-conf/issues/199)
277
+ * The declarative configuration [target schema](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md#target-schema) has been updated. The idType attribute can now be a function. [#145](https://github.com/medic/cht-conf/issues/145)
278
+ * Support for Node 12, refactoring, and code cleanup.
279
+
280
+ ## 2.0.0
281
+
282
+ cht-conf v2.0 contains breaking interface changes for the command-line and for the [declarative configuration system](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md).
283
+
284
+ ### Breaking Changes to Command-line Arguments
285
+ Version 2.0 replaces our custom code for command-line argument parsing with [minimist](https://github.com/substack/minimist). This increases flexibility and more flexible ordering, but includes an interface change for a few commands. Refer to `cht --help` for an overview of all command-line options and syntax.
286
+
287
+ #### Commands Requiring '='
288
+ The command
289
+
290
+ cht --url http://admin:pass@localhost:5988
291
+
292
+ now includes an `=` sign after the `--url`
293
+
294
+ cht --url=http://admin:pass@localhost:5988
295
+
296
+ This affects the `url`, `instance`, `user`, and `shell-completion` arguments.
297
+
298
+ #### Action List Requires '--' Prefix
299
+ The command
300
+
301
+ cht --local convert-app-forms upload-app-forms my_form_1 my_form_2
302
+
303
+ now requires a '--' to separate actions from the list of forms:
304
+
305
+ cht --local convert-app-forms upload-app-forms -- my_form_1 my_form_2
306
+
307
+ ### Breaking Changes to Declarative Configuration
308
+
309
+ The [declarative configuration system documentation](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md) contains up-to-date documentation of all interfaces.
310
+
311
+ #### Using Node Modules
312
+ We are hoping to converge on the interfaces used for [node modules](https://nodejs.org/api/modules.html) for sharing code between files. Moving forward, Medic configuration files will `export` and `require` code between files. This should 1) simplify the contract between the Medic web app and your configuration code, 2) simplify and facilitate unit testing, 3) allow you to use third-party libraries in your configuration code in future releases, and 4) simplify the cht-conf infrastructure and process for debugging issues.
313
+
314
+ Your `nools-extras.js` file now need to export any values, functions, objects which are to be shared with `tasks.js` or `targets.js` via the `module.exports` interface. [Example](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md#nools-extrasjs)
315
+
316
+ Your `tasks.js` and `targets.js` files should now access the exported attributes in your `nools-extras.js` via the global `extras` object. [Example](https://github.com/medic/medic-docs/blob/master/configuration/developing-community-health-applications.md#targetsjs)
317
+
318
+ #### Re-ordered Parameters
319
+ It can be hard to understand (and hard to document) the parameters which define the interfaces of the declarative configuration system. Version 2.0 includes interface changes which help to keep interfaces more consistent by moving optional variables to the end of the interface. The following interfaces have changed:
320
+
321
+ Interface | v1.x | v2.x
322
+ -- | -- | --
323
+ tasks.events[n].dueDate | (contact/reports, event, scheduledTaskIdx) | (event, contact, reports, scheduledTaskIdx)
324
+ tasks.actions[n].modifyContent | (contact/reports, content) | (content, contact, reports)
325
+ targets.emitCustom | (contact, reports) | (instance, contact, reports)
326
+ targets.date | ( contact ) | (contact, reports)
327
+
328
+ ### New Features in Declarative Configuration
329
+
330
+ #### this
331
+ The `this` keyword is now available in all declarative configuration function interfaces to facilitate basic data sharing. This allows you to re-use a variable in another part of the task or target, rather than re-calculating it's value again.
332
+
333
+ For example, you may have a complex seek through a set of reports in your `appliesIf` function, and need the same seek in calculating the `dueDate`, as seen in the example below.
334
+
335
+ {
336
+ appliesTo: 'contacts',
337
+ appliesIf: function(contact) {
338
+ const necessaryReport = extras.findNecessaryReport(contact.reports);
339
+ this.necessaryReport = necessaryReport;
340
+ return !!necessaryReport;
341
+ },
342
+ events: [{
343
+ start: 1,
344
+ end: 3,
345
+ dueDate: function (event, contact, report) {
346
+ // Use the saved value of this.necessaryReport
347
+ return this.necessaryReport.reported_date;
348
+ },
349
+ }],
350
+ ...
351
+ }
package/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ubuntu:16.04
1
+ FROM ubuntu:18.04
2
2
  MAINTAINER DevOps "devops@medicmobile.org"
3
3
 
4
4
 
@@ -14,7 +14,7 @@ RUN echo "==> Installing Python dependencies" && \
14
14
  RUN echo "====> Installing cht-conf python stuff" &&\
15
15
  python -m pip install git+https://github.com/medic/pyxform.git@medic-conf-1.17#egg=pyxform-medic
16
16
 
17
- RUN curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh
17
+ RUN curl -sL https://deb.nodesource.com/setup_12.x -o nodesource_setup.sh
18
18
  RUN bash nodesource_setup.sh
19
19
  RUN apt-get install -y nodejs
20
20
 
package/README.md CHANGED
@@ -11,29 +11,44 @@ CHT Conf is a command-line interface tool to manage and configure your apps buil
11
11
 
12
12
  # Installation
13
13
 
14
- ## Docker
14
+ ## Operating System Specific
15
15
 
16
- docker build -t cht-conf:v0 .
17
- docker run cht-conf:v0
18
- docker exec -it <container_name> /bin/bash
19
-
20
- ## Ubuntu
16
+ ### Ubuntu
21
17
 
22
18
  npm install -g cht-conf
23
19
  sudo python -m pip install git+https://github.com/medic/pyxform.git@medic-conf-1.17#egg=pyxform-medic
24
20
 
25
- ## OSX
21
+ ### OSX
26
22
 
27
23
  npm install -g cht-conf
28
24
  pip install git+https://github.com/medic/pyxform.git@medic-conf-1.17#egg=pyxform-medic
29
25
 
30
- ## Windows
26
+ ### Windows
31
27
 
32
28
  As Administrator:
33
29
 
34
30
  npm install -g cht-conf
35
31
  python -m pip install git+https://github.com/medic/pyxform.git@medic-conf-1.17#egg=pyxform-medic --upgrade
36
32
 
33
+ ### Docker
34
+
35
+ **NB** - `cht-conf` in a Docker container should only be used if you're familiar with running containers. You will have to copy or mount your config files into the container in order to be able to use the `cht` call to interact with a remote CHT instance.
36
+
37
+ Build and then run the `cht-conf` container:
38
+
39
+ docker build -t cht-conf:v0 .
40
+ docker run -d --name cht-conf cht-conf:v0
41
+
42
+ You now have a container running in the background called `cht-conf`. You can get a shell on this container with:
43
+
44
+ docker exec -it cht-conf /bin/bash
45
+
46
+ And then you can run the utility with `cht`.
47
+
48
+ When you are done with the container, you can stop it with:
49
+
50
+ docker stop cht-conf
51
+
37
52
  ## Bash completion
38
53
 
39
54
  To enable tab completion in bash, add the following to your `.bashrc`/`.bash_profile`:
@@ -46,7 +61,7 @@ To upgrade to the latest version
46
61
 
47
62
  npm install -g cht-conf
48
63
 
49
- # Usage
64
+ ## Usage
50
65
 
51
66
  `cht` will upload the configuration **from your current directory**.
52
67
 
@@ -60,9 +75,9 @@ For developers, this is the instance defined in your `COUCH_URL` environment var
60
75
 
61
76
  cht --local
62
77
 
63
- ### A specific Medic Mobile instance
78
+ ### A specific Medic-hosted instance
64
79
 
65
- For configuring against Medic Mobile-hosted instances.
80
+ For configuring Medic-hosted instances.
66
81
 
67
82
  cht --instance=instance-name.dev
68
83
 
@@ -80,7 +95,7 @@ If a different username is required, add the `--user` switch:
80
95
 
81
96
  cht --archive
82
97
 
83
- The resulting archive is consumable by Medic's API >v3.7 to create default configurations.
98
+ The resulting archive is consumable by CHT API >v3.7 to create default configurations.
84
99
 
85
100
  ## Perform specific action(s)
86
101
 
@@ -96,7 +111,7 @@ The list of available actions can be seen via `cht --help`.
96
111
 
97
112
  _Added in v3.2.0_
98
113
 
99
- In order to avoid overwriting someone elses configuration cht-conf records the last uploaded configuration snapshot in the `.snapshots` directory. The `remote.json` file should be committed to your repository along with the associated configuration change. When uploading future configuration if cht-conf detects the snapshot doesn't match the configuration on the server you will be prompted to overwrite or cancel.
114
+ In order to avoid overwriting someone else's configuration cht-conf records the last uploaded configuration snapshot in the `.snapshots` directory. The `remote.json` file should be committed to your repository along with the associated configuration change. When uploading future configuration if cht-conf detects the snapshot doesn't match the configuration on the server you will be prompted to overwrite or cancel.
100
115
 
101
116
  # Currently supported
102
117
 
@@ -144,22 +159,27 @@ Parameter | Description | Required
144
159
  column(s) | Comma delimited list of columns you wish to add/edit. If this is not specified all columns will be added. | No
145
160
  docDirectoryPath | This action outputs files to local disk at this destination | No. Default `json-docs`
146
161
  file(s) | Comma delimited list of files you wish to process using edit-contacts. By default, contact.csv is searched for in the current directory and processed. | No.
162
+ updateOfflineDocs | If passed, this updates the docs already in the docDirectoryPath instead of downloading from the server. | No.
147
163
 
148
164
 
149
165
  ### Example
150
166
  1. Create a contact.csv file with your columns in the csv folder in your current path. The documentID column is a requirement. (The documentID column contains the document IDs to be fetched from couchdb.)
151
167
 
152
- | documentID | is_in_emnch:bool |
153
- | ----------------- | ---------------- |
154
- | documentID1 | false |
155
- | documentID2 | false |
156
- | documentID3 | true |
168
+ | documentID | is_in_emnch:bool |
169
+ | ----------------- | ---------------- |
170
+ | documentID1 | false |
171
+ | documentID2 | false |
172
+ | documentID3 | true |
157
173
 
158
174
  1. Use the following command to download and edit the documents:
159
175
 
160
- ```
161
- cht --instance=*instance* edit-contacts -- --column=*is_in_emnch* --docDirectoryPath=*my_folder*
162
- ```
176
+ ```
177
+ cht --instance=*instance* edit-contacts -- --column=*is_in_emnch* --docDirectoryPath=*my_folder*
178
+ ```
179
+ or this one to update already downloaded docs
180
+ ```
181
+ cht --instance=*instance* edit-contacts -- --column=*is_in_emnch* --docDirectoryPath=*my_folder* --updateOfflineDocs
182
+ ```
163
183
  1. Then upload the edited documents using the [upload-docs ](#examples) command.
164
184
 
165
185
 
@@ -262,7 +282,7 @@ To develop a new action or improve an existing one, check the ["Actions" doc](sr
262
282
 
263
283
  ## Testing
264
284
 
265
- 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.
266
286
 
267
287
  ## Executing your local branch
268
288
 
@@ -274,16 +294,25 @@ Execute `npm test` to run static analysis checks and the test suite.
274
294
 
275
295
  ## Releasing
276
296
 
277
- 1. Create a pull request with prep for the new release. This should contain changes to release notes if required and anything else that needs to be done. As commit messages should be clear and readable for every change, [release-notes.md](./release-notes.md) does not need to be updated for every single change. Instead, it should include information about significant changes, breaking changes, changes to interfaces, changes in behavior, new feature details, etc.
278
- 1. Get the pull request reviewed and approved
279
- 1. Run `npm version patch`, `npm version minor`, or `npm version major` as appropriate. This will:
280
- - Update versions in `package.json` and `package-lock.json`
281
- - Commit those changes locally and tag that commit with the new version
282
- 1. Run `npm publish` to publish the new tag to npm
283
- 1. `git push && git push --tags` to push the npm generated commit and tag up to your pre-approved pull request
284
- 1. Merge the pull request back into master
297
+ 1. Create a pull request with prep for the new release.
298
+ 1. Get the pull request reviewed and approved.
299
+ 1. When doing the squash and merge, make sure that your commit message is clear and readable and follows the strict format described in the commit format section below. If the commit message does not comply, automatic release will fail.
300
+ 1. In case you are planning to merge the pull request with a merge commit, make sure that every commit in your branch respects the format.
285
301
  1. Announce the release on the [CHT forum](https://forum.communityhealthtoolkit.org), under the "Product - Releases" category.
286
302
 
303
+ ### Commit format
304
+ The commit format should follow this [conventional-changelog angular preset](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular). Examples are provided below.
305
+
306
+ Type | Example commit message | Release type
307
+ -- | -- | --
308
+ Bug fixes | fix(#123): infinite spinner when clicking contacts tab twice | patch
309
+ Performance | perf(#789): lazily loaded angular modules | patch
310
+ Features | feat(#456): add home tab | minor
311
+ Non-code | chore(#123): update README | none
312
+ Breaking| perf(#2): remove reporting rates feature <br/> BREAKING CHANGE: reporting rates no longer supported | major
313
+
314
+
315
+
287
316
  ### Releasing betas
288
317
 
289
318
  1. Checkout `master`
@@ -0,0 +1 @@
1
+ module.exports = {extends: ['@commitlint/config-conventional']}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cht-conf",
3
- "version": "3.7.0",
3
+ "version": "3.9.2",
4
4
  "description": "Configure CHT deployments",
5
5
  "main": "./src/lib/main.js",
6
6
  "engines": {
@@ -9,7 +9,10 @@
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 && 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",
15
+ "semantic-release": "semantic-release"
13
16
  },
14
17
  "bin": {
15
18
  "medic-conf": "src/bin/index.js",
@@ -23,7 +26,7 @@
23
26
  },
24
27
  "repository": {
25
28
  "type": "git",
26
- "url": "git+ssh://git@github.com/medic/cht-conf.git"
29
+ "url": "https://github.com/medic/cht-conf.git"
27
30
  },
28
31
  "author": "",
29
32
  "license": "AGPL-3.0-only",
@@ -33,48 +36,55 @@
33
36
  "homepage": "https://github.com/medic/cht-conf#readme",
34
37
  "dependencies": {
35
38
  "@hapi/joi": "^16.1.8",
39
+ "@medic/translation-checker": "1.0.0",
36
40
  "canonical-json": "0.0.4",
37
- "csv-parse": "^4.4.3",
41
+ "csv-parse": "^4.16.0",
38
42
  "dom-compare": "^0.6.0",
39
- "eslint": "^6.5.0",
40
- "eslint-loader": "^3.0.2",
41
- "googleapis": "^43.0.0",
42
- "iso-639-1": "^2.1.4",
43
+ "eslint": "^6.8.0",
44
+ "eslint-loader": "^3.0.4",
45
+ "googleapis": "^84.0.0",
46
+ "iso-639-1": "^2.1.9",
43
47
  "json-diff": "^0.5.4",
44
48
  "json-stringify-safe": "^5.0.1",
45
- "json2csv": "^4.5.1",
46
- "mime-types": "^2.1.27",
47
- "minimist": "^1.2.0",
48
- "mkdirp": "^0.5.1",
49
- "open": "^7.0.2",
50
- "pluralize": "^7.0.0",
51
- "pouchdb-adapter-http": "^7.1.1",
52
- "pouchdb-core": "^7.1.1",
53
- "pouchdb-mapreduce": "^7.1.1",
49
+ "json2csv": "^4.5.4",
50
+ "mime-types": "^2.1.32",
51
+ "minimist": "^1.2.5",
52
+ "mkdirp": "^1.0.4",
53
+ "open": "^7.4.2",
54
+ "pluralize": "^8.0.0",
55
+ "pouchdb-adapter-http": "^7.2.2",
56
+ "pouchdb-core": "^7.2.2",
57
+ "pouchdb-mapreduce": "^7.2.2",
54
58
  "properties": "^1.2.1",
55
59
  "readline-sync": "^1.4.10",
56
- "redact-basic-auth": "^1.0.0",
57
- "request": "^2.88.0",
58
- "request-promise-native": "^1.0.7",
60
+ "redact-basic-auth": "^1.0.1",
61
+ "request": "^2.88.2",
62
+ "request-promise-native": "^1.0.9",
59
63
  "semver": "^6.1.1",
60
- "svgo": "^1.2.2",
61
- "@medic/translation-checker": "1.0.0",
62
- "uuid": "^3.3.3",
63
- "webpack": "^4.41.0",
64
- "xmldom": "^0.1.27"
64
+ "svgo": "^1.3.2",
65
+ "uuid": "^8.3.2",
66
+ "webpack": "^4.46.0",
67
+ "xmldom": "^0.6.0"
65
68
  },
66
69
  "devDependencies": {
70
+ "@commitlint/cli": "^13.2.0",
71
+ "@commitlint/config-conventional": "^13.2.0",
72
+ "@semantic-release/changelog": "^6.0.0",
73
+ "@semantic-release/git": "^10.0.0",
67
74
  "body-parser": "^1.19.0",
68
- "chai": "^4.2.0",
69
- "chai-exclude": "^2.0.1",
75
+ "chai": "^4.3.4",
76
+ "chai-exclude": "^2.0.3",
70
77
  "chai-shallow-deep-equal": "^1.4.6",
71
- "chai-xml": "^0.3.2",
78
+ "chai-xml": "^0.4.0",
72
79
  "eslint-plugin-node": "^11.1.0",
73
80
  "express": "^4.17.1",
74
81
  "express-pouchdb": "^4.2.0",
75
- "mocha": "^6.1.4",
76
- "pouchdb-adapter-memory": "^7.1.1",
77
- "rewire": "^4.0.1",
82
+ "husky": "^7.0.2",
83
+ "mocha": "^6.2.3",
84
+ "nyc": "^15.1.0",
85
+ "pouchdb-adapter-memory": "^7.2.2",
86
+ "rewire": "^5.0.0",
87
+ "semantic-release": "^18.0.0",
78
88
  "sinon": "^7.3.2"
79
89
  }
80
90
  }
package/release-notes.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Release Notes
2
2
 
3
+ ## 3.8.0
4
+
5
+ ### Ability to edit a contact type
6
+
7
+ It is now possible to edit a contact type with the `edit-contacts` action. Additionally, `edit-contacts` accepts a new flag `--updateOfflineDocs`, that allows editing json docs that already exist in the directory provided in `--docDirectoryPath` instead of downloading the docs from the database.
8
+
9
+ [#408](https://github.com/medic/cht-conf/issues/408)
10
+
11
+ ### Fail early if .eslintrc file is not provided
12
+
13
+ Running the `compile-app-settings` command now fails early and provides a more descriptive error message to users.
14
+
15
+ [#333](https://github.com/medic/cht-conf/issues/333)
16
+
17
+ ### Check to make sure CHT instance is up
18
+
19
+ Commands that require an instance now fail with a more descriptive error message if the instance provided is not reachable
20
+
21
+ [#380](https://github.com/medic/cht-conf/issues/380)
22
+
3
23
  ## 3.7.0
4
24
 
5
25
  ### The repository has been renamed to `cht-conf`.
@@ -33,6 +33,7 @@
33
33
  "reports": true,
34
34
  "lineage": true,
35
35
  "uhcStats": true,
36
- "targetDoc": true
36
+ "targetDoc": true,
37
+ "cht": true
37
38
  }
38
39
  }
@@ -43,6 +43,12 @@ const compileAppSettingsForProject = async (projectDir, options) => {
43
43
  let appSettings;
44
44
  const baseSettingsPath = path.join(projectDir, 'app_settings/base_settings.json');
45
45
  const appSettingsPath = path.join(projectDir, 'app_settings.json');
46
+ const esLintFilePath = path.join(projectDir, '.eslintrc');
47
+
48
+ // Fail if no eslintrc file is found
49
+ if (!fs.exists(esLintFilePath)) {
50
+ throw new Error('No eslint configuration defined please create a .eslintrc file with the desired linting rules');
51
+ }
46
52
 
47
53
  if (!fs.exists(baseSettingsPath) && !fs.exists(appSettingsPath)) {
48
54
  throw new Error('No configuration defined please create a base_settings.json file in app_settings folder with the desired configuration');
@@ -1,5 +1,5 @@
1
1
  const stringify = require('canonical-json/index2');
2
- const uuid5 = require('uuid/v5');
2
+ const { v5: uuid5 } = require('uuid');
3
3
 
4
4
  const environment = require('../lib/environment');
5
5
  const fs = require('../lib/sync-fs');
@@ -9,7 +9,7 @@ const safeStringify = require('../lib/safe-stringify');
9
9
 
10
10
  const pretty = o => JSON.stringify(o, null, 2);
11
11
 
12
- const RESERVED_COL_NAMES = [ 'type', 'form' ];
12
+ const RESERVED_COL_NAMES = [ 'form' ];
13
13
  const REF_MATCHER = /^(?:GET )?((\w+) OF )?(\w+) WHERE (.*)$/i;
14
14
  const PLACE_TYPES = [ 'clinic', 'district_hospital', 'health_center' ];
15
15
 
@@ -7,18 +7,52 @@ const pouch = require('../lib/db');
7
7
  const safeStringify = require('../lib/safe-stringify');
8
8
  const toDocs = require('./csv-to-docs');
9
9
  const EDIT_RESERVED_COL_NAMES = [ 'parent', '_id', 'name', 'reported_date' ];
10
- const DOC_TYPES = ['district_hospital', 'health_center', 'clinic', 'person', 'user', 'user-settings', 'contact'];
11
10
  const DOCUMENT_ID = 'documentID';
11
+ const userPrompt = require('../lib/user-prompt');
12
+ const fetchDocumentList = require('../lib/fetch-document-list');
13
+ const OVERWRITE_ALL_FILES_OPTION = 1;
14
+ const CANCEL_OVERWRITE_OPTION = 2;
15
+
16
+ const jsonDocPath = (directoryPath, docID) => `${directoryPath}/${docID}.doc.json`;
17
+
18
+ // check to see if we should write/overwrite the file
19
+ const overwriteFileCheck = (doc, args, overwriteAllFiles) => {
20
+ return args.updateOfflineDocs || environment.force || overwriteAllFiles || !fs.exists(jsonDocPath(args.docDirectoryPath, doc._id));
21
+ };
22
+
23
+ const saveJsonDoc = (doc, args) => {
24
+ return fs.write(jsonDocPath(args.docDirectoryPath, doc._id), safeStringify(doc) + '\n');
25
+ };
26
+
27
+ const writeDocs = (docs, args) => {
28
+ let overwriteAllFiles = false;
29
+
30
+ return Promise.all(Object.values(docs).map(doc => {
31
+ const overwriteFile = overwriteFileCheck(doc, args, overwriteAllFiles);
32
+
33
+ if (!overwriteFile) {
34
+ const userSelection = userPrompt.keyInSelect(
35
+ ['overwrite this file', 'overwrite this file and all subsequent files'],
36
+ `${doc._id}.doc.json already exists in the chosen directory. What do you want to do?`
37
+ );
38
+
39
+ if (userSelection === undefined || userSelection === CANCEL_OVERWRITE_OPTION) {
40
+ throw new Error('User canceled the action.');
41
+ }
42
+ overwriteAllFiles = (userSelection === OVERWRITE_ALL_FILES_OPTION);
43
+ }
44
+ return saveJsonDoc(doc, args);
45
+ }));
46
+ };
12
47
 
13
48
  const execute = () => {
14
49
  const args = parseExtraArgs(environment.pathToProject, environment.extraArgs);
15
50
  const db = pouch();
16
51
  const docDirectoryPath = args.docDirectoryPath;
17
52
  fs.mkdir(docDirectoryPath);
18
- const saveJsonDoc = doc => fs.write(`${docDirectoryPath}/${doc._id}.doc.json`, safeStringify(doc) + '\n');
19
53
 
20
54
  const csvDir = `${environment.pathToProject}/csv`;
21
- if(!fs.exists(csvDir)) {
55
+ if (!fs.exists(csvDir)) {
22
56
  warn(`No csv directory found at ${csvDir}.`);
23
57
  return Promise.resolve();
24
58
  }
@@ -34,8 +68,8 @@ const execute = () => {
34
68
  const nameParts = fs.path.basename(csv).split('.');
35
69
  const prefix = nameParts[0];
36
70
  switch(prefix) {
37
- case 'contact': return processDocuments(csv, getIDs(csv, prefix), db, args);
38
- case 'users': return processUsers(csv, getIDs(csv, prefix), db, args);
71
+ case 'contact': return processDocuments('contact', csv, getIDs(csv, prefix), db, args);
72
+ case 'users': return processDocuments('user', csv, getIDs(csv, prefix), db, args);
39
73
  default: throw new Error(`Unrecognised CSV type ${prefix} for file ${csv}`);
40
74
  }
41
75
  })
@@ -43,7 +77,7 @@ const execute = () => {
43
77
  Promise.resolve())
44
78
 
45
79
  .then(() => model.exclusions.forEach(toDocs.removeExcludedField))
46
- .then(() => Promise.all(Object.values(model.docs).map(saveJsonDoc)));
80
+ .then(() => writeDocs(model.docs, args));
47
81
  };
48
82
 
49
83
  const model = {
@@ -111,7 +145,7 @@ function processCsv(docType, cols, row, uuidIndex, toIncludeIndex, documentDocs)
111
145
  const idPrefix = docType === 'contact' ? '' : 'org.couchdb.user:';
112
146
  const doc = documentDocs[idPrefix + documentId];
113
147
 
114
- if(toIncludeIndex.length > 0){
148
+ if (toIncludeIndex.length > 0) {
115
149
  row = toIncludeIndex.map(index => row[index]);
116
150
  } else {
117
151
  row.splice(uuidIndex,1);
@@ -120,12 +154,12 @@ function processCsv(docType, cols, row, uuidIndex, toIncludeIndex, documentDocs)
120
154
  for(let i=0; i<cols.length; ++i) {
121
155
  const { col, val, excluded } = toDocs.parseColumn(cols[i], row[i]);
122
156
 
123
- if(EDIT_RESERVED_COL_NAMES.includes(col.split('.')[0])) {
157
+ if (EDIT_RESERVED_COL_NAMES.includes(col.split('.')[0])) {
124
158
  throw new Error(`Cannot set property defined by column '${col}' - this property name is protected.`);
125
159
  }
126
160
 
127
161
  toDocs.setCol(doc, col, val);
128
- if(excluded) {
162
+ if (excluded) {
129
163
  model.exclusions.push({
130
164
  doc: doc,
131
165
  propertyName: col,
@@ -135,34 +169,9 @@ function processCsv(docType, cols, row, uuidIndex, toIncludeIndex, documentDocs)
135
169
  return doc;
136
170
  }
137
171
 
138
- const processUsers = async (csv, ids, db, args) => {
139
- const documentDocs = await fetchDocumentList(db, ids);
140
- return processDocs('user', csv, documentDocs, args);
141
- };
142
-
143
- const processDocuments = async (csv, ids, db, args) => {
144
- const documentDocs = await fetchDocumentList(db, ids);
145
- return processDocs('contact', csv, documentDocs, args);
146
- };
147
-
148
- const fetchDocumentList = async (db, ids) => {
149
- info('Downloading doc(s)...');
150
- const documentDocs = await db.allDocs({
151
- keys: ids,
152
- include_docs: true,
153
- });
154
-
155
- const missingDocumentErrors = documentDocs.rows.filter(row => !row.doc).map(row => `Document with id '${row.key}' could not be found.`);
156
- if (missingDocumentErrors && missingDocumentErrors.length) {
157
- throw Error(missingDocumentErrors);
158
- }
159
-
160
- const documentTypeErrors = documentDocs.rows.filter(row => !DOC_TYPES.includes(row.doc.type)).map(row => ` Document with id ${row.key} of type ${row.doc.type} cannot be edited`);
161
- if (documentTypeErrors && documentTypeErrors.length) {
162
- throw Error(documentTypeErrors);
163
- }
164
-
165
- return documentDocs.rows.reduce((agg, curr) => Object.assign(agg, { [curr.doc._id]: curr.doc }), {});
172
+ const processDocuments = async (docType, csv, ids, db, args) => {
173
+ const documentDocs = await fetchDocumentList(db, ids, args);
174
+ return processDocs(docType, csv, documentDocs, args);
166
175
  };
167
176
 
168
177
  // Parses extraArgs and asserts if required parameters are not present
@@ -175,11 +184,12 @@ const parseExtraArgs = (projectDir, extraArgs = []) => {
175
184
  const csvFiles = (args.files || args.file || 'contact.csv')
176
185
  .split(',')
177
186
  .filter(id => id);
178
-
187
+
179
188
  return {
180
189
  colNames,
181
190
  csvFiles,
182
191
  docDirectoryPath: path.resolve(projectDir, args.docDirectoryPath || 'json_docs'),
192
+ updateOfflineDocs: args.updateOfflineDocs,
183
193
  };
184
194
  };
185
195
 
@@ -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).`);
@@ -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);
@@ -1,4 +1,4 @@
1
- const uuid = require('uuid/v4');
1
+ const { v4: uuid } = require('uuid');
2
2
 
3
3
  const api = require('../lib/api');
4
4
  const environment = require('../lib/environment');
package/src/lib/api.js CHANGED
@@ -87,6 +87,24 @@ const api = {
87
87
  });
88
88
  },
89
89
 
90
+ /**
91
+ * Check whether the API is alive or not. The request
92
+ * is made to a "lightweight" endpoint that only returns a HTTP 302,
93
+ * and an promise is returned with true (available) or false.
94
+ */
95
+ available: async () => {
96
+ const url = `${environment.apiUrl}/`;
97
+ try {
98
+ log.info(`Checking that ${url} is available...`);
99
+ await request.get(url);
100
+ return true;
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.');
104
+ return false;
105
+ }
106
+ },
107
+
90
108
  version() {
91
109
  return request({ uri: `${environment.instanceUrl}/api/deploy-info`, method: 'GET', json: true }) // endpoint added in 3.5
92
110
  .then(deploy_info => deploy_info && deploy_info.version);
@@ -21,6 +21,8 @@ const archivingApi = {
21
21
  return Promise.resolve('{ "success": true }');
22
22
  },
23
23
 
24
+ available: async () => true,
25
+
24
26
  version() {
25
27
  return '1000.0.0'; // assume the latest version when archiving
26
28
  },
@@ -0,0 +1,68 @@
1
+ const { info } = require('../lib/log');
2
+ const fs = require('../lib/sync-fs');
3
+ const VALID_DOC_TYPES = ['district_hospital', 'health_center', 'clinic', 'person', 'user', 'user-settings', 'contact'];
4
+
5
+ const jsonDocPath = (directoryPath, docID) => `${directoryPath}/${docID}.doc.json`;
6
+
7
+ const findErrors = (rows) => {
8
+ const errors = { invalidDocType: [], docNotFound: [] };
9
+
10
+ rows.forEach(row => {
11
+ if (!row.doc) {
12
+ errors.docNotFound.push(`Document with id '${row.key}' could not be found.`);
13
+ return;
14
+ }
15
+
16
+ if (!VALID_DOC_TYPES.includes(row.doc.type)) {
17
+ errors.invalidDocType.push(`Document with id ${row.key} of type ${row.doc.type} cannot be edited`);
18
+ }
19
+ });
20
+
21
+ return errors;
22
+ };
23
+
24
+ const fetchDocumentListFromDB = async (db, ids) => {
25
+ info('Downloading doc(s)...');
26
+ const documentDocs = await db.allDocs({
27
+ keys: ids,
28
+ include_docs: true,
29
+ });
30
+
31
+ const errors = findErrors(documentDocs.rows);
32
+
33
+ if (errors.docNotFound.length || errors.invalidDocType.length) {
34
+ throw Error([...errors.docNotFound, ...errors.invalidDocType]);
35
+ }
36
+
37
+ return documentDocs.rows.reduce((agg, curr) => Object.assign(agg, { [curr.doc._id]: curr.doc }), {});
38
+ };
39
+
40
+ /**
41
+ * Gets the docs associated with the provided IDs. It first checks if the docs are saved offline in the
42
+ * provided directory and fetches them from the DB if they are not.
43
+ */
44
+ module.exports = async (db, ids, args) => {
45
+ if (!args.updateOfflineDocs) {
46
+ return fetchDocumentListFromDB(db, ids);
47
+ }
48
+
49
+ const missingDocs = [];
50
+ const docs = {};
51
+
52
+ ids.forEach(id => {
53
+ const docPath = jsonDocPath(args.docDirectoryPath, id);
54
+ if (fs.exists(docPath)) {
55
+ docs[id] = fs.readJson(docPath);
56
+ return;
57
+ }
58
+
59
+ missingDocs.push(id);
60
+ });
61
+
62
+ if (missingDocs.length) {
63
+ const docsFromDB = await fetchDocumentListFromDB(db, missingDocs);
64
+ Object.assign(docs, docsFromDB);
65
+ }
66
+
67
+ return docs;
68
+ };
package/src/lib/main.js CHANGED
@@ -10,6 +10,7 @@ const redactBasicAuth = require('redact-basic-auth');
10
10
  const shellCompletionSetup = require('../cli/shell-completion-setup');
11
11
  const supportedActions = require('../cli/supported-actions');
12
12
  const usage = require('../cli/usage');
13
+ const api = require('../lib/api');
13
14
 
14
15
  const { error, info, warn } = log;
15
16
  const defaultActions = [
@@ -182,6 +183,10 @@ module.exports = async (argv, env) => {
182
183
  cmdArgs['skip-translation-check']
183
184
  );
184
185
 
186
+ if (apiUrl && !await api().available()) {
187
+ return false;
188
+ }
189
+
185
190
  const productionUrlMatch = environment.instanceUrl && environment.instanceUrl.match(/^https:\/\/(?:[^@]*@)?(.*)\.(app|dev)\.medicmobile\.org(?:$|\/)/);
186
191
  const expectedOptions = ['alpha', projectName];
187
192
  if (productionUrlMatch && !expectedOptions.includes(productionUrlMatch[1])) {
@@ -30,6 +30,7 @@
30
30
  },
31
31
  "globals": {
32
32
  "user": true,
33
- "Utils": true
33
+ "Utils": true,
34
+ "cht": true
34
35
  }
35
36
  }