return empty array if no candidates for home feed selection
This commit is contained in:
@ -258,12 +258,13 @@ const rankCandidates = async (
|
||||
}
|
||||
|
||||
const redisKey = (userId: string) => `home:${userId}`
|
||||
const emptyHomeKey = (key: string) => `${key}:empty`
|
||||
|
||||
export const getHomeSections = async (
|
||||
userId: string,
|
||||
limit = 100,
|
||||
maxScore?: number
|
||||
): Promise<Array<{ member: Section; score: number }>> => {
|
||||
): Promise<Array<{ member: Section; score: number }> | null> => {
|
||||
const redisClient = redisDataSource.redisClient
|
||||
if (!redisClient) {
|
||||
throw new Error('Redis client not available')
|
||||
@ -285,6 +286,19 @@ export const getHomeSections = async (
|
||||
limit
|
||||
)
|
||||
|
||||
if (!results.length) {
|
||||
logger.info('No sections found in redis')
|
||||
// check if the feed is empty
|
||||
const isEmpty = await redisClient.exists(emptyHomeKey(key))
|
||||
if (isEmpty) {
|
||||
logger.info('Empty feed')
|
||||
return []
|
||||
}
|
||||
|
||||
logger.info('Feed not found')
|
||||
return null
|
||||
}
|
||||
|
||||
const sections = []
|
||||
for (let i = 0; i < results.length; i += 2) {
|
||||
const member = JSON.parse(results[i]) as Section
|
||||
@ -317,6 +331,14 @@ const appendSectionsToHome = async (
|
||||
}
|
||||
|
||||
const key = redisKey(userId)
|
||||
const emptyKey = emptyHomeKey(key)
|
||||
|
||||
if (!sections.length) {
|
||||
logger.info('No available sections to add')
|
||||
// set expiration to 1 hour
|
||||
await redisClient.set(emptyKey, 'true', 'EX', 60 * 60)
|
||||
return
|
||||
}
|
||||
|
||||
// store candidates in redis sorted set
|
||||
const pipeline = redisClient.pipeline()
|
||||
@ -337,6 +359,8 @@ const appendSectionsToHome = async (
|
||||
// add section to the sorted set
|
||||
pipeline.zadd(key, ...scoreMembers)
|
||||
|
||||
pipeline.del(emptyKey)
|
||||
|
||||
// keep only the new sections and remove the oldest ones
|
||||
pipeline.zremrangebyrank(key, 0, -(sections.length + 1))
|
||||
|
||||
@ -375,6 +399,10 @@ const mixHomeItems = (
|
||||
items: Array<Candidate>,
|
||||
batches: Array<Array<Candidate>>
|
||||
) => {
|
||||
if (batches.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const batchSize = Math.ceil(items.length / batches.length)
|
||||
|
||||
for (const item of items) {
|
||||
@ -424,32 +452,36 @@ const mixHomeItems = (
|
||||
>,
|
||||
}
|
||||
|
||||
distributeItems(shortItems, batches.short)
|
||||
distributeItems(longItems, batches.long)
|
||||
batches.short.length && distributeItems(shortItems, batches.short)
|
||||
batches.long.length && distributeItems(longItems, batches.long)
|
||||
|
||||
// convert batches to sections
|
||||
const sections = []
|
||||
const hiddenCandidates = rankedHomeItems.slice(50)
|
||||
|
||||
sections.push({
|
||||
items: hiddenCandidates.map(candidateToItem),
|
||||
layout: 'hidden',
|
||||
})
|
||||
hiddenCandidates.length &&
|
||||
sections.push({
|
||||
items: hiddenCandidates.map(candidateToItem),
|
||||
layout: 'hidden',
|
||||
})
|
||||
|
||||
sections.push({
|
||||
items: batches.short.flat().map(candidateToItem),
|
||||
layout: 'quick_links',
|
||||
})
|
||||
batches.short.length &&
|
||||
sections.push({
|
||||
items: batches.short.flat().map(candidateToItem),
|
||||
layout: 'quick_links',
|
||||
})
|
||||
|
||||
sections.push({
|
||||
items: batches.long.flat().map(candidateToItem),
|
||||
layout: 'top_picks',
|
||||
})
|
||||
batches.long.length &&
|
||||
sections.push({
|
||||
items: batches.long.flat().map(candidateToItem),
|
||||
layout: 'top_picks',
|
||||
})
|
||||
|
||||
sections.push({
|
||||
items: justAddedCandidates.map(candidateToItem),
|
||||
layout: 'just_added',
|
||||
})
|
||||
justAddedCandidates &&
|
||||
sections.push({
|
||||
items: justAddedCandidates.map(candidateToItem),
|
||||
layout: 'just_added',
|
||||
})
|
||||
|
||||
return sections
|
||||
}
|
||||
@ -495,7 +527,6 @@ export const updateHome = async (data: UpdateHomeJobData) => {
|
||||
|
||||
if (!justAddedCandidates.length && !candidates.length) {
|
||||
logger.info('No candidates found')
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: integrity check on candidates
|
||||
|
||||
@ -41,7 +41,8 @@ export const homeResolver = authorized<
|
||||
const sections = await getHomeSections(uid, limit, cursor)
|
||||
log.info('Home sections fetched')
|
||||
|
||||
if (sections.length === 0) {
|
||||
if (!sections) {
|
||||
// home feed creation pending
|
||||
const existingJob = await getJob(updateHomeJobId(uid))
|
||||
if (existingJob) {
|
||||
log.info('Update job job already enqueued')
|
||||
@ -63,6 +64,17 @@ export const homeResolver = authorized<
|
||||
}
|
||||
}
|
||||
|
||||
if (sections.length === 0) {
|
||||
// no available candidates
|
||||
return {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
hasPreviousPage: false,
|
||||
hasNextPage: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const endCursor = sections[sections.length - 1].score.toString()
|
||||
|
||||
const edges = sections.map((section) => {
|
||||
@ -127,7 +139,7 @@ export const hiddenHomeSectionResolver = authorized<
|
||||
const sections = await getHomeSections(uid)
|
||||
log.info('Home sections fetched')
|
||||
|
||||
if (sections.length === 0) {
|
||||
if (!sections) {
|
||||
return {
|
||||
errorCodes: [HomeErrorCode.Pending],
|
||||
}
|
||||
|
||||
@ -79,4 +79,4 @@ class ScoreClientImpl implements ScoreClient {
|
||||
}
|
||||
}
|
||||
|
||||
export const scoreClient = new ScoreClientImpl()
|
||||
export const scoreClient = new StubScoreClientImpl()
|
||||
|
||||
Reference in New Issue
Block a user