Skip to content
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

Add Task Title Validation on Main Composer Text Change #52941

Conversation

wildan-m
Copy link
Contributor

@wildan-m wildan-m commented Nov 22, 2024

Explanation of Change

This PR will add task title validation on main composer text change to prevent server errors after submission.

Fixed Issues

$ #50398
PROPOSAL: #50398 (comment)

Tests

  1. Open app
  2. Open a chat in Main Composer
  3. Enter [] task name with more than 100 character (e.g. 101 Character Text below)
  4. Verify that submit button disabled and error shown with 100 number The maximum task title length is 100 characters.
  5. Remove one character
  6. Verify that submit button enabled and not max length error
  7. Submit the text
  8. Verify that no error from server after submit
  9. Focus back to main composer and type until 10001 character (or paste 10001 Character Text below)
  10. Verify that submit button disabled and error shown with 10000 number The maximum comment length is 10000 characters.
  11. Remove one character
  12. Verify that submit button enabled and not max length error
  13. Submit the text
  14. Verify that no error from server after submit
  • Verify that no errors appear in the JS console
101 Character Text
[] Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma
10001 Character Text
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae

Offline tests

Same as Test

QA Steps

Same as Test

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Kapture.2024-11-22.at.10.44.37.mp4
Android: mWeb Chrome
Kapture.2024-11-22.at.10.47.30.mp4
iOS: Native
Kapture.2024-11-22.at.10.33.03.mp4
iOS: mWeb Safari
Kapture.2024-11-22.at.10.34.57.mp4
MacOS: Chrome / Safari
Kapture.2024-11-22.at.10.25.10.mp4
MacOS: Desktop
Kapture.2024-11-22.at.10.51.48.mp4

@wildan-m wildan-m requested a review from a team as a code owner November 22, 2024 03:53
@melvin-bot melvin-bot bot requested review from hoangzinh and removed request for a team November 22, 2024 03:53
Copy link

melvin-bot bot commented Nov 22, 2024

@hoangzinh 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]

@ChavdaSachin

This comment was marked as off-topic.

return false;
}, []);

const validateTitleMaxLength = useMemo(() => debounce(handleValueChange, 100, {leading: true}), [handleValueChange]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add debounce time to this constant list?

App/src/CONST.ts

Line 1320 in 056e8ee

LIST_SCROLLING_DEBOUNCE_TIME: 200,

Btw, should we increase it a little bit? Oh there is an existing debounce time for comment length https://github.com/Expensify/App/blob/056e8ee563b1136ba5749988d89723fad467c7fd/src/CONST.ts#L1314C9-L1314C37

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -399,9 +408,12 @@ function ReportActionCompose({
if (value.length === 0 && isComposerFullSize) {
Report.setIsComposerFullSize(reportID, false);
}
if (validateTitleMaxLength(value)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a debounce function, can we trust it to return true/false value here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.geeksforgeeks.org/lodash-_-debounce-method/

We set leading true, it will immediately executed at the initial attempt

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I missed this comment. Hmm. I don't think it's a good idea to check conditions based on result of a debounce function. It's better if we detect comment format and use properly validation function here. For example:

const isCreatingTaskComment = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION);
if (isCreatingTaskComment) {
   validateTitleMaxLength(value)
} else {
   validateCommentMaxLength(value, {reportID});
}

what do you think?

Comment on lines 178 to 183
let exceededMaxLength = null;
if (hasExceededMaxTitleLength) {
exceededMaxLength = CONST.TITLE_CHARACTER_LIMIT;
} else if (hasExceededMaxCommentLength) {
exceededMaxLength = CONST.MAX_COMMENT_LENGTH;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking should we wrap exceededMaxLength into a useState and useEffect hook?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh Agree, the PR has been updated

@wildan-m
Copy link
Contributor Author

Thanks for your suggestion @ChavdaSachin

IMO error message should be improved.
It should not confuse the user that why suddenly max comment length dropped to 100!

@hoangzinh what is your opinion?

@hoangzinh
Copy link
Contributor

Yep, I think it's a good idea. Can you raise it in the original issue? So we can discuss it with the internal engineer.

@wildan-m
Copy link
Contributor Author

I think we can call @Expensify/design here. Can I do that or it should be called by internal?

@wildan-m
Copy link
Contributor Author

@Expensify/design
@shawnborton @dubielzyk-expensify @dannymcclain

Based on this comment #52941 (comment), do we need to distinguish validation text other than its length number?

@dannymcclain
Copy link
Contributor

I think we could just update the one for the task situation to read:

The maximum task title length is 100 characters.

Thoughts?

@marcaaron
Copy link
Contributor

@ChavdaSachin Let's please keep any conversations on PRs limited to the internal reviewers and assignees.

@ChavdaSachin

This comment was marked as off-topic.

@marcaaron marcaaron requested a review from hoangzinh November 22, 2024 21:07
wildan-m and others added 2 commits November 23, 2024 07:56
…x/50398-fix-max-length-validation-for-task
Co-authored-by: Marc Glasser <[email protected]>
@wildan-m
Copy link
Contributor Author

Waiting translation confirmation https://expensify.slack.com/archives/C01GTK53T8Q/p1732322764789539

@hoangzinh
Copy link
Contributor

I think we could just update the one for the task situation to read:

The maximum task title length is 100 characters.

Thoughts?

Looks good to me. Thanks @dannymcclain

…x/50398-fix-max-length-validation-for-task
@wildan-m
Copy link
Contributor Author

@@ -399,9 +408,12 @@ function ReportActionCompose({
if (value.length === 0 && isComposerFullSize) {
Report.setIsComposerFullSize(reportID, false);
}
if (validateTitleMaxLength(value)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I missed this comment. Hmm. I don't think it's a good idea to check conditions based on result of a debounce function. It's better if we detect comment format and use properly validation function here. For example:

const isCreatingTaskComment = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION);
if (isCreatingTaskComment) {
   validateTitleMaxLength(value)
} else {
   validateCommentMaxLength(value, {reportID});
}

what do you think?

@@ -172,6 +173,8 @@ function ReportActionCompose({
* Shows red borders and prevents the comment from being sent
*/
const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength();
const {hasExceededMaxTitleLength, validateTitleMaxLength} = useHandleExceedMaxTaskTitleLength();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const {hasExceededMaxTitleLength, validateTitleMaxLength} = useHandleExceedMaxTaskTitleLength();
const {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength} = useHandleExceedMaxTaskTitleLength();

Can we name it more specific to task title? 🤔

Copy link
Contributor Author

@wildan-m wildan-m Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name it more specific to task title?

sure

Copy link
Contributor Author

@wildan-m wildan-m Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a good idea to check conditions based on result of a debounce function. It's better if we detect comment format and use properly validation function here

@hoangzinh In my opinion using a separate variable like isCreatingTaskComment would be redundant as we already perform regex matching in useHandleExceedMaxTaskTitleLength. If we implement leading true, it should be safe. But let me know if we still need to do that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can move regrex matching out of useHandleExceedMaxTaskTitleLength hook. And add another wrapper debounce for validation methods like this. I think it works better. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh after test it might looks like this

src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx

    const onValueChange = useCallback(
        (value: string) => {
            if (value.length === 0 && isComposerFullSize) {
                Report.setIsComposerFullSize(reportID, false);
            }

            const taskCommentMatch = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION);
            if (taskCommentMatch) {
                const title = taskCommentMatch?.[3] ? taskCommentMatch[3].trim().replace(/\n/g, ' ') : '';
                validateTaskTitleMaxLength(title);
            } else {
                validateCommentMaxLength(value, {reportID});
            }
        },
        [isComposerFullSize, reportID, validateCommentMaxLength, validateTaskTitleMaxLength],
    );

src/hooks/useHandleExceedMaxTaskTitleLength.ts

const useHandleExceedMaxTaskTitleLength = () => {
    const [hasExceededMaxTaskTitleLength, setHasExceededMaxTitleLength] = useState(false);

    const handleValueChange = useCallback((title: string) => {
        const exceeded = title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false;
        setHasExceededMaxTitleLength(exceeded);
    }, []);

    const validateTaskTitleMaxLength = useMemo(() => debounce(handleValueChange, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, {leading: true}), [handleValueChange]);

    return {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength};
};

proceed with the above change? or alternatively we can keep current useHandleExceedMaxTaskTitleLength logic but no need to debounce it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right @wildan-m . Hmm, what do you think if we refactor it a bit:

  1. Move 1500 as a constant
  2. Remove debounce in 2 above hooks
  3. Add a debounce where it's used.

So in main-composer, we will use 1500 as a debounce time for both plain function validations above. And in edit composer, we will have another debounce with 1500ms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh if we're going to extract the debounce and not need faster debounce specific for task title, how about keeping the regex matching inside the hook?

const useHandleExceedMaxTaskTitleLength = () => {
    const [hasExceededMaxTaskTitleLength, setHasExceededMaxTitleLength] = useState(false);

    const validateTaskTitleMaxLength = useCallback((value: string) => {
        const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION);
        if (match) {
            const title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined;
            const exceeded = title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false;
            setHasExceededMaxTitleLength(exceeded);
            return true;
        }
        setHasExceededMaxTitleLength(false);
        return false;
    }, []);

    return {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength};
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can @wildan-m, but it's kind of wrong in a case, let's say validateTaskTitleMaxLength returns false. It also means it's a task creating comment, however, the length is still in capacity. But then we have to use another validation of validateCommentMaxLength, which is unnecessary and incorrect because it's a task creating comment but we use a standard max length validation on it. Kind of double validations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option we can consider refactoring existing validateCommentMaxLength by passing max_length.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh the code has been updated. I use and modify COMMENT_LENGTH_DEBOUNCE_TIME since it is not being used anywhere.

Comment on lines 313 to 321
if (hasExceededMaxTitleLength) {
setExceededMaxLength(CONST.TITLE_CHARACTER_LIMIT);
return;
}
if (hasExceededMaxCommentLength) {
setExceededMaxLength(CONST.MAX_COMMENT_LENGTH);
return;
}
setExceededMaxLength(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (hasExceededMaxTitleLength) {
setExceededMaxLength(CONST.TITLE_CHARACTER_LIMIT);
return;
}
if (hasExceededMaxCommentLength) {
setExceededMaxLength(CONST.MAX_COMMENT_LENGTH);
return;
}
setExceededMaxLength(null);
if (hasExceededMaxTitleLength) {
setExceededMaxLength(CONST.TITLE_CHARACTER_LIMIT);
} else if (hasExceededMaxCommentLength) {
setExceededMaxLength(CONST.MAX_COMMENT_LENGTH);
} else {
setExceededMaxLength(null);
}

I think we can use nested if/else to read code easier here. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh Although the lint check will pass, I believe we may prefer an early return?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer an early return if it's only 1 if. For example

if (true) {
   // then do something
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see, updated

Copy link
Contributor

@hoangzinh hoangzinh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks good. Can you check perf-tests failure? Thank you.

setIsCreatingTaskComment(!!taskCommentMatch);
if (taskCommentMatch) {
const title = taskCommentMatch?.[3] ? taskCommentMatch[3].trim().replace(/\n/g, ' ') : '';
validateTaskTitleMaxLength(title);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of creating a new state isCreatingTaskComment, should we reset hasExceededMaxCommentLength=false here. Also set hasExceededMaxTaskTitleLength in below condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh sure, updated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why perf-tests failed, it pass in local
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot to set baseline, here is the result

image

@hoangzinh do you know what that means? It seems our change is causing extra rendering, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be because our new deboundValidate haven't been wrapped in useCallback?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it's because of useDebounce is not ready yet when reload the page. Okay, let back to use useMemo and useCallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh We still utilize lodashDebounce. please let me know if you have other feedback

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes, it's correct. I'm assuming it would be same as #52941 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh bump

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops sorry, I thought this PR was not ready for next review. I will review it today

@hoangzinh
Copy link
Contributor

hoangzinh commented Dec 6, 2024

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: Native
Screen.Recording.2024-12-06.at.17.27.33.android.mov
Android: mWeb Chrome
Screen.Recording.2024-12-06.at.17.44.32.android.chrome.mov
iOS: Native
Screen.Recording.2024-12-06.at.17.50.18.mov
iOS: mWeb Safari
Screen.Recording.2024-12-06.at.17.46.27.ios.safari.mov
MacOS: Chrome / Safari
Screen.Recording.2024-12-06.at.17.17.49.web.mov
MacOS: Desktop
Screen.Recording.2024-12-06.at.17.22.22.desktop.mov

Copy link
Contributor

@hoangzinh hoangzinh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@marcaaron marcaaron merged commit c7f4e6c into Expensify:main Dec 9, 2024
17 of 24 checks passed
@OSBotify
Copy link
Contributor

OSBotify commented Dec 9, 2024

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

Copy link
Contributor

🚀 Deployed to staging by https://github.com/marcaaron in version: 9.0.74-0 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅
🤖🔄 android HybridApp 🤖🔄 success ✅
🍎🔄 iOS HybridApp 🍎🔄 success ✅

Copy link
Contributor

🚀 Deployed to production by https://github.com/luacmartins in version: 9.0.74-8 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅
🤖🔄 android HybridApp 🤖🔄 failure ❌
🍎🔄 iOS HybridApp 🍎🔄 success ✅

* Group 2: Optional email group between \s+....\s* start rule with @+valid email or short mention
* Group 3: Title is remaining characters
*/
TASK_TITLE_WITH_OPTONAL_SHORT_MENTION: `^\\[\\]\\s+(?:@(?:${EMAIL_WITH_OPTIONAL_DOMAIN}))?\\s*([\\s\\S]*)`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants