[HOLD Web#54898] Add yes/no buttons for RuleBot's retroactive agent rule offer - #97162
[HOLD Web#54898] Add yes/no buttons for RuleBot's retroactive agent rule offer#97162NicolasBonet wants to merge 14 commits into
Conversation
|
@ChavdaSachin Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
This comment has been minimized.
This comment has been minimized.
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b90b0a746
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Addressed the review feedback in 3e71385:
|
|
Follow-up on the Codecov report in 8af21c1:
All three new tests pass, along with the existing |
Renders the new ACTIONABLEAPPLYAGENTRULE report action (RuleBot's offer to retroactively apply a new/edited agent rule) with Apply/Dismiss buttons for policy admins, resolved via the new ResolveActionableApplyAgentRule command.
…n tests Pass actionOwnerReportID to ApplyAgentRuleContent so optimistic updates target the action's owning report, clear stale errors when retrying a resolution, and cover resolveActionableApplyAgentRule with unit tests. Adopts the suggested German translation (house-style quotes).
8af21c1 to
1c0ed65
Compare
This comment has been minimized.
This comment has been minimized.
|
@MelvinBot let's fix the failing workflows |
- Add ACTIONABLEAPPLYAGENTRULE to cspell dictionary (new action type token) - Bump no-unsafe-type-assertion seatbelt for ReportActionsUtilsTest.ts to 37 Co-authored-by: Nicolás Bonet <NicolasBonet@users.noreply.github.com>
|
Fixed both failing checks and pushed to the branch. ESLint check — spellcheck — Both are minimal, targeted changes. Note I couldn't run the linters locally in this environment, but each change exactly matches the reported error, so CI should go green on the next run. |
…g' into nicolas-ruleBotApplyOfferUI
This comment has been minimized.
This comment has been minimized.
|
@thelullabyy Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
Yes/No style buttons can't be understood on their own, so use "Apply retroactively" / "Don't apply" instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
…uting Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve eslint.seatbelt.tsv conflict and remove the unsafe type assertion in the isActionableApplyAgentRule test helper so the tightened baseline still passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦜 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 c94515d02fd..060723ee9a7 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -7883,7 +7883,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
createRule: 'Regel erstellen',
applyOffer: {
message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
- `hat angeboten, die Agentenregel „${ruleTitle}“ auf ${count} offene ${count === 1 ? 'Ausgabe' : 'Ausgaben'} anzuwenden. Soll sie rückwirkend angewendet werden?`,
+ `anbot, die Agentenregel „${ruleTitle}“ auf ${count} offene ${count === 1 ? 'Ausgabe' : 'Ausgaben'} anzuwenden. Soll sie rückwirkend angewendet werden?`,
applyButtonText: 'Rückwirkend anwenden',
dismissButtonText: 'Nicht anwenden',
},
@@ -8689,8 +8689,8 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
agentRule: {
added: ({title, prompt}: {title: string; prompt: string}) => (title ? `hat die Agentenregel „${title}“ hinzugefügt: ${prompt}` : `hat eine Agentenregel hinzugefügt: ${prompt}`),
updated: ({title, prompt}: {title: string; prompt: string}) =>
- title ? `hat die Agentenregel „${title}“ aktualisiert zu: ${prompt}` : `eine Agentenregel aktualisiert zu: ${prompt}`,
- deleted: ({title}: {title: string}) => (title ? `hat die Agentenregel „${title}“ entfernt` : 'hat eine Agentenregel entfernt'),
+ title ? `hat die Agentenregel „${title}“ aktualisiert auf: ${prompt}` : `hat eine Agentenregel aktualisiert in: ${prompt}`,
+ deleted: ({title}: {title: string}) => (title ? `hat die Agentenregel „${title}“ entfernt` : 'eine Agentenregel entfernt'),
},
expensifyCardRule: {
actionVerb: {block: 'blockiert', allow: 'erlaubt'},
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 876a53f1ad1..41b0b588445 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -7790,9 +7790,9 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
createRule: 'Crear regla',
applyOffer: {
message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
- `ofreció aplicar la regla de agente "${ruleTitle}" a ${count} ${count === 1 ? 'gasto abierto' : 'gastos abiertos'}. ¿Quieres aplicarla retroactivamente?`,
+ `ofreció aplicar la regla de agente «${ruleTitle}» a ${count} ${count === 1 ? 'gasto' : 'gastos'} abiertos. ¿Quieres aplicarla con efecto retroactivo?`,
applyButtonText: 'Aplicar retroactivamente',
- dismissButtonText: 'No aplicar',
+ dismissButtonText: 'No solicitar',
},
},
tabs: {
@@ -8169,7 +8169,7 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
agentRule: {
added: ({title, prompt}: {title: string; prompt: string}) => (title ? `añadió la regla de agente «${title}»: ${prompt}` : `añadió una regla de agente: ${prompt}`),
updated: ({title, prompt}: {title: string; prompt: string}) => (title ? `actualizó la regla del agente «${title}» a: ${prompt}` : `actualizó una regla de agente a: ${prompt}`),
- deleted: ({title}: {title: string}) => (title ? `eliminó la regla de agente «${title}»` : 'eliminó una regla de agente'),
+ deleted: ({title}: {title: string}) => (title ? `eliminó la regla del agente «${title}»` : 'eliminó una regla de agente'),
},
expensifyCardRule: {
actionVerb: {block: 'bloqueado', allow: 'permitido'},
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index b289e17ae23..5aea4b8c148 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -7911,7 +7911,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
createRule: 'Créer une règle',
applyOffer: {
message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
- `a proposé d’appliquer la règle d’agent « ${ruleTitle} » à ${count} ${count === 1 ? 'dépense ouverte' : 'dépenses ouvertes'}. Voulez-vous l’appliquer rétroactivement ?`,
+ `a proposé d’appliquer la règle d’agent « ${ruleTitle} » à ${count} ${count === 1 ? 'dépense' : 'dépenses'} ouverts. Voulez-vous l’appliquer rétroactivement ?`,
applyButtonText: 'Appliquer rétroactivement',
dismissButtonText: 'Ne pas appliquer',
},
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 4ec6fe89831..e872a0fc556 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -7856,7 +7856,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
createRule: 'Crea regola',
applyOffer: {
message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
- `ha proposto di applicare la regola agente "${ruleTitle}" a ${count} ${count === 1 ? 'spesa aperta' : 'spese aperte'}. Vuoi applicarla retroattivamente?`,
+ `ha proposto di applicare la regola dell’agente "${ruleTitle}" a ${count} ${count === 1 ? 'spesa' : 'spese'} aperte. Vuoi applicarla in modo retroattivo?`,
applyButtonText: 'Applica retroattivamente',
dismissButtonText: 'Non applicare',
},
@@ -8660,10 +8660,11 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
`ha modificato la categoria di spesa predefinita per "${mccGroupName}" in "${newCategory}" (precedentemente "${oldCategory}")`,
updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? 'abilitato' : 'disabilitato'} il requisito per gli acquisti con carta aziendale`,
agentRule: {
- added: ({title, prompt}: {title: string; prompt: string}) => (title ? `ha aggiunto la regola dell’agente "${title}": ${prompt}` : `ha aggiunto una regola agente: ${prompt}`),
+ added: ({title, prompt}: {title: string; prompt: string}) =>
+ title ? `ha aggiunto la regola dell'agente "${title}": ${prompt}` : `ha aggiunto una regola dell'agente: ${prompt}`,
updated: ({title, prompt}: {title: string; prompt: string}) =>
- title ? `ha aggiornato la regola dell’agente "${title}" in: ${prompt}` : `ha aggiornato una regola dell’agente in: ${prompt}`,
- deleted: ({title}: {title: string}) => (title ? `ha rimosso la regola agente "${title}"` : 'ha rimosso una regola agente'),
+ title ? `ha aggiornato la regola dell'agente "${title}" in: ${prompt}` : `ha aggiornato una regola agente in: ${prompt}`,
+ deleted: ({title}: {title: string}) => (title ? `ha rimosso la regola dell’agente "${title}"` : 'ha rimosso una regola agente'),
},
expensifyCardRule: {
actionVerb: {block: 'bloccato', allow: 'consentito'},
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 612de82a40e..dd8e370147c 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -7760,7 +7760,7 @@ ${reportName}`,
createRule: 'ルールを作成',
applyOffer: {
message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
- `エージェントルール「${ruleTitle}」を${count}件の未処理の経費に適用することを提案しました。遡って適用しますか?`,
+ `エージェントルール「${ruleTitle}」を未処理の${count === 1 ? '経費' : '経費'} ${count}件に適用することを提案しました。過去分にも遡って適用しますか?`,
applyButtonText: '遡って適用',
dismissButtonText: '適用しない',
},
@@ -8549,7 +8549,7 @@ ${reportName}`,
`「${mccGroupName}」のデフォルト支出カテゴリーを「${newCategory}」に変更しました(以前は「${oldCategory}」)`,
updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? '有効' : '無効'} の法人カード購入要件`,
agentRule: {
- added: ({title, prompt}: {title: string; prompt: string}) => (title ? `エージェントルール「${title}」を追加しました:${prompt}` : `エージェントルールを追加しました:${prompt}`),
+ added: ({title, prompt}: {title: string; prompt: string}) => (title ? `エージェントルール「${title}」を追加しました:${prompt}` : `エージェントルールを追加しました: ${prompt}`),
updated: ({title, prompt}: {title: string; prompt: string}) =>
title ? `エージェントルール「${title}」を次の内容に更新しました:${prompt}` : `エージェントルールを次の内容に更新しました:${prompt}`,
deleted: ({title}: {title: string}) => (title ? `エージェントルール「${title}」を削除しました` : 'エージェントルールを削除しました'),
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 9e5601ea569..cd268a6a8cc 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -7837,7 +7837,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
createRule: 'Regel maken',
applyOffer: {
message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
- `bood aan de agentregel "${ruleTitle}" toe te passen op ${count} openstaande ${count === 1 ? 'uitgave' : 'uitgaven'}. Wil je deze met terugwerkende kracht toepassen?`,
+ `stelde voor om de agentregel "${ruleTitle}" toe te passen op ${count} open ${count === 1 ? 'uitgave' : 'uitgaven'}. Wil je deze met terugwerkende kracht toepassen?`,
applyButtonText: 'Met terugwerkende kracht toepassen',
dismissButtonText: 'Niet toepassen',
},
@@ -8636,8 +8636,8 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
agentRule: {
added: ({title, prompt}: {title: string; prompt: string}) => (title ? `heeft de agentregel "${title}" toegevoegd: ${prompt}` : `heeft een agentregel toegevoegd: ${prompt}`),
updated: ({title, prompt}: {title: string; prompt: string}) =>
- title ? `heeft de agentregel „${title}” bijgewerkt naar: ${prompt}` : `heeft een agentregel bijgewerkt naar: ${prompt}`,
- deleted: ({title}: {title: string}) => (title ? `heeft de agentregel ‘${title}’ verwijderd` : 'heeft een agentregel verwijderd'),
+ title ? `heeft de agentregel ‘${title}’ bijgewerkt naar: ${prompt}` : `heeft een agentregel bijgewerkt naar: ${prompt}`,
+ deleted: ({title}: {title: string}) => (title ? `heeft de agentregel "${title}" verwijderd` : 'heeft een agentregel verwijderd'),
},
expensifyCardRule: {
actionVerb: {block: 'geblokkeerd', allow: 'toegestaan'},
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 5c18429fd2f..7cf8f6bbb34 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -7817,9 +7817,9 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
createRule: 'Utwórz regułę',
applyOffer: {
message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
- `zaproponował zastosowanie reguły agenta „${ruleTitle}” do ${count} otwartych wydatków. Zastosować ją wstecznie?`,
+ `zaoferował(-a) zastosowanie reguły agenta „${ruleTitle}” do ${count} otwartych ${count === 1 ? 'wydatek' : 'wydatki'}. Zastosować ją też wstecznie?`,
applyButtonText: 'Zastosuj wstecznie',
- dismissButtonText: 'Nie stosuj',
+ dismissButtonText: 'Nie aplikuj',
},
},
tabs: {
@@ -8616,7 +8616,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
agentRule: {
added: ({title, prompt}: {title: string; prompt: string}) => (title ? `dodał(a) regułę agenta „${title}”: ${prompt}` : `dodano regułę agenta: ${prompt}`),
updated: ({title, prompt}: {title: string; prompt: string}) => (title ? `zaktualizowano regułę agenta „${title}” na: ${prompt}` : `zaktualizowano regułę agenta na: ${prompt}`),
- deleted: ({title}: {title: string}) => (title ? `usunął(-ę) regułę agenta „${title}”` : 'usunięto regułę agenta'),
+ deleted: ({title}: {title: string}) => (title ? `usunął regułę agenta „${title}”` : 'usunął(-ę) regułę agenta'),
},
expensifyCardRule: {
actionVerb: {block: 'zablokowane', allow: 'dozwolone'},
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 9e2eff26e55..dfab7a6d114 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -7834,7 +7834,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
createRule: 'Criar regra',
applyOffer: {
message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
- `ofereceu aplicar a regra de agente "${ruleTitle}" a ${count} ${count === 1 ? 'despesa aberta' : 'despesas abertas'}. Quer aplicá-la retroativamente?`,
+ `se ofereceu para aplicar a regra de agente "${ruleTitle}" a ${count} ${count === 1 ? 'despesa' : 'despesas'} em aberto. Quer aplicá-la retroativamente?`,
applyButtonText: 'Aplicar retroativamente',
dismissButtonText: 'Não aplicar',
},
@@ -8626,7 +8626,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
`alterou a categoria de gasto padrão de "${mccGroupName}" para "${newCategory}" (antes "${oldCategory}")`,
updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? 'ativado' : 'desativado'} o requisito de compras com cartão corporativo`,
agentRule: {
- added: ({title, prompt}: {title: string; prompt: string}) => (title ? `adicionou a regra de agente "${title}": ${prompt}` : `adicionou uma regra de agente: ${prompt}`),
+ added: ({title, prompt}: {title: string; prompt: string}) => (title ? `adicionou a regra do agente "${title}": ${prompt}` : `adicionou uma regra de agente: ${prompt}`),
updated: ({title, prompt}: {title: string; prompt: string}) =>
title ? `atualizou a regra do agente "${title}" para: ${prompt}` : `atualizou uma regra de agente para: ${prompt}`,
deleted: ({title}: {title: string}) => (title ? `removeu a regra de agente "${title}"` : 'removeu uma regra de agente'),
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index eba55d92d9e..cbb0efddd17 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -7584,9 +7584,10 @@ ${reportName}`,
gotIt: '知道了',
createRule: '创建规则',
applyOffer: {
- message: ({ruleTitle, count}: {ruleTitle: string; count: number}) => `提议将代理规则“${ruleTitle}”应用于 ${count} 笔未处理的报销。要追溯应用吗?`,
+ message: ({ruleTitle, count}: {ruleTitle: string; count: number}) =>
+ `建议将代理规则“${ruleTitle}”应用到 ${count} 个未关闭的 ${count === 1 ? '报销费用' : '报销费用'}。是否要追溯应用?`,
applyButtonText: '追溯应用',
- dismissButtonText: '不应用',
+ dismissButtonText: '不适用',
},
},
tabs: {general: '常规', cardRestrictions: '卡片限制', expenseDefaults: '报销默认设置', requireFields: '字段要求', flagForReview: '标记以供审核', agents: '代理人'},
@@ -8343,9 +8344,9 @@ ${reportName}`,
addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `已添加 ${fieldType} 报告字段“${fieldName}”${defaultValue ? ` 默认值为“${defaultValue}”` : ''}`,
updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? '已启用' : '已禁用'} 公司商务卡消费要求`,
agentRule: {
- added: ({title, prompt}: {title: string; prompt: string}) => (title ? `已添加代理规则“${title}”:${prompt}` : `已添加代理规则:${prompt}`),
- updated: ({title, prompt}: {title: string; prompt: string}) => (title ? `已将代理规则“${title}”更新为:${prompt}` : `已将代理规则更新为:${prompt}`),
- deleted: ({title}: {title: string}) => (title ? `已移除代理规则“${title}”` : '已移除代理规则'),
+ added: ({title, prompt}: {title: string; prompt: string}) => (title ? `已添加代理规则“${title}”:${prompt}` : `添加了一个代理规则:${prompt}`),
+ updated: ({title, prompt}: {title: string; prompt: string}) => (title ? `已将代理规则“${title}”更新为:${prompt}` : `将代理规则更新为:${prompt}`),
+ deleted: ({title}: {title: string}) => (title ? `已移除代理规则“${title}”` : '已移除一个代理规则'),
},
expensifyCardRule: {
actionVerb: {block: '已阻止', allow: '允许'},
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
Explanation of Change
NewDot UI for the RuleBot retroactive-apply flow (design): when an agent rule is added/edited, RuleBot posts an actionable offer in the workspace #admins room ("offered to apply the agent rule …") stating how many open expenses it would affect. This PR renders that new
ACTIONABLEAPPLYAGENTRULEreport action with Yes, apply it / No thanks buttons, following the existing actionable pattern (ActionContentRouter+ActionableItemButtons, modeled on the card fraud alert):apply/nothingresolution constants,OriginalMessageActionableApplyAgentRule(policyID/ruleID/ruleTitle/estimatedCount/resolution), and anisActionableApplyAgentRuleguard.ApplyAgentRuleContentrenders the translated offer and shows the buttons only to admins of the rule's policy while the offer is unresolved.resolveActionableApplyAgentRuleoptimistically mergesoriginalMessage.resolution(with failure rollback) and calls the newResolveActionableApplyAgentRulewrite command. Onapply, the backend queues a RuleBot run that applies the rule to the open expenses/reports; onnothingit just records the dismissal. Every admin sees the buttons collapse via a report-channel Onyx update from the backend.Companion PRs: Auth#23265 (trigger + commands), Web-Expensify#54898 (instructions + API route), plus a Web activation follow-up that turns the feature on. Until that activation PR deploys, this UI is unreachable (the backend never creates the action), so this PR is safe to merge behind it. Related rendering PR for the underlying changelog messages: #96773.
Note: this branch currently includes #96773's commits (merged in so the agent rule changelog messages render during local E2E testing of the full flow). Those changes will drop out of this PR's diff once #96773 merges to main and main is merged back in here.
Fixed Issues
$ https://github.com/Expensify/Expensify/issues/659132
PROPOSAL:
Tests
Requires the companion Auth + Web-Expensify PRs (including activation) on the backend.
Automated:
npx jest tests/unit/ReportActionsUtilsTest.ts -t isActionableApplyAgentRulecovers the new type guard.Offline tests
QA Steps
Same as tests.
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, 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.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
iOS: mWeb Safari
MacOS: Chrome / Safari