• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yext / search-ui-react / 17986846418

24 Sep 2025 07:05PM UTC coverage: 87.704% (-0.6%) from 88.287%
17986846418

Pull #508

github

web-flow
Merge d9b1f2121 into f4c07d723
Pull Request #508: ksearch: upgrade to Events API

817 of 1029 branches covered (79.4%)

Branch coverage included in aggregate %.

40 of 67 new or added lines in 8 files covered. (59.7%)

3 existing lines in 3 files now uncovered.

2086 of 2281 relevant lines covered (91.45%)

134.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.0
/src/hooks/useCardAnalytics.ts
1
import {DirectAnswer as DirectAnswerData, DirectAnswerType, Result, useSearchState} from '@yext/search-headless-react';
20✔
2
import {useCallback} from 'react';
20✔
3
import {FeedbackType} from '../components/ThumbsFeedback';
4
import {DefaultRawDataType} from '../models/index';
5
import {useAnalytics} from './useAnalytics';
20✔
6
import {GdaClickEventData} from '../components/GenerativeDirectAnswer'
7
import {SearchAction} from "../models/SearchEventPayload";
8

9
/**
10
 * Analytics event types for cta click, title click, and citation click.
11
 *
12
 * @public
13
 */
14
export type CardCtaEventType = 'CTA_CLICK' | 'TITLE_CLICK' | 'CITATION_CLICK' | 'DRIVING_DIRECTIONS' | 'VIEW_WEBSITE' | 'TAP_TO_CALL';
15

16
/**
17
 * The data types use to construct the payload in the analytics event.
18
 *
19
 * @public
20
 */
21
export type CardAnalyticsDataType<T = DefaultRawDataType> = DirectAnswerData | Result<T> | GdaClickEventData;
22

23
/**
24
 * Analytics event types for interactions on a card.
25
 *
26
 * @public
27
 */
28
export type CardAnalyticsType = CardCtaEventType | FeedbackType;
29

30
function isDirectAnswer(data: unknown): data is DirectAnswerData {
31
  return (data as DirectAnswerData)?.type === DirectAnswerType.FeaturedSnippet ||
14✔
32
      (data as DirectAnswerData)?.type === DirectAnswerType.FieldValue;
33
}
34

35
function isGenerativeDirectAnswer(data: unknown): data is GdaClickEventData {
36
  return !!(data as GdaClickEventData)?.destinationUrl;
2✔
37
}
38

39
export function useCardAnalytics<T>(): (
20✔
40
  cardResult: CardAnalyticsDataType<T>, analyticsEventType: CardAnalyticsType
41
) => void {
42
  const analytics = useAnalytics();
113✔
43
  const verticalKey = useSearchState(state => state.vertical?.verticalKey);
141✔
44
  const queryId = useSearchState(state => state.query.queryId);
141✔
45
  const searchId = useSearchState(state => state.meta.uuid);
141✔
46
  const locale = useSearchState(state => state.meta.locale);
141✔
47
  const experienceKey = useSearchState(state => state.meta.experienceKey);
141✔
48

49
  const reportCtaEvent = useCallback((
113✔
50
    result: CardAnalyticsDataType<T>,
51
    eventType: CardCtaEventType
52
  ) => {
53
    let url: string | undefined, entityId: string | undefined;
54
    let directAnswer = false;
4✔
55
    let generativeDirectAnswer = false;
4✔
56
    if (isDirectAnswer(result)) {
4✔
57
      url = result.relatedResult.link;
2✔
58
      entityId = result.relatedResult.id;
2✔
59
      directAnswer = true;
2✔
60
    } else if (isGenerativeDirectAnswer(result)) {
2!
61
      url = result.destinationUrl;
2✔
62
      entityId = result.searchResult?.id;
2✔
63
      directAnswer = true;
2✔
64
      generativeDirectAnswer = true;
2✔
65
    } else {
66
      url = result.link;
×
67
      entityId = result.id;
×
68
    }
69

70
    if (!queryId) {
4!
71
      console.error('Unable to report a CTA event. Missing field: queryId.');
×
72
      return;
×
73
    }
74
    if (!searchId) {
4!
NEW
75
      console.error('Unable to report a CTA event. Missing field: searchId.');
×
NEW
76
      return;
×
77
    }
78
    if (!experienceKey) {
4!
NEW
79
      console.error('Unable to report a CTA event. Missing field: experienceKey.');
×
NEW
80
      return;
×
81
    }
82
    // convert the legacy card event type to the format the current reporter expects.
83
    let action:SearchAction = eventType === 'TITLE_CLICK' ? 'TITLE' : eventType === 'VIEW_WEBSITE' ? 'WEBSITE': eventType;
4!
84
    analytics?.report({
4✔
85
      action: action,
86
      destinationUrl: url,
87
      entity: entityId,
88
      locale,
89
      searchId,
90
      queryId,
91
      verticalKey: verticalKey || '',
8✔
92
      isDirectAnswer: directAnswer,
93
      isGenerativeDirectAnswer: generativeDirectAnswer,
94
      experienceKey,
95
    });
96
  }, [analytics, queryId, verticalKey]);
97

98
  const reportFeedbackEvent = useCallback((
113✔
99
    result: CardAnalyticsDataType<T>,
100
    feedbackType: FeedbackType
101
  ) => {
102
    if (!queryId) {
10!
103
      console.error('Unable to report a result feedback event. Missing field: queryId.');
×
104
      return;
×
105
    }
106
    if (!searchId) {
10!
NEW
107
      console.error('Unable to report a result feedback event. Missing field: searchId.');
×
NEW
108
      return;
×
109
    }
110
    if (!experienceKey) {
10!
NEW
111
      console.error('Unable to report a result feedback event. Missing field: experienceKey.');
×
NEW
112
      return;
×
113
    }
114
    let directAnswer = false;
10✔
115
    let generativeDirectAnswer = false;
10✔
116
    let entityId: string | undefined;
117
    if (isDirectAnswer(result)) {
10!
118
      directAnswer = true;
10✔
119
      entityId = result.relatedResult.id;
10✔
120
    } else if (isGenerativeDirectAnswer(result)) {
×
121
      directAnswer = true;
×
NEW
122
      generativeDirectAnswer = true;
×
UNCOV
123
      entityId = result.searchResult?.id;
×
124
    } else {
125
      entityId = result.id;
×
126
    }
127
    analytics?.report({
10✔
128
      action: feedbackType,
129
      entity: entityId,
130
      locale,
131
      searchId,
132
      queryId,
133
      verticalKey: verticalKey || '',
20✔
134
      isDirectAnswer: directAnswer,
135
      isGenerativeDirectAnswer: generativeDirectAnswer,
136
      experienceKey
137
    });
138
  }, [analytics, queryId, verticalKey]);
139

140
  return useCallback((
113✔
141
      cardResult: CardAnalyticsDataType<T>,
142
      analyticsEventType: CardAnalyticsType
143
  ) => {
144
    if (!analytics) {
14!
145
      return;
×
146
    }
147
    if (analyticsEventType === 'TITLE_CLICK' || analyticsEventType === 'CTA_CLICK' || analyticsEventType === 'CITATION_CLICK' || analyticsEventType === 'DRIVING_DIRECTIONS' || analyticsEventType === 'VIEW_WEBSITE' || analyticsEventType === 'TAP_TO_CALL') {
14✔
148
      reportCtaEvent(cardResult, analyticsEventType);
4✔
149
    }
150
    if (analyticsEventType === 'THUMBS_DOWN' || analyticsEventType === 'THUMBS_UP') {
14✔
151
      reportFeedbackEvent(cardResult, analyticsEventType);
10✔
152
    }
153
  }, [analytics, reportCtaEvent, reportFeedbackEvent]);
154
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc