Skip to content

[Due for payment 2026-04-23] [$250] Report - Formula report field initial value {report:i} is not persisted & resets to {report:id} #79855

Closed

Description

@jponikarchuk

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: V9.3.4-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/test-runs/TR-3028/folder/13176707/41236876/1131712084
Email or phone of affected tester (no customers): dreamnitethedragon+yydyd373@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Redminote 10S Android 13
App Component: Workspace Settings

Action Performed:

Pre-condition: report field is enabled

  1. Launch app
  2. Go to workspace settings
  3. Tapping add field
  4. Select type - formula
  5. Tap initial value and change it to {report:i} and save
  6. Enter a name and save
  7. Created report field is not shown in list
  8. Tap add field
  9. Enter a name, select type formula and save
  10. Tap the report field created
  11. Tap initial value and change it to {report:i} and save
  12. Note reverts to original as {report:id}

Expected Result:

User should not be allowed to save an unsupported formula value OR
App should show a clear validation error for {report:i}.
If saved, the formula report field should:

  • Appear in the fields list
  • Retain {report:i} when reopened or edited

Actual Result:

App allows saving {report:i} without any validation.
The created report field does not appear in the fields list.
When editing the report field, {report:i} automatically reverts back to {report:id} without user action or warning.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Screenshots/Videos

1.mp4

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~022013597456074804416
  • Upwork Job ID: 2013597456074804416
  • Last Price Increase: 2026-02-10
  • Automatic offers:
    • linhvovan29546 | Contributor | 110521220
Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Jan 19, 2026
  2. melvin-bot commented on Jan 19, 2026

    @melvin-bot

    Triggered auto assignment to @dylanexpensify (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

  3. annaweber830 commented on Jan 19, 2026

    @annaweber830
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    User should not be allowed to save an unsupported formula value OR
    App should show a clear validation error for {report:i}.
    If saved, the formula report field should:

    • Appear in the fields list
    • Retain {report:i} when reopened or edited

    What is the root cause of that problem?

    The UI does not validate formula placeholders/tokens in the “Initial value” input. So unsupported tokens like {report:i} get submitted. The backend then normalizes/rejects/ignores unsupported formula parts (e.g., reverting to a supported default like {report:id}), leading to inconsistent UI state (missing field / unexpected value changes) without any user-facing error.

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

    • Add client-side validation for report-field formulas that rejects unsupported {report:*} tokens (including {report:i}) and shows a clear inline validation error, preventing save.
    • Apply this validation in both places users can set the initial value:
      • Create report field flow
      • Edit initial value flow
    • Add a dedicated translation string for the validation message so the UI clearly communicates what’s wrong and suggests a supported token (e.g., {report:id}).
      const validateForm = useCallback(
      (values: FormOnyxValues<typeof ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM> => {
      const {initialValue: formInitialValue} = values;
      const errors: FormInputErrors<typeof ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM> = {};
      if (formInitialValue.length > CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH) {
      errors[INPUT_IDS.INITIAL_VALUE] = translate('common.error.characterLimitExceedCounter', formInitialValue.length, CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH);
      }
    const validateForm = useCallback(
            (values: FormOnyxValues<typeof ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM> => {
                const {initialValue: formInitialValue} = values;
                const errors: FormInputErrors<typeof ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM> = {};
    
                if (formInitialValue.length > CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH) {
                    errors[INPUT_IDS.INITIAL_VALUE] = translate('common.error.characterLimitExceedCounter', formInitialValue.length, CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH);
                }
    
                if (
                    (reportField?.type === CONST.REPORT_FIELD_TYPES.TEXT || reportField?.type === CONST.REPORT_FIELD_TYPES.FORMULA) &&
                    !!formInitialValue &&
                    !errors[INPUT_IDS.INITIAL_VALUE]
                ) {
                    const unsupportedFormulaParts = getUnsupportedReportFieldFormulaParts(formInitialValue);
                    if (unsupportedFormulaParts.length > 0) {
                        errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.unsupportedFormulaValueError', {value: unsupportedFormulaParts.join(', ')});
                    }
                }
    

    if (type === CONST.REPORT_FIELD_TYPES.TEXT && !!formInitialValue && formInitialValue.length > CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH) {
    errors[INPUT_IDS.INITIAL_VALUE] = translate('common.error.characterLimitExceedCounter', formInitialValue.length, CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH);
    }

    
    if (type === CONST.REPORT_FIELD_TYPES.TEXT && !!formInitialValue && formInitialValue.length > CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH) {
                    errors[INPUT_IDS.INITIAL_VALUE] = translate('common.error.characterLimitExceedCounter', formInitialValue.length, CONST.WORKSPACE_REPORT_FIELD_POLICY_MAX_LENGTH);
                }
    
                if ((type === CONST.REPORT_FIELD_TYPES.TEXT || type === CONST.REPORT_FIELD_TYPES.FORMULA) && !!formInitialValue && !errors[INPUT_IDS.INITIAL_VALUE]) {
                    const unsupportedFormulaParts = getUnsupportedReportFieldFormulaParts(formInitialValue);
                    if (unsupportedFormulaParts.length > 0) {
                        errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.unsupportedFormulaValueError', {value: unsupportedFormulaParts.join(', ')});
                    }
                }
    

    We should add this code to WorkspaceReportFieldUtils

    type FormulaParsePart = {
        definition: string;
        type: string;
        fieldPath: string[];
        functions: string[];
    };
    function getUnsupportedReportFieldFormulaParts(initialValue?: string): string[] {
        if (!initialValue || typeof initialValue !== 'string') {
            return [];
        }
    
        // Dynamically require to avoid circular dependency with ReportActionsUtils
        const {parse, FORMULA_PART_TYPES} = require('./Formula') as {
            parse: (formula?: string) => FormulaParsePart[];
            FORMULA_PART_TYPES: {REPORT: string};
        };
    
        const supportedReportFields = new Set([
            'id',
            'status',
            'expensescount',
            'type',
            'startdate',
            'enddate',
            'total',
            'reimbursable',
            'currency',
            'policyname',
            'workspacename',
            'created',
            'submit',
            'autoreporting',
        ]);
    
        const supportedSubmitDirections = new Set(['from', 'to', 'date']);
        const supportedSubmitPersonFields = new Set(['firstname', 'lastname', 'fullname', 'email', 'userid', 'customfield1', 'payrollid', 'customfield2']);
        const supportedAutoReportingFields = new Set(['start', 'end']);
    
        const unsupported: string[] = [];
        const parts = parse(initialValue);
    
        for (const part of parts) {
            if (part.type !== FORMULA_PART_TYPES.REPORT) {
                continue;
            }
    
            const [field, ...rest] = part.fieldPath ?? [];
            const normalizedField = field?.trim().toLowerCase();
    
            if (!normalizedField || !supportedReportFields.has(normalizedField)) {
                unsupported.push(part.definition);
                continue;
            }
    
            if (normalizedField === 'submit') {
                const direction = rest.at(0)?.trim().toLowerCase();
                if (!direction || !supportedSubmitDirections.has(direction)) {
                    unsupported.push(part.definition);
                    continue;
                }
    
                // report:submit:date is valid (format is optional and validated elsewhere)
                if (direction === 'date') {
                    continue;
                }
    
                // report:submit:from:* and report:submit:to:* need a subfield
                const submitField = rest.at(1)?.trim().toLowerCase();
                if (!submitField || !supportedSubmitPersonFields.has(submitField)) {
                    unsupported.push(part.definition);
                }
                continue;
            }
    
            if (normalizedField === 'autoreporting') {
                const subField = rest.at(0)?.trim().toLowerCase();
                if (!subField || !supportedAutoReportingFields.has(subField)) {
                    unsupported.push(part.definition);
                }
            }
        }
    
        return unsupported;
    }
    

    And we should add language params:
    type UnsupportedFormulaValueErrorParams = {value: string};

    What alternative solutions did you explore? (Optional)

    We add all locales (de, fr, it, ja, nl, pl, pt-BR, zh-hans, es)

  4. abbasifaizan70 commented on Jan 19, 2026

    @abbasifaizan70
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When creating or editing a formula report field, users can enter unsupported placeholders like {report:i} without any validation. This causes:

    1. Created fields don't appear in the fields list
    2. Invalid values silently revert to {report:id} without warning
    3. No error message shown to users

    What is the root cause of that problem?

    The root cause is missing client-side validation for formula field placeholders. The app doesn't validate whether a formula uses supported placeholders (like {report:id}) or unsupported ones (like {report:i}) before attempting to save, leading to:

    1. No validation error shown to users for invalid formulas
    2. Server-side auto-correction that silently changes values without user feedback
    3. Fields not appearing in the list when invalid formulas are saved

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

    Add client-side validation to check formula placeholders before saving. The solution includes:

    1. Formula Validation Function (App/src/libs/Formula.ts)

    • Added getSupportedReportFields() function that returns a list of all supported report field placeholders: id, status, expensescount, type, startdate, enddate, total, reimbursable, currency, policyname, workspacename, created, submit, autoreporting
    • Added validateFormula() function that parses the formula and checks if all placeholders are in the supported list
    • Returns an object with isValid boolean and unsupportedPlaceholders array
    • Exports both functions for use in other components

    2. Edit Page Validation (App/src/pages/workspace/reports/ReportFieldsInitialValuePage.tsx)

    • Imported validateFormula from Formula.ts
    • Added validation check in the validateForm callback for FORMULA type fields
    • If validation fails, displays error message with the unsupported placeholders
    • Prevents saving when invalid placeholders are detected

    3. Create Page Validation (App/src/pages/workspace/reports/CreateReportFieldsPage.tsx)

    • Imported validateFormula from Formula.ts
    • Added same validation logic in the validateForm callback when creating new formula fields
    • Shows validation error before field creation

    4. Translation Support (App/src/languages/params.ts)

    • Added InvalidFormulaErrorParams type with placeholders: string property
    • Exported the type for use in language files

    5. Error Messages (All language files)

    • Added invalidFormulaError translation string to en.ts and all other language files (es.ts, pt-BR.ts, de.ts, fr.ts, it.ts, ja.ts, nl.ts, pl.ts, zh-hans.ts)
    • Message displays which placeholders are invalid and suggests supported ones like {report:id}, {report:name}, {report:created}
    • Imported InvalidFormulaErrorParams type in all language files

    Test Branch

    #79899

    Demo

    Image
    Image
  5. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Jan 20, 2026
  6. changed the title [-]Report - Formula report field initial value {report:i} is not persisted & resets to {report:id}[/-] [+][$250] Report - Formula report field initial value {report:i} is not persisted & resets to {report:id}[/+] on Jan 20, 2026
  7. melvin-bot commented on Jan 20, 2026

    @melvin-bot
  8. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Jan 20, 2026
  9. melvin-bot commented on Jan 20, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @dominictb (External)

  10. yatin-italy commented on Jan 20, 2026

    @yatin-italy

    Proposal

    Restate the problem this issue is trying to solve.
    Formula report fields let unsupported {report:*} placeholders (e.g., {report:i}) be saved; the server normalizes or drops them, so the field disappears from the list and the value reverts without warning on reopen.

    What is the root cause of that problem?
    Shared policy actions (createReportField/updateReportFieldInitialValue in src/libs/actions/Policy/ReportField.ts) never validate report-formula placeholders—they optimistically persist any string and only infer type from brace presence. Unsupported tokens reach the API, which mutates/drops them, violating the guarantee that persisted initial values use only supported formula tokens.

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

    • Enforce this rule:
      “A workspace report field initial value is saved only if all {report:*} placeholders are supported by the Formula evaluator.”

    • Add validation in Policy/ReportField.ts.
      Use Formula.parse and FORMULA_PART_TYPES.
      Detect unsupported report placeholders like {report:i} or invalid subfields.
      Run this check before any optimistic update or API call.

    • Show an error on the form when validation fails.
      Use Localize.translate('common.error.invalidValue', …).
      Set the error on INPUT_IDS.INITIAL_VALUE with FormActions.setErrors.
      Do not add new translation strings.

    • Export the helper for tests.
      Export getUnsupportedReportFormulaParts.
      Do not change its logic.

    • Add unit tests.
      Reject {report:i}.
      Allow {report:id} and {report:submit:from}.

    Exact code deltas (snippets, no filepaths)

    import Localize from '@libs/Localize';
    
    // ...setErrors(
    ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM,
    {
        [INPUT_IDS.INITIAL_VALUE]: Localize.translate('common.error.invalidValue', {
            expectedValues:
                'report:{id,status,expensescount,type,startdate,enddate,total,reimbursable,currency,policyname,workspacename,created,submit:{from|to|date},autoreporting:{start|end}}',
        }),
    }
    );
    // In Policy/ReportField.ts
    export function getUnsupportedReportFormulaParts(initialValue?: string): string[] {
        // existing logic to parse and collect unsupported report parts
    }
    // tests/unit/PolicyReportFieldTest.ts
    import {getUnsupportedReportFormulaParts} from '@libs/actions/Policy/ReportField';
    
    describe('getUnsupportedReportFormulaParts', () => {
        it('rejects unsupported report token', () => {
            expect(getUnsupportedReportFormulaParts('{report:i}')).toEqual(['{report:i}']);
        });
    
        it('allows supported report tokens', () => {
            expect(getUnsupportedReportFormulaParts('{report:id}')).toEqual([]);
            expect(getUnsupportedReportFormulaParts('{report:submit:from}')).toEqual([]);
        });
    });

    What alternative solutions did you explore? (Optional)
    N/A

  11. melvin-bot commented on Jan 24, 2026

    @melvin-bot

    @dominictb Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

  12. 60 remaining items

  13. melvin-bot commented on Apr 16, 2026

    @melvin-bot

    Reviewing label has been removed, please complete the "BugZero Checklist".

  14. melvin-bot commented on Apr 16, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.59-4 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-04-23. 🎊

    For reference, here are some details about the assignees on this issue:

  15. melvin-bot commented on Apr 16, 2026

    @melvin-bot

    @linhvovan29546 The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]

  16. melvin-bot commented on Apr 21, 2026

    @melvin-bot

    @linhvovan29546 Eep! 4 days overdue now. Issues have feelings too...

  17. melvin-bot commented on Apr 22, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  18. linhvovan29546 commented on Apr 23, 2026

    @linhvovan29546
    Contributor

    BugZero Checklist:

    • [Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.

      Link to the comment on the PR: N/A - This was not a regression. The backend already validates unsupported formula tokens, but the FW never had client-side validation for formula report field initial values. This is a missing validation edge case, not a bug introduced by a specific PR.
      Link to the comment on the Issue: N/A

    • [Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.

      Link to discussion: N/A - Not critical

    • [Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.

    • [BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.

      Link to issue: https://github.com/Expensify/Expensify/issues/627940

    Regression Test Proposal

    Precondition:

    • A workspace with report fields enabled
    • User is logged in and has access to workspace settings

    Test:

    1. Go to workspace settings > Report Fields
    2. Tap "Add field"
    3. Select type: Formula
    4. Tap "Initial value" and enter {report:i}
    5. Verify a validation error is shown: "Formula field {report:i} not recognized"
    6. Change the initial value to {report:id} and save
    7. Verify the field is created successfully and appears in the fields list
    8. Tap the created field
    9. Tap "Initial value" and change it to {report:unknown}
    10. Verify a validation error is shown: "Formula field {report:unknown} not recognized"
    11. Change value back to a valid formula (e.g. {report:title}) and save
    12. Verify the field retains the value when reopened

    Do we agree 👍 or 👎

    Zapier Logs Run ID: 00040eee-ca59-a16b-0126-699fb97020d4
  19. mallenexpensify commented on Apr 23, 2026

    @mallenexpensify
    Contributor

    @annaweber830 can you please accept the job below? Please reply here and tag me once you have.
    https://www.upwork.com/jobs/~022047381003309283930

    @twisterdotcom can you pay @annaweber830 once they've accepted the job? Can you also pay @linhvovan29546 ? Their contract is here. Thx

  20. twisterdotcom commented on Apr 24, 2026

    @twisterdotcom
    Contributor

    Payment Summary:

  21. mallenexpensify commented on Apr 24, 2026

    @mallenexpensify
    Contributor

    @annaweber830 paid, summary updated above. Thx.

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions