/** * A builder for all SearchResultsContainers variants except for `SearchLockupCollection`. * To be cleaned in the future. */ import * as validation from "@jet/environment/json/validation"; import { isSome } from "@jet/environment/types/optional"; import * as models from "../../../api/models"; import { EditorialSearchResult } from "../../../api/models"; import * as serverData from "../../../foundation/json-parsing/server-data"; import * as mediaAttributes from "../../../foundation/media/attributes"; import * as mediaRelationship from "../../../foundation/media/relationships"; import * as errors from "../../../foundation/util/errors"; import * as client from "../../../foundation/wrappers/client"; import * as appEvents from "../../app-promotions/app-event"; import * as appPromotionsCommon from "../../app-promotions/app-promotions-common"; import { createArtworkForResource } from "../../content/artwork/artwork"; import * as content from "../../content/content"; import { editorialDisplayOptionsFromClientParams, extractEditorialClientParams, } from "../../editorial-pages/editorial-data-util"; import * as filtering from "../../filtering"; import * as lockups from "../../lockups/lockups"; import * as metricsHelpersImpressions from "../../metrics/helpers/impressions"; import * as metricsHelpersUtil from "../../metrics/helpers/util"; import * as onDevicePersonalization from "../../personalization/on-device-personalization"; import { defaultTodayCardConfiguration, todayCardFromData } from "../../today/today-card-util"; import { TodayCardDisplayStyle, TodayParseContext } from "../../today/today-types"; import * as common from "./search-content-common"; import * as searchLockupCollection from "./search-lockup-collection"; export function searchResultFromData(objectGraph, resultData, searchResponseMetadata, personalizationDataContainer, metricsOptions, isNetworkConstrained, searchEntity, searchExperimentsData) { return validation.context("searchResultFromData", () => { let searchResult = null; const resultType = resultData.type; const standardLockupOptions = { metricsOptions: { pageInformation: metricsOptions.pageInformation, locationTracker: metricsOptions.locationTracker, targetType: "card", createUniqueImpressionId: true, }, hideZeroRatings: true, artworkUseCase: 8 /* content.ArtworkUseCase.SearchIcon */, isNetworkConstrained: isNetworkConstrained, canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mixedMediaLockup"), clientIdentifierOverride: clientIdentifierOverrideForSearchEntity(objectGraph, searchEntity), isMultilineTertiaryTitleAllowed: false, }; const condensedBehavior = condensedBehaviorForData(objectGraph, resultData, searchExperimentsData); switch (resultType) { case "rooms": case "multirooms": case "developers": case "editorial-items": case "groupings": if (resultType !== "editorial-items" && resultType !== "developers" && objectGraph.client.isVision) { return null; } const todayCardConfig = defaultTodayCardConfiguration(objectGraph); todayCardConfig.isSearchContext = true; const todayCard = todayCardFromData(objectGraph, resultData, todayCardConfig, new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker)); if (todayCard && todayCard.media && todayCard.media.kind === "inAppPurchase") { if (objectGraph.client.isVision) { return null; } const iapMedia = todayCard.media; const todayCardInAppPurchaseLockup = iapMedia.lockup; todayCardInAppPurchaseLockup.theme = "dark"; searchResult = new models.InAppPurchaseSearchResult(todayCardInAppPurchaseLockup); } else if (searchLockupCollection.resultDataShouldRenderLockupCollection(objectGraph, resultData, searchResponseMetadata)) { const lockupCollection = searchLockupCollection.lockupCollectionFromResultData(objectGraph, resultData, metricsOptions); if (lockupCollection) { searchResult = lockupCollection; } } else { const editorialSearchResult = editorialSearchResultFromData(objectGraph, resultData, standardLockupOptions.metricsOptions, condensedBehavior); if (editorialSearchResult) { if (editorialSearchResult.title) { editorialSearchResult.title = editorialSearchResult.title.replace(/\n/g, " "); } if (editorialSearchResult instanceof EditorialSearchResult && editorialSearchResult.subtitle) { editorialSearchResult.subtitle = editorialSearchResult.subtitle.replace(/\n/g, " "); } searchResult = editorialSearchResult; } } break; case "in-apps": if (objectGraph.client.isVision || objectGraph.client.isWeb) { return null; } // Ensure the parent app data is available before proceeding with building the lockup. const parentData = lockups.parentDataFromInAppData(objectGraph, resultData); if (serverData.isNullOrEmpty(parentData)) { return null; } const inAppPurchaseLockup = lockups.inAppPurchaseLockupFromData(objectGraph, resultData, standardLockupOptions); inAppPurchaseLockup.theme = "dark"; modifyMetadataBadgeForSearchExperiment(objectGraph, inAppPurchaseLockup, searchExperimentsData); searchResult = new models.InAppPurchaseSearchResult(inAppPurchaseLockup); break; case "apps": case "app-bundles": default: // There should never be an iad in the non-ad search results, so remove it here // before creating the lockups. delete resultData.attributes["iad"]; if (resultType === "app-bundles") { if (objectGraph.client.isVision) { return null; } standardLockupOptions.shouldIncludeScreenshotsForChildren = objectGraph.featureFlags.isEnabled("voyager_bundles_2025A"); const bundleLockup = lockups.lockupFromData(objectGraph, resultData, standardLockupOptions); bundleLockup.showMetadataInformationInLockup = true; modifyMetadataBadgeForSearchExperiment(objectGraph, bundleLockup, searchExperimentsData); searchResult = new models.BundleSearchResult(bundleLockup); } else { const lockup = lockups.mixedMediaLockupFromData(objectGraph, resultData, standardLockupOptions, { canPlayFullScreen: false, playbackControls: {}, }, searchExperimentsData); modifyMetadataBadgeForSearchExperiment(objectGraph, lockup, searchExperimentsData); // Extract out any associated app event from meta const appEventSearchResult = appEventSearchResultFromData(objectGraph, resultData, lockup, standardLockupOptions, personalizationDataContainer, metricsOptions); if (serverData.isDefinedNonNull(appEventSearchResult)) { searchResult = appEventSearchResult; searchResult.condensedBehavior = "never"; } else { // If no app event in meta, fallback to a regular mixed media lockup searchResult = new models.AppSearchResult(lockup); } } break; } if (serverData.isDefinedNonNull(searchResult) && serverData.isNull(searchResult.condensedBehavior)) { searchResult.condensedBehavior = condensedBehavior; } return searchResult; }); } /** * Indicates whether a search result will display an app event * @param objectGraph The object graph * @param searchResultData The data for the search result * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user * @param appStates A mapping of adamIDs to their respective app states that is used to determine if the app has been installed by the user in the past * @param metricsOptions Metrics options for built lockups * @param personalizationDataContainer Personalization criteria used for sorting and filtering app events * @returns a boolean result */ export function searchResultWillUseAppEventDisplay(objectGraph, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer) { const appEventEligibleToDisplay = searchResultIsEligibleToDisplayAppEvent(objectGraph, searchResultData); if (!appEventEligibleToDisplay) { return false; } const { dataItems } = selectedAppEventDataItems(objectGraph, searchResultData, personalizationDataContainer); let appEvent; for (const appEventDataItem of dataItems) { appEvent = sanitizedAppEvent(objectGraph, appEventDataItem, searchResultData, metricsOptions, undefined, undefined); if (serverData.isDefinedNonNull(appEvent)) { break; } } const appIsInstalled = serverData.isDefinedNonNull(installStates) && serverData.isDefinedNonNull(installStates[searchResultData.id]) ? installStates[searchResultData.id] : false; const appHasBeenInstalled = serverData.isDefinedNonNull(appStates) && serverData.isDefinedNonNull(appStates[searchResultData.id]) ? ["downloadable"].includes(appStates[searchResultData.id]) : false; // App Events are only displayed in native when the user has installed the app previously and there is a valid app event return (appIsInstalled || appHasBeenInstalled) && serverData.isDefinedNonNull(appEvent); } /** * Derive the condensed behavior from a search result * @param objectGraph The global object graph instance * @param resultData The data blob for this specific search result * @param pageSearchExperimentsData The page level search experiments data object */ function condensedBehaviorForData(objectGraph, resultData, pageSearchExperimentsData) { var _a, _b; /// Guard early against incompatible client devices if (!canHaveCondensedBehaviorForClient(objectGraph)) { return "never"; } const itemSearchExperimentData = resultData.meta; const itemCondensedStyle = (_a = itemSearchExperimentData === null || itemSearchExperimentData === void 0 ? void 0 : itemSearchExperimentData.displayStyle) === null || _a === void 0 ? void 0 : _a.condensed; if (serverData.isDefinedNonNull(itemCondensedStyle)) { return condensedBehaviorFromStyle(objectGraph, itemCondensedStyle); } const pageCondensedStyle = (_b = pageSearchExperimentsData === null || pageSearchExperimentsData === void 0 ? void 0 : pageSearchExperimentsData.displayStyle) === null || _b === void 0 ? void 0 : _b.condensed; if (serverData.isDefinedNonNull(pageCondensedStyle)) { return condensedBehaviorFromStyle(objectGraph, pageCondensedStyle); } return defaultCondensedBehaviorForClient(objectGraph); } /** * Gets the condensed behavior from a given condensed style, or a default behavior based on the client type * @param objectGraph The global object graph instance * @param condensedStyle The condensed style on the data model * @returns The condensed behavior for the native search result view */ function condensedBehaviorFromStyle(objectGraph, condensedStyle) { switch (condensedStyle) { case "always": return "always"; case "never": return "never"; case "when-installed": return "whenInstalled"; default: return defaultCondensedBehaviorForClient(objectGraph); } } /** * Determines the default condensed behavior based for a given client type * @param objectGraph The global object graph instance * @param condensedStyle The condensed style on the data model * @returns The condensed behavior for the native search result view */ function defaultCondensedBehaviorForClient(objectGraph) { switch (objectGraph.client.deviceType) { case "phone": return "whenInstalled"; default: return "never"; } } /** * Determines if the current client type supports condensed behavior * @param objectGraph The global object graph instance * @returns Whether condensed behavior is allowed */ function canHaveCondensedBehaviorForClient(objectGraph) { switch (objectGraph.client.deviceType) { case "phone": return true; default: return false; } } /** * Modifies the editor's choice badge based on whether the search experiment overrides the metadataPrecendence * for the badge * @param objectGraph App Store ObjectGraph * @param lockup The lockup we need to modify for the experiment * @param searchExperimentData The experiment data to pull the metadata info from */ function modifyMetadataBadgeForSearchExperiment(objectGraph, lockup, searchExperimentData) { const shouldShowEditorsChoice = metadataPrecedenceTypePreceedsType(objectGraph, searchExperimentData, "editorialBadgeInfo", "userRating"); if (serverData.isDefinedNonNull(shouldShowEditorsChoice)) { lockup.isEditorsChoice = lockup.isEditorsChoice && shouldShowEditorsChoice; } } /** * Determines whether the first metadata type should precede the second type for the given experiment data * @param objectGraph App Store ObjectGraph * @param experimentData The experiment data to pull the metadata info from * @param firstType Does this precede the second type in the experiment order * @param secondType Does this succeed the second type in the experiment order * @returns whether the first type precedes the second type */ function metadataPrecedenceTypePreceedsType(objectGraph, experimentData, firstType, secondType) { var _a; if (serverData.isNull(experimentData) || !objectGraph.client.isPhone) { return null; } const order = (_a = experimentData === null || experimentData === void 0 ? void 0 : experimentData.displayStyle) === null || _a === void 0 ? void 0 : _a.metadataPrecedenceOrder; if (!serverData.isDefinedNonNullNonEmpty(order)) { return null; } const firstIndex = order.indexOf(firstType); const secondIndex = order.indexOf(secondType); if (firstIndex === -1 && secondIndex === -1) { return null; } if (firstIndex === -1) { return false; } if (secondIndex === -1) { return true; } return firstIndex < secondIndex; } function editorialSearchResultFromData(objectGraph, resultData, metricsOptions, resultCondensedBehavior) { return validation.context("editorialSearchResultFromData", () => { let searchResult; const title = mediaAttributes.attributeAsString(resultData, "name"); const resultType = resultData.type; switch (resultType) { case "groupings": { const editorialSearchResult = new models.EditorialSearchResult(title); const collectionAdamIds = mediaAttributes.attributeAsArrayOrEmpty(resultData, "contentIds"); if (serverData.isDefinedNonNullNonEmpty(collectionAdamIds)) { editorialSearchResult.collectionAdamIds = collectionAdamIds; } else { const iconArtwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "artwork"), { useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */, allowingTransparency: true, }); editorialSearchResult.iconArtwork = iconArtwork; } editorialSearchResult.type = "category"; searchResult = editorialSearchResult; break; } case "rooms": case "multirooms": { const editorialSearchResult = new models.EditorialSearchResult(title); editorialSearchResult.artwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "artwork"), { useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */, cropCode: "sr", }); editorialSearchResult.collectionAdamIds = mediaAttributes.attributeAsArrayOrEmpty(resultData, "contentIds"); editorialSearchResult.type = "collection"; searchResult = editorialSearchResult; break; } case "editorial-items": { if (objectGraph.bag.searchFilterEditorialItemIds.has(resultData.id)) { return null; } // Bridge objects to Today builder. const todayParseContext = new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker); const shouldResultBeCondensed = resultCondensedBehavior === "always"; const editorialSearchResult = editorialSearchResultFromTodayCardData(objectGraph, resultData, todayParseContext, shouldResultBeCondensed); searchResult = editorialSearchResult; break; } case "developers": { const editorialSearchResult = new models.EditorialSearchResult(title); editorialSearchResult.artwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "editorialArtwork.bannerUber"), { useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */, cropCode: "sr", }); editorialSearchResult.type = "developer"; if (isSome(editorialSearchResult.artwork)) { searchResult = editorialSearchResult; } else { let topApps = mediaRelationship.relationshipCollection(resultData, "top-apps"); topApps = topApps.filter((topApp) => { return !filtering.shouldFilter(objectGraph, topApp, 76532 /* filtering.Filter.DeveloperPage */); }); const topAppIds = []; const topAppArtwork = []; topApps.forEach((app) => { topAppIds.push(app.id); const appIcon = content.iconFromData(objectGraph, app, { useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */, }); if (serverData.isDefinedNonNull(appIcon)) { topAppArtwork.push(appIcon); } }); editorialSearchResult.collectionAdamIds = topAppIds; editorialSearchResult.collectionAppIcons = topAppArtwork; // On visionOS, the developer result falls back to a lockup collection if no art is available. if (objectGraph.client.isVision) { const lockupCollection = searchLockupCollection.lockupCollectionFromResultData(objectGraph, resultData, metricsOptions); searchResult = lockupCollection; } else { searchResult = editorialSearchResult; } } break; } default: break; } if (serverData.isNull(searchResult)) { return null; } if (searchResult instanceof EditorialSearchResult) { if (searchResult.collectionAdamIds != null && searchResult.collectionAdamIds.length) { const lockupCount = searchResult.collectionAdamIds.length; if (lockupCount <= 5) { searchResult.artworkGridType = "extraLarge"; } else if (lockupCount <= 8) { searchResult.artworkGridType = "large"; } else if (lockupCount <= 16) { searchResult.artworkGridType = "mixed"; } else { searchResult.artworkGridType = "small"; } } if (objectGraph.client.isVision) { let badgeText; let badgeArtwork = createArtworkForResource(objectGraph, "systemimage://appstore"); switch (searchResult.type) { case "developer": badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_DEVELOPER_TITLE_CASE"); badgeArtwork = createArtworkForResource(objectGraph, "systemimage://person.crop.square"); break; case "category": badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_CATEGORY_TITLE_CASE"); break; case "collection": badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_COLLECTION_TITLE_CASE"); break; case "story": badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_STORY_TITLE_CASE"); break; default: break; } searchResult.badgeText = badgeText; searchResult.badgeArtwork = badgeArtwork; } } const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, resultData, searchResult.title, metricsOptions); searchResult.clickAction = lockups.actionFromData(objectGraph, resultData, impressionOptions, null); metricsHelpersImpressions.addImpressionFields(objectGraph, searchResult, impressionOptions); return searchResult; }); } function editorialSearchResultFromTodayCardData(objectGraph, cardData, todayParseContext, shouldResultBeCondensed) { // Card configuration to use for building TodayCards that will be converted into editorial search results. const cardConfig = defaultTodayCardConfiguration(objectGraph); cardConfig.isSearchContext = true; if (!objectGraph.client.isVision) { cardConfig.prevailingCropCodes = shouldResultBeCondensed && objectGraph.client.isPhone ? { defaultCrop: "DMGE.AppST01" } : { defaultCrop: "fo" }; } // If we are on visionOS, drop any EIs that have a not-compatible app as the primary content. if (objectGraph.client.isVision) { const primaryContent = content.primaryContentForData(objectGraph, cardData); if (isSome(primaryContent)) { const runnableOnDevice = content.runnableOnDeviceWithData(objectGraph, primaryContent, objectGraph.client.deviceType, objectGraph.appleSilicon.isSupportEnabled); if (!runnableOnDevice) { return null; } } } const todayCard = todayCardFromData(objectGraph, cardData, cardConfig, todayParseContext); if (!todayCard) { return null; } const editorialSearchResult = new models.EditorialSearchResult(todayCard.title); editorialSearchResult.type = "story"; editorialSearchResult.clickAction = todayCard.clickAction; let collectionLockups = null; if (todayCard.media) { switch (todayCard.media.kind) { case "brandedSingleApp": const mediaSingleApp = todayCard.media; editorialSearchResult.artwork = mediaSingleApp.artworks[0]; if (serverData.isNull(editorialSearchResult.artwork)) { editorialSearchResult.iconArtwork = mediaSingleApp.icon; } const cardDisplayStyle = mediaAttributes.attributeAsString(cardData, "cardDisplayStyle"); switch (cardDisplayStyle) { case TodayCardDisplayStyle.AppOfTheDay: case TodayCardDisplayStyle.GameOfTheDay: const relatedContentData = mediaRelationship.relationshipData(objectGraph, cardData, "card-contents"); if (relatedContentData) { editorialSearchResult.title = mediaAttributes.attributeAsString(relatedContentData, "name") || editorialSearchResult.title; } break; default: break; } break; case "list": const mediaList = todayCard.media; collectionLockups = mediaList.lockups; break; case "river": const mediaRiver = todayCard.media; collectionLockups = mediaRiver.lockups; break; case "artwork": const mediaArtwork = todayCard.media; editorialSearchResult.artwork = mediaArtwork.artworks[0]; break; case "grid": const mediaGrid = todayCard.media; collectionLockups = mediaGrid.lockups; break; case "multiApp": const multiApp = todayCard.media; collectionLockups = multiApp.lockups; break; case "video": const mediaVideo = todayCard.media; editorialSearchResult.artwork = mediaVideo.videos[0].preview; editorialSearchResult.video = mediaVideo.videos[0]; if (todayCard.overlay instanceof models.TodayCardThreeLineOverlay) { const overlay = todayCard.overlay; editorialSearchResult.title = overlay.title; editorialSearchResult.subtitle = overlay.description; } else { editorialSearchResult.subtitle = mediaVideo.description; } break; case "appEvent": const media = todayCard.media; editorialSearchResult.artwork = media.artworks[0]; editorialSearchResult.appEventFormattedDates = media.formattedDates; editorialSearchResult.subtitle = todayCard.inlineDescription; editorialSearchResult.tintColor = media.tintColor; editorialSearchResult.type = "appEventStory"; if (serverData.isDefinedNonNull(todayCard.style)) { switch (todayCard.style) { case "light": case "white": editorialSearchResult.mediaOverlayStyle = "light"; break; case "dark": editorialSearchResult.mediaOverlayStyle = "dark"; break; default: errors.unreachable(todayCard.style); break; } } break; default: break; } } if (todayCard.overlay) { switch (todayCard.overlay.kind) { case "lockup": const cardOverlayLockup = todayCard.overlay; if (!editorialSearchResult.artwork || objectGraph.client.isVision) { collectionLockups = [cardOverlayLockup.lockup]; } break; case "lockupList": const cardOverlayList = todayCard.overlay; collectionLockups = cardOverlayList.lockups; break; case "paragraph": const cardOverlayParagraph = todayCard.overlay; editorialSearchResult.subtitle = cardOverlayParagraph.paragraph.text; break; default: break; } } if (serverData.isDefinedNonNull(collectionLockups)) { editorialSearchResult.collectionAdamIds = []; editorialSearchResult.collectionAppIcons = []; for (const lockup of collectionLockups) { editorialSearchResult.collectionAdamIds.push(lockup.adamId); editorialSearchResult.collectionAppIcons.push(lockup.icon); } if (collectionLockups.length === 1) { editorialSearchResult.lockup = collectionLockups[0]; } } const editorialClientParams = extractEditorialClientParams(objectGraph, cardData); editorialSearchResult.editorialDisplayOptions = editorialDisplayOptionsFromClientParams(editorialClientParams); /** * Editorial tagline */ const storyTagline = common.editorialSearchResultTagline(objectGraph, cardData); if ((storyTagline === null || storyTagline === void 0 ? void 0 : storyTagline.length) > 0 && storyTagline !== editorialSearchResult.title) { editorialSearchResult.tagline = storyTagline; } const heroMedia = todayCard.heroMedia; if (serverData.isDefinedNonNullNonEmpty(heroMedia)) { if (serverData.isDefinedNonNullNonEmpty(heroMedia.artworks[0])) { editorialSearchResult.artwork = heroMedia.artworks[0]; editorialSearchResult.artwork.crop = "em"; } else if (serverData.isDefinedNonNullNonEmpty(heroMedia.videos[0])) { editorialSearchResult.video = heroMedia.videos[0]; } } if (editorialSearchResult.video) { editorialSearchResult.video.canPlayFullScreen = false; editorialSearchResult.video.playbackControls = {}; } if (!editorialSearchResult.collectionAdamIds && !editorialSearchResult.artwork && !editorialSearchResult.iconArtwork) { return null; } return editorialSearchResult; } /// Gets the client identifier (or null) that should be used when building lockups for a given search entity. function clientIdentifierOverrideForSearchEntity(objectGraph, searchEntity) { return searchEntity === "watch" ? client.watchIdentifier : null; } /** * Indicates whether a search result meets basic sanity requirements to display app events. This does not include business rules for massaging a valid app event. For that, see `sanitizedAppEvent` function * @param objectGraph The object graph * @param searchResultData The data for the search result * @returns a boolean result */ function searchResultIsEligibleToDisplayAppEvent(objectGraph, searchResultData) { if (!appPromotionsCommon.appEventsAreEnabled(objectGraph) || serverData.isNull(searchResultData.meta)) { return false; } // The first organic can't use CPP data from the ad if it has an app event that will be displayed. // In this case, it should continue to use DPP assets const appEventDataItems = serverData.asArrayOrEmpty(searchResultData.meta, "associations.app-events.data"); const hasInAppEvents = appEventDataItems.length > 0; // App events can be displayed on most search result types, except for these ones const typesIneligibleToDisplayAppEvent = [ "rooms", "multirooms", "developers", "editorial-items", "groupings", "in-apps", "app-bundles", ]; const typeIsEligibleToDisplayAppEvent = !typesIneligibleToDisplayAppEvent.includes(searchResultData.type); return typeIsEligibleToDisplayAppEvent && hasInAppEvents; } /** * Sorts and filters app event for personalization, if applicable. May filter app events, and may select just the best one * @param objectGraph The object graph * @param resultData The data for the search result * @param personalizationDataContainer Personalization criteria used for sorting and filtering app events * @returns an object representing the personalized app events for a user, along with the personalization result */ function selectedAppEventDataItems(objectGraph, resultData, personalizationDataContainer) { const alwaysShowAppEvent = serverData.asBooleanOrFalse(resultData.meta, "associations.app-events.attributes.forceAppEvent"); const appEventDataItems = serverData.asArrayOrEmpty(resultData.meta, "associations.app-events.data"); if (alwaysShowAppEvent) { // In this scenario, there should always be only one event to choose from, // and we don't want to do any personalization. return { dataItems: [appEventDataItems[0]] }; } const personalizedDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "search", appEventDataItems, false, personalizationDataContainer, false, undefined, resultData.id); const personalizedDataItems = personalizedDataResult.personalizedData; if (personalizedDataItems.length <= 0) { return { dataItems: [] }; } return { dataItems: personalizedDataItems, personalizationData: personalizedDataResult }; } /** * Sanitizes a raw app event metadata into an AppEvent model. If the event is not hydratable or has not started, this returns null. * @param objectGraph The object graph * @param appEventDataItem The app event that needs to be processed * @param resultData The data for the search result * @param baseMetricsOptions Metrics options for built lockups * @param offerEnvironment Offer environment for the lockup. Typically comes from lockup options * @param offerStyle Offer style for the lockup. Typically comes from lockup options * @returns the processed app event with all business rules applied, or null if the app event was disqualified */ function sanitizedAppEvent(objectGraph, appEventDataItem, resultData, baseMetricsOptions, offerEnvironment, offerStyle) { const metricsOptions = { ...baseMetricsOptions, targetType: "eventModule", }; const appEventOrDate = appEvents.appEventOrPromotionStartDateFromData(objectGraph, appEventDataItem, resultData, false, true, offerEnvironment, offerStyle, false, metricsOptions, false, true, null, false, false); // Ignore a future AppEvent promotionStartDate here. if (serverData.isNull(appEventOrDate) || appEventOrDate instanceof Date) { return null; } else { return appEventOrDate; } } /** * Creates an app event search result from the data and associated lockup, if an app event exists in the metadata * @param resultData The data blob * @param lockup The associated mixed media lockup * @param standardLockupOptions: The standard lockup options for search results * @param personalizationDataContainer The data container to use for personalizing the selected app event * @param baseMetricsOptions Base metrics options for this result * @returns {AppEventSearchResult} The app event search result, or null */ function appEventSearchResultFromData(objectGraph, resultData, lockup, standardLockupOptions, personalizationDataContainer, baseMetricsOptions) { return validation.context("appEventSearchResultFromData", () => { const appEventEligibleToDisplay = searchResultIsEligibleToDisplayAppEvent(objectGraph, resultData); if (!appEventEligibleToDisplay) { return null; } const { dataItems, personalizationData } = selectedAppEventDataItems(objectGraph, resultData, personalizationDataContainer); let appEvent; let parentAppData; for (const appEventDataItem of dataItems) { const appEventOrNull = sanitizedAppEvent(objectGraph, appEventDataItem, resultData, baseMetricsOptions, standardLockupOptions.offerEnvironment, standardLockupOptions.offerStyle); if (serverData.isDefinedNonNull(appEventOrNull)) { appEvent = appEventOrNull; parentAppData = resultData !== null && resultData !== void 0 ? resultData : mediaRelationship.relationshipData(objectGraph, appEventDataItem, "app"); break; } } if (serverData.isNull(appEvent)) { return null; } const alwaysShowAppEvent = serverData.asBooleanOrFalse(resultData.meta, "associations.app-events.attributes.forceAppEvent"); const searchResult = new models.AppEventSearchResult(); searchResult.lockup = lockup; searchResult.appEvent = appEvent; searchResult.alwaysShowAppEvent = alwaysShowAppEvent; searchResult.clickAction = lockup.clickAction; const recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(null, personalizationData === null || personalizationData === void 0 ? void 0 : personalizationData.processingType, null); const impressionOptions = { ...baseMetricsOptions, id: appEvent.appEventId, kind: "inAppEvent", targetType: "eventModule", title: appEvent.title, softwareType: null, recoMetricsData: recoMetricsData, }; if (serverData.isDefinedNonNull(parentAppData)) { impressionOptions.relatedSubjectIds = [parentAppData.id]; } metricsHelpersImpressions.addImpressionFields(objectGraph, searchResult, impressionOptions); return searchResult; }); } //# sourceMappingURL=search-results.js.map