[NoQA] [HR Import] Initial sync modal, sync action and info on Members page#91315
Conversation
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx ts-node ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
This comment has been minimized.
This comment has been minimized.
| @@ -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)} | |||
There was a problem hiding this comment.
@mhawryluk do we hide the three dot modal during initial sync?
There was a problem hiding this comment.
no, there's a spinner in its place
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
| result?: GustoSyncResult; | ||
|
|
||
| /** Whether this is the initial sync after the connection was established */ | ||
| isInitialSync?: boolean; |
There was a problem hiding this comment.
instead of a boolean, we'll have an enum named syncType which will have 'initial' value
| CUSTOM: 'custom', | ||
| }, | ||
| SYNC_TYPE: { | ||
| INITIAL: 'initial', |
There was a problem hiding this comment.
SYNC_STATUS: "SYNCING" | "DONE" | "FAILED" | "DISABLED";
There was a problem hiding this comment.
SYNC_TYPE: "initial" | "manual" | "auto" | "webhook";
There was a problem hiding this comment.
So the loading state is
First sync:
{
...
syncStatus: 'SYNCING',
syncType: 'initial'
}When done:
{
...
successfulDate: `<idk some date format off top of head>`
syncStatus: 'DONE' | 'FAILED' | 'DISABLED',
syncType: 'initial'
}We only care about initial vs not initial for now, so we can ignore the other syncTypes for the time status
|
|
||
| if (hasGustoConnection) { | ||
| const hrProvider = getConnectedHRProvider(policy); | ||
| if (hrProvider) { |
There was a problem hiding this comment.
We already have connectedHRProvider defined above, so let's reuse that.
There was a problem hiding this comment.
when I put connectedHRProvider in the dependency array there is an error saying that react-compiler cannot preserve the memoization, so I would leave it for now
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index 7c522774..ebe0ec73 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -6093,7 +6093,7 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU
approvers: 'Genehmigende',
auditors: 'Prüfer',
emptyRoleFilter: {title: 'Keine Mitglieder entsprechen diesem Filter', subtitle: 'Laden Sie ein Mitglied ein oder ändern Sie den Filter oben.'},
- configureHRSync: (providerName: string) => `Synchronisierung mit ${providerName} einrichten.`,
+ configureHRSync: (providerName: string) => `${providerName}-Synchronisierung konfigurieren.`,
syncWithHR: (providerName: string) => `Mit ${providerName} synchronisieren`,
},
card: {
@@ -7177,15 +7177,15 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
case 'gustoSyncLoadData':
return 'Daten werden von Gusto geladen';
case 'gustoSyncProvisioning':
- return 'Mitarbeiterbereitstellung in Richtlinie';
+ return 'Mitarbeitende in der Richtlinie bereitstellen';
case 'zenefitsSyncTitle':
return 'TriNet-Mitarbeitende werden synchronisiert';
case 'zenefitsSyncLoadData':
- return 'Lade Daten von TriNet';
+ return 'Daten werden von TriNet geladen';
case 'zenefitsSyncProvisioning':
- return 'Mitarbeiterbereitstellung in Richtlinie';
+ return 'Mitarbeitende in der Richtlinie bereitstellen';
case 'jobDone':
- return 'Warten auf das Laden der importierten Daten';
+ return 'Warten, bis importierte Daten geladen sind';
default: {
return `Übersetzung fehlt für Stufe: ${stage}`;
}
@@ -7210,6 +7210,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
},
syncingModalTitle: 'Ihre Verbindung wird synchronisiert',
syncingModalDescription: 'Die erste Verbindung kann einige Zeit dauern. Sie werden über alle Fehler benachrichtigt.',
+ syncing: 'Mitarbeitende werden synchronisiert',
},
},
getAssistancePage: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 95f43143..5c870bca 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -6285,10 +6285,10 @@ ${amount} para ${merchant} - ${date}`,
},
custom: {label: 'Aprobación personalizada', description: 'Configuraré manualmente los flujos de aprobación en Expensify.'},
},
- syncStageName: ({stage}) => {
+ syncStageName: ({stage}: SyncStageNameConnectionsParams) => {
switch (stage) {
case 'gustoSyncTitle':
- return 'Sincronizar empleados de Gusto';
+ return 'Sincronizando empleados de Gusto';
case 'gustoSyncLoadData':
return 'Cargando datos desde Gusto';
case 'gustoSyncProvisioning':
@@ -6302,7 +6302,7 @@ ${amount} para ${merchant} - ${date}`,
case 'jobDone':
return 'Esperando a que se carguen los datos importados';
default: {
- return `Falta la traducción para la etapa: ${stage}`;
+ return `Falta traducción para la etapa: ${stage}`;
}
}
},
@@ -6325,6 +6325,7 @@ ${amount} para ${merchant} - ${date}`,
},
syncingModalTitle: 'Tu conexión se está sincronizando',
syncingModalDescription: 'La primera conexión puede tardar un poco. Se te notificará de cualquier error.',
+ syncing: 'Sincronizando empleados',
},
export: {
notReadyHeading: 'No está listo para exportar',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 2ce170a9..bc476b2b 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -7206,17 +7206,17 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
case 'gustoSyncLoadData':
return 'Chargement des données depuis Gusto';
case 'gustoSyncProvisioning':
- return 'Affectation des employés à la politique';
+ return 'Provisionnement des employés dans la politique';
case 'zenefitsSyncTitle':
return 'Synchronisation des employés TriNet';
case 'zenefitsSyncLoadData':
return 'Chargement des données depuis TriNet';
case 'zenefitsSyncProvisioning':
- return 'Affectation des employés à la politique';
+ return 'Provisionnement des employés dans la politique';
case 'jobDone':
return 'En attente du chargement des données importées';
default: {
- return `Traduction manquante pour l'étape : ${stage}`;
+ return `Traduction manquante pour l’étape : ${stage}`;
}
}
},
@@ -7238,7 +7238,8 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
title: 'TriNet',
},
syncingModalTitle: 'Votre connexion est en cours de synchronisation',
- syncingModalDescription: 'La première connexion peut prendre un certain temps. Vous serez informé de toute erreur.',
+ syncingModalDescription: 'La première connexion peut prendre un certain temps. Vous serez averti(e) en cas d’erreur.',
+ syncing: 'Synchronisation des employés',
},
},
getAssistancePage: {
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 4047098b..db12436d 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -6082,7 +6082,7 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST.
approvers: 'Approvatori',
auditors: 'Revisori',
emptyRoleFilter: {title: 'Nessun membro corrisponde a questo filtro', subtitle: 'Invita un membro o modifica il filtro qui sopra.'},
- configureHRSync: (providerName: string) => `Configura la sincronizzazione di ${providerName}.`,
+ configureHRSync: (providerName: string) => `Configura la sincronizzazione con ${providerName}.`,
syncWithHR: (providerName: string) => `Sincronizza con ${providerName}`,
},
card: {
@@ -7160,13 +7160,13 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
syncStageName: ({stage}: SyncStageNameConnectionsParams) => {
switch (stage) {
case 'gustoSyncTitle':
- return 'Sincronizzazione dei dipendenti Gusto';
+ return 'Sincronizzazione dipendenti Gusto';
case 'gustoSyncLoadData':
- return 'Caricamento dei dati da Gusto';
+ return 'Caricamento dati da Gusto';
case 'gustoSyncProvisioning':
return 'Provisioning dei dipendenti nella policy';
case 'zenefitsSyncTitle':
- return 'Sincronizzazione dipendenti TriNet';
+ return 'Sincronizzazione dei dipendenti TriNet';
case 'zenefitsSyncLoadData':
return 'Caricamento dei dati da TriNet';
case 'zenefitsSyncProvisioning':
@@ -7196,7 +7196,8 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
title: 'TriNet',
},
syncingModalTitle: 'La tua connessione è in sincronizzazione',
- syncingModalDescription: "La prima connessione può richiedere un po' di tempo. Ti verrà notificato qualsiasi errore.",
+ syncingModalDescription: "La prima connessione può richiedere un po' di tempo. Ti avviseremo in caso di errori.",
+ syncing: 'Sincronizzazione dipendenti',
},
},
getAssistancePage: {
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index c3f09613..e4e3368f 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -7081,19 +7081,19 @@ ${reportName}
case 'gustoSyncTitle':
return 'Gusto 従業員を同期中';
case 'gustoSyncLoadData':
- return 'Gusto からデータを読み込んでいます';
+ return 'Gusto からデータを読み込み中です';
case 'gustoSyncProvisioning':
- return 'ポリシー内で従業員をプロビジョニングする';
+ return 'ポリシー内で従業員をプロビジョニング';
case 'zenefitsSyncTitle':
- return 'TriNet 従業員を同期しています';
+ return 'TriNet 従業員を同期中';
case 'zenefitsSyncLoadData':
return 'TriNet からデータを読み込んでいます';
case 'zenefitsSyncProvisioning':
- return 'ポリシー内で従業員をプロビジョニングする';
+ return 'ポリシー内で従業員をプロビジョニング';
case 'jobDone':
return 'インポートしたデータの読み込みを待機しています';
default: {
- return `ステージ「${stage}」の翻訳が見つかりません`;
+ return `ステージの翻訳が見つかりません: ${stage}`;
}
}
},
@@ -7116,6 +7116,7 @@ ${reportName}
},
syncingModalTitle: '接続を同期しています',
syncingModalDescription: '最初の接続には時間がかかる場合があります。エラーが発生した場合は通知されます。',
+ syncing: '従業員を同期しています',
},
},
getAssistancePage: {
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 2601159d..07b7807d 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -6061,7 +6061,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_
approvers: 'Fiatteurs',
auditors: 'Accountants',
emptyRoleFilter: {title: 'Geen leden komen overeen met dit filter', subtitle: 'Nodig een lid uit of wijzig het filter hierboven.'},
- configureHRSync: (providerName: string) => `Stel ${providerName}-synchronisatie in.`,
+ configureHRSync: (providerName: string) => `${providerName}-synchronisatie instellen.`,
syncWithHR: (providerName: string) => `Synchroniseren met ${providerName}`,
},
card: {
@@ -7143,11 +7143,11 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
case 'zenefitsSyncTitle':
return 'TriNet-medewerkers synchroniseren';
case 'zenefitsSyncLoadData':
- return 'Gegevens laden van TriNet';
+ return 'Gegevens van TriNet laden';
case 'zenefitsSyncProvisioning':
return 'Medewerkers toewijzen in beleid';
case 'jobDone':
- return 'Wachten tot geïmporteerde gegevens worden geladen';
+ return 'Wachten tot geïmporteerde gegevens zijn geladen';
default: {
return `Vertaling ontbreekt voor fase: ${stage}`;
}
@@ -7172,6 +7172,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
},
syncingModalTitle: 'Je verbinding wordt gesynchroniseerd',
syncingModalDescription: 'De eerste verbinding kan even duren. Je krijgt een melding als er fouten optreden.',
+ syncing: 'Werknemers synchroniseren',
},
},
getAssistancePage: {
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index f013ec6b..10fdefd3 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -6055,7 +6055,7 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy
approvers: 'Osoby zatwierdzające',
auditors: 'Audytorzy',
emptyRoleFilter: {title: 'Żadni członkowie nie pasują do tego filtra', subtitle: 'Zaproś członka lub zmień filtr powyżej.'},
- configureHRSync: (providerName: string) => `Skonfiguruj synchronizację ${providerName}.`,
+ configureHRSync: (providerName: string) => `Skonfiguruj synchronizację z ${providerName}.`,
syncWithHR: (providerName: string) => `Synchronizuj z ${providerName}`,
},
card: {
@@ -7134,15 +7134,15 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
case 'gustoSyncLoadData':
return 'Wczytywanie danych z Gusto';
case 'gustoSyncProvisioning':
- return 'Przydzielanie pracowników w polisie';
+ return 'Przydzielanie pracowników w polityce';
case 'zenefitsSyncTitle':
return 'Synchronizowanie pracowników TriNet';
case 'zenefitsSyncLoadData':
return 'Wczytywanie danych z TriNet';
case 'zenefitsSyncProvisioning':
- return 'Przydzielanie pracowników w polisie';
+ return 'Przydzielanie pracowników w polityce';
case 'jobDone':
- return 'Oczekiwanie na załadowanie zaimportowanych danych';
+ return 'Trwa ładowanie zaimportowanych danych';
default: {
return `Brak tłumaczenia dla etapu: ${stage}`;
}
@@ -7166,7 +7166,8 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
title: 'TriNet',
},
syncingModalTitle: 'Twoje połączenie jest synchronizowane',
- syncingModalDescription: 'Pierwsze połączenie może chwilę potrwać. Zostaniesz powiadomiony o wszelkich błędach.',
+ syncingModalDescription: 'Pierwsze połączenie może zająć trochę czasu. O wszelkich błędach zostaniesz powiadomiony.',
+ syncing: 'Synchronizowanie pracowników',
},
},
getAssistancePage: {
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 12336717..60a5ea12 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -6061,7 +6061,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS
approvers: 'Aprovadores',
auditors: 'Auditores',
emptyRoleFilter: {title: 'Nenhum membro corresponde a este filtro', subtitle: 'Convide um membro ou altere o filtro acima.'},
- configureHRSync: (providerName: string) => `Configurar a sincronização do ${providerName}.`,
+ configureHRSync: (providerName: string) => `Configurar a sincronização com ${providerName}.`,
syncWithHR: (providerName: string) => `Sincronizar com ${providerName}`,
},
card: {
@@ -7135,7 +7135,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
syncStageName: ({stage}: SyncStageNameConnectionsParams) => {
switch (stage) {
case 'gustoSyncTitle':
- return 'Sincronizando funcionários do Gusto';
+ return 'Sincronizando funcionários da Gusto';
case 'gustoSyncLoadData':
return 'Carregando dados do Gusto';
case 'gustoSyncProvisioning':
@@ -7170,8 +7170,9 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
zenefits: {
title: 'TriNet',
},
- syncingModalTitle: 'Sua conexão está sincronizando',
- syncingModalDescription: 'A primeira conexão pode levar algum tempo. Você será notificado sobre quaisquer erros.',
+ syncingModalTitle: 'Sua conexão está sendo sincronizada',
+ syncingModalDescription: 'A primeira conexão pode levar algum tempo. Você será avisado sobre quaisquer erros.',
+ syncing: 'Sincronizando funcionários',
},
},
getAssistancePage: {
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 658795b4..e71b4e40 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -6952,17 +6952,17 @@ ${reportName}
syncStageName: ({stage}: SyncStageNameConnectionsParams) => {
switch (stage) {
case 'gustoSyncTitle':
- return '同步 Gusto 员工';
+ return '正在同步 Gusto 员工';
case 'gustoSyncLoadData':
return '正在从 Gusto 加载数据';
case 'gustoSyncProvisioning':
- return '在政策中为员工开通账号';
+ return '在策略中为员工开通账户';
case 'zenefitsSyncTitle':
return '正在同步 TriNet 员工';
case 'zenefitsSyncLoadData':
return '正在从 TriNet 加载数据';
case 'zenefitsSyncProvisioning':
- return '在政策中为员工开通账号';
+ return '在策略中为员工开通账户';
case 'jobDone':
return '正在加载导入的数据';
default: {
@@ -6988,7 +6988,8 @@ ${reportName}
title: 'TriNet',
},
syncingModalTitle: '您的连接正在同步',
- syncingModalDescription: '首次连接可能需要一些时间。若发生任何错误,我们会通知你。',
+ syncingModalDescription: '首次连接可能需要一些时间。若出现任何错误,我们会通知你。',
+ syncing: '正在同步员工',
},
},
getAssistancePage: {
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
|
🚧 @grgia has triggered a test Expensify/App build. You can view the workflow run here. |
|
🚧 @grgia has triggered a test Expensify/App build. You can view the workflow run here. |
|
🚧 @grgia has triggered a test Expensify/App build. You can view the workflow run here. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| const isInitialSyncInProgress = mergeLastSync?.syncStatus === CONST.MERGE_HR.SYNC_STATUS.SYNCING && mergeLastSync?.syncType === CONST.MERGE_HR.SYNC_TYPE.INITIAL; | ||
| if (!isFocused || !isInitialSyncInProgress || !isAppVisible) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
[isInitialSyncInProgress, isFocused] @mhawryluk have you tried passing in isInitialSyncInProgress rather than rerender on both changes?
|
🚧 @grgia has triggered a test Expensify/App build. You can view the workflow run here. |
|
🚧 @grgia has triggered a test Expensify/App build. You can view the workflow run here. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
cristipaval
left a comment
There was a problem hiding this comment.
I've just tested and I have nothing more to add other that @grgia's comments
|
SKKRRT |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safari |
|
🚧 @grgia has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/grgia in version: 9.3.80-0 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are required for this PR. Reasoning:
Once this feature exits beta and becomes generally available, a new help article covering the Merge HR connection flow (including the sync modal and Members page sync options) would be appropriate. |


Explanation of Change
Adds support for some of the employee syncing features for Merge HR integrations: the initial sync info modal, as well as the Members page link to HR, syncing spinner and sync action in the menu. Adjusts the data structures for Merge HR syncing info to match the backend.
Fixed Issues
$ #91273
PROPOSAL: N/A
Tests
Prerequisites: Enable the
mergeHRConnectionsbeta.Offline tests
QA Steps
gustoNewDotbeta.PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-05-22.at.15.29.35.mov
iOS: mWeb Safari
MacOS: Chrome / Safari
Nagranie.z.ekranu.2026-05-22.o.12.56.57-kopia.mov