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

[WIP] refactor: load limited-time offer signal on the client side #14807

Closed

Conversation

starsirius
Copy link
Member

@starsirius starsirius commented Nov 5, 2024

The type of this PR is: Refactor

This PR solves EMI-2161

Opening a draft PR from the spike. Will need polish and tests, and I realized there are more to be discussed/done, but would like to align on overall direction. 🙏

Description

This loads the partner offer collector signal on the client-side, with the goal of being able to cache artwork grids more comprehensively. It follows the migration pattern by moving the data needing authentication from FragmentContainer to a deferred QueryRenderer.

It's more nuanced than a straight migration because the authenticated data can be displayed in-between server-side rendered content, and "inserting" the data on the client side after page renders can lead to content shift. In particular for limited-time offer signals, there 3 pieces of information:

  • The "Limited-time offer" badge
  • Partner offered price that replaces the publicly listed price
  • Partner offer expiration

What about the badge that's in the middle of an artwork card?

To avoid content shift for the badge, we agreed on migrating to the new artwork card design on app which combines the badge into the fifth line.

Screenshot 2024-10-11 at 8 41 33 AM

How about the final price that we don't know until fetching partner offers?

To support rendering the client-side loaded data, specifically for partner offered price that replaces the listed price, I spiked on 2 approaches and the team agreed on option 1, simply overwriting server-rendered data when client-side data come back. See below:

Slack discussion and recordings about the 2 options

  • Option 1 (review app): Render publicly listed price on the backend and dynamically overwrite it after fetching partner offer on the client side.

    client-side-signal-overwrite

  • Option 2 (review app): Defer rendering entire pricing until partner offer is fetched on the client side.

    client-side-signal-deferred

Ah, and I realized there are more... 😓

The limited-time offer signal competes with the "increased interest" and "curator's pick", meaning that an artwork can have multiple badges and we'll only show one, based on defined precedence, in which the limited-time offer is the highest. I guess I'll need to start a new discussion about it...

Screenshot 2024-11-05 at 4 21 44 PM

@artsy/emerald-devs

}
const LINE_HEIGHT = 22
const NUM_OF_LINES = 5
const CONTAINER_HEIGHT = LINE_HEIGHT * NUM_OF_LINES
Copy link
Member Author

Choose a reason for hiding this comment

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

The current implementation requires logic to make sure we have consistent number of lines (e.g. if there is no badge, we will pad with an empty line). Since we agreed on fixed 5 lines, I feel it's easier to set a fixed container height and just add lines to it, without maintaining the # of lines logic which gets a bit tricky with client-side loaded content.

)
}

const PartnerOfferLine: React.FC<PartnerOfferLineProps> = ({ artwork }) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the combined fifth line that's now fetched and rendered on the client side.

fallback: object
}

const PartnerOfferedPrice: React.FC<PartnerOfferedPriceProps> = ({
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the artwork price line which takes the server-side rendered prices (publicly listed price) as the placeholder and dynamically overwrite it, if there is a partner offer fetched on the client side.

I think I'll need to rename this component to make it clear.

mzikherman
mzikherman previously approved these changes Nov 5, 2024
Copy link
Contributor

@mzikherman mzikherman left a comment

Choose a reason for hiding this comment

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

This looks great! Fairly straightforward and nothing 'weird' - good and simple.

I think there's the primaryLabel thing which is what you're referring to in terms of determining precedence to only display one label. My initial thought is that we should be ok w/ sometimes displaying two labels, since we dont know if the offer label exists in the context of caching. Perhaps that means the design needs to change (partner offer collector signal represented differently)?

@@ -430,12 +403,6 @@ export const DetailsFragmentContainer = createFragmentContainer(Details, {
registrationEndsAt
onlineBiddingExtended
}
partnerOffer {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hooray!

I wonder if primaryLabel needs to also be removed here (or updated in MP), as I think it can return the personalized 'offer' label. Or is this perhaps what you refer to in the PR description about the precedence of only displaying one label at most?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I decided to remove partner offer from the primary label field for that reason. After artsy/metaphysics#6220, the primary label will be fully cacheable. I think it's also more aligned with the pattern that separates authenticated data out from non-auth data.

I shared more context on Slack.

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool - makes sense. I think you're right that schema-wise - of course it's a case-by-case basis - but not mixing authenticated and cacheable data as part of the resolving of the same field (in this case collectorSignals under the Artwork type) - is something we should be doing.

I think collector signals might be the only case of that really (🤞), as typically an entire field would be either authenticated or not - and not a more generic-y mix of both as collector signals is.

placeholder={<SaleMessage artwork={artwork} />}
variables={{ id }}
render={({ error, props }) => {
const publicPrice = <SaleMessage artwork={artwork} />
Copy link
Contributor

Choose a reason for hiding this comment

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

clever!

}

return (
<PartnerOfferedPriceFragmentContainer
Copy link
Contributor

Choose a reason for hiding this comment

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

Can maybe include a subtle/nice little 'pulse' animation like in

@mzikherman
Copy link
Contributor

mzikherman commented Nov 5, 2024

The limited-time offer signal competes with the "increased interest" and "curator's pick", meaning that an artwork can have multiple badges and we'll only show one, based on defined precedence, in which the limited-time offer is the highest. I guess I'll need to start a new discussion about it...

My initial thought is that we should be ok w/ sometimes displaying two labels, since we dont know if the offer label exists in the context of caching. Perhaps that means the design needs to change (partner offer collector signal represented differently)?

Maybe another way of thinking about it schema/product/design-wise, is that 'collector signals' are always public, cached, show up how they show up - the extra line. You know before rendering if that line / any works in a rail or grid will have them. And then the 'offer'/user-specific messaging has a different appearance entirely, maybe isn't even called a 'collector signal' in our schema and is modeled differently, and is also allowed to show up in addition to the regular public collector signal (maybe as a 'badge' in the corner of the image - not sure the best design here). Do you know if there are any plans for other personalized signals/messaging? That may help inform direction.

Copy link

relativeci bot commented Nov 11, 2024

#943 Bundle Size — 9.51MiB (-0.35%).

c172c74(current) vs ffe4f84 main#484(baseline)

Important

Bundle introduced 1 and removed 4 duplicate packages – View changed duplicate packages

Warning

Bundle introduced 3 new packages: web-vitals, @sentry-internal/browser-utils, stylis – View changed packages

Bundle metrics  Change 9 changes Regression 1 regression Improvement 3 improvements
                 Current
#943
     Baseline
#484
Improvement  Initial JS 3.83MiB(-3.07%) 3.95MiB
No change  Initial CSS 0B 0B
Change  Cache Invalidation 84.09% 2.04%
Change  Chunks 140(-2.1%) 143
Change  Assets 144(-1.37%) 146
Change  Modules 5671(+0.6%) 5637
Regression  Duplicate Modules 472(+3.74%) 455
Change  Duplicate Code 6.18%(+5.1%) 5.88%
Improvement  Packages 282(-3.09%) 291
Improvement  Duplicate Packages 39(-7.14%) 42
Bundle size by type  Change 2 changes Improvement 2 improvements
                 Current
#943
     Baseline
#484
Improvement  JS 9.28MiB (-0.35%) 9.31MiB
Improvement  Other 237.37KiB (-0.2%) 237.84KiB

Bundle analysis reportBranch review-app-client-side-offer-sig...Project dashboard


Generated by RelativeCIDocumentationReport issue

@starsirius starsirius force-pushed the review-app-client-side-offer-signal-overwrite branch from a4755f2 to c172c74 Compare November 20, 2024 01:47
@starsirius
Copy link
Member Author

We decided to take a different route and this PR got too big. I'm going to close it and open a new one. Thank you all for the patience! 🙏

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

Successfully merging this pull request may close these issues.

2 participants