-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[NoQA] [HR Import] Initial sync modal, sync action and info on Members page #91315
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
Changes from 4 commits
de5c54c
5e693c2
08076cf
df31cef
0d933d5
86095ba
e573980
ffbfb77
1f148e6
897ea31
2d31562
0a22e99
dbfd1fd
7d16afb
b6e4c26
158a688
38199e7
50e428e
bbb9810
ab55e28
ce62021
581fea7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import {useEffect, useEffectEvent} from 'react'; | ||
| import type {OnyxEntry} from 'react-native-onyx'; | ||
| import {setMergeHRInitialSyncModalShown} from '@libs/actions/connections/MergeHR'; | ||
| import CONST from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import type {PolicyConnectionSyncProgress} from '@src/types/onyx/Policy'; | ||
| import useConfirmModal from './useConfirmModal'; | ||
| import useLocalize from './useLocalize'; | ||
| import useOnyx from './useOnyx'; | ||
|
|
||
| /** | ||
| * Shows a one-time informational modal when the Merge HR connection's first backend-initiated sync starts. | ||
| * The modal is suppressed for subsequent page loads during the same sync by persisting the sync timestamp in Onyx. | ||
| */ | ||
| function useMergeHRInitialSyncingModal(policyID: string, connectionSyncProgress: OnyxEntry<PolicyConnectionSyncProgress>, isFocused: boolean) { | ||
| const {showConfirmModal} = useConfirmModal(); | ||
| const {translate} = useLocalize(); | ||
| const [shownForTimestamp] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_MERGE_HR_INITIAL_SYNC_MODAL_SHOWN}${policyID}`); | ||
|
|
||
| const showSyncingModal = useEffectEvent((timestamp: string) => { | ||
| if (shownForTimestamp === timestamp) { | ||
| return; | ||
| } | ||
| setMergeHRInitialSyncModalShown(policyID, timestamp); | ||
| showConfirmModal({ | ||
| id: `merge-hr-syncing-${policyID}`, | ||
| title: translate('workspace.hr.syncingModalTitle'), | ||
| prompt: translate('workspace.hr.syncingModalDescription'), | ||
| confirmText: translate('common.buttonConfirm'), | ||
| shouldShowCancelButton: false, | ||
| }); | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| const isMergeHRInitialSyncStarting = | ||
| connectionSyncProgress?.connectionName === CONST.POLICY.CONNECTIONS.NAME.MERGE_HR && | ||
| connectionSyncProgress?.stageInProgress === CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.MERGE_HR_SYNC_TITLE && | ||
| connectionSyncProgress?.isInitialSync; | ||
|
|
||
| if (!isFocused || !isMergeHRInitialSyncStarting || !connectionSyncProgress?.timestamp) { | ||
| return; | ||
| } | ||
|
|
||
| showSyncingModal(connectionSyncProgress.timestamp); | ||
| }, [connectionSyncProgress?.connectionName, connectionSyncProgress?.stageInProgress, connectionSyncProgress?.isInitialSync, connectionSyncProgress?.timestamp, isFocused]); | ||
| } | ||
|
|
||
| export default useMergeHRInitialSyncingModal; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,7 @@ import {isPersonalDetailsReady, sortAlphabetically} from '@libs/OptionsListUtils | |
| import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; | ||
| import { | ||
| canEditWorkspaceSettings, | ||
| getConnectedHRProvider, | ||
| getConnectionExporters, | ||
| getMemberAccountIDsForWorkspace, | ||
| isControlPolicy, | ||
|
|
@@ -582,10 +583,10 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers | |
| }, [isLoading, policy?.employeeList, translate, isOfflineAndNoMemberDataAvailable]); | ||
|
|
||
| const memberCount = data.filter((member) => member.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length; | ||
| const hasGustoConnection = !!policy?.connections?.gusto; | ||
| const shouldShowGustoSyncLink = isPolicyAdmin && hasGustoConnection; | ||
| const isGustoSyncInProgress = | ||
| hasGustoConnection && connectionSyncProgress?.connectionName === CONST.POLICY.CONNECTIONS.NAME.GUSTO && isConnectionInProgress(connectionSyncProgress, policy); | ||
| const connectedHRProvider = getConnectedHRProvider(policy); | ||
| const shouldShowHRSyncLink = isPolicyAdmin && !!connectedHRProvider; | ||
| const isHRSyncInProgress = | ||
| shouldShowHRSyncLink && connectionSyncProgress?.connectionName === connectedHRProvider?.connectionName && isConnectionInProgress(connectionSyncProgress, policy); | ||
| const isPendingAddOrDelete = | ||
| isOffline && data?.some((member) => member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); | ||
| const shouldShowSearchBar = data.length > CONST.SEARCH_ITEM_LIMIT; | ||
|
|
@@ -633,16 +634,18 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers | |
| <View style={[styles.pl5, styles.mb5, styles.mt3, styles.flexRow, styles.alignItemsCenter]}> | ||
| <Text style={[styles.textSupporting, styles.flexShrink1, isPendingAddOrDelete && styles.offlineFeedbackPending]}> | ||
| {translate('workspace.people.workspaceMembersCount', memberCount)} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mhawryluk do we hide the three dot modal during initial sync?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, there's a spinner in its place |
||
| {shouldShowGustoSyncLink && '. '} | ||
| {shouldShowGustoSyncLink && ( | ||
| <TextLink onPress={() => Navigation.navigate(ROUTES.WORKSPACE_HR.getRoute(policyID))}>{translate('workspace.people.configureGustoSync')}</TextLink> | ||
| {shouldShowHRSyncLink && '. '} | ||
| {shouldShowHRSyncLink && ( | ||
| <TextLink onPress={() => Navigation.navigate(ROUTES.WORKSPACE_HR.getRoute(policyID))}> | ||
| {translate('workspace.people.configureHRSync', connectedHRProvider?.displayName ?? '')} | ||
| </TextLink> | ||
| )} | ||
| </Text> | ||
| {shouldShowGustoSyncLink && isGustoSyncInProgress && ( | ||
| {shouldShowHRSyncLink && isHRSyncInProgress && ( | ||
| <ActivityIndicator | ||
| size="small" | ||
| style={styles.ml2} | ||
| reasonAttributes={{context: 'WorkspaceMembersPage.gustoSync'}} | ||
| reasonAttributes={{context: 'WorkspaceMembersPage.hrSync'}} | ||
| /> | ||
| )} | ||
| </View> | ||
|
|
@@ -841,20 +844,21 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers | |
| }, | ||
| ]; | ||
|
|
||
| if (hasGustoConnection) { | ||
| const hrProvider = getConnectedHRProvider(policy); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ CONSISTENCY-3 (docs)
if (connectedHRProvider) {
menuItems.push({
icon: icons.Sync,
text: translate('workspace.people.syncWithHR', connectedHRProvider.displayName),
...
close(() => syncConnection(policy, connectedHRProvider.connectionName));
});
}Add Reviewed at: 38199e7 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it causes an eslint/react-compiler error, because we pass both policy and connectedHRProvider which depends on policy |
||
| if (hrProvider) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when I put |
||
| menuItems.push({ | ||
| icon: icons.Sync, | ||
| text: translate('workspace.people.syncWithGusto'), | ||
| text: translate('workspace.people.syncWithHR', hrProvider.displayName), | ||
| onSelected: () => { | ||
| if (isOffline) { | ||
| close(showRequiresInternetModal); | ||
| return; | ||
| } | ||
|
|
||
| close(() => syncConnection(policy, CONST.POLICY.CONNECTIONS.NAME.GUSTO)); | ||
| close(() => syncConnection(policy, hrProvider.connectionName)); | ||
| }, | ||
| value: CONST.POLICY.SECONDARY_ACTIONS.SYNC_WITH_GUSTO, | ||
| disabled: isGustoSyncInProgress, | ||
| value: CONST.POLICY.SECONDARY_ACTIONS.SYNC_WITH_HR, | ||
| disabled: isHRSyncInProgress, | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -870,8 +874,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers | |
| policyID, | ||
| showLockedAccountModal, | ||
| showRequiresInternetModal, | ||
| hasGustoConnection, | ||
| isGustoSyncInProgress, | ||
| isHRSyncInProgress, | ||
| policy, | ||
| ]); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2373,6 +2373,9 @@ type PolicyConnectionSyncProgress = { | |
|
|
||
| /** Optional result payload shown after a completed sync */ | ||
| result?: GustoSyncResult; | ||
|
|
||
| /** Whether this is the initial sync after the connection was established */ | ||
| isInitialSync?: boolean; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of a boolean, we'll have an enum named |
||
| }; | ||
|
|
||
| export default Policy; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[isInitialSyncInProgress, isFocused] @mhawryluk have you tried passing in isInitialSyncInProgress rather than rerender on both changes?