Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(meditrakApp): RN-1361: User type question #5820

Merged
merged 38 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6c5a826
Add User and Task question types
alexd-bes Jul 24, 2024
961d633
Add question config types
alexd-bes Jul 24, 2024
59c4333
Update types
alexd-bes Jul 24, 2024
a44321b
Update types
alexd-bes Jul 24, 2024
5a7e102
Fix surveyCode type
alexd-bes Jul 24, 2024
4d407da
WIP
alexd-bes Jul 25, 2024
6d3bcab
Merge branch 'epic-tasks' into rn-1381-import-task-user-question-configs
alexd-bes Jul 26, 2024
34eabb4
Ability to import config
alexd-bes Jul 26, 2024
f2740e4
WIP
alexd-bes Jul 26, 2024
07c8dce
User permission group validation
alexd-bes Jul 28, 2024
3c4bbcf
Add comment
alexd-bes Jul 28, 2024
195660d
Handle exporting of questions
alexd-bes Jul 28, 2024
9fae75d
Hide the task question always
alexd-bes Jul 28, 2024
a56b506
Handle BES admin users
alexd-bes Jul 28, 2024
ee81c44
Handle multiple task questions
alexd-bes Jul 28, 2024
b4a1a86
rename entityCode to entityId
alexd-bes Jul 29, 2024
9d6c786
Generate types
alexd-bes Jul 29, 2024
729f770
Add sync configs
alexd-bes Jul 29, 2024
d9062b8
Update permissions based sync queue for user entity permissions
alexd-bes Jul 30, 2024
07777ab
Merge branch 'epic-tasks' into rn-1361-user-app-question
alexd-bes Jul 30, 2024
f21f031
Add user entity permissions to sync queue
alexd-bes Jul 30, 2024
ba8c85a
Sync user accounts
alexd-bes Jul 30, 2024
88bfc26
User account syncing
alexd-bes Jul 30, 2024
ef1cd42
Make realm explorer scroll
alexd-bes Jul 30, 2024
f16abed
Fix permissions based sync queue for user entity permissions
alexd-bes Jul 30, 2024
0cadc24
Add database type for user entity permission
alexd-bes Jul 30, 2024
f2f3965
Make separate user account model
alexd-bes Jul 30, 2024
84e5347
Working question
alexd-bes Jul 30, 2024
ee19e7d
Fix user model usage
alexd-bes Jul 30, 2024
6ffed1b
Working question with permission groups
alexd-bes Jul 31, 2024
e507024
Update schema.jsx
alexd-bes Jul 31, 2024
93b1ea5
add comments
alexd-bes Jul 31, 2024
f03c8cd
Handle filtering and pagination
alexd-bes Jul 31, 2024
434d655
Merge branch 'epic-tasks' into rn-1361-user-app-question
alexd-bes Jul 31, 2024
b826125
Use permission group id instead of name
alexd-bes Jul 31, 2024
02e867b
Update filtering
alexd-bes Jul 31, 2024
c5febd8
PR fixes
alexd-bes Aug 5, 2024
7643ecb
Add internal field to users
alexd-bes Aug 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/central-server/src/apiV2/meditrakApp/getChanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import keyBy from 'lodash.keyby';
import groupBy from 'lodash.groupby';
import { respond, DatabaseError } from '@tupaia/utils';
import { RECORDS } from '@tupaia/database';
import { camel } from 'case';
import { getColumnsForMeditrakApp } from './utilities';
import {
supportsPermissionsBasedSync,
Expand All @@ -33,10 +32,11 @@ function getRecordForSync(models, record, recordType, appVersion) {
}
});

const model = models.getModelForDatabaseRecord(recordType);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is because we can get the correct model more accurately this way (e.g. for user_account database record type that i actually the User model)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good change! Tho tbh it always bothers me when our model names don't match the underlying database tables, the worst one is Facility/clinic! Tho now that we mention it, I'm surprised that table was working here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, because in the meditrak sync queue I was calling these records user_account, what it was doing was looking for a model called UserAccount which doesn't exist


// Translate values in columns based on meditrak app version
const selectedModel = models[camel(recordType)];
const translatedRecord = selectedModel?.meditrakConfig.translateRecordForSync
? selectedModel.meditrakConfig.translateRecordForSync(recordWithoutNulls, appVersion)
const translatedRecord = model?.meditrakConfig.translateRecordForSync
? model.meditrakConfig.translateRecordForSync(recordWithoutNulls, appVersion)
: recordWithoutNulls;
return translatedRecord;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
supportsPermissionsBasedSync,
} from './supportsPermissionsBasedSync';

const recordTypesToAlwaysSync = ['country', 'permission_group'];
const recordTypesToAlwaysSync = ['country', 'permission_group', 'user_account'];
const entityTypesToAlwaysSync = ['world', 'country'];

// TODO: Tidy this up as part of RN-502
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ChangeHandler } from '@tupaia/database';
import { MeditrakSyncRecordUpdater } from './MeditrakSyncRecordUpdater';

const modelValidator = model => {
if (!model.meditrakConfig.minAppVersion) {
if (!model.meditrakConfig?.minAppVersion) {
throw new Error(
`Model for ${model.databaseRecord} must have a meditrakConfig.minAppVersion property`,
);
Expand All @@ -33,6 +33,7 @@ export class MeditrakSyncQueue extends ChangeHandler {
super(models, 'meditrak-sync-queue');

const typesToSync = models.getTypesToSyncWithMeditrak();

const modelNamesToSync = Object.entries(models)
.filter(([, model]) => typesToSync.includes(model.databaseRecord))
.map(([modelName]) => modelName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ SELECT msq.*,
${groupToFlatArrayOrNull('ssc_ss_s.country_ids')},
${groupToFlatArrayOrNull('q_ssc_ss_s.country_ids')},
${groupToFlatArrayOrNull('os_q_ssc_ss_s.country_ids')},
${groupToFlatArrayOrNull('o_os_q_ssc_ss_s.country_ids')}
${groupToFlatArrayOrNull('o_os_q_ssc_ss_s.country_ids')},
${groupToArrayOrNull('uep_e_co.id')}
) as country_ids,

COALESCE(
Expand All @@ -48,7 +49,8 @@ SELECT msq.*,
${groupToArrayOrNull('ssc_ss_s_pg."name"')},
${groupToArrayOrNull('q_ssc_ss_s_pg."name"')},
${groupToArrayOrNull('os_q_ssc_ss_s_pg."name"')},
${groupToArrayOrNull('o_os_q_ssc_ss_s_pg."name"')}
${groupToArrayOrNull('o_os_q_ssc_ss_s_pg."name"')},
${groupToArrayOrNull('uep_pg."name"')}
) as permission_groups
FROM meditrak_sync_queue msq
LEFT JOIN country co ON msq.record_id = co.id
Expand Down Expand Up @@ -87,6 +89,10 @@ LEFT JOIN survey_screen_component o_os_q_ssc ON o_os_q_ssc.question_id = o_os_q.
LEFT JOIN survey_screen o_os_q_ssc_ss ON o_os_q_ssc.screen_id = o_os_q_ssc_ss.id
LEFT JOIN survey o_os_q_ssc_ss_s ON o_os_q_ssc_ss.survey_id = o_os_q_ssc_ss_s.id
LEFT JOIN permission_group o_os_q_ssc_ss_s_pg ON o_os_q_ssc_ss_s.permission_group_id = o_os_q_ssc_ss_s_pg.id
LEFT JOIN user_entity_permission uep ON msq.record_id = uep.id
LEFT JOIN entity uep_e ON uep.entity_id = uep_e.id
LEFT JOIN country uep_e_co ON uep_e_co.code = uep_e.country_code
LEFT JOIN permission_group uep_pg ON uep.permission_group_id = uep_pg.id
GROUP BY msq.id;
CREATE UNIQUE INDEX permissions_based_meditrak_sync_queue_id_idx ON permissions_based_meditrak_sync_queue (id);
`);
Expand Down
45 changes: 45 additions & 0 deletions packages/central-server/src/database/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@

import { UserRecord as CommonUserRecord, UserModel as CommonUserModel } from '@tupaia/database';

// Internal users who should be flagged in the meditrak app to exclude from user lists
const INTERNAL_USERS = [
'edmofro@gmail.com', // Edwin
'kahlinda.mahoney@gmail.com', // Kahlinda
'lparish1980@gmail.com', // Lewis
'sus.lake@gmail.com', // Susie
'michaelnunan@hotmail.com', // Michael
'vanbeekandrew@gmail.com', // Andrew
'gerardckelly@gmail.com', // Gerry K
'geoffreyfisher@hotmail.com', // Geoff F
'josh@sussol.net', // mSupply API Client
'unicef.laos.edu@gmail.com', // Laos Schools Data Collector
];

const INTERNAL_EMAIL_REGEXP = /((@bes.au)|(@tupaia.org)|(@beyondessential.com.au))/;

// Currently our pattern is that session tables don't have models
// in the generic database package, this is a quick and dirty way to get
// context for them into central-server
Expand All @@ -30,6 +46,35 @@ class UserRecord extends CommonUserRecord {
}

export class UserModel extends CommonUserModel {
meditrakConfig = {
// only sync id and first and last name
ignorableFields: [
'gender',
'creation_date',
'employer',
'position',
'mobile_number',
'password_hash',
'password_salt',
'verified_email',
'profile_image',
'primary_platform',
'preferences',
'full_name', // ignore this because it isn't a real field, it's a derived field
],
translateRecordForSync: record => {
const { email, ...restOfRecord } = record;
const isInternal = INTERNAL_USERS.includes(email) || INTERNAL_EMAIL_REGEXP.test(email);

// Flag internal users. These will be filtered out in meditrak-app
if (isInternal) {
return { ...restOfRecord, internal: true };
}
return restOfRecord;
},
minAppVersion: '1.14.143',
};

get DatabaseRecordClass() {
return UserRecord;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { UserEntityPermissionModel as CommonUserEntityPermissionModel } from '@t
import { sendEmail } from '@tupaia/server-utils';

export class UserEntityPermissionModel extends CommonUserEntityPermissionModel {
meditrakConfig = {
minAppVersion: '1.14.143',
};

notifiers = [onUpsertSendPermissionGrantEmail, expireAccess];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';

import { getSyncQueueChangeTime } from '@tupaia/tsutils';
import { generateId } from '../utilities/generateId';

var dbm;
var type;
var seed;

/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function (options, seedLink) {
dbm = options.dbmigrate;
type = dbm.dataType;
seed = seedLink;
};

// Get the IDs of all user entity permissions that are not already in the sync queue
const getAllUserEntityPermissionIds = async db => {
const result = await db.runSql(`
SELECT user_entity_permission.id FROM user_entity_permission
LEFT JOIN meditrak_sync_queue on meditrak_sync_queue.record_id = user_entity_permission.id
WHERE meditrak_sync_queue.id IS NULL
`);
return result.rows.map(row => row.id);
};

exports.up = async function (db) {
const userEntityPermissionIds = await getAllUserEntityPermissionIds(db);
await db.runSql(`
INSERT INTO meditrak_sync_queue (id, type, record_type, record_id, change_time)
VALUES ${userEntityPermissionIds
.map(
(id, i) =>
// the timestamp is incremented by i to ensure that each record has a unique timestamp
`('${generateId()}', 'update', 'user_entity_permission', '${id}', ${getSyncQueueChangeTime(
i,
)})`,
)
.join(',\n')};
`);
};

exports.down = function (db) {
return null;
};

exports._meta = {
version: 1,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

import { getSyncQueueChangeTime } from '@tupaia/tsutils';
import { generateId } from '../utilities/generateId';

var dbm;
var type;
var seed;

/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function (options, seedLink) {
dbm = options.dbmigrate;
type = dbm.dataType;
seed = seedLink;
};

const getAllUserIds = async db => {
const result = await db.runSql(`
SELECT user_account.id FROM user_account
LEFT JOIN meditrak_sync_queue ON meditrak_sync_queue.record_id = user_account.id
WHERE meditrak_sync_queue.id IS NULL
`);
return result.rows.map(row => row.id);
};

exports.up = async function (db) {
const userIds = await getAllUserIds(db);
await db.runSql(`
INSERT INTO meditrak_sync_queue (id, type, record_type, record_id, change_time)
VALUES ${userIds
.map(
(id, i) =>
// the timestamp is incremented by i to ensure that each record has a unique timestamp
`('${generateId()}', 'update', 'user_account', '${id}', ${getSyncQueueChangeTime(i)})`,
)
.join(',\n')};
`);
};

exports.down = function (db) {
return null;
};

exports._meta = {
version: 1,
};
4 changes: 2 additions & 2 deletions packages/meditrak-app/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ android {
applicationId "com.tupaiameditrak"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 142
versionName "1.14.142"
versionCode 143
versionName "1.14.143"
}
signingConfigs {
debug {
Expand Down
2 changes: 2 additions & 0 deletions packages/meditrak-app/app/assessment/Question.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
ArithmeticQuestion,
ConditionQuestion,
CodeGeneratorQuestion,
UserQuestion,
} from './specificQuestions';

const QUESTION_TYPES = {
Expand All @@ -54,6 +55,7 @@ const QUESTION_TYPES = {
Arithmetic: ArithmeticQuestion,
Condition: ConditionQuestion,
File: FileQuestion,
User: UserQuestion,
};

const TYPES_CONTROLLING_QUESTION_TEXT = ['Instruction', 'Checkbox'];
Expand Down
Loading