diff options
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js')
| -rw-r--r-- | node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js new file mode 100644 index 0000000..f6b64c1 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js @@ -0,0 +1,248 @@ +/** + * Builder methods for building a collection of search results models for a series of search result data. + */ +import * as validation from "@jet/environment/json/validation"; +import { isNothing } from "@jet/environment/types/optional"; +import * as models from "../../api/models"; +import * as serverData from "../../foundation/json-parsing/server-data"; +import { isDataHydrated } from "../../foundation/media/data-structure"; +import { shouldFilter } from "../filtering"; +import { nextPosition, pushBasicLocation, popLocation } from "../metrics/helpers/location"; +import * as onDevicePersonalization from "../personalization/on-device-personalization"; +import { searchResultFromData } from "./content/search-results"; +import * as searchAds from "./search-ads"; +import * as guidedSearch from "./guided-search/guided-search"; +import { addImpressionFields } from "../metrics/helpers/impressions"; +import { searchAdMissedOpportunityFromId } from "../lockups/ad-lockups"; +/** + * Determines where to display guided search, e.g. as mid-scroll module or pinned to top. + * @param meta The metadata from the MAPI response for search catalog request. + * @param objectGraph The App Store object graph. + * @returns The position of the mid-scroll guided search module in search results, or `undefined` for the pinned to top experience. + */ +export function guidedSearchPositionFromSearchResponseMeta(meta, objectGraph) { + var _a, _b, _c; + // Client debug override + const guidedSearchPositionOverride = (_a = objectGraph.userDefaults) === null || _a === void 0 ? void 0 : _a.integer("GuidedSearchOverrides.position"); + if (serverData.isNumber(guidedSearchPositionOverride) && guidedSearchPositionOverride > 1) { + return guidedSearchPositionOverride; + } + // Server controlled by default + return (_c = (_b = meta === null || meta === void 0 ? void 0 : meta.displayStyle) === null || _b === void 0 ? void 0 : _b.guidedSearch) === null || _c === void 0 ? void 0 : _c.position; +} +/** + * Create a `SearchResultsBuildResult` containing set of built and deferred results from the top-level search data container. + * This method supports prepending adverts. + * @param objectGraph + * @param requestMetadata Request metadata for search being performed. + * @param searchResponseMetadata Response metadata for search that was performed. + * @param metricsOptions Metrics options for built models. + * @param resultsData Array of search results data. + * @param advertData Data container with advert results. + * @param facetData Array of guided search token response data. + * @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 state to determine if the ad/first result has been installed by the user in the past + */ +export async function createSearchResults(objectGraph, requestMetadata, searchResponseMetadata, metricsOptions, resultsData, advertData = undefined, facetData = undefined, installedStates = undefined, appStates = undefined) { + var _a, _b, _c, _d, _e, _f; + /// Built models + const builtResults = []; + // Unhydrated items that will be fetched in pagination. + const deferredResults = []; + // Search Experiments Data + const searchExperimentsData = searchResponseMetadata || null; + // Generate the personalization data + const appIds = resultsData + .filter((resultData) => { + return resultData.type === "apps"; + }) + .map((resultData) => { + return resultData.id; + }); + const personalizationDataContainer = onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, new Set(appIds)); + // Build Adverts + let advertsSearchResult; + let advertsDisplayStyle; + if (searchAds.platformSupportsAdverts(objectGraph) && serverData.isDefinedNonNullNonEmpty(advertData)) { + const adsResultAndDisplayStyle = searchAds.adsResultFromSearchResults(objectGraph, advertData, resultsData, requestMetadata, metricsOptions, installedStates !== null && installedStates !== void 0 ? installedStates : null, appStates !== null && appStates !== void 0 ? appStates : null, searchExperimentsData, personalizationDataContainer); + advertsSearchResult = adsResultAndDisplayStyle.result; + advertsDisplayStyle = adsResultAndDisplayStyle.displayStyle; + if (serverData.isDefinedNonNullNonEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups)) { + advertsSearchResult.searchAdOpportunity = advertsSearchResult.lockups[0].searchAdOpportunity; + builtResults.push(advertsSearchResult); + } + } + // Flag for Ad Media Deduping + let isFirstResult = true; + const guidedSearchPosition = guidedSearchPositionFromSearchResponseMeta(searchResponseMetadata, objectGraph); + for (const [index, resultData] of resultsData.entries()) { + // Inject the mid-scroll guided search module if we've reached the desired position. + if (index === guidedSearchPosition) { + const tokens = createGuidedSearchTokens(objectGraph, requestMetadata.requestDescriptor, facetData, metricsOptions); + if (tokens.length > 0) { + const title = (_c = (_b = (_a = searchResponseMetadata === null || searchResponseMetadata === void 0 ? void 0 : searchResponseMetadata.displayStyle) === null || _a === void 0 ? void 0 : _a.guidedSearch) === null || _b === void 0 ? void 0 : _b.title) !== null && _c !== void 0 ? _c : objectGraph.loc.string("Search.Guided.Title.ExploreMore"); // static fallback query context + const guidedSearchResult = new models.GuidedSearchResult(title, tokens); + const impressionOptions = { + ...metricsOptions, + id: "midScrollGuidedSearch", + kind: "grouping", + targetType: "module", + title: title, + softwareType: null, + }; + addImpressionFields(objectGraph, guidedSearchResult, impressionOptions); + builtResults.push(guidedSearchResult); + nextPosition(metricsOptions.locationTracker); + } + } + // Deferred items for subsequent pagination. + if (!isDataHydrated(resultData)) { + // On the first unhydrated item, attach the rest of the queue to `deferredResults` to preserve ordering. + deferredResults.push(...resultsData.slice(index)); + break; + } + // Filter + if (shouldFilter(objectGraph, resultData, 10750 /* Filter.Search */)) { + continue; + } + // Advert: Update CPP data on first organic result. + // We must do this *after* the ad results are built, because we need to ensure we're picking the first lockup that will appear, + // not just the first data (that may be filtered somehow). + if (isFirstResult && serverData.isDefinedNonNullNonEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups)) { + searchAds.updateDupeOrganicResultCPPData(objectGraph, advertData !== null && advertData !== void 0 ? advertData : [], advertsSearchResult, resultData, installedStates !== null && installedStates !== void 0 ? installedStates : null, appStates !== null && appStates !== void 0 ? appStates : null, metricsOptions, personalizationDataContainer); + } + // Build model + const searchResult = searchResultFromData(objectGraph, resultData, searchResponseMetadata, personalizationDataContainer, metricsOptions, requestMetadata.requestDescriptor.isNetworkConstrained, requestMetadata.requestDescriptor.searchEntity, searchExperimentsData); + if (!searchResult || !platformSupportsResultType(objectGraph, searchResult)) { + continue; + } + /** + * Advert: When first advert and result matches, modify media. + */ + if (isFirstResult && + serverData.isDefinedNonNullNonEmpty(advertsSearchResult) && + serverData.isDefinedNonNullNonEmpty(advertsSearchResult.lockups)) { + searchAds.dedupeAdMediaFromMatchingResult(objectGraph, advertsSearchResult, searchResult, searchExperimentsData, advertsDisplayStyle); + } + /** + * Advert: When advert isn't available, mark the organic as a missed opportunity slot + */ + if (isFirstResult && + searchAds.platformSupportsAdverts(objectGraph) && + serverData.isDefinedNonNull((_d = metricsOptions.pageInformation) === null || _d === void 0 ? void 0 : _d.iAdInfo) && + (serverData.isNull(advertsSearchResult) || serverData.isNullOrEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups))) { + searchResult.searchAdOpportunity = searchAdMissedOpportunityFromId(objectGraph, metricsOptions.pageInformation); + (_e = searchResult.searchAdOpportunity) === null || _e === void 0 ? void 0 : _e.setMissedOpportunityReason("NOAD"); + (_f = searchResult.searchAdOpportunity) === null || _f === void 0 ? void 0 : _f.setTemplateType("APPLOCKUP"); + } + builtResults.push(searchResult); + isFirstResult = false; + nextPosition(metricsOptions.locationTracker); + } + return await applyClientFilteringToIAPs(objectGraph, builtResults).then((builtResultsFiltered) => { + return { + builtSearchResults: builtResultsFiltered, + deferredSearchResults: deferredResults, + }; + }); +} +// endregion +// region Internals +/** + * Create guided search tokens for the given search request descriptor and facet MAPI response data. + * @param objectGraph The App Store object graph. + * @param requestDescriptor The search request descriptor. + * @param facetData The media API response for guided search facets. + * @param metricsOptions The metrics options. + * @returns The guided search tokens to display. + */ +function createGuidedSearchTokens(objectGraph, requestDescriptor, facetData, metricsOptions) { + if (!objectGraph.host.isiOS || isNothing(facetData) || facetData.length === 0) { + return []; + } + pushBasicLocation(objectGraph, { + pageInformation: metricsOptions.pageInformation, + locationTracker: metricsOptions.locationTracker, + targetType: "SearchRevisions", + }, ""); + // Tokens from facet data + const tokens = []; + for (const data of facetData) { + const token = guidedSearch.createGuidedSearchToken(objectGraph, "rewrite", requestDescriptor, data, metricsOptions); + if (token) { + tokens.push(token); + nextPosition(metricsOptions.locationTracker); + } + } + popLocation(metricsOptions.locationTracker); + return tokens; +} +/** + * Apply client-side iAP filtering to set of results. + * @param objectGraph + * @param resultsToFilter Results to apply iAP filtering on. + */ +async function applyClientFilteringToIAPs(objectGraph, resultsToFilter) { + return await validation.context("applyClientFilteringToIAPs", async () => { + const iAPProductIDToParentBundleID = {}; + for (const result of resultsToFilter) { + if (result.resultType === "inAppPurchase") { + const inAppPurchaseResult = result; + const inAppPurchaseLockup = inAppPurchaseResult.lockup; + if (inAppPurchaseLockup.parent && + inAppPurchaseLockup.productIdentifier && + inAppPurchaseLockup.parent.bundleId) { + iAPProductIDToParentBundleID[inAppPurchaseLockup.productIdentifier] = + inAppPurchaseLockup.parent.bundleId; + } + else { + validation.unexpectedNull("ignoredValue", "string", `required fields for ${inAppPurchaseLockup.adamId}`); + } + } + } + if (Object.keys(iAPProductIDToParentBundleID).length === 0) { + return await Promise.resolve(resultsToFilter); + } + return await objectGraph.clientOrdering.visibilityForIAPs(iAPProductIDToParentBundleID).then((visibilities) => { + const filteredResults = resultsToFilter.filter((result) => { + if (result.resultType !== "inAppPurchase") { + return true; + } + const inAppPurchaseResult = result; + const inAppPurchaseLockup = inAppPurchaseResult.lockup; + if (inAppPurchaseLockup.productIdentifier && visibilities[inAppPurchaseLockup.productIdentifier]) { + return true; + } + else { + return inAppPurchaseLockup.isVisibleByDefault; + } + }); + return filteredResults; + }); + }); +} +/** + * Whether or not current platform supports displaying given `searchResult`. + */ +function platformSupportsResultType(objectGraph, searchResult) { + if (objectGraph.host.isTV) { + switch (searchResult.resultType) { + case "content": + case "editorial": + return true; + default: + return false; + } + } + if (!objectGraph.host.isiOS && !objectGraph.client.isWeb) { + switch (searchResult.resultType) { + case "appEvent": + return false; + default: + break; + } + } + return true; +} +// endregion +//# sourceMappingURL=search-results-pipeline.js.map
\ No newline at end of file |
