Skip to content

Commit

Permalink
Merge branch 'main' into 653-merge-primaries
Browse files Browse the repository at this point in the history
  • Loading branch information
kennsippell committed Dec 16, 2024
2 parents 0231e60 + bcaedc7 commit 6eb7cf8
Show file tree
Hide file tree
Showing 11 changed files with 509 additions and 42 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# [4.4.0](https://github.com/medic/cht-conf/compare/v4.3.0...v4.4.0) (2024-12-16)


### Features

* **#650:** delete-contacts action ([#652](https://github.com/medic/cht-conf/issues/652)) ([129f38f](https://github.com/medic/cht-conf/commit/129f38fca83fa84717c7ba16857b315c25d33b9e)), closes [#650](https://github.com/medic/cht-conf/issues/650)

# [4.3.0](https://github.com/medic/cht-conf/compare/v4.2.0...v4.3.0) (2024-12-16)


### Features

* **#648:** add --disable-users command for merge-contacts action ([#649](https://github.com/medic/cht-conf/issues/649)) ([c78d3fd](https://github.com/medic/cht-conf/commit/c78d3fd1ed1588cfefdf21384840743f5faea62d)), closes [#648](https://github.com/medic/cht-conf/issues/648)

# [4.2.0](https://github.com/medic/cht-conf/compare/v4.1.3...v4.2.0) (2024-12-11)


Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cht-conf",
"version": "4.2.0",
"version": "4.4.0",
"description": "Configure CHT deployments",
"main": "./src/lib/main.js",
"engines": {
Expand Down
8 changes: 4 additions & 4 deletions src/fn/delete-contacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module.exports = {
const parseExtraArgs = (projectDir, extraArgs = []) => {
const args = minimist(extraArgs, { boolean: true });

const sourceIds = (args.ids || args.id || '')
const sourceIds = (args.contacts || args.contact || '')
.split(',')
.filter(id => id);

Expand All @@ -49,14 +49,14 @@ ${bold('cht-conf\'s delete-contacts action')}
When combined with 'upload-docs' this action recursively deletes a contact and all of their descendant contacts and data. ${bold('This operation is permanent. It cannot be undone.')}
${bold('USAGE')}
cht --local delete-contacts -- --ids=<id1>,<id2>
cht --local delete-contacts -- --contacts=<id1>,<id2>
${bold('OPTIONS')}
--ids=<id1>,<id2>
--contacts=<id1>,<id2> (or --contact=<id1>,<id2>)
A comma delimited list of ids of contacts to be deleted.
--disable-users
When flag is present, users at any deleted place will be permanently disabled.
When flag is present, users at any deleted place will be updated and may be permanently disabled. Supported by CHT Core 4.7 and above.
--docDirectoryPath=<path to stage docs>
Specifies the folder used to store the documents representing the changes in hierarchy.
Expand Down
2 changes: 1 addition & 1 deletion src/fn/merge-contacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ ${bold('OPTIONS')}
A comma delimited list of IDs of contacts which will be deleted. The hierarchy of contacts and reports under it will be moved to be under the destination contact.
--disable-users
When flag is present, users at any deleted place will be permanently disabled.
When flag is present, users at any deleted place will be updated and may be permanently disabled. Supported by CHT Core 4.7 and above.
--merge-primary-contacts
When flag is present, the primary contacts for all the top-level places will also be merged into a single resulting contact.
Expand Down
21 changes: 8 additions & 13 deletions src/fn/upload-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async function execute() {
throw new Error(`upload-docs: ${errors.join('\n')}`);
}

warnAndPrompt(`This operation will permanently write ${totalCount} docs. Are you sure you want to continue?`);
userPrompt.warnPromptAbort(`This operation will permanently write ${totalCount} docs. Are you sure you want to continue?`);

const deletedDocIds = analysis.map(result => result.delete).filter(Boolean);
await handleUsersAtDeletedFacilities(deletedDocIds);
Expand Down Expand Up @@ -100,13 +100,6 @@ async function execute() {
return processNextBatch(filenamesToUpload, INITIAL_BATCH_SIZE);
}

function warnAndPrompt(warningMessage) {
warn(warningMessage);
if (!userPrompt.keyInYN()) {
throw new Error('User aborted execution.');
}
}

function analyseFiles(filePaths) {
return filePaths
.map(filePath => {
Expand All @@ -117,24 +110,28 @@ function analyseFiles(filePaths) {
return { error: `File '${filePath}' sets _id:'${json._id}' but the file's expected _id is '${idFromFilename}'.` };
}

if (json._deleted && json.disableUsers) {
if (json._deleted && json.cht_disable_linked_users) {
return { delete: json._id };
}
})
.filter(Boolean);
}

async function handleUsersAtDeletedFacilities(deletedDocIds) {
await assertCoreVersion();
if (!deletedDocIds?.length) {
return;
}

await assertCoreVersion();

const affectedUsers = await getAffectedUsers(deletedDocIds);
const usernames = affectedUsers.map(userDoc => userDoc.username).join(', ');
if (affectedUsers.length === 0) {
trace('No users found needing an update.');
return;
}

warnAndPrompt(`This operation will update permissions for ${affectedUsers.length} user accounts: ${usernames}. Are you sure you want to continue?`);
userPrompt.warnPromptAbort(`This operation will update ${affectedUsers.length} user accounts: ${usernames} and cannot be undone. Are you sure you want to continue?`);
await updateAffectedUsers(affectedUsers);
}

Expand All @@ -152,8 +149,6 @@ async function getAffectedUsers(deletedDocIds) {
const places = Array.isArray(apiResponse.place) ? apiResponse.place.filter(Boolean) : [apiResponse.place];
const placeIds = places.map(place => place?._id);
return {
_id: apiResponse.id,
_rev: apiResponse.rev,
username: apiResponse.username,
place: placeIds,
};
Expand Down
2 changes: 1 addition & 1 deletion src/lib/hierarchy-operations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ function updateContacts(options, constraints, moveContext) {
_id: descendant._id,
_rev: descendant._rev,
_deleted: true,
disableUsers: !!toDeleteUsers,
cht_disable_linked_users: !!toDeleteUsers,
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/hierarchy-operations/jsdocFolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function deleteDoc(options, doc, disableUsers) {
_id: doc._id,
_rev: doc._rev,
_deleted: true,
disableUsers: !!disableUsers,
cht_disable_linked_users: !!disableUsers,
});
}

Expand Down
11 changes: 10 additions & 1 deletion src/lib/user-prompt.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const environment = require('./environment');
const readline = require('readline-sync');
const { warn } = require('./log');


/**
Expand Down Expand Up @@ -33,8 +34,16 @@ function keyInSelect(items, question, options = {}) {
return readline.keyInSelect(items, question, options);
}

function warnPromptAbort(warningMessage) {
warn(warningMessage);
if (!keyInYN()) {
throw new Error('User aborted execution.');
}
}

module.exports = {
keyInYN,
question,
keyInSelect
keyInSelect,
warnPromptAbort,
};
15 changes: 3 additions & 12 deletions test/fn/upload-docs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ describe('upload-docs', function() {
});

it('should upload docs to pouch', async () => {
apiStub.giveResponses(API_VERSION_RESPONSE);

await assertDbEmpty();
await uploadDocs.execute();
const res = await apiStub.db.allDocs();
Expand Down Expand Up @@ -90,7 +88,6 @@ describe('upload-docs', function() {
expectedDocs = new Array(10).fill('').map((x, i) => ({ _id: i.toString() }));
const clock = sinon.useFakeTimers(0);
const imported_date = new Date().toISOString();
apiStub.giveResponses(API_VERSION_RESPONSE);
return uploadDocs.__with__({
INITIAL_BATCH_SIZE: 4,
Date,
Expand Down Expand Up @@ -132,7 +129,6 @@ describe('upload-docs', function() {
});

it('should not throw if force is set', async () => {
apiStub.giveResponses(API_VERSION_RESPONSE);
userPrompt.__set__('environment', { force: () => true });
await assertDbEmpty();
sinon.stub(process, 'exit');
Expand Down Expand Up @@ -184,7 +180,6 @@ describe('upload-docs', function() {

it('users associated with docs without truthy deleteUser attribute are not deleted', async () => {
const writtenDoc = await apiStub.db.put({ _id: 'one' });
apiStub.giveResponses(API_VERSION_RESPONSE);

const oneDoc = expectedDocs[0];
oneDoc._rev = writtenDoc.rev;
Expand All @@ -193,9 +188,7 @@ describe('upload-docs', function() {
await uploadDocs.execute();
const res = await apiStub.db.allDocs();
expect(res.rows.map(doc => doc.id)).to.deep.eq(['three', 'two']);
assert.deepEqual(apiStub.requestLog(), [
{ method: 'GET', url: '/api/deploy-info', body: {} }
]);
assert.deepEqual(apiStub.requestLog(), []);
});

it('user with multiple places gets updated', async () => {
Expand All @@ -207,7 +200,6 @@ describe('upload-docs', function() {
expect(res.rows.map(doc => doc.id)).to.deep.eq(['three', 'two']);

const expectedBody = {
_id: 'org.couchdb.user:user1',
username: 'user1',
place: [ 'two' ],
};
Expand Down Expand Up @@ -235,7 +227,7 @@ describe('upload-docs', function() {
]);
});

it('two users disabled when single place has multiple users', async () => {
it('one user disabled and one updated when single place has multiple users', async () => {
await setupDeletedFacilities('one');
setupApiResponses(2, [
{ id: 'org.couchdb.user:user1', username: 'user1', place: [{ _id: 'one' }] },
Expand All @@ -247,7 +239,6 @@ describe('upload-docs', function() {
expect(res.rows.map(doc => doc.id)).to.deep.eq(['three', 'two']);

const expectedUser2 = {
_id: 'org.couchdb.user:user2',
username: 'user2',
place: ['two'],
};
Expand Down Expand Up @@ -277,7 +268,7 @@ async function setupDeletedFacilities(...docIds) {
const expected = expectedDocs.find(doc => doc._id === id);
expected._rev = writtenDoc.rev;
expected._deleted = true;
expected.disableUsers = true;
expected.cht_disable_linked_users = true;
}
}

Expand Down
Loading

0 comments on commit 6eb7cf8

Please sign in to comment.