Skip to content

Commit

Permalink
(PC-34400) feat(headlineOffer): handle headline offer from Algolia
Browse files Browse the repository at this point in the history
  • Loading branch information
tconte-pass committed Feb 7, 2025
1 parent 6a95b51 commit c6a7c6d
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type OfferToHeadlineOfferData = {
}

type OfferToHeadlineParams = {
offer: Offer | null
offer?: Offer
transformParameters: OfferToHeadlineOfferData
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/home/api/useVideoOffers.native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('useVideoOffers', () => {
})

it('should return offers when only OffersModuleParameters are provided', async () => {
mockFetchMultipleOffers.mockResolvedValueOnce({ hits: mockOffers, nbHits: 6 })
mockFetchMultipleOffers.mockResolvedValueOnce([{ hits: mockOffers, nbHits: 6 }])

const { result } = renderHook(
() => useVideoOffers([{}] as OffersModuleParameters[], 'moduleId', undefined, undefined),
Expand Down
2 changes: 1 addition & 1 deletion src/features/home/api/useVideoOffers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const useVideoOffers = (
isUserUnderage,
})

return result.hits
return result.flatMap((data) => data.hits)
}
const queryByQueryMode = {
[QueryMode.OFFER_IDS]: offersByIdsQuery,
Expand Down
97 changes: 66 additions & 31 deletions src/features/venue/api/useVenueOffers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,37 @@ const EXPECTED_CALL_PARAM = {
},
},
},
{
locationParams: {
aroundMeRadius: 100,
aroundPlaceRadius: 100,
selectedLocationMode: 'AROUND_ME',
userLocation: { latitude: 48.90374, longitude: 2.48171 },
},
offerParams: {
beginningDatetime: undefined,
date: null,
endingDatetime: undefined,
hitsPerPage: 50,
isDigital: false,
isHeadline: true,
locationFilter: { locationType: 'EVERYWHERE' },
offerCategories: [],
offerIsDuo: false,
offerIsFree: false,
offerSubcategories: [],
priceRange: [0, 300],
query: '',
tags: [],
timeRange: null,
venue: {
_geoloc: { lat: 48.8536, lng: 2.34199 },
info: 'PARIS 6',
label: 'Cinéma St André des Arts',
venueId: 26235,
},
},
},
],
}

Expand All @@ -143,10 +174,12 @@ describe('useVenueOffers', () => {
})

it('should return empty artists when there are no offers', async () => {
mockFetchMultipleOffers.mockResolvedValueOnce({
hits: [],
nbHits: 0,
})
mockFetchMultipleOffers.mockResolvedValueOnce([
{
hits: [],
nbHits: 0,
},
])

const { result } = renderHook(() => useVenueOffers(mockVenueResponse), {
wrapper: ({ children }) => reactQueryProviderHOC(children),
Expand All @@ -158,34 +191,36 @@ describe('useVenueOffers', () => {
})

it('should return artists after filtering and transforming hits', async () => {
mockFetchMultipleOffers.mockResolvedValueOnce({
hits: [
{
offer: {
dates: [],
isDigital: false,
isDuo: false,
name: 'I want something more',
prices: [28.0],
subcategoryId: SubcategoryIdEnum.CONCERT,
thumbUrl:
'https://storage.googleapis.com/passculture-metier-prod-production-assets-fine-grained/thumbs/mediations/CDZQ',
artist: 'Céline Dion',
},
_geoloc: { lat: 4.90339, lng: -52.31663 },
objectID: '102310',
venue: {
id: 4,
name: 'Lieu 4',
publicName: 'Lieu 4',
address: '4 rue de la paix',
postalCode: '75000',
city: 'Paris',
mockFetchMultipleOffers.mockResolvedValueOnce([
{
hits: [
{
offer: {
dates: [],
isDigital: false,
isDuo: false,
name: 'I want something more',
prices: [28.0],
subcategoryId: SubcategoryIdEnum.CONCERT,
thumbUrl:
'https://storage.googleapis.com/passculture-metier-prod-production-assets-fine-grained/thumbs/mediations/CDZQ',
artist: 'Céline Dion',
},
_geoloc: { lat: 4.90339, lng: -52.31663 },
objectID: '102310',
venue: {
id: 4,
name: 'Lieu 4',
publicName: 'Lieu 4',
address: '4 rue de la paix',
postalCode: '75000',
city: 'Paris',
},
},
},
],
nbHits: 1,
})
],
nbHits: 1,
},
])

const { result } = renderHook(() => useVenueOffers(mockVenueResponse), {
wrapper: ({ children }) => reactQueryProviderHOC(children),
Expand Down
36 changes: 21 additions & 15 deletions src/features/venue/api/useVenueOffers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { SearchQueryParameters } from 'libs/algolia/types'
import { env } from 'libs/environment/env'
import { useLocation } from 'libs/location'
import { QueryKeys } from 'libs/queryKeys'
import { Offer } from 'shared/offer/types'

export const useVenueOffers = (venue?: VenueResponse): UseQueryResult<VenueOffers> => {
// TODO(PC-33493): hook refacto
Expand All @@ -24,7 +23,7 @@ export const useVenueOffers = (venue?: VenueResponse): UseQueryResult<VenueOffer
const { searchState } = useSearch()
const isUserUnderage = useIsUserUnderage()

const buildPlaylistOfferParams = useCallback(
const buildVenueOffersQueryParams = useCallback(
(offerParams: SearchQueryParameters) => ({
locationParams: {
userLocation,
Expand All @@ -37,31 +36,38 @@ export const useVenueOffers = (venue?: VenueResponse): UseQueryResult<VenueOffer
[userLocation, selectedLocationMode]
)

const venueSearchedOffers = buildVenueOffersQueryParams({
...searchState,
venue: venueSearchParams.venue,
hitsPerPage: venueSearchParams.hitsPerPage,
})
const venueTopOffers = buildVenueOffersQueryParams(venueSearchParams)
const headlineOffer = buildVenueOffersQueryParams({ ...venueSearchParams, isHeadline: true })

const paramsList = [venueSearchedOffers, venueTopOffers, headlineOffer]

return useQuery(
[QueryKeys.VENUE_OFFERS, venue?.id, userLocation, selectedLocationMode],
() =>
fetchMultipleOffers({
paramsList: [
buildPlaylistOfferParams({
...searchState,
venue: venueSearchParams.venue,
hitsPerPage: venueSearchParams.hitsPerPage,
}),
buildPlaylistOfferParams(venueSearchParams),
],
paramsList,
isUserUnderage,
indexName: env.ALGOLIA_TOP_OFFERS_INDEX_NAME,
}),
{
enabled: !!venue,
select: ({ hits, nbHits }) => {
const filteredHits = hits.filter(filterOfferHit).map(transformHits)
select: ([venueSearchedOffersResults, venueTopOffersResults, headlineOfferResults]) => {
const hits = [venueSearchedOffersResults, venueTopOffersResults]
.flatMap((result) => result?.hits)
.filter(filterOfferHit)
.map(transformHits)

const offers = filteredHits.filter((hit): hit is Offer => hit !== null)
const headlineOffer = headlineOfferResults?.hits[0]

return {
hits: uniqBy(offers, 'objectID'),
nbHits,
hits: uniqBy(hits, 'objectID'),
nbHits: hits.length,
headlineOffer,
}
},
}
Expand Down
4 changes: 1 addition & 3 deletions src/features/venue/pages/Venue/Venue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { useLocation } from 'libs/location'
import { useCategoryHomeLabelMapping, useCategoryIdMapping } from 'libs/subcategories'
import { useGetCurrencyToDisplay } from 'shared/currency/useGetCurrencyToDisplay'
import { useGetPacificFrancToEuroRate } from 'shared/exchangeRates/useGetPacificFrancToEuroRate'
import { offersFixture } from 'shared/offer/offer.fixture'
import { SectionWithDivider } from 'ui/components/SectionWithDivider'
import { ViewGap } from 'ui/components/ViewGap/ViewGap'
import { getSpacing } from 'ui/theme'
Expand All @@ -48,8 +47,7 @@ export const Venue: FunctionComponent = () => {

const headlineOfferData = isVenueHeadlineOfferActive
? offerToHeadlineOfferData({
// Fake data to remove
offer: offersFixture[0],
offer: venueOffers?.headlineOffer,
transformParameters: {
currency,
euroToPacificFrancRate,
Expand Down
1 change: 1 addition & 0 deletions src/features/venue/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type TabType<TabKeyType extends string> = {
export type VenueOffers = {
hits: Offer[]
nbHits: number
headlineOffer?: Offer
}

export type Artist = {
Expand Down
2 changes: 2 additions & 0 deletions src/libs/algolia/enums/facetsEnums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum FACETS_FILTERS_ENUM {
OFFER_IS_DUO = 'offer.isDuo',
OFFER_IS_EDUCATIONAL = 'offer.isEducational',
OFFER_IS_EVENT = 'offer.isEvent',
OFFER_IS_HEADLINE = 'offer.isHeadline',
OFFER_IS_THING = 'offer.isThing',
OFFER_MOVIE_GENRES = 'offer.movieGenres',
OFFER_MUSIC_TYPE = 'offer.musicType',
Expand All @@ -27,6 +28,7 @@ export enum FACETS_FILTERS_ENUM {
}

export enum NUMERIC_FILTERS_ENUM {
OFFER_IS_HEADLINE_UNTIL = 'offer.isHeadlineUntil',
OFFER_PRICES = 'offer.prices',
OFFER_DATES = 'offer.dates',
OFFER_TIMES = 'offer.times',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
buildAccessibiltyFiltersPredicate,
buildAllocineIdPredicate,
buildEanPredicate,
buildHeadlinePredicate,
buildObjectIdsPredicate,
buildOfferCategoriesPredicate,
buildOfferGenreTypesPredicate,
Expand All @@ -25,6 +26,7 @@ export const buildFacetFilters = ({
allocineId,
isUserUnderage,
objectIds,
isHeadline,
offerCategories,
offerGenreTypes,
offerGtlLabel,
Expand All @@ -49,6 +51,7 @@ export const buildFacetFilters = ({
| 'isDigital'
| 'tags'
| 'gtls'
| 'isHeadline'
> & {
isUserUnderage: boolean
objectIds?: string[]
Expand Down Expand Up @@ -124,5 +127,11 @@ export const buildFacetFilters = ({
const accessibilityFilters = buildAccessibiltyFiltersPredicate(disabilitiesProperties)
facetFilters.push(...accessibilityFilters)
}

if (isHeadline) {
const isHeadlineFilter = buildHeadlinePredicate(isHeadline)
facetFilters.push(isHeadlineFilter)
}

return { facetFilters }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
buildDatePredicate,
buildHeadlineUntilPredicate,
buildHomepageDatePredicate,
buildOfferLast30DaysBookings,
buildOfferPriceRangePredicate,
Expand All @@ -17,6 +18,7 @@ export const buildNumericFilters = ({
maxPrice,
maxPossiblePrice,
minBookingsThreshold,
isHeadline,
}: Pick<
SearchQueryParameters,
| 'beginningDatetime'
Expand All @@ -29,6 +31,7 @@ export const buildNumericFilters = ({
| 'maxPrice'
| 'maxPossiblePrice'
| 'minBookingsThreshold'
| 'isHeadline'
>): null | {
numericFilters: FiltersArray
} => {
Expand All @@ -42,12 +45,14 @@ export const buildNumericFilters = ({
const datePredicate = buildDatePredicate({ date, timeRange })
const homepageDatePredicate = buildHomepageDatePredicate({ beginningDatetime, endingDatetime })
const last30DaysBookingsPredicate = buildOfferLast30DaysBookings(minBookingsThreshold)
const headlineUntilPredicate = buildHeadlineUntilPredicate(isHeadline)
const numericFilters: FiltersArray = []

if (priceRangePredicate) numericFilters.push(priceRangePredicate)
if (datePredicate) numericFilters.push(datePredicate)
if (homepageDatePredicate) numericFilters.push(homepageDatePredicate)
if (last30DaysBookingsPredicate) numericFilters.push(last30DaysBookingsPredicate)
if (headlineUntilPredicate) numericFilters.push(headlineUntilPredicate)

return numericFilters.length > 0 ? { numericFilters } : null
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const buildOfferSearchParameters = (
endingDatetime = undefined,
excludedObjectIds = [],
isFullyDigitalOffersCategory = false,
isHeadline = false,
maxPossiblePrice = '',
maxPrice = '',
minBookingsThreshold = 0,
Expand Down Expand Up @@ -72,6 +73,7 @@ export const buildOfferSearchParameters = (
tags,
gtls,
disabilitiesProperties,
isHeadline,
}),
...buildNumericFilters({
beginningDatetime,
Expand All @@ -84,6 +86,7 @@ export const buildOfferSearchParameters = (
offerIsFree,
priceRange,
timeRange,
isHeadline,
}),
...locationParameter,
...buildFilters({ excludedObjectIds }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export const buildTagsPredicate = (
return undefined
}

export const buildHeadlinePredicate = (isHeadline: boolean) => [
`${FACETS_FILTERS_ENUM.OFFER_IS_HEADLINE}:${isHeadline.toString()}`,
]

export const buildAccessibiltyFiltersPredicate = ({
isAudioDisabilityCompliant,
isMentalDisabilityCompliant,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,8 @@ export const buildDateOnlyPredicate = (

export const getDatePredicate = (lowerDate: number, higherDate: number): string =>
`${NUMERIC_FILTERS_ENUM.OFFER_DATES}: ${lowerDate} TO ${higherDate}`

export const buildHeadlineUntilPredicate = (isHeadline?: boolean) =>
isHeadline
? [`${NUMERIC_FILTERS_ENUM.OFFER_IS_HEADLINE_UNTIL} >= ${TIMESTAMP.getFromDate(new Date())}`]
: undefined
Loading

0 comments on commit c6a7c6d

Please sign in to comment.