Skip to content

[Category Visibility] 1.18 [EC][App] Show category visibility change logs in #admins #105085

Open

Description

@chiragsalian

Here is the,

Before starting: review your section of the design doc first. If it has any pending comments, let's discuss them before work begins so everyone is on the same page.

PURPOSE

Render the POLICYCHANGELOG_SET_CATEGORY_VISIBILITY entries Auth posts to #admins (1.17), in the format "{set|changed} {members} to {only} see {categories}", with long lists shown in full at the top of the entry's thread.
Auth sends each entry as data (categoryNames, accountIDs, hadRestriction, reason), not as text. The App builds the sentence when it renders the entry, like every other category change log, so it's translated into the viewer's language and shows members' current names.
A mock of how the App will display this is,

SOLUTION

  • Add SET_CATEGORY_VISIBILITY: 'POLICYCHANGELOG_SET_CATEGORY_VISIBILITY' to CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.
  • Add categoryNames?: string[], accountIDs?: number[], hadRestriction: boolean and reason: 'setByAdmin' | 'categoriesDeleted' | 'categoriesDisabled' to OriginalMessagePolicyChangeLog.
  • Add the new action to isCategoryModificationAction, so the LHN preview and copy-to-clipboard use the same message, and map it to categoryResolver in POLICY_CHANGE_LOG_RESOLVERS.
  • In getWorkspaceCategoryUpdateMessage, add a branch for the new action, before the fallback here:
    • Build the categories text:
      • When categoryNames is []: no categories text, since the entry uses clearedCategoryVisibility.
      • When it has 10 or more: categoryCount ("14 categories").
      • Otherwise: each name in quotes, joined with formatList, which adds the serial "and", as oneCategory for one name (the "Meals" category) or someCategories for more (the "Meals" and "Travel" categories).
    • Build the members text the same way: "everyone" for [], "{count} members" for 10 or more, otherwise display names from getDisplayNameForParticipant joined with formatList.
    • When the entry is shown as its thread's parent action (ReportActionItemParentAction), return the full lists instead: a "Categories: …" line and/or a "Members: …" line, one for each list with 10 or more entries, naming every item. Pass this as a new shouldShowFullList param of getWorkspaceCategoryUpdateMessage, set only from that component.
    • Otherwise, return setCategoryVisibility ("set {members} to only see {categories}"), or changedCategoryVisibility ("changed …") when hadRestriction is true. When categoryNames is [], return clearedCategoryVisibility ("changed {members} to see all categories") instead. Follow it with the reason sentence for categoriesDeleted and categoriesDisabled.
  • Add the copy to workspaceActions in en.ts. We'll handle the translations for the other languages in the GitHub issue.
    • setCategoryVisibility: "set {members} to only see {categories}", changedCategoryVisibility: "changed {members} to only see {categories}", clearedCategoryVisibility: "changed {members} to see all categories"
    • oneCategory: "the {name} category", someCategories: "the {list} categories", everyone: "everyone", categoryCount: "{count} categories", memberCount: "{count} members"
    • categoryVisibilityCategoriesList: "Categories: {list}", categoryVisibilityMembersList: "Members: {list}"
    • categoryVisibilityReasonDeleted: ", since their only category was deleted. Update their category visibility if needed."
    • categoryVisibilityReasonDisabled: ", since their only category was disabled. Update their category visibility if needed."

Examples, matching the 1.17 entries:

Change #admins entry Top of its thread
Admin restricts Bob, who had no restriction, to Meals and Fuel set Bob Sterling to only see the "Meals" and "Fuel" categories None
Admin gives Bob, who was restricted, every enabled category changed Bob Sterling to see all categories None
Admin restricts every member to Meals and Fuel, when no one had a restriction set everyone to only see the "Meals" and "Fuel" categories None
Admin changes Cathy's restriction to 14 categories changed Cathy Langford to only see 14 categories Categories: "Advertising", "Airfare", "Benefits", ...
Admin restricts 12 members, none restricted before, to Fees set 12 members to only see the "Fees" category Members: Alice Monroe, Ben Carter, Bob Sterling, ...
Bob's only category, Fuel, is deleted changed Bob Sterling to see all categories, since their only category was deleted. Update their category visibility if needed. None
Bob's only category, Fuel, is disabled by a sync changed Bob Sterling to see all categories, since their only category was disabled. Update their category visibility if needed. None

MANUAL TEST STEPS

  • Restrict an unrestricted member to Meals and Fuel, and confirm #admins shows "set {member} to only see the "Meals" and "Fuel" categories", and the LHN preview shows the same text.
  • Restrict them to only Meals, and confirm the entry reads "changed {member} to only see the "Meals" category".
  • Give the member every enabled category, and confirm the entry reads "changed {member} to see all categories".
  • Restrict a member to 10 or more categories, and confirm the entry shows the count, and opening its thread shows every category at the top.
  • Delete a member's only visible category, and confirm the admin's entry reads "changed {member} to see all categories, since their only category was deleted. Update their category visibility if needed."
Issue OwnerCurrent Issue Owner: @daledah

Activity

  1. self-assigned this
    on Oct 26, 2026
  2. self-assigned this
    on Oct 30, 2026
  3. huult commented on Oct 30, 2026

    @huult
    Contributor

    Per my discussion with @chiragsalian and the comment here, I'll assign this ticket to myself as a C+.

  4. huult commented on Oct 30, 2026

    @huult
    Contributor

    This ticket can be implemented by MelvinBot. @MelvinBot, please post a proposal.

  5. MelvinBot commented on Oct 30, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    POLICYCHANGELOG_SET_CATEGORY_VISIBILITY is a new action type that arrives as data, not text. The App has no constant for it, no originalMessage fields, and no branch in getWorkspaceCategoryUpdateMessage. Any unknown category action falls through to getReportActionText(action) at line 3922.

    What changes do you think we should make in order to solve the problem?

    I'll follow the issue's solution, with three adjustments based on the current code.

    1. Constant and type. Add SET_CATEGORY_VISIBILITY to POLICY_CHANGE_LOG. Add categoryNames, accountIDs, hadRestriction and reason to OriginalMessagePolicyChangeLog.
    2. Routing. Add the action to isCategoryModificationAction. That gives the LHN preview, thread title and copy-to-clipboard the short message. Map it to categoryResolver in POLICY_CHANGE_LOG_RESOLVERS.
    3. Message. Add a branch before the fallback in getWorkspaceCategoryUpdateMessage:
      • Categories: none for [], categoryCount for 10 or more, otherwise quoted names joined with formatList as oneCategory or someCategories.
      • Members: everyone for [], memberCount for 10 or more, otherwise getDisplayNameForParticipant names joined with formatList.
      • Return setCategoryVisibility, changedCategoryVisibility (when hadRestriction) or clearedCategoryVisibility (when categoryNames is []). Append the reason sentence for categoriesDeleted and categoriesDisabled.
      • When shouldShowFullList is true, return the Categories: … and/or Members: … lines for any list with 10 or more items.
    4. Full list at the top of the thread. The adjustment: don't add a new flag in ReportActionItemParentAction. Reuse the existing isThreadReportParentAction prop. AncestorReportActionItem already sets it for the thread parent. Pass it from ReportActionItem to ActionContentRouter, then to PolicyChangeLogContent, then through categoryResolver as shouldShowFullList.
    5. Copy. Add the keys listed in the issue to workspaceActions in en.ts. Two adjustments:
      • Put the quotes in the translation, not in shared code (for example, a quotedCategoryName key). Each locale can then use its own marks: "…" in en, „…“ in de, « … » in fr.
      • The other 10 locales are typed against en, so they need the keys too or typecheck fails. I'll add them and let the translation workflow fill in the copy.
    6. Tests. Add cases for every row of the issue's example table to the getWorkspaceCategoryUpdateMessage tests. Also guard the branch against an empty originalMessage, because PolicyChangeLogContentTest renders every type with {}.
    Open question for the C+ and internal engineer

    AncestorReportActionItem sets isThreadReportParentAction for every ancestor shown above a thread, not only the direct parent. So a nested thread would show the full list for this entry too. That seems correct, but please confirm.

    What alternative solutions did you explore? (Optional)

    Adding a new shouldShowFullList prop starting in ReportActionItemParentAction, as the issue suggests. It would duplicate isThreadReportParentAction, which already flows to ReportActionItem.


    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/105085) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run

  6. huult commented on Oct 30, 2026

    @huult
    Contributor

    @MelvinBot implement this to create a draft PR.

  7. MelvinBot commented on Oct 30, 2026

    @MelvinBot
    Contributor

    🤖 Draft PR created: Show category visibility change logs in #admins
    Please mark it as "Ready for review" or ask me to do that for you when it is ready for review.

    It follows the proposal. Two things need a human:

    • Translations: I added first-pass copy for the 10 non-English locales so typecheck passes. Please review or regenerate it.
    • Testing: unit tests cover every row of the issue's example table. I didn't test in the UI, because the backend doesn't send this action yet. The Tests and QA sections are left for you.

    view run

  8. self-assigned this
    on Nov 5, 2026
  9. daledah commented on Nov 5, 2026

    @daledah
    Contributor

    I assigned myself since I’m also the C+ on the PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions