From c789c4c835874dd53c8d73113b54d492c471d29b Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Fri, 16 Oct 2026 14:04:23 +0530 Subject: [PATCH 1/3] OpenConceptLab/ocl_online#517 | treat pro as community --- src/common/authGroups.js | 2 ++ src/common/utils.js | 23 +------------------ .../__tests__/aiVisibility.test.js | 2 ++ .../__tests__/requestLimits.test.js | 9 ++++++++ src/components/map-projects/aiVisibility.js | 9 ++++---- src/components/map-projects/requestLimits.js | 9 +++++--- 6 files changed, 25 insertions(+), 29 deletions(-) create mode 100644 src/common/authGroups.js diff --git a/src/common/authGroups.js b/src/common/authGroups.js new file mode 100644 index 00000000..fa42892d --- /dev/null +++ b/src/common/authGroups.js @@ -0,0 +1,2 @@ +// Exact group-name checks, kept free of UI imports so the Node unit tests can load them. +export const hasAuthGroup = (user, groupName) => Boolean(user?.auth_groups?.includes(groupName)); diff --git a/src/common/utils.js b/src/common/utils.js index 1a2ec48b..bb6a44b9 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -10,7 +10,7 @@ import { } from 'lodash'; import { DATE_FORMAT, TIME_FORMAT, DATETIME_FORMAT, OCL_SERVERS_GROUP, OCL_FHIR_SERVERS_GROUP, HAPI_FHIR_SERVERS_GROUP, - OPENMRS_URL, DEFAULT_FHIR_SERVER_FOR_LOCAL_ID, OPERATIONS_PANEL_GROUP + OPENMRS_URL, DEFAULT_FHIR_SERVER_FOR_LOCAL_ID } from './constants'; import APIService from '../services/APIService'; import GAService from '../services/GAService'; @@ -453,27 +453,6 @@ export const getFHIRServerConfigFromCurrentContext = () => { return getLocalFHIRServerConfig() || getDefaultFHIRServerConfig(); } -export const canSwitchServer = () => { - const user = getCurrentUser(); - - return Boolean( - getSelectedServerConfig() || - get(user, 'is_superuser') || - hasAuthGroup(user, 'server') - ); -} - -export const hasAuthGroup = (user, groupName) => Boolean(find(user?.auth_groups, group => group.includes(groupName))) - -export const canViewOperationsPanel = () => { - const user = getCurrentUser() - - return Boolean( - get(user, 'is_staff') || - hasAuthGroup(user, OPERATIONS_PANEL_GROUP) - ) -} - export const isFHIRServer = () => get(getAppliedServerConfig(), 'type') === 'fhir'; export const isConcept = uri => Boolean(uri.match('/concepts/')); diff --git a/src/components/map-projects/__tests__/aiVisibility.test.js b/src/components/map-projects/__tests__/aiVisibility.test.js index db30a115..d1cc4933 100644 --- a/src/components/map-projects/__tests__/aiVisibility.test.js +++ b/src/components/map-projects/__tests__/aiVisibility.test.js @@ -30,6 +30,8 @@ test('getAIAssistantChoices: non-staff core, early-access and unlimited-AI users const localeOnly = { canSelectAIModel: false, canSetAIOutputLocale: true } assert.deepEqual(getAIAssistantChoices({ is_staff: false, auth_groups: ['core_user'] }), localeOnly) assert.deepEqual(getAIAssistantChoices({ auth_groups: ['early_access'] }), localeOnly) + assert.deepEqual(getAIAssistantChoices({ auth_groups: ['pro'] }), localeOnly) + assert.deepEqual(getAIAssistantChoices({ auth_groups: ['mapper-approved'] }), { canSelectAIModel: false, canSetAIOutputLocale: false }) assert.deepEqual(getAIAssistantChoices({ auth_groups: ['preview'] }, { unlimitedAICalls: true }), localeOnly) }) diff --git a/src/components/map-projects/__tests__/requestLimits.test.js b/src/components/map-projects/__tests__/requestLimits.test.js index 795ee035..6bbd5635 100644 --- a/src/components/map-projects/__tests__/requestLimits.test.js +++ b/src/components/map-projects/__tests__/requestLimits.test.js @@ -43,6 +43,15 @@ test('getRequestLimits: early access, 10 rows × 5 requests', () => { assert.deepEqual(EARLY_ACCESS, { batchSize: 10, concurrentRequests: 5, capped: true }) }) +test('getRequestLimits: pro gets early access limits (ocl_online#517)', () => { + assert.deepEqual(getRequestLimits({ auth_groups: ['pro'] }), EARLY_ACCESS) + assert.equal(hasFullRequestLimits({ auth_groups: ['pro'] }), false) +}) + +test('getRequestLimits: group names match exactly, so mapper-approved is not pro', () => { + assert.deepEqual(getRequestLimits({ auth_groups: ['mapper-approved'] }), PREVIEW) +}) + test('getRequestLimits: preview, 10 rows × 2 requests', () => { assert.deepEqual(PREVIEW, { batchSize: 10, concurrentRequests: 2, capped: true }) }) diff --git a/src/components/map-projects/aiVisibility.js b/src/components/map-projects/aiVisibility.js index 8d082cec..3843d12f 100644 --- a/src/components/map-projects/aiVisibility.js +++ b/src/components/map-projects/aiVisibility.js @@ -1,20 +1,21 @@ +import { hasAuthGroup } from '../../common/authGroups.js' +import { isEarlyAccessUser } from './requestLimits.js' + // An AI recommendation's internals are staff-only (ocl_online#254): the model, // the prompt template and its URI, and the raw JSON that carries them. Core, // early-access and preview users see the assessment and the candidates only. export const canSeeAIInternals = user => Boolean(user?.is_staff || user?.is_superuser) -const inAuthGroup = (user, group) => Boolean(user?.auth_groups?.some(name => name.includes(group))) - // What a user can choose for the OCL AI Assistant (ocl_online#259): // - the model and the prompt template: staff only, like seeing them. The AI // Assistant honours a model override from staff only, so everyone else runs // the project's template on that template's default model. -// - the output language: staff, core, early-access and unlimited-AI users. +// - the output language: staff, core, early-access (and pro, ocl_online#517) and unlimited-AI users. export const getAIAssistantChoices = (user, { unlimitedAICalls = false } = {}) => ({ canSelectAIModel: canSeeAIInternals(user), canSetAIOutputLocale: Boolean( canSeeAIInternals(user) || unlimitedAICalls || - inAuthGroup(user, 'core_user') || inAuthGroup(user, 'early_access') + hasAuthGroup(user, 'core_user') || isEarlyAccessUser(user) ), }) diff --git a/src/components/map-projects/requestLimits.js b/src/components/map-projects/requestLimits.js index c3ede851..d9d077fc 100644 --- a/src/components/map-projects/requestLimits.js +++ b/src/components/map-projects/requestLimits.js @@ -1,3 +1,5 @@ +import { hasAuthGroup } from '../../common/authGroups.js' + /** * How many rows go in one matching request, and how many requests an * algorithm keeps in flight, during Auto Match (ocl_online#274). @@ -19,9 +21,10 @@ const FULL_LIMITS = { batchSize: 1000, concurrentRequests: 50, capped: false } const EARLY_ACCESS_LIMITS = { batchSize: 10, concurrentRequests: 5, capped: true } const PREVIEW_LIMITS = { batchSize: 10, concurrentRequests: 2, capped: true } -const inAuthGroup = (user, group) => Boolean(user?.auth_groups?.some(name => name.includes(group))) +// Pro gets early access's limits and choices (ocl_online#517). +export const isEarlyAccessUser = user => hasAuthGroup(user, 'early_access') || hasAuthGroup(user, 'pro') -export const hasFullRequestLimits = user => Boolean(user?.is_staff || user?.is_superuser || inAuthGroup(user, 'core_user')) +export const hasFullRequestLimits = user => Boolean(user?.is_staff || user?.is_superuser || hasAuthGroup(user, 'core_user')) // The most rows per request, and requests in flight, a user may type in the // form, by their highest tier. capped: false for core users and staff, whose @@ -29,7 +32,7 @@ export const hasFullRequestLimits = user => Boolean(user?.is_staff || user?.is_s export const getRequestLimits = user => { if(hasFullRequestLimits(user)) return FULL_LIMITS - if(inAuthGroup(user, 'early_access')) + if(isEarlyAccessUser(user)) return EARLY_ACCESS_LIMITS return PREVIEW_LIMITS } From 04c8ae8f0afb9054f176d45cb4eb91ef930b351d Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Fri, 16 Oct 2026 14:11:28 +0530 Subject: [PATCH 2/3] OpenConceptLab/ocl_online#517 | import hasAuthGroup from authGroups in MapProject hasAuthGroup moved out of common/utils, so the map-project screen had an undefined binding. Co-Authored-By: Claude Haiku 5.5 --- src/components/map-projects/MapProject.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/map-projects/MapProject.jsx b/src/components/map-projects/MapProject.jsx index fcaaebc7..540d19c8 100644 --- a/src/components/map-projects/MapProject.jsx +++ b/src/components/map-projects/MapProject.jsx @@ -73,7 +73,8 @@ import { OperationsContext } from '../app/LayoutContext'; import APIService, { isTransientNetworkError, retryWithBackoff } from '../../services/APIService'; import { buildAttributionHeaders, buildConfigSnapshot, summarizeRunCompletion, matchesOnOCL } from '../../services/attribution' import { CAPACITY_LIMIT, RATE_LIMIT, CAPACITY_WAIT_CAP_MS, HEAVY_REQUEST_TIMEOUT_MS, LIGHT_REQUEST_TIMEOUT_MS, createCapacityGate, createLimiter, isRetryableError, requestWithCapacityRetry, sleepUnlessCancelled, untilCancelled } from '../../services/capacity' -import { highlightTexts, dropVersion, getCurrentUser, hasAuthGroup, hasCapability, getMapperPreview, getNewProjectBlockReason, downloadObject, currentUserToken, refreshCurrentUserCapabilitiesCache } from '../../common/utils'; +import { hasAuthGroup } from '../../common/authGroups'; +import { highlightTexts, dropVersion, getCurrentUser, hasCapability, getMapperPreview, getNewProjectBlockReason, downloadObject, currentUserToken, refreshCurrentUserCapabilitiesCache } from '../../common/utils'; import { WHITE, SURFACE_COLORS, TEXT_GRAY } from '../../common/colors'; import { useDoubleClick } from '../common/useDoubleClick' From 46cc57533acf85b28d5e47d351a9963e18be998f Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Fri, 16 Oct 2026 14:16:17 +0530 Subject: [PATCH 3/3] OpenConceptLab/ocl_online#517 | re-export hasAuthGroup from utils Co-Authored-By: Claude Haiku 5.5 --- src/common/utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/utils.js b/src/common/utils.js index bb6a44b9..02cad213 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -15,6 +15,7 @@ import { import APIService from '../services/APIService'; import GAService from '../services/GAService'; import { ensureIdToken } from './ssoSession'; +export { hasAuthGroup } from './authGroups'; import { SERVER_CONFIGS } from './serverConfigs'; import { legacyHashRoute, isSameSitePath } from './history';