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

fix: Guard against unnecessary timer rerenders on /artwork/id for closed auction artworks #15053

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"publish-assets": "node scripts/uploadToS3.js",
"publish-assets:local": "yarn clean && yarn build:client:prod && yarn publish-assets",
"relay": "relay-compiler",
"scan": "npx react-scan@latest",
"scan": "npx react-scan@0.0.39",
"start": "scripts/start.sh",
"start:prod": "yarn build && SESSION_LOCAL_INSECURE=true NODE_ENV=production yarn start",
"start:prod:debug": "SESSION_LOCAL_INSECURE=true NODE_ENV=production NODE_PATH=src node --inspect --max_old_space_size=3072 -r @swc-node/register ./src/prod.ts",
Expand Down Expand Up @@ -194,10 +194,10 @@
"@graphql-tools/jest-transform": "^2.0.0",
"@loadable/webpack-plugin": "5.15.2",
"@relative-ci/agent": "^4.2.10",
"@rsbuild/core": "^1.1.8",
"@rsbuild/plugin-assets-retry": "^1.0.6",
"@rsbuild/core": "^1.1.13",
"@rsbuild/plugin-assets-retry": "^1.0.7",
"@rsbuild/plugin-node-polyfill": "^1.2.0",
"@rsbuild/plugin-react": "^1.0.7",
"@rsbuild/plugin-react": "^1.1.0",
"@storybook/addon-actions": "^8.4.7",
"@storybook/addon-essentials": "^8.4.7",
"@storybook/addon-links": "^8.4.7",
Expand Down
18 changes: 16 additions & 2 deletions src/Apps/Artwork/ArtworkApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ArtworkErrorApp } from "Apps/Artwork/Components/ArtworkErrorApp/Artwork
import { ArtworkPageBanner } from "Apps/Artwork/Components/ArtworkPageBanner"
import { PrivateArtworkDetails } from "Apps/Artwork/Components/PrivateArtwork/PrivateArtworkDetails"
import { SelectedEditionSetProvider } from "Apps/Artwork/Components/SelectedEditionSetContext"
import { lotIsClosed } from "Apps/Artwork/Utils/lotIsClosed"
Copy link
Member

Choose a reason for hiding this comment

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

Finding it surprising that there's no field to determine if a lot is closed in Metaphysics

import { AlertProvider } from "Components/Alert/AlertProvider"
import { useAuthDialog } from "Components/AuthDialog"
import { RecentlyViewed } from "Components/RecentlyViewed"
Expand Down Expand Up @@ -334,7 +335,14 @@ export const ArtworkApp: React.FC<React.PropsWithChildren<Props>> = props => {

const WrappedArtworkApp: React.FC<React.PropsWithChildren<Props>> = props => {
const {
artwork: { artists, attributionClass, internalID, mediumType, sale },
artwork: {
artists,
attributionClass,
internalID,
mediumType,
sale,
saleArtwork,
},
} = props

const {
Expand All @@ -346,8 +354,10 @@ const WrappedArtworkApp: React.FC<React.PropsWithChildren<Props>> = props => {
// Check to see if referrer comes from link interception.
// @see interceptLinks.ts
const referrer = state && state.previousHref
const isLotClosed = lotIsClosed(sale, saleArtwork)

const websocketEnabled = !!sale?.extendedBiddingIntervalMinutes
const websocketEnabled =
!!sale?.extendedBiddingIntervalMinutes && isLotClosed === false
Copy link
Contributor

Choose a reason for hiding this comment

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

Haven't totally grokked this, but does this imply that a sale without extended bidding will always skip polling?

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 should probably be ||


const initialAlertCriteria = {
attributionClass: compact([attributionClass?.internalID]),
Expand Down Expand Up @@ -433,6 +443,10 @@ const ArtworkAppFragmentContainer = createFragmentContainer(
internalID
slug
extendedBiddingIntervalMinutes
isClosed
}
saleArtwork {
endedAt
}
saleMessage
artists(shallow: true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,20 @@ const ArtworkAuctionCreateAlertHeader: FC<
> = ({ artwork }) => {
const biddingEndAt =
artwork?.saleArtwork?.extendedBiddingEndAt ?? artwork?.saleArtwork?.endAt
const { hasEnded } = useTimer(
biddingEndAt as string,
artwork?.sale?.startAt as string,
)

const initialIsLotClosedCheck = lotIsClosed(artwork.sale, artwork.saleArtwork)

const { hasEnded } = useTimer({
endDate: biddingEndAt as string,
startAt: artwork?.sale?.startAt as string,
enabled: !initialIsLotClosedCheck,
Copy link
Member

Choose a reason for hiding this comment

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

The thing about this entire approach is that we're not polling on the status right? So if you load the page before it starts the status will be closed and then if startAt comes to pass it will never enable.

})

const isLotClosed = hasEnded || lotIsClosed(artwork.sale, artwork.saleArtwork)
const displayAuctionCreateAlertHeader =
artwork.isEligibleToCreateAlert && artwork.isInAuction && isLotClosed

const artistName = artwork.artistNames ? ", " + artwork.artistNames : ""
const artistName = artwork.artistNames ? `, ${artwork.artistNames}` : ""
const artistSlug = artwork.artists?.[0]?.slug
let aggregations: Aggregations = []
let additionalGeneIDs: string[] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const ArtworkSidebar: React.FC<
const extendedBiddingEndAt = saleArtwork?.extendedBiddingEndAt
const biddingEndAt = extendedBiddingEndAt ?? endAt
const isUnlisted = artwork?.isUnlisted
const isLotClosed = lotIsClosed(sale, saleArtwork)

const [updatedBiddingEndAt, setUpdatedBiddingEndAt] = useState(biddingEndAt)

Expand All @@ -87,7 +88,11 @@ export const ArtworkSidebar: React.FC<

const timerEndAt = sale?.isAuction ? updatedBiddingEndAt : sale?.endAt

const { hasEnded } = useTimer(timerEndAt as string, startAt as string)
const { hasEnded } = useTimer({
endDate: timerEndAt as string,
startAt: startAt as string,
enabled: !isLotClosed,
})

const shouldHideDetailsCreateAlertCTA =
isUnlisted ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const ArtworkSidebarAuctionPolling: React.FC<
const isMounted = useRef(false)
const tracking = useTracking()

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
if (isMounted.current) {
setCurrentBidChanged(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from "./ArtworkSidebarEditionSets"

import { useSelectedEditionSetContext } from "Apps/Artwork/Components/SelectedEditionSetContext"
import { lotIsClosed } from "Apps/Artwork/Utils/lotIsClosed"
import { usePartnerOfferCheckoutMutation } from "Apps/PartnerOffer/Routes/Mutations/UsePartnerOfferCheckoutMutation"
import { CreateAlertButton } from "Components/Alert/Components/CreateAlertButton"
import { useAuthDialog } from "Components/AuthDialog"
Expand Down Expand Up @@ -71,8 +72,13 @@ export const ArtworkSidebarCommercialButtons: React.FC<
extractNodes(me.partnerOffersConnection)[0]) ||
null

const isLotClosed = lotIsClosed(artwork.sale, artwork.saleArtwork)

// Fall back to a definitely past value because the timer hook doesn't like nulls
const partnerOfferTimer = useTimer(partnerOffer?.endAt || THE_PAST)
const partnerOfferTimer = useTimer({
endDate: partnerOffer?.endAt || THE_PAST,
enabled: !isLotClosed,
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be based on the offer rather than lot status?

Copy link
Member Author

Choose a reason for hiding this comment

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

Just set up so that if the lot is closed, we don't poll. I'm not too sure beyond that.

Copy link
Member

Choose a reason for hiding this comment

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

It seems to me that we can just have use timer disable itself once it reaches the end date. And then we wouldn't need to explicitly enable or disable in these cases.

})
const partnerIcon = artwork.partner?.profile?.icon?.url

const activePartnerOffer =
Expand Down Expand Up @@ -329,6 +335,7 @@ export const ArtworkSidebarCommercialButtons: React.FC<
const { setSelectedEditionSet: setSelectedEditionSetInContext } =
useSelectedEditionSetContext()

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
setSelectedEditionSet(firstAvailableEcommerceEditionSet())
setSelectedEditionSetInContext(
Expand All @@ -353,7 +360,9 @@ export const ArtworkSidebarCommercialButtons: React.FC<
}
if (artwork.isOfferable && !(activePartnerOffer && artwork.isInquireable)) {
renderButtons.makeOffer =
Object.keys(renderButtons).length == 0 ? "primaryBlack" : "secondaryBlack"
Object.keys(renderButtons).length === 0
? "primaryBlack"
: "secondaryBlack"
}
if (artwork.isInquireable && Object.keys(renderButtons).length < 2) {
renderButtons.contactGallery =
Expand Down Expand Up @@ -699,6 +708,12 @@ const ARTWORK_FRAGMENT = graphql`
collectorSignals {
primaryLabel(ignore: [PARTNER_OFFER])
}
sale {
isClosed
}
saleArtwork {
endedAt
}
}
`

Expand Down
8 changes: 4 additions & 4 deletions src/Apps/Artwork/Components/ArtworkSidebar/LotTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ export const LotTimer: React.FC<React.PropsWithChildren<LotTimerProps>> = ({
},
})

const { hasEnded, time, hasStarted } = useTimer(
updatedBiddingEndAt!,
startAt!,
)
const { hasEnded, time, hasStarted } = useTimer({
endDate: updatedBiddingEndAt!,
startAt: startAt!,
})

if (!endAt) {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ export const SaleDetailTimer: React.FC<
const endedAt = sale?.endedAt

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { hasEnded, time, hasStarted } = useTimer(endAt!, startAt!)
const { hasEnded, time, hasStarted } = useTimer({
endDate: endAt!,
startAt: startAt!,
})

if (!endAt || endedAt) {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export const ConversationCTA: React.FC<
const artwork = liveArtwork?.__typename === "Artwork" ? liveArtwork : null

const partnerOffer = artwork && findPartnerOffer(artwork.internalID)
const partnerOfferTimer = useTimer(
partnerOffer?.endAt ?? new Date(0).toISOString(),
)
const partnerOfferTimer = useTimer({
endDate: partnerOffer?.endAt ?? new Date(0).toISOString(),
})

const activeOrder = extractNodes(data.activeOrderCTA)[0]
const activePartnerOffer =
Expand Down
2 changes: 1 addition & 1 deletion src/Apps/Fair/Components/FairOverview/FairTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface FairTimerProps {
export const FairTimer: React.FC<React.PropsWithChildren<FairTimerProps>> = ({
fair: { endAt },
}) => {
const { hasEnded } = useTimer(endAt!)
const { hasEnded } = useTimer({ endDate: endAt! })

return (
<Box my={[2, 0]}>
Expand Down
8 changes: 6 additions & 2 deletions src/Components/Artwork/Details/BidTimerLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ export const BidTimerLine: React.FC<
},
})

const { time, hasEnded: hasBiddingEnded } = useTimer(updatedLotClosesAt!)
const { time, hasEnded: hasBiddingEnded } = useTimer({
endDate: updatedLotClosesAt!,
})
const { days, hours, minutes, seconds } = time
const { isAuctionArtwork } = useArtworkGridContext()
const { hasEnded: hasRegistrationEnded } = useTimer(registrationEndsAt!)
const { hasEnded: hasRegistrationEnded } = useTimer({
endDate: registrationEndsAt!,
})

const numDays = Number(days)
const numHours = Number(hours)
Expand Down
12 changes: 6 additions & 6 deletions src/Components/Artwork/Details/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import { SaveArtworkToListsButtonQueryRenderer } from "Components/Artwork/SaveBu
import { SaveButtonQueryRenderer } from "Components/Artwork/SaveButton/SaveButton"
import { useArtworkGridContext } from "Components/ArtworkGrid/ArtworkGridContext"
import { RouterLink, type RouterLinkProps } from "System/Components/RouterLink"
import { useFeatureFlag } from "System/Hooks/useFeatureFlag"
import { useTimer } from "Utils/Hooks/useTimer"
import type { Details_artwork$data } from "__generated__/Details_artwork.graphql"
import { isFunction } from "lodash"
import type * as React from "react"
import { createFragmentContainer, graphql } from "react-relay"
import styled from "styled-components"
import { BidTimerLine } from "./BidTimerLine"
import { LegacyPrimaryLabelLine } from "./LegacyPrimaryLabelLine"
import { PrimaryLabelLineQueryRenderer } from "./PrimaryLabelLine"
import { SaleMessageQueryRenderer } from "./SaleMessage"
import { useTimer } from "Utils/Hooks/useTimer"
import { useFeatureFlag } from "System/Hooks/useFeatureFlag"
import { LegacyPrimaryLabelLine } from "./LegacyPrimaryLabelLine"

export interface DetailsProps {
artwork: Details_artwork$data
Expand All @@ -38,7 +38,7 @@ export interface DetailsProps {
const LINE_HEIGHT = 22
const NUM_OF_LINES = 5
const CONTAINER_HEIGHT = LINE_HEIGHT * NUM_OF_LINES
const LINE_HEIGHT_PX = LINE_HEIGHT + "px"
const LINE_HEIGHT_PX = `${LINE_HEIGHT}px`

const StyledConditionalLink = styled(RouterLink)`
color: ${themeGet("colors.black100")};
Expand Down Expand Up @@ -311,7 +311,7 @@ const LegacyActivePartnerOfferTimer: React.FC<
> = ({ artwork: { collectorSignals } }) => {
const SEPARATOR = <>&nbsp;</>
const { endAt } = collectorSignals?.partnerOffer ?? {}
const { time } = useTimer(endAt ?? "")
const { time } = useTimer({ endDate: endAt ?? "" })
const { days, hours } = time

return (
Expand Down Expand Up @@ -401,7 +401,7 @@ export const Details: React.FC<React.PropsWithChildren<DetailsProps>> = ({
}

return (
<Box height={CONTAINER_HEIGHT + "px"}>
<Box height={`${CONTAINER_HEIGHT}px`}>
{isAuctionArtwork && (
<Flex flexDirection="row">
<Join separator={<Spacer x={1} />}>
Expand Down
12 changes: 6 additions & 6 deletions src/Components/Artwork/Details/SaleMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { FC, ReactElement } from "react"
import { createFragmentContainer, graphql } from "react-relay"
import { Flex, Text } from "@artsy/palette"
import { SystemQueryRenderer } from "System/Relay/SystemQueryRenderer"
import { useTimer } from "Utils/Hooks/useTimer"
import { SaleMessageQuery } from "__generated__/SaleMessageQuery.graphql"
import { SaleMessage_artwork$data } from "__generated__/SaleMessage_artwork.graphql"
import { Details_artwork$data } from "__generated__/Details_artwork.graphql"
import type { Details_artwork$data } from "__generated__/Details_artwork.graphql"
import type { SaleMessageQuery } from "__generated__/SaleMessageQuery.graphql"
import type { SaleMessage_artwork$data } from "__generated__/SaleMessage_artwork.graphql"
import type { FC, ReactElement } from "react"
import { createFragmentContainer, graphql } from "react-relay"

interface PublicSaleMessageProps {
artwork: Details_artwork$data
Expand Down Expand Up @@ -41,7 +41,7 @@ const ActivePartnerOfferTimer: React.FC<ActivePartnerOfferTimerProps> = ({
}) => {
const SEPARATOR = <>&nbsp;</>
const { endAt } = collectorSignals?.partnerOffer ?? {}
const { time } = useTimer(endAt ?? "")
const { time } = useTimer({ endDate: endAt ?? "" })
const { days, hours } = time

return (
Expand Down
5 changes: 4 additions & 1 deletion src/Components/Artwork/FlatGridItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ const FlatGridItem: React.FC<React.PropsWithChildren<FlatGridItemProps>> = ({
},
})

const { time, hasEnded } = useTimer(updatedBiddingEndAt!, startAt!)
const { time, hasEnded } = useTimer({
endDate: updatedBiddingEndAt!,
startAt: startAt!,
})

const { isAuctionArtwork } = useArtworkGridContext()
const shouldRenderProgressBar =
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Notifications/ExpiresInTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const WatchIcon: FC<React.PropsWithChildren<{ fill?: string }>> = ({
export const ExpiresInTimer: FC<
React.PropsWithChildren<ExpiresInTimerProps>
> = ({ expiresAt = "", available = false }) => {
const { hasEnded, time } = useTimer(expiresAt ?? "")
const { hasEnded, time } = useTimer({ endDate: expiresAt ?? "" })

if (!available) {
return (
Expand Down
5 changes: 2 additions & 3 deletions src/Components/Notifications/PartnerOfferArtwork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const PartnerOfferArtwork: FC<
const { theme } = useTheme()

const { tracking } = useNotificationsTracking()
const { hasEnded } = useTimer(endAt || "")
const { hasEnded } = useTimer({ endDate: endAt || "" })
const fullyAvailable = !!(available && !hasEnded && priceWithDiscount)

const artwork = useFragment(partnerOfferArtworkFragment, artworkProp)
Expand All @@ -45,8 +45,7 @@ export const PartnerOfferArtwork: FC<
(artwork.title ?? "Artwork") +
(artwork.artistNames ? ` by ${artwork.artistNames}` : "")
const partnerIcon = artwork.partner?.profile?.icon?.url
const artworkListingHref =
artwork.href + "?partner_offer_id=" + partnerOfferID
const artworkListingHref = `${artwork.href}?partner_offer_id=${partnerOfferID}`

let buttonText = "Purchase"
if (hasEnded) buttonText = "View Work"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const PartnerOfferCreatedNotification: FC<

const partnerOffer = item?.partnerOffer
const artwork = extractNodes(offerArtworksConnection)[0]
const { hasEnded } = useTimer(partnerOffer?.endAt || "")
const { hasEnded } = useTimer({ endDate: partnerOffer?.endAt || "" })
const isOfferFromSaves = partnerOffer?.source === "SAVE"

let subtitle = isOfferFromSaves
Expand Down Expand Up @@ -78,7 +78,7 @@ export const PartnerOfferCreatedNotification: FC<
<Text
variant="xs"
fontWeight="bold"
aria-label={`Notification type: Offer`}
aria-label="Notification type: Offer"
>
Offer
</Text>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Timer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const Timer: React.FC<
color = "blue100",
...rest
}) => {
const { hasEnded, time } = useTimer(endDate, startDate)
const { hasEnded, time } = useTimer({ endDate, startAt: startDate })
const { days, hours, minutes, seconds } = time

return (
Expand Down
Loading