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
1 change: 0 additions & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,6 @@ const CONST = {
EUR_BILLING: 'eurBilling',
PAY_INVOICE_VIA_EXPENSIFY: 'payInvoiceViaExpensify',
SUGGESTED_FOLLOWUPS: 'suggestedFollowups',
GUSTO: 'gustoNewDot',
ZENEFITS: 'zenefitsNewDot',
BULK_EDIT: 'bulkEdit',
BULK_EDIT_WORKSPACES: 'bulkEditWorkspaces',
Expand Down
5 changes: 1 addition & 4 deletions src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
[CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED]: policy?.tax?.trackingEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED]: policy?.areCompanyCardsEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED]: !!policy?.areConnectionsEnabled || hasAccountingFeatureConnection(policy),
[CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED]:
(isBetaEnabled(CONST.BETAS.GUSTO) || isBetaEnabled(CONST.BETAS.ZENEFITS) || isBetaEnabled(CONST.BETAS.MERGE_HR)) &&
(policy?.isHREnabled === true || isAnyHRConnected(policy)) &&
canPolicyAccessFeature(policy, CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED),
[CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED]: (policy?.isHREnabled === true || isAnyHRConnected(policy)) && canPolicyAccessFeature(policy, CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED),
[CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED]: policy?.areExpensifyCardsEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_REPORT_FIELDS_ENABLED]: policy?.areReportFieldsEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED]: policy?.areRulesEnabled,
Expand Down
58 changes: 27 additions & 31 deletions src/pages/workspace/WorkspaceMoreFeaturesPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import usePolicyData from '@hooks/usePolicyData';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -77,7 +76,6 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {translate} = useLocalize();
const {isBetaEnabled} = usePermissions();
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const {showConfirmModal} = useConfirmModal();
const illustrations = useMemoizedLazyIllustrations([
Expand Down Expand Up @@ -326,35 +324,33 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
Navigation.navigate(ROUTES.WORKSPACE_RECEIPT_PARTNERS.getRoute(policyID));
}}
/>
{(isBetaEnabled(CONST.BETAS.GUSTO) || isBetaEnabled(CONST.BETAS.ZENEFITS) || isBetaEnabled(CONST.BETAS.MERGE_HR)) && (
<MoreFeatureToggle
icon={illustrations.Members}
title={translate('workspace.hr.title')}
subtitle={translate('workspace.hr.subtitle')}
isActive={((policy?.isHREnabled === true || isAnyHRConnected(policy)) && canPolicyAccessFeature(policy, CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED)) ?? false}
pendingAction={policy?.pendingFields?.isHREnabled}
disabled={isAnyHRConnected(policy)}
disabledAction={warnDisconnectHRFirst}
onToggle={(isEnabled) => {
if (!policyID) {
return;
}
if (isEnabled && !isControlPolicy(policy)) {
Navigation.navigate(
ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.hr.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)),
);
return;
}
enablePolicyHR(policyID, isEnabled);
}}
onPress={() => {
if (!policyID) {
return;
}
Navigation.navigate(ROUTES.WORKSPACE_HR.getRoute(policyID));
}}
/>
)}
<MoreFeatureToggle
icon={illustrations.Members}
title={translate('workspace.hr.title')}
subtitle={translate('workspace.hr.subtitle')}
isActive={((policy?.isHREnabled === true || isAnyHRConnected(policy)) && canPolicyAccessFeature(policy, CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED)) ?? false}
pendingAction={policy?.pendingFields?.isHREnabled}
disabled={isAnyHRConnected(policy)}
disabledAction={warnDisconnectHRFirst}
onToggle={(isEnabled) => {
if (!policyID) {
return;
}
if (isEnabled && !isControlPolicy(policy)) {
Navigation.navigate(
ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.hr.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)),
);
return;
}
enablePolicyHR(policyID, isEnabled);
}}
onPress={() => {
if (!policyID) {
return;
}
Navigation.navigate(ROUTES.WORKSPACE_HR.getRoute(policyID));
}}
/>
</MoreFeaturesSection>

<MoreFeaturesSection title={translate('workspace.moreFeatures.organizeSection.title')}>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/workspace/hr/HRApprovalModePageBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type ApprovalModeValue = ValueOf<typeof CONST.GUSTO.APPROVAL_MODE> | ValueOf<typ

type HRApprovalModeProviderConfig<T extends ApprovalModeValue = ApprovalModeValue> = {
testID: string;
beta: Beta;
beta?: Beta;
isConnected: (policy: OnyxEntry<Policy>) => boolean;
approvalModes: {BASIC: T; MANAGER: T; CUSTOM: T};
getCurrentApprovalMode: (policy: OnyxEntry<Policy>) => T | null;
Expand Down Expand Up @@ -119,7 +119,7 @@ function HRApprovalModePageBase<T extends ApprovalModeValue>({policyID, config}:
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.CONTROL]}
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED}
shouldBeBlocked={!isBetaEnabled(config.beta) || (!!policy && !config.isConnected(policy))}
shouldBeBlocked={(!!config.beta && !isBetaEnabled(config.beta)) || (!!policy && !config.isConnected(policy))}
>
<ScreenWrapper
enableEdgeToEdgeBottomSafeAreaPadding
Expand Down
4 changes: 2 additions & 2 deletions src/pages/workspace/hr/HRFinalApproverPageBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type Policy from '@src/types/onyx/Policy';

type HRFinalApproverProviderConfig = {
testID: string;
beta: Beta;
beta?: Beta;
isConnected: (policy: OnyxEntry<Policy>) => boolean;
getCurrentFinalApprover: (policy: OnyxEntry<Policy>) => string | null;
getHeaderTitle: (providerName: string) => string;
Expand All @@ -44,7 +44,7 @@ function HRFinalApproverPageBase({policyID, config}: HRFinalApproverPageBaseProp
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.CONTROL]}
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED}
shouldBeBlocked={!isBetaEnabled(config.beta) || (!!policy && !config.isConnected(policy))}
shouldBeBlocked={(!!config.beta && !isBetaEnabled(config.beta)) || (!!policy && !config.isConnected(policy))}
>
<ScreenWrapper
enableEdgeToEdgeBottomSafeAreaPadding
Expand Down
5 changes: 0 additions & 5 deletions src/pages/workspace/hr/WorkspaceHRPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ import HRProviderCard from './HRProviderCard';
import type {HRCardDescriptor} from './utils';
import {getHRCards} from './utils';

const HR_BETAS = [CONST.BETAS.GUSTO, CONST.BETAS.ZENEFITS, CONST.BETAS.MERGE_HR] as const;

type WorkspaceHRPageProps = PlatformStackScreenProps<WorkspaceSplitNavigatorParamList, typeof SCREENS.WORKSPACE.HR>;

function WorkspaceHRPage({
Expand Down Expand Up @@ -86,8 +84,6 @@ function WorkspaceHRPage({
connectedCards.sort(byName);
disconnectedCards.sort(byName);

const shouldBeBlocked = !HR_BETAS.some(isBetaEnabled);

const handleConnect = (setupLink: string | undefined) => {
if (!setupLink) {
return;
Expand All @@ -113,7 +109,6 @@ function WorkspaceHRPage({
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.CONTROL]}
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED}
shouldBeBlocked={shouldBeBlocked}
>
<ScreenWrapper
enableEdgeToEdgeBottomSafeAreaPadding
Expand Down
1 change: 0 additions & 1 deletion src/pages/workspace/hr/gusto/GustoApprovalModePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ function GustoApprovalModePage({

const config: HRApprovalModeProviderConfig<ValueOf<typeof CONST.GUSTO.APPROVAL_MODE>> = {
testID: 'GustoApprovalModePage',
beta: CONST.BETAS.GUSTO,
isConnected: isGustoConnected,
approvalModes: CONST.GUSTO.APPROVAL_MODE,
getCurrentApprovalMode: (policy) => policy?.connections?.gusto?.config?.approvalMode ?? null,
Expand Down
2 changes: 0 additions & 2 deletions src/pages/workspace/hr/gusto/GustoFinalApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {isGustoConnected} from '@libs/PolicyUtils';
import HRFinalApproverPageBase from '@pages/workspace/hr/HRFinalApproverPageBase';
import type {HRFinalApproverProviderConfig} from '@pages/workspace/hr/HRFinalApproverPageBase';
import CONST from '@src/CONST';
import type SCREENS from '@src/SCREENS';

type GustoFinalApproverPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.HR_GUSTO_FINAL_APPROVER>;
Expand All @@ -20,7 +19,6 @@ function GustoFinalApproverPage({

const config: HRFinalApproverProviderConfig = {
testID: 'GustoFinalApproverPage',
beta: CONST.BETAS.GUSTO,
isConnected: isGustoConnected,
getCurrentFinalApprover: (policy) => policy?.connections?.gusto?.config?.finalApprover ?? null,
getProviderName: () => translate('workspace.hr.gusto.title'),
Expand Down
8 changes: 4 additions & 4 deletions src/pages/workspace/hr/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ function getCardConfig(policy: OnyxEntry<Policy>, connectionName: HRConnectionNa
const STATIC_HR_PROVIDERS = [
{
key: 'gusto',
beta: CONST.BETAS.GUSTO,
beta: undefined,
connectionName: CONST.POLICY.CONNECTIONS.NAME.GUSTO,
titleKey: 'workspace.hr.gusto.title',
iconParam: 'gustoIcon',
Expand Down Expand Up @@ -238,11 +238,11 @@ function getHRCards({policy, connectionSyncProgress, isBetaEnabled, getLocalDate
const cards: HRCardDescriptor[] = [];

for (const provider of STATIC_HR_PROVIDERS) {
if (!isBetaEnabled(provider.beta)) {
continue;
}
const {connectionName} = provider;
const state = getHRCardState({policy, connectionName, connectionSyncProgress, getLocalDateFromDatetime});
if (provider.beta && !isBetaEnabled(provider.beta) && !state.isConnected) {
continue;
}
const config = getCardConfig(policy, connectionName);
cards.push({
key: provider.key,
Expand Down
49 changes: 35 additions & 14 deletions tests/unit/HrUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,15 @@ describe('getApprovalModeLabel', () => {
});

describe('getHRCards', () => {
it('returns no cards when no betas are enabled', () => {
it('returns only the Gusto card when no betas are enabled', () => {
const cards = getHRCards(makeGetHRCardsParams({isBetaEnabled: noBetasEnabled}));
expect(cards).toHaveLength(0);
expect(cards).toHaveLength(1);
expect(cards?.at(0)?.key).toBe('gusto');
expect(cards?.at(0)?.connectionName).toBe(GUSTO);
});

it('returns Gusto and Zenefits cards when their betas are enabled', () => {
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.GUSTO || beta === CONST.BETAS.ZENEFITS;
it('returns Gusto and Zenefits cards when the Zenefits beta is enabled', () => {
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.ZENEFITS;
const cards = getHRCards(makeGetHRCardsParams({isBetaEnabled}));

expect(cards).toHaveLength(2);
Expand All @@ -325,15 +327,13 @@ describe('getHRCards', () => {
const cards = getHRCards(makeGetHRCardsParams({isBetaEnabled}));

const mergeKeys = Object.keys(MERGE_HR_PROVIDERS);
expect(cards).toHaveLength(mergeKeys.length);
for (const slug of mergeKeys) {
expect(cards.find((c) => c.key === `merge_${slug}`)).toBeDefined();
}
});

it('sets correct routes for Gusto cards', () => {
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.GUSTO;
const cards = getHRCards(makeGetHRCardsParams({isBetaEnabled}));
const cards = getHRCards(makeGetHRCardsParams({isBetaEnabled: noBetasEnabled}));

expect(cards?.at(0)?.approvalModeRoute).toBe(ROUTES.WORKSPACE_HR_GUSTO_APPROVAL_MODE.getRoute(POLICY_ID));
expect(cards?.at(0)?.finalApproverRoute).toBe(ROUTES.WORKSPACE_HR_GUSTO_FINAL_APPROVER.getRoute(POLICY_ID));
Expand All @@ -342,21 +342,41 @@ describe('getHRCards', () => {
it('sets correct routes for Zenefits cards', () => {
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.ZENEFITS;
const cards = getHRCards(makeGetHRCardsParams({isBetaEnabled}));
const zenefits = cards.find((c) => c.key === 'zenefits');

expect(cards?.at(0)?.approvalModeRoute).toBe(ROUTES.WORKSPACE_HR_ZENEFITS_APPROVAL_MODE.getRoute(POLICY_ID));
expect(cards?.at(0)?.finalApproverRoute).toBe(ROUTES.WORKSPACE_HR_ZENEFITS_FINAL_APPROVER.getRoute(POLICY_ID));
expect(zenefits?.approvalModeRoute).toBe(ROUTES.WORKSPACE_HR_ZENEFITS_APPROVAL_MODE.getRoute(POLICY_ID));
expect(zenefits?.finalApproverRoute).toBe(ROUTES.WORKSPACE_HR_ZENEFITS_FINAL_APPROVER.getRoute(POLICY_ID));
});

it('sets correct routes for Merge HR cards', () => {
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.MERGE_HR;
const cards = getHRCards(makeGetHRCardsParams({isBetaEnabled}));
const mergeCards = cards.filter((c) => c.key.startsWith('merge_'));

for (const card of cards) {
expect(mergeCards.length).toBeGreaterThan(0);
for (const card of mergeCards) {
expect(card.approvalModeRoute).toBe(ROUTES.WORKSPACE_HR_MERGE_APPROVAL_MODE.getRoute(POLICY_ID));
expect(card.finalApproverRoute).toBe(ROUTES.WORKSPACE_HR_MERGE_FINAL_APPROVER.getRoute(POLICY_ID));
}
});

it('returns the connected Zenefits card even when the Zenefits beta is disabled', () => {
const policy = makePolicy({
connections: {
zenefits: {
config: {approvalMode: CONST.ZENEFITS.APPROVAL_MODE.BASIC, finalApprover: 'admin@test.com'},
data: {},
lastSync: {isSuccessful: true},
},
} as unknown as Policy['connections'],
});
const cards = getHRCards(makeGetHRCardsParams({policy, isBetaEnabled: noBetasEnabled}));
const zenefits = cards.find((c) => c.key === 'zenefits');

expect(zenefits?.isConnected).toBe(true);
expect(zenefits?.config).toBeDefined();
});

it('marks the connected Gusto card as connected with config', () => {
const policy = makePolicy({
connections: {
Expand All @@ -367,8 +387,7 @@ describe('getHRCards', () => {
},
} as unknown as Policy['connections'],
});
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.GUSTO;
const cards = getHRCards(makeGetHRCardsParams({policy, isBetaEnabled}));
const cards = getHRCards(makeGetHRCardsParams({policy, isBetaEnabled: noBetasEnabled}));

expect(cards?.at(0)?.isConnected).toBe(true);
expect(cards?.at(0)?.config).toBeDefined();
Expand Down Expand Up @@ -466,7 +485,7 @@ describe('getHRCards', () => {
it('uses provider icons from params for static providers', () => {
const gustoIcon = {testId: 'gusto'} as unknown as IconAsset;
const trinetIcon = {testId: 'zenefits'} as unknown as IconAsset;
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.GUSTO || beta === CONST.BETAS.ZENEFITS;
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.ZENEFITS;
const cards = getHRCards(makeGetHRCardsParams({gustoIcon, trinetIcon, isBetaEnabled}));

expect(cards?.at(0)?.icon).toBe(gustoIcon);
Expand All @@ -476,8 +495,10 @@ describe('getHRCards', () => {
it('uses provider iconUrl for Merge cards', () => {
const isBetaEnabled: GetHRCardsParams['isBetaEnabled'] = (beta) => beta === CONST.BETAS.MERGE_HR;
const cards = getHRCards(makeGetHRCardsParams({isBetaEnabled}));
const mergeCards = cards.filter((c) => c.key.startsWith('merge_'));

for (const card of cards) {
expect(mergeCards.length).toBeGreaterThan(0);
for (const card of mergeCards) {
const slug = card.key.replace('merge_', '');
const expected = MERGE_HR_PROVIDERS[slug as keyof typeof MERGE_HR_PROVIDERS]?.iconUrl;
expect(card.icon).toBe(expected);
Expand Down
Loading