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

yext / answers-search-ui / 13118033733

03 Feb 2025 04:23PM UTC coverage: 61.767% (-0.4%) from 62.179%
13118033733

Pull #1914

github

web-flow
Merge 4bafd1b70 into 4bbe5b26d
Pull Request #1914: Generative Direct Answers integration

2026 of 3430 branches covered (59.07%)

Branch coverage included in aggregate %.

67 of 113 new or added lines in 7 files covered. (59.29%)

30 existing lines in 4 files now uncovered.

3483 of 5489 relevant lines covered (63.45%)

26.65 hits per line

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

48.35
/src/ui/components/results/generativedirectanswercomponent.js
1
/** @module GenerativeDirectAnswerComponent */
2

3
import Component from '../component';
4
import AnalyticsEvent from '../../../core/analytics/analyticsevent';
5
import StorageKeys from '../../../core/storage/storagekeys';
6
import DOM from '../../dom/dom';
7
import SearchStates from '../../../core/storage/searchstates';
8
import { getContainerClass } from '../../../core/utils/resultsutils';
9

10
export default class GenerativeDirectAnswerComponent extends Component {
11
  constructor (config = {}, systemConfig = {}) {
×
12
    super(config, systemConfig);
5✔
13

14
    /**
15
     * The user given config, without any defaults applied.
16
     * @type {Object}
17
     */
18
    this._userConfig = { ...config };
5✔
19

20
    /**
21
     * Recieve updates from storage based on this index
22
     * @type {StorageKey}
23
     */
24
    this.moduleId = StorageKeys.GENERATIVE_DIRECT_ANSWER;
5✔
25

26
    /**
27
     * The citations css selector to bind ui interactions for reporting
28
     * @type {string}
29
     */
30
    this._citationsSelector = config.citationsSelector || '.js-gda-citation';
5✔
31

32
    /**
33
     * The custom generative direct answer card to use, if any.
34
     */
35
    this._customCard = config.cardType;
5✔
36
  }
37

38
  static get type () {
39
    return 'GenerativeDirectAnswer';
33✔
40
  }
41

42
  /**
43
   * The template to render
44
   * @returns {string}
45
   * @override
46
   */
47
  static defaultTemplateName (config) {
48
    return 'results/generativedirectanswer';
5✔
49
  }
50

51
  /**
52
   * beforeMount, only display the generative direct answer component if
53
   * it has data or is in a loading state
54
   */
55
  beforeMount () {
56
    const searchState = this.getState('searchState');
6✔
57
    if (searchState === SearchStates.PRE_SEARCH) {
6!
58
      // Don't display GDA if we haven't yet searched
NEW
59
      return false;
×
60
    }
61
    if (searchState === SearchStates.SEARCH_COMPLETE &&
6✔
62
      (!this.hasState('resultStatus') || this.getState('resultStatus') === 'NO_ANSWER')) {
63
      // Don't display the GDA if there is no result status (default state of GDA object)
64
      // or if there was no answer found.
65
      return false;
2✔
66
    }
67

68
    return true;
4✔
69
  }
70

71
  /**
72
   * When the DOM is constructed, we want to wire up the behavior for
73
   * interacting with the citations
74
   */
75
  onMount () {
76
    const customCard = this.getState('customCard');
4✔
77
    // Avoid bindings if using a custom card.
78
    if (customCard) {
4!
NEW
79
      return this;
×
80
    }
81

82
    const citationsElements = DOM.queryAll(this._container, this._citationsSelector);
4✔
83
    citationsElements.forEach(citationElement => DOM.on(citationElement, 'click', e => this._handleCitationClickAnalytics(e)));
4✔
84

85
    const rtfElement = DOM.query(this._container, '.js-yxt-rtfValue');
4✔
86
    rtfElement && DOM.on(rtfElement, 'click', e => this._handleRtfClickAnalytics(e));
4✔
87
  }
88

89
  /**
90
   * A click handler for citations in a Generated Direct Answer.
91
   *
92
   * @param {MouseEvent} event The click event.
93
   */
94
  _handleCitationClickAnalytics (event) {
95
    // Climbing up the DOM to find the parent citation element, in case an interior element was clicked.
NEW
96
    let target = event.target;
×
NEW
97
    const citationTargetClassName = this._citationsSelector.substring(1);
×
NEW
98
    while (target && !target.classList.contains(citationTargetClassName)) {
×
NEW
99
      target = target.parentElement;
×
100
    }
NEW
101
    if (!target) {
×
NEW
102
      console.error('No citation target found for analytics.');
×
NEW
103
      return;
×
104
    }
NEW
105
    const entityId = target.dataset.entityid;
×
NEW
106
    if (!entityId) {
×
NEW
107
      console.error('No entityId found on citation element for analytics.');
×
NEW
108
      return;
×
109
    }
NEW
110
    const eventType = target.dataset.eventtype || 'CITATION_CLICK';
×
NEW
111
    const analyticsOptions = {
×
112
      generativeDirectAnswer: true,
113
      directAnswer: true,
114
      searcher: this.getState('searcher'),
115
      entityId
116
    };
NEW
117
    const analyticsEvent = new AnalyticsEvent(eventType);
×
NEW
118
    analyticsEvent.addOptions(analyticsOptions);
×
NEW
119
    this.analyticsReporter.report(analyticsEvent);
×
120
  }
121

122
  /**
123
   * A click handler for links in a Generated Direct Answer. When such a link
124
   * is clicked, an {@link AnalyticsEvent} needs to be fired.
125
   *
126
   * @param {MouseEvent} event The click event.
127
   */
128
  _handleRtfClickAnalytics (event) {
NEW
129
    if (!event.target.dataset.ctaType) {
×
NEW
130
      return;
×
131
    }
NEW
132
    const ctaType = event.target.dataset.ctaType;
×
133

NEW
134
    const analyticsOptions = {
×
135
      generativeDirectAnswer: true,
136
      directAnswer: true,
137
      fieldName: 'gda-snippet',
138
      searcher: this.getState('searcher'),
139
      url: event.target.href
140
    };
141

NEW
142
    const analyticsEvent = new AnalyticsEvent(ctaType);
×
NEW
143
    analyticsEvent.addOptions(analyticsOptions);
×
NEW
144
    this.analyticsReporter.report(analyticsEvent);
×
145
  }
146

147
  /**
148
   * updateState enables for partial updates (the delta between the old and new)
149
   * @type {object} The new state to apply to the old
150
   */
151
  updateState (state = {}) {
×
NEW
152
    const newState = Object.assign({}, this.getState(), state);
×
NEW
153
    this.setState(newState);
×
154
  }
155

156
  /**
157
   * Updates the search state css class on this component's container.
158
   */
159
  updateContainerClass (searchState) {
160
    Object.values(SearchStates).forEach(searchState => {
8✔
161
      this.removeContainerClass(getContainerClass(searchState));
24✔
162
    });
163
    this.addContainerClass(getContainerClass(searchState));
8✔
164
  }
165

166
  setState (data) {
167
    const searchState = data.searchState || SearchStates.PRE_SEARCH;
8✔
168
    this.updateContainerClass(searchState);
8✔
169
    if (searchState !== SearchStates.SEARCH_COMPLETE || !data.directAnswer) {
8✔
170
      return super.setState(Object.assign({}, data, {
7✔
171
        searchState
172
      }));
173
    }
174

175
    return super.setState(Object.assign({}, data, {
1✔
176
      searchState,
177
      searcher: data.searcher,
178
      generativeDirectAnswer: data,
179
      customCard: this._getCard()
180
    }));
181
  }
182

183
  /**
184
   * Determines the card that should be used for GDA
185
   * @returns {string}
186
   */
187
  _getCard () {
188
    return this._customCard;
3✔
189
  }
190

191
  addChild (data, type, opts) {
NEW
192
    if (type === this.getState('customCard')) {
×
NEW
193
      return super.addChild(this.getState('generativeDirectAnswer'), type, {
×
194
        ...this._userConfig,
195
        ...opts
196
      });
197
    }
NEW
198
    return super.addChild(data, type, opts);
×
199
  }
200
}
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