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

Refactor comment message rendering #25524

Merged
merged 28 commits into from
Nov 15, 2023
Merged

Conversation

cubuspl42
Copy link
Contributor

@cubuspl42 cubuspl42 commented Aug 19, 2023

...so the logic for rendering attachments is clearly separated from the logic for rendering textual comments.

Details

Fixing a regression caused by #24539, which was solving #24216.

Fixed Issues

$ #25415
PROPOSAL:

Tests

Same as QA Steps

Offline tests

QA Steps

  • Open any chat from LHN
  • Add an attachment
  • Create a reply in the thread for the attachment
  • Send a message in that thread
  • Go back to the parent chat (a link is in the header)
  • Delete the attachment message
  • Verify that no surrounding UI element "jumps" or behaves unexpectedly

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 / Chrome
    • iOS / native
    • iOS / 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 the left part of a conditional rendering a React component is a boolean and NOT a string, e.g. myBool && <MyComponent />.
    • 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
      • If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • 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 approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • 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 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
      • 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(themeColors.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 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.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
fix-name-jump-web.mp4
Mobile Web - Chrome
fix-name-jump-android-web-compressed.mp4
Mobile Web - Safari
fix-name-jump-ios-web.mp4
Desktop
iOS
fix-name-jump-ios.mp4
Android
fix-name-jump-android-compressed.mp4

@bernhardoj
Copy link
Contributor

Here is my thought so far...

I think the refactoring makes it clear between text and attachment fragment, but I also think the current code is good enough to handle both in one code even though the strikethrough style is currently useless for an attachment (I say currently to anticipate in the future where we can send attachment and text in one action).

// Only render HTML if we have html in the fragment
if (!differByLineBreaksOnly) {
const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && props.network.isOffline;
const editedTag = props.fragment.isEdited ? `<edited ${isPendingDelete ? 'deleted' : ''}></edited>` : '';
const htmlContent = applyStrikethrough(html + editedTag, isPendingDelete);
return <RenderHTML html={props.source === 'email' ? `<email-comment>${htmlContent}</email-comment>` : `<comment>${htmlContent}</comment>`} />;
}

So, if we ignore the refactoring, I see what we did here is moving the isAttachment condition to ReportActionItemFragment. If isAttachment is true, we will render the extra View and add the margin-top if needed.

But with the above concern, I still think it's best to put the style at the View that wraps the fragments. All of my concerns above are actually trying to be future-proof though, so let me know what you think.

btw, here is my improved solution (from what I proposed in my draft PR) that I could think of

// we gonna use the last fragment to get the `isDeletedParentAction` with the assumption if the last message is an attachment, the whole action is an attachment action.
const lastFragment = _.last(fragments);
const shouldShowDeletedMessage = (!props.network.isOffline && hasCommentThread && pendingDelete) || lastFragment.isDeletedParentAction;
const isAttachment = !shouldShowDeletedMessage && ReportUtils.isReportMessageAttachment(lastFragment);
return (
    <View style={[styles.chatItemMessage, !props.displayAsGroup && isAttachment ? styles.mt2 : {}, ...props.style]}>
        {!props.isHidden ? (_.map(fragments, (fragment, index) => (
if (props.shouldShowDeletedMessage) {
    return <RenderHTML html={`<comment>${props.translate('parentReportAction.deletedMessage')}</comment>`} />;
}

@cubuspl42
Copy link
Contributor Author

I also think the current code is good enough to handle both in one code even though the strikethrough style is currently useless for an attachment (I say currently to anticipate in the future where we can send attachment and text in one action).

I don't agree. This whole situation is getting a bit embarrassing, because we, as a team, are struggling to add a goddamn 8px margin over an attachment miniature. We two tried to fix that and we failed again.

So, no, I don't think that suggests that the surrounding code is good enough.

But with the #25524 (comment) concern, I still think it's best to put the style at the View that wraps the fragments. All of my concerns above are actually trying to be future-proof though, so let me know what you think.

Again, we're struggling to be present-proof, so I don't think we have the comfort of becoming so future-proof. Also, I feel that this whole "message as a list of fragments" model is more a kind of legacy and poor modeling than a preparation for a single message with multiple attachments.

If we assumed that all combinations of message fragments are valid, we should check if the first fragment is an attachment, by the way, and then add a margin on top of the whole message (still making assumptions about what will be rendered a bit far from that point, though).

Finally, in the worst case, if the backend actually served a message with multiple attachment COMMENT fragment, we'll have some extra 8px margins. This is not the end of the world.

I think that this whole thing already took too much time, so I really would like to at least leave the surrounding code (which I now consider the actual root cause of the whole mess) in better shape...

@bernhardoj
Copy link
Contributor

bernhardoj commented Aug 20, 2023

I think I agree to just focus on what we have now instead of trying to think too far ahead. I believe the refactor will make it easier to make any changes to a specific type of fragment (either attachment or text). So, should I go ahead now updating my PR with this refactor?

EDIT: but I think I will keep the isAttachment and attachmentInfo prop name.

@cubuspl42
Copy link
Contributor Author

@venture1981

check those 2 steps?

Sure, I'll take care of this.

But I'd prefer to have #25557 and #25560 merged first, if you don't mind.

@cubuspl42
Copy link
Contributor Author

  • Rebased on top of upstream/main (with some necessary changes)
  • Runned Prettier, which should fix the lint issues
  • I'll start doing the PR Author checklist

@cubuspl42 cubuspl42 force-pushed the fix-name-jump-1 branch 2 times, most recently from 00dbc0c to 22acd8c Compare October 2, 2023 11:13
@cubuspl42
Copy link
Contributor Author

There are some serious issues on main right now, I'll get back to it when they're resolved

...so the logic for rendering attachments is clearly separated from the
logic for rendering textual comments.

This fixes Expensify#25415
@cubuspl42 cubuspl42 marked this pull request as ready for review October 2, 2023 11:21
@cubuspl42 cubuspl42 requested a review from a team as a code owner October 2, 2023 11:21
@melvin-bot melvin-bot bot requested review from situchan and removed request for a team October 2, 2023 11:21
@situchan
Copy link
Contributor

situchan commented Oct 2, 2023

Lint is failing

@cubuspl42
Copy link
Contributor Author

Lint is failing

Damn, it should be the last one. Fixed.

@flodnv flodnv requested a review from bernhardoj October 3, 2023 06:46
@cubuspl42
Copy link
Contributor Author

There are some serious issues on main right now, I'll get back to it when they're resolved

I will resume the work on Monday (checklist, testing, etc.), sorry for the extra delay

@situchan
Copy link
Contributor

situchan commented Nov 7, 2023

Checklist completed

Copy link
Contributor

@situchan situchan left a comment

Choose a reason for hiding this comment

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

:shipit:

I tested as many cases as possible. Hopefully there wouldn't be any minor regressions after this refactor 🙏

@melvin-bot melvin-bot bot requested a review from flodnv November 7, 2023 10:49
Copy link
Contributor

@tgolen tgolen left a comment

Choose a reason for hiding this comment

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

I love the direction this is going!

src/pages/home/report/comment/RenderCommentHTML.js Outdated Show resolved Hide resolved
src/pages/home/report/comment/RenderCommentHTML.js Outdated Show resolved Hide resolved
src/pages/home/report/comment/AttachmentCommentFragment.js Outdated Show resolved Hide resolved

const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);

return (
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the difference between what is rendered here and what is rendered in RenderCommentHTML? It feels like all the stuff here should also be in its own component.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What is the difference between what is rendered here and what is rendered in RenderCommentHTML?

Uh, here we render a bunch of text, and there we render HTML? 😁

It feels like all the stuff here should also be in its own component.

It's already extracted from ReportActionItemFragment.js (you can check the red diff).

RenderCommentHTML was extracted because it is used at least twice. A pragmatic thing.

Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

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

Thanks for the changes! Just leaving a small review and saying the changes look more or less fine to me as far as I understood them.

feedback: I see there is a lot of moving of components around - this made it a bit hard for me to tell which is the change that fixes the bug. The reusable part looks to be the RenderCommentHTML (which seems fine).

I would be interested in seeing a version of this PR that fixes the bug only without additional refactoring. We gain a lot from fixing the bug and less from moving things around.

I would also request that in the future we also please update the description of the issue to clearly explain what we are trying to achieve. That way, new reviewers can quickly catch up on the context.

...so the logic for rendering attachments is clearly separated from the logic for rendering textual comments

I didn't get much from this tbh.

Hope these thoughts find you well 🙇

case 'COMMENT': {
const {html, text} = props.fragment;
const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && props.network.isOffline;
Copy link
Contributor

Choose a reason for hiding this comment

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

question: Can you remind us we are removing the props.network.isOffline 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.

Just treat is as removing the old variable completely and adding a new one with a similar name.

If you track the usages, everything is still behaving as it previously was.

A crucial place: https://github.com/Expensify/App/pull/25524/files#r1387825465

src/pages/home/report/ReportActionItemFragment.js Outdated Show resolved Hide resolved
src/pages/home/report/ReportActionItemFragment.js Outdated Show resolved Hide resolved
return <RenderHTML html={props.source === 'email' ? `<email-comment>${htmlWithTag}</email-comment>` : `<comment>${htmlWithTag}</comment>`} />;
if (isFragmentAttachment) {
return (
<AttachmentCommentFragment
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB/IMO, I think it doesn't need it's own component as it is not used anywhere else and the component we created looks very simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I created it for symmetry. I agree that it's not strictly needed, but do you think it needs to be removed now? It can grow if New Expensify goes more into a fat client architecture direction.

Copy link
Contributor

Choose a reason for hiding this comment

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

If it was me reviewing the proposal I would personally say to not bother with this so we can move forward.

I did find this:

https://github.com/Expensify/App/blame/aeb7f28091bdd8c0fffa7ed5158e41d2227d06a9/contributingGuides/REVIEWER_CHECKLIST.md#L44

But that doesn't really instruct us on when it's appropriate to create a new component.

Our style guide doesn't say much on this so the ideal thing would be to hash out what the best practice should be and then pop the outcome in the style guide so people can put their bias aside.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not much of a checklist/style guide person (I appreciate automated code formatters, though!), so let me know what can we do to resolve this thread in the context of this PR

If we were forced to add a new checkbox or something similar, I would vote for:

If there's nothing wrong with a chunk of PR code being reviewed, and there's no confidence that any alternative would be apparently or measurably better, it should be left as-is

</Text>
</>
)}
</Text>
Copy link
Contributor

Choose a reason for hiding this comment

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

noting/nab: this looks logically equivalent as far as I can tell, but let us know if there is a material change to look out for 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.

It's meant to be exactly equivalent beside the bugfix! Technically, in the old code, a lot of text-related code was also applied to attachments, but it was a no-op (as there's no text in an attachment image).

@@ -84,7 +83,7 @@ function ReportActionItemMessage(props) {
};

return (
<View style={[styles.chatItemMessage, !props.displayAsGroup && isAttachment ? styles.mt2 : {}, ...props.style]}>
<View style={[styles.chatItemMessage, ...props.style]}>
Copy link
Contributor

Choose a reason for hiding this comment

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

confirming: So, I see we no longer need this because we are handling it inside ReportActionFragment. But not sure exactly why this is being moved to that view. Is this the bug fix part?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think you meant:

because we are handling it inside AttachmentCommentFragment

Yes, this is the fix part. This margin is explicitly meant for the attachment presentation (think: an actual displayed attachment image), but because of various conditions, when the message is deleted, the booleans here can be true, while deeper inside, we take a rendering path that doesn't actually display the attachment image. Hence the "jump".

In the new suggested structure, things are more clearly separated, so it's quite obvious that a margin meant for attachment can be displayed if-and-only-if we display an actual attachment image.

src/pages/home/report/ReportActionItemFragment.js Outdated Show resolved Hide resolved
<TextCommentFragment
source={props.source}
fragment={fragment}
styleAsDeleted={isPendingDelete && props.network.isOffline}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@marcaaron ☝️

@marcaaron marcaaron dismissed their stale review November 10, 2023 21:45

Going OOO this week so removing myself as a blocker here.

@situchan
Copy link
Contributor

@cubuspl42 please fix lint.
@tgolen mind completing review?

tgolen
tgolen previously approved these changes Nov 13, 2023
@cubuspl42
Copy link
Contributor Author

@cubuspl42 please fix lint.

Done

@tgolen
Copy link
Contributor

tgolen commented Nov 15, 2023

Do we need any more reviews on this or is it OK to merge? I see lots of people as assigned reviewers :D

@situchan
Copy link
Contributor

Seems like @flodnv is assigned as official reviewer after C+ approval

Copy link
Contributor

@flodnv flodnv left a comment

Choose a reason for hiding this comment

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

Thanks for your help in reviewing this @tgolen 👍

@flodnv
Copy link
Contributor

flodnv commented Nov 15, 2023

Not waiting on @marcaaron as he's ooo.

@flodnv flodnv merged commit 48782c7 into Expensify:main Nov 15, 2023
15 checks passed
@OSBotify
Copy link
Contributor

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

@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/mountiny in version: 1.4.0-3 🚀

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

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