1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
|
/**
* 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
|