-
Notifications
You must be signed in to change notification settings - Fork 8
Users grouping improvements #4331
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
Merged
Merged
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1a996cc
improved user group overview update
SteRiccio daee416
user grouping: improved filtering
SteRiccio 9eb87a6
filter exported data by user group
SteRiccio 0db5dd8
code cleanup
SteRiccio de9f8ae
updated version number
SteRiccio d210a77
code cleanup
SteRiccio 9b65bed
track whether the remove step actually completed
SteRiccio 288adf3
qualifier filtering: fixed pending node null value
SteRiccio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 27 additions & 73 deletions
100
server/modules/record/manager/_recordManager/recordQualifierMatcher.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,96 +1,50 @@ | ||
| import { Objects, ServiceRegistry } from '@openforis/arena-core' | ||
| import { ServerServiceType } from '@openforis/arena-server' | ||
| import { Objects } from '@openforis/arena-core' | ||
|
|
||
| import * as User from '@core/user/user' | ||
| import * as UserGroup from '@core/user/userGroup/userGroup' | ||
| import * as UserGroupQualifier from '@core/user/userGroup/userGroupQualifier' | ||
| import * as Survey from '@core/survey/survey' | ||
| import * as NodeDef from '@core/survey/nodeDef' | ||
| import * as CategoryItem from '@core/survey/categoryItem' | ||
| import * as Record from '@core/record/record' | ||
| import * as Node from '@core/record/node' | ||
| import { NodeValues } from '@core/record/nodeValues' | ||
|
|
||
| import { db } from '@server/db/db' | ||
| import { CategoryItemProviderDefault } from '@server/modules/category/manager/categoryItemProviderDefault' | ||
|
|
||
| /** | ||
| * Determines the qualifier attribute/value pairs that the given user's group restricts them to, if any. | ||
| * Mirrors the lookup used to auto-fill qualifier attributes when a new record is created | ||
| * (see _applyGroupQualifierValues in recordUpdateManager.js), so write-time and read-time | ||
| * restrictions stay consistent: only the user's first UserGroup in the survey is considered. | ||
| * For code attributes, the qualifier's code is resolved to its category item via a direct DB lookup | ||
| * (works regardless of category size, unlike the survey's category items ref data index, which | ||
| * excludes "big" categories), so the returned value can be compared against a record's node value by | ||
| * item uuid rather than relying on the ref data index being loaded. | ||
| * @param {object} params - The function parameters. | ||
| * @param {object} params.user - The current user. | ||
| * @param {object} params.survey - The survey, with node defs loaded. | ||
| * @param {pgPromise.IDatabase} [client] - The db client. | ||
| * @returns {Promise<Array<{nodeDef: object, value: *}>>} - An empty array when the user is | ||
| * unrestricted (no group, or group with no matching qualifiers), otherwise the list of qualifier | ||
| * filters to apply. | ||
| */ | ||
| export const fetchUserQualifierFilters = async ({ user, survey }, client = db) => { | ||
| const qualifierNodeDefs = Survey.getQualifierNodeDefs(survey) | ||
| if (qualifierNodeDefs.length === 0) return [] | ||
|
|
||
| const userGroupService = ServiceRegistry.getInstance().getService(ServerServiceType.userGroup) | ||
| const userGroups = await userGroupService.getManyByUser( | ||
| { userUuid: User.getUuid(user), surveyUuid: Survey.getUuid(survey) }, | ||
| client | ||
| ) | ||
| const userGroup = userGroups[0] | ||
| if (!userGroup) return [] | ||
|
|
||
| const qualifiers = UserGroup.getQualifiers(userGroup) | ||
| if (qualifiers.length === 0) return [] | ||
|
|
||
| const filters = [] | ||
| for (const nodeDef of qualifierNodeDefs) { | ||
| const qualifier = qualifiers.find((q) => UserGroupQualifier.getName(q) === NodeDef.getName(nodeDef)) | ||
| const qualifierValue = qualifier ? UserGroupQualifier.getValue(qualifier) : null | ||
| if (Objects.isEmpty(qualifierValue)) continue | ||
|
|
||
| if (NodeDef.isCode(nodeDef)) { | ||
| const categoryUuid = NodeDef.getCategoryUuid(nodeDef) | ||
| const item = await CategoryItemProviderDefault.getItemByCode( | ||
| { survey, categoryUuid, code: qualifierValue }, | ||
| client | ||
| ) | ||
| if (!item) continue | ||
| filters.push({ | ||
| nodeDef, | ||
| value: Node.newNodeValueCode({ itemUuid: CategoryItem.getUuid(item), code: qualifierValue }), | ||
| }) | ||
| } else { | ||
| filters.push({ nodeDef, value: qualifierValue }) | ||
| } | ||
| } | ||
| return filters | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether a record's qualifier attribute values match the given qualifier filters. | ||
| * @param {object} params - The function parameters. | ||
| * @param {object} params.survey - The survey, with node defs and categories loaded. | ||
| * @param {object} params.record - The record (with nodes loaded) to check. | ||
| * @param {Array<{nodeDef: object, value: string}>} params.qualifierFilters - The qualifier filters, | ||
| * as returned by fetchUserQualifierFilters. | ||
| * as returned by SurveyManager.fetchUserQualifierFilters. | ||
| * @param {object} [params.pendingNode] - A node carried by the current request but not yet persisted | ||
| * (e.g. a create/update of some attribute), applied on top of the record's own nodes when checking, so | ||
| * an edit that sets a qualifier attribute to a value outside the user's group qualifiers is rejected | ||
| * too. Deliberately not merged into `record` itself via `Record.assocNode`: doing so on a record whose | ||
| * `_nodesIndex` hasn't been built (see `fetchForUpdate` in `fetchRecordAndNodesByUuid`) makes | ||
| * `assocNode` initialize a new index containing only this one node, which then shadows the full node | ||
| * list for every other lookup (`Record.getNodeChildrenByDefUuid` and friends trust a present index | ||
| * instead of falling back to a full scan), making every other already-persisted node - including the | ||
| * qualifier attribute - invisible. | ||
| * @returns {boolean} - True if qualifierFilters is empty (unrestricted), if the record has not been | ||
| * initialized yet (no nodes: checkin will auto-fill qualifier attributes to match, see | ||
| * _applyGroupQualifierValues in recordUpdateManager.js), or every filter matches the record's | ||
| * corresponding attribute value; false otherwise. | ||
| * initialized yet (no nodes), if a qualifier attribute node is missing or not yet given a value | ||
| * (record creation will auto-fill qualifier attributes to match, see _applyGroupQualifierValues in | ||
| * recordUpdateManager.js, but that happens as one of the last steps of record creation, so a | ||
| * just-created record may briefly have the node without its value yet), or every filter matches the | ||
| * record's corresponding attribute value; false only when a qualifier attribute has an actual value | ||
| * that differs from the expected one. | ||
| */ | ||
| export const recordMatchesQualifierFilters = ({ survey, record, qualifierFilters }) => { | ||
| export const recordMatchesQualifierFilters = ({ survey, record, qualifierFilters, pendingNode = null }) => { | ||
| if (qualifierFilters.length === 0) return true | ||
| if (Record.getNodesArray(record).length === 0) return true | ||
|
|
||
| const rootNode = Record.getRootNode(record) | ||
|
|
||
| return qualifierFilters.every(({ nodeDef, value }) => { | ||
| const node = Record.getNodeChildrenByDefUuid(rootNode, NodeDef.getUuid(nodeDef))(record)[0] | ||
| if (!node) return false | ||
| const nodeDefUuid = NodeDef.getUuid(nodeDef) | ||
| const node = | ||
| pendingNode && Node.getNodeDefUuid(pendingNode) === nodeDefUuid | ||
| ? pendingNode | ||
| : Record.getNodeChildByDefUuid(rootNode, nodeDefUuid)(record) | ||
| // node missing or not yet given a value: record is still being initialized (the qualifier auto-fill | ||
| // is one of the last steps of record creation), so this is not (yet) evidence of a cross-group | ||
| // mismatch; only a value that has actually been set and differs should be rejected | ||
| if (!node || Objects.isEmpty(Node.getValue(node))) return true | ||
| return NodeValues.isValueEqual({ survey, nodeDef, value: Node.getValue(node), valueSearch: value }) | ||
| }) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
server/modules/survey/manager/surveyUserGroupQualifierFilters.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { Objects, ServiceRegistry } from '@openforis/arena-core' | ||
| import { ServerServiceType } from '@openforis/arena-server' | ||
|
|
||
| import * as User from '@core/user/user' | ||
| import * as UserGroup from '@core/user/userGroup/userGroup' | ||
| import * as UserGroupQualifier from '@core/user/userGroup/userGroupQualifier' | ||
| import * as Survey from '@core/survey/survey' | ||
| import * as NodeDef from '@core/survey/nodeDef' | ||
| import * as CategoryItem from '@core/survey/categoryItem' | ||
| import * as Node from '@core/record/node' | ||
|
|
||
| import { db } from '@server/db/db' | ||
| import { CategoryItemProviderDefault } from '@server/modules/category/manager/categoryItemProviderDefault' | ||
|
|
||
| /** | ||
| * Determines the qualifier attribute/value pairs that the given user's group restricts them to, if any. | ||
| * Mirrors the lookup used to auto-fill qualifier attributes when a new record is created | ||
| * (see _applyGroupQualifierValues in recordUpdateManager.js), so write-time and read-time | ||
| * restrictions stay consistent: only the user's first UserGroup in the survey is considered. | ||
| * For code attributes, the qualifier's code is resolved to its category item via a direct DB lookup | ||
| * (works regardless of category size, unlike the survey's category items ref data index, which | ||
| * excludes "big" categories), so the returned value can be compared against a record's node value by | ||
| * item uuid rather than relying on the ref data index being loaded. | ||
| * @param {object} params - The function parameters. | ||
| * @param {object} params.user - The current user. | ||
| * @param {object} params.survey - The survey, with node defs loaded. | ||
| * @param {pgPromise.IDatabase} [client] - The db client. | ||
| * @returns {Promise<Array<{nodeDef: object, value: *}>>} - An empty array when the user is | ||
| * unrestricted (no group, or group with no matching qualifiers), otherwise the list of qualifier | ||
| * filters to apply. | ||
| */ | ||
| export const fetchUserQualifierFilters = async ({ user, survey }, client = db) => { | ||
| const qualifierNodeDefs = Survey.getQualifierNodeDefs(survey) | ||
| if (qualifierNodeDefs.length === 0) return [] | ||
|
|
||
| const userGroupService = ServiceRegistry.getInstance().getService(ServerServiceType.userGroup) | ||
| const userGroups = await userGroupService.getManyByUser( | ||
| { userUuid: User.getUuid(user), surveyUuid: Survey.getUuid(survey) }, | ||
| client | ||
| ) | ||
| const userGroup = userGroups[0] | ||
| if (!userGroup) return [] | ||
|
|
||
| const qualifiers = UserGroup.getQualifiers(userGroup) | ||
| if (qualifiers.length === 0) return [] | ||
|
|
||
| const filters = [] | ||
| for (const nodeDef of qualifierNodeDefs) { | ||
| const qualifier = qualifiers.find((q) => UserGroupQualifier.getName(q) === NodeDef.getName(nodeDef)) | ||
| const qualifierValue = qualifier ? UserGroupQualifier.getValue(qualifier) : null | ||
| if (Objects.isEmpty(qualifierValue)) continue | ||
|
|
||
| if (NodeDef.isCode(nodeDef)) { | ||
| const categoryUuid = NodeDef.getCategoryUuid(nodeDef) | ||
| const item = await CategoryItemProviderDefault.getItemByCode( | ||
| { survey, categoryUuid, code: qualifierValue }, | ||
| client | ||
| ) | ||
| if (!item) continue | ||
| filters.push({ | ||
| nodeDef, | ||
| value: Node.newNodeValueCode({ itemUuid: CategoryItem.getUuid(item), code: qualifierValue }), | ||
| }) | ||
| } else { | ||
| filters.push({ nodeDef, value: qualifierValue }) | ||
| } | ||
| } | ||
| return filters | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.