Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 27 additions & 11 deletions src/apps/wallet-admin/src/config/access.config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable import/no-extraneous-dependencies */
import {
canAccessDuplicatePaymentsReport,
canAccessPayments,
canAccessTrolleyLogs,
canAccessWalletAdmin,
WALLET_ADMIN_ALLOWED_ROLES,
WALLET_ADMIN_PAYMENT_ROLES,
} from './access.config'

describe('canAccessWalletAdmin', () => {
Expand All @@ -11,12 +13,14 @@ describe('canAccessWalletAdmin', () => {
.toBe(true)
})

it('allows a payment role regardless of casing', () => {
it('allows a role regardless of casing', () => {
expect(canAccessWalletAdmin(['payment admin']))
.toBe(true)
expect(canAccessWalletAdmin(['Administrator']))
.toBe(true)
})

it('denies members without a payment role', () => {
it('denies members without a payment or Admin role', () => {
expect(canAccessWalletAdmin(['Topcoder User']))
.toBe(false)
expect(canAccessWalletAdmin([]))
Expand All @@ -26,23 +30,35 @@ describe('canAccessWalletAdmin', () => {
})
})

describe('canAccessDuplicatePaymentsReport', () => {
it('allows Payment Admin regardless of casing', () => {
expect(canAccessDuplicatePaymentsReport(['Payment Admin']))
describe('canAccessPayments', () => {
it.each([...WALLET_ADMIN_PAYMENT_ROLES])('allows %s', role => {
expect(canAccessPayments([role]))
.toBe(true)
})

it('denies Admins without a payment role', () => {
expect(canAccessPayments(['administrator']))
.toBe(false)
})
})

describe('canAccessTrolleyLogs', () => {
it('allows Admins regardless of casing', () => {
expect(canAccessTrolleyLogs(['administrator']))
.toBe(true)
expect(canAccessDuplicatePaymentsReport([' payment admin ']))
expect(canAccessTrolleyLogs([' Administrator ']))
.toBe(true)
})

it.each(WALLET_ADMIN_ALLOWED_ROLES.filter(role => role !== 'Payment Admin'))('denies %s', role => {
expect(canAccessDuplicatePaymentsReport([role]))
it.each([...WALLET_ADMIN_PAYMENT_ROLES])('denies %s', role => {
expect(canAccessTrolleyLogs([role]))
.toBe(false)
})

it('denies members without roles', () => {
expect(canAccessDuplicatePaymentsReport([]))
expect(canAccessTrolleyLogs([]))
.toBe(false)
expect(canAccessDuplicatePaymentsReport(undefined))
expect(canAccessTrolleyLogs(undefined))
.toBe(false)
})
})
59 changes: 41 additions & 18 deletions src/apps/wallet-admin/src/config/access.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* Wallet Admin roles accepted by the finance admin search APIs.
* Wallet Admin payment roles accepted by the finance admin search APIs.
*
* Keep this list aligned with tc-finance-api admin winnings search roles so
* members without payment access never reach the filters UI.
* members without payment access never reach the payments UI.
*/
export const WALLET_ADMIN_ALLOWED_ROLES: ReadonlyArray<string> = [
export const WALLET_ADMIN_PAYMENT_ROLES: ReadonlyArray<string> = [
'Payment Admin',
'Payment BA Admin',
'Payment Approver',
Expand All @@ -14,34 +14,57 @@ export const WALLET_ADMIN_ALLOWED_ROLES: ReadonlyArray<string> = [
]

/**
* Returns whether the member has any Wallet Admin payment role.
* Role allowed to see the Trolley Logs report.
*
* @param roles Profile roles from Auth0.
* @returns True when at least one allowed payment role is present.
* The report lists releases for every member and is not filtered by billing
* account, so it is limited to platform Admins; Payment Admin and Payment BA
* Admin do not get it. Keep aligned with tc-finance-api
* `GET /admin/payment-releases/duplicates`.
*/
export function canAccessWalletAdmin(roles: string[] | undefined): boolean {
export const TROLLEY_LOGS_ROLE = 'administrator'

/**
* Roles that can open Wallet Admin: the payment roles, plus Admins for the
* Trolley Logs report.
*/
export const WALLET_ADMIN_ALLOWED_ROLES: ReadonlyArray<string> = [
...WALLET_ADMIN_PAYMENT_ROLES,
TROLLEY_LOGS_ROLE,
]

function hasAnyRole(roles: string[] | undefined, allowedRoles: ReadonlyArray<string>): boolean {
const normalizedRoles = new Set((roles || []).map(role => role.trim()
.toLowerCase()))

return WALLET_ADMIN_ALLOWED_ROLES.some(role => normalizedRoles.has(role.toLowerCase()))
return allowedRoles.some(role => normalizedRoles.has(role.toLowerCase()))
}

/**
* Role allowed to see the duplicate payments report.
* Returns whether the member can open Wallet Admin.
*
* The report lists releases for every member and is not filtered by billing
* account, so it is limited to the role that already sees all payments.
* Keep aligned with tc-finance-api `GET /admin/payment-releases/duplicates`.
* @param roles Profile roles from Auth0.
* @returns True when the member has a payment role or is an Admin.
*/
export function canAccessWalletAdmin(roles: string[] | undefined): boolean {
return hasAnyRole(roles, WALLET_ADMIN_ALLOWED_ROLES)
}

/**
* Returns whether the member can see the Payments tab.
*
* @param roles Profile roles from Auth0.
* @returns True when at least one payment role is present.
*/
export const DUPLICATE_PAYMENTS_REPORT_ROLE = 'Payment Admin'
export function canAccessPayments(roles: string[] | undefined): boolean {
return hasAnyRole(roles, WALLET_ADMIN_PAYMENT_ROLES)
}

/**
* Returns whether the member can see the duplicate payments report.
* Returns whether the member can see the Trolley Logs report.
*
* @param roles Profile roles from Auth0.
* @returns True when the member has the Payment Admin role.
* @returns True when the member is an Admin.
*/
export function canAccessDuplicatePaymentsReport(roles: string[] | undefined): boolean {
return (roles || []).some(role => role.trim()
.toLowerCase() === DUPLICATE_PAYMENTS_REPORT_ROLE.toLowerCase())
export function canAccessTrolleyLogs(roles: string[] | undefined): boolean {
return hasAnyRole(roles, [TROLLEY_LOGS_ROLE])
}
4 changes: 2 additions & 2 deletions src/apps/wallet-admin/src/home/tabs/WalletAdminTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { UserProfile } from '~/libs/core'
import { PageTitle, TabsNavbar, TabsNavItem } from '~/libs/ui'

import { getHashFromTabId, getTabIdFromHash, getWalletAdminTabs, WalletAdminTabViews } from './config'
import { DuplicatePaymentsTab } from './duplicate-payments'
import { PaymentsTab } from './payments'
import { TrolleyLogsTab } from './trolley-logs'
import { HomeTab } from './home'
import styles from './WalletAdminTabs.module.scss'

Expand Down Expand Up @@ -52,7 +52,7 @@ const WalletAdminTabs: FC<WalletHomeProps> = (props: WalletHomeProps) => {

{activeTab === WalletAdminTabViews.payments && <PaymentsTab profile={props.profile} />}

{activeTab === WalletAdminTabViews.duplicatePayments && <DuplicatePaymentsTab />}
{activeTab === WalletAdminTabViews.trolleyLogs && <TrolleyLogsTab />}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,47 @@ import {
WalletAdminTabViews,
} from './wallet-tabs-config'

function tabIds(roles: string[]): string[] {
return getWalletAdminTabs(roles)
.map(tab => tab.id)
}

describe('getWalletAdminTabs', () => {
it('adds the Duplicate Payments tab for Payment Admins', () => {
expect(getWalletAdminTabs(['Payment Admin'])
.map(tab => tab.id))
it('shows Trolley Logs, and not Payments, to Admins without a payment role', () => {
expect(tabIds(['administrator']))
.toEqual([WalletAdminTabViews.home, WalletAdminTabViews.trolleyLogs])
})

it('shows every tab to Admins who also have a payment role', () => {
expect(tabIds(['administrator', 'Payment Admin']))
.toEqual([
WalletAdminTabViews.home,
WalletAdminTabViews.payments,
WalletAdminTabViews.duplicatePayments,
WalletAdminTabViews.trolleyLogs,
])
})

it('hides the Duplicate Payments tab from other payment roles', () => {
expect(getWalletAdminTabs(['Payment Viewer', 'Payment BA Admin'])
.map(tab => tab.id))
it.each([
['Payment Admin'],
['Payment BA Admin'],
['Payment Viewer'],
])('hides Trolley Logs from %s', role => {
expect(tabIds([role]))
.toEqual([WalletAdminTabViews.home, WalletAdminTabViews.payments])
})

it('titles the tab Trolley Logs', () => {
expect(getWalletAdminTabs(['administrator'])
.find(tab => tab.id === WalletAdminTabViews.trolleyLogs)?.title)
.toBe('Trolley Logs')
})
})

describe('duplicate payments tab hash', () => {
it('maps the tab to #duplicate-payments and back', () => {
expect(getHashFromTabId(WalletAdminTabViews.duplicatePayments))
.toBe('#duplicate-payments')
expect(getTabIdFromHash('#duplicate-payments'))
.toBe(WalletAdminTabViews.duplicatePayments)
describe('trolley logs tab hash', () => {
it('maps the tab to #trolley-logs and back', () => {
expect(getHashFromTabId(WalletAdminTabViews.trolleyLogs))
.toBe('#trolley-logs')
expect(getTabIdFromHash('#trolley-logs'))
.toBe(WalletAdminTabViews.trolleyLogs)
})
})
49 changes: 25 additions & 24 deletions src/apps/wallet-admin/src/home/tabs/config/wallet-tabs-config.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
import { TabsNavItem } from '~/libs/ui'

import { canAccessDuplicatePaymentsReport } from '../../../config/access.config'
import { canAccessPayments, canAccessTrolleyLogs } from '../../../config/access.config'

export enum WalletAdminTabViews {
home = '0',
payments = '1',
duplicatePayments = '2',
trolleyLogs = '2',
}

export const WalletAdminTabsConfig: TabsNavItem[] = [
{
id: WalletAdminTabViews.home,
title: 'Dashboard',
},
{
id: WalletAdminTabViews.payments,
title: 'Payments',
},
]

const DuplicatePaymentsTabConfig: TabsNavItem = {
id: WalletAdminTabViews.duplicatePayments,
title: 'Duplicate Payments',
const DashboardTabConfig: TabsNavItem = {
id: WalletAdminTabViews.home,
title: 'Dashboard',
}

const PaymentsTabConfig: TabsNavItem = {
id: WalletAdminTabViews.payments,
title: 'Payments',
}

const TrolleyLogsTabConfig: TabsNavItem = {
id: WalletAdminTabViews.trolleyLogs,
title: 'Trolley Logs',
}

/**
* Returns the tabs the member can see.
*
* @param roles Profile roles from Auth0.
* @returns The base tabs, plus Duplicate Payments for Payment Admins.
* @returns Dashboard, plus Payments for payment roles and Trolley Logs for Admins.
*/
export function getWalletAdminTabs(roles: string[] | undefined): TabsNavItem[] {
return canAccessDuplicatePaymentsReport(roles)
? [...WalletAdminTabsConfig, DuplicatePaymentsTabConfig]
: WalletAdminTabsConfig
return [
DashboardTabConfig,
...(canAccessPayments(roles) ? [PaymentsTabConfig] : []),
...(canAccessTrolleyLogs(roles) ? [TrolleyLogsTabConfig] : []),
]
}

export function getHashFromTabId(tabId: string): string {
Expand All @@ -42,8 +43,8 @@ export function getHashFromTabId(tabId: string): string {
return '#home'
case WalletAdminTabViews.payments:
return '#payments'
case WalletAdminTabViews.duplicatePayments:
return '#duplicate-payments'
case WalletAdminTabViews.trolleyLogs:
return '#trolley-logs'
default:
return '#home'
}
Expand All @@ -53,8 +54,8 @@ export function getTabIdFromHash(hash: string): string {
switch (hash) {
case '#payments':
return WalletAdminTabViews.payments
case '#duplicate-payments':
return WalletAdminTabViews.duplicatePayments
case '#trolley-logs':
return WalletAdminTabViews.trolleyLogs
default:
return WalletAdminTabViews.home
}
Expand Down
Loading
Loading