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

(PC-31746) feat(home): add hybrid playlist with recommendation #7060

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 3 additions & 3 deletions scripts/noUncheckedIndexedAccess_snapshot.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
./src/features/deeplinks/helpers/getScreenFromDeeplink.ts:24
./src/features/home/api/helpers/mapVenuesDataAndModules.ts:13
./src/features/home/api/helpers/mapVenuesDataAndModules.ts:15
./src/features/home/components/modules/OffersModule.tsx:51
./src/features/home/components/modules/OffersModule.tsx:59
./src/features/home/components/modules/OffersModule.tsx:68
./src/features/home/components/modules/OffersModule.tsx:74
./src/features/home/components/modules/OffersModule.tsx:82
./src/features/home/components/modules/OffersModule.tsx:95
./src/features/home/components/modules/video/OldVideoModuleDesktop.tsx:96
./src/features/home/components/modules/video/OldVideoModuleMobile.tsx:70
./src/features/home/components/modules/video/VideoModuleDesktop.tsx:86
Expand Down
5 changes: 3 additions & 2 deletions src/features/home/api/useHomeRecommendedOffers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const useHomeRecommendedOffers = (
moduleId: string,
recommendationParameters?: RecommendedOffersModule['recommendationParameters'],
userId?: number
): { offers?: Offer[]; recommendationApiParams?: RecommendationApiParams } => {
): { offers: Offer[]; recommendationApiParams?: RecommendationApiParams } => {
const subcategoryLabelMapping = useSubcategoryLabelMapping()
const isFocused = useIsFocused()
const requestParameters = getRecommendationParameters(
Expand All @@ -72,7 +72,8 @@ export const useHomeRecommendedOffers = (
})

return {
offers: useAlgoliaRecommendedOffers(data?.playlistRecommendedOffers ?? [], moduleId, true),
offers:
useAlgoliaRecommendedOffers(data?.playlistRecommendedOffers ?? [], moduleId, true) || [],
recommendationApiParams: data?.params,
}
}
48 changes: 42 additions & 6 deletions src/features/home/components/modules/OffersModule.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import React, { useCallback, useEffect } from 'react'
import React, { useCallback, useEffect, useMemo } from 'react'

import { useAuthContext } from 'features/auth/context/AuthContext'
import { useHomeRecommendedOffers } from 'features/home/api/useHomeRecommendedOffers'
import { HomeOfferTile } from 'features/home/components/HomeOfferTile'
import { ModuleData, OffersModule as OffersModuleType } from 'features/home/types'
import {
ModuleData,
OffersModule as OffersModuleType,
RecommendedOffersModule,
} from 'features/home/types'
import { getSearchStackConfig } from 'features/navigation/SearchStackNavigator/helpers'
import { useAdaptOffersPlaylistParameters } from 'libs/algolia/fetchAlgolia/fetchMultipleOffers/helpers/useAdaptOffersPlaylistParameters'
import { analytics } from 'libs/analytics'
Expand All @@ -11,6 +16,7 @@ import { usePlaylistItemDimensionsFromLayout } from 'libs/contentful/usePlaylist
import { useFeatureFlag } from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
import useFunctionOnce from 'libs/hooks/useFunctionOnce'
import { useLocation } from 'libs/location'
import { formatDates } from 'libs/parsers/formatDates'
import { getDisplayPrice } from 'libs/parsers/getDisplayPrice'
import { useCategoryHomeLabelMapping, useCategoryIdMapping } from 'libs/subcategories'
Expand All @@ -26,17 +32,34 @@ export type OffersModuleProps = {
index: number
homeEntryId: string | undefined
data: ModuleData | undefined
recommendationParameters?: RecommendedOffersModule['recommendationParameters']
}

const keyExtractor = (item: Offer) => item.objectID

export const OffersModule = (props: OffersModuleProps) => {
const isNewOfferTileDisplayed = useFeatureFlag(RemoteStoreFeatureFlags.WIP_NEW_OFFER_TILE)
const { displayParameters, offersModuleParameters, index, moduleId, homeEntryId, data } = props
const {
displayParameters,
offersModuleParameters,
index,
moduleId,
homeEntryId,
data,
recommendationParameters,
} = props
const adaptedPlaylistParameters = useAdaptOffersPlaylistParameters()
const mapping = useCategoryIdMapping()
const labelMapping = useCategoryHomeLabelMapping()
const { user } = useAuthContext()
const { userLocation } = useLocation()

const { offers: recommandationOffers, recommendationApiParams } = useHomeRecommendedOffers(
userLocation,
moduleId,
recommendationParameters,
user?.id
)

const { playlistItems, nbPlaylistResults } = data ?? {
playlistItems: [],
Expand All @@ -59,7 +82,11 @@ export const OffersModule = (props: OffersModuleProps) => {
// @ts-expect-error: because of noUncheckedIndexedAccess
const moduleName = displayParameters.title ?? parameters.title
const logHasSeenAllTilesOnce = useFunctionOnce(() =>
analytics.logAllTilesSeen({ moduleName, numberOfTiles: playlistItems.length })
analytics.logAllTilesSeen({
moduleName,
numberOfTiles: playlistItems.length,
apiRecoParams: props.recommendationParameters ? recommendationApiParams : undefined,
})
)

const showSeeMore =
Expand Down Expand Up @@ -122,13 +149,22 @@ export const OffersModule = (props: OffersModuleProps) => {
},
[onPressSeeMore, showSeeMore, searchTabConfig]
)
const hybridPlaylistItems = useMemo(
() => [...playlistItems, ...recommandationOffers],
[recommandationOffers, playlistItems]
)

const offersToDisplay = useMemo(() => {
return props.recommendationParameters ? hybridPlaylistItems : playlistItems
}, [hybridPlaylistItems, playlistItems, props.recommendationParameters])

const shouldModuleBeDisplayed =
playlistItems.length > 0 && playlistItems.length >= displayParameters.minOffers
offersToDisplay.length > 0 && offersToDisplay.length >= displayParameters.minOffers

useEffect(() => {
if (shouldModuleBeDisplayed) {
analytics.logModuleDisplayedOnHomepage({
call_id: props.recommendationParameters ? recommendationApiParams?.call_id : undefined,
moduleId,
moduleType: ContentTypes.ALGOLIA,
index,
Expand All @@ -146,7 +182,7 @@ export const OffersModule = (props: OffersModuleProps) => {
testID="offersModuleList"
title={displayParameters.title}
subtitle={displayParameters.subtitle}
data={playlistItems}
data={offersToDisplay}
itemHeight={itemHeight}
itemWidth={itemWidth}
onPressSeeMore={onPressSeeMore}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ const defaultRecommendationApiParams: RecommendationApiParams = {
reco_origin: 'unknown',
}

const mockUseHomeRecommendedOffers = jest.fn().mockReturnValue({
offers: mockedAlgoliaResponse.hits,
recommendationApiParams: defaultRecommendationApiParams,
})

jest.mock('features/home/api/useHomeRecommendedOffers', () => ({
useHomeRecommendedOffers: jest.fn(() => ({
offers: mockedAlgoliaResponse.hits,
recommendationApiParams: defaultRecommendationApiParams,
})),
useHomeRecommendedOffers: () => mockUseHomeRecommendedOffers(),
}))

const useFeatureFlagSpy = jest.spyOn(useFeatureFlagAPI, 'useFeatureFlag').mockReturnValue(false)
Expand Down Expand Up @@ -84,6 +86,19 @@ describe('RecommendationModule', () => {

expect(analytics.logModuleDisplayedOnHomepage).not.toHaveBeenCalled()
})

it('should not display RecommendationModule if no offer', async () => {
useFeatureFlagSpy.mockReturnValueOnce(true)
mockUseHomeRecommendedOffers.mockReturnValueOnce({
offers: [],
recommendationApiParams: defaultRecommendationApiParams,
})
renderRecommendationModule()

await waitFor(() => {
expect(screen.toJSON()).toBeNull()
})
})
})

const renderRecommendationModule = (additionalDisplayParams?: DisplayParametersFields) =>
Expand Down
6 changes: 3 additions & 3 deletions src/features/home/components/modules/RecommendationModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const RecommendationModule = (props: RecommendationModuleProps) => {
recommendationParameters,
profile?.id
)
const nbOffers = offers?.length ?? 0
const nbOffers = offers.length
const shouldModuleBeDisplayed = nbOffers > displayParameters.minOffers

const moduleName = displayParameters.title
Expand All @@ -58,7 +58,7 @@ export const RecommendationModule = (props: RecommendationModuleProps) => {
moduleType: ContentTypes.RECOMMENDATION,
index,
homeEntryId,
offers: offers?.length ? offers.map((offer) => offer.objectID) : undefined,
offers: offers.map((offer) => offer.objectID),
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -106,7 +106,7 @@ export const RecommendationModule = (props: RecommendationModuleProps) => {
testID="recommendationModuleList"
title={displayParameters.title}
subtitle={displayParameters.subtitle}
data={offers ?? []}
data={offers}
itemHeight={itemHeight}
itemWidth={itemWidth}
renderItem={renderItem}
Expand Down
1 change: 1 addition & 0 deletions src/features/home/fixtures/homepage.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export const formattedOffersModule: OffersModule = {
bookTypes: ['Carrière/Concours', 'Scolaire & Parascolaire', 'Gestion/entreprise'],
},
],
recommendationParameters: undefined,
}

export const formattedCategoryListModule: CategoryListModule = {
Expand Down
1 change: 1 addition & 0 deletions src/features/home/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export type OffersModule = {
offersModuleParameters: OffersModuleParameters[]
displayParameters: DisplayParameters
data?: ModuleData
recommendationParameters?: RecommendedOffersParameters
}

type DisplayParameters = {
Expand Down
5 changes: 4 additions & 1 deletion src/libs/contentful/adapters/modules/adaptOffersModule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HomepageModuleType, OffersModule } from 'features/home/types'
import { buildOffersParams } from 'libs/contentful/adapters/helpers/buildOffersParams'
import { buildRecommendationParams } from 'libs/contentful/adapters/modules/adaptRecommendationModule'
import { AlgoliaContentModel } from 'libs/contentful/types'

export const adaptOffersModule = (module: AlgoliaContentModel): OffersModule | null => {
Expand All @@ -8,7 +9,8 @@ export const adaptOffersModule = (module: AlgoliaContentModel): OffersModule | n
if (module.fields.displayParameters.fields === undefined) return null

const additionalAlgoliaParameters = module.fields.additionalAlgoliaParameters ?? []

const { recommendationParameters } = module.fields
const cleanRecommendationParameters = buildRecommendationParams(recommendationParameters)
const offersList = buildOffersParams(module.fields.algoliaParameters, additionalAlgoliaParameters)

if (offersList.length === 0) return null
Expand All @@ -19,5 +21,6 @@ export const adaptOffersModule = (module: AlgoliaContentModel): OffersModule | n
title: module.fields.title,
displayParameters: module.fields.displayParameters.fields,
offersModuleParameters: offersList,
recommendationParameters: cleanRecommendationParameters,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const mapMusicTypes = (recoMusicTypes: RecommendationParametersFields['musicType
const mapShowTypes = (recoShowTypes: RecommendationParametersFields['showTypes']) =>
recoShowTypes?.fields?.showTypes

const buildRecommendationParams = (
export const buildRecommendationParams = (
recommendationParams?: RecommendationParameters
): RecommendedOffersModule['recommendationParameters'] | undefined => {
if (recommendationParams?.fields === undefined) return undefined
Expand Down
1 change: 1 addition & 0 deletions src/libs/contentful/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export interface AlgoliaFields {
displayParameters: DisplayParameters
cover?: Cover
additionalAlgoliaParameters?: AlgoliaParameters[]
recommendationParameters?: RecommendationParameters
}

// Taken from https://app.contentful.com/spaces/2bg01iqy0isv/environments/testing/content_types/venuesPlaylist/fields
Expand Down
Loading