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

yext / search-ui-react / 10287642039

07 Aug 2024 03:54PM UTC coverage: 85.252% (-0.03%) from 85.286%
10287642039

push

github

web-flow
Check for search id before executing GDA (#454)

J=CLIP-1461
TEST=auto,manual

ran `npm run test` and tested manually on test-site to verified gda is executed as expected

1337 of 1744 branches covered (76.66%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

4 existing lines in 2 files now uncovered.

1906 of 2060 relevant lines covered (92.52%)

146.43 hits per line

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

80.65
/src/components/GenerativeDirectAnswer.tsx
1
import { 
6✔
2
  GenerativeDirectAnswerResponse, 
3
  useSearchActions, 
4
  useSearchState, 
5
  SearchTypeEnum,
6
  Result
7
} from '@yext/search-headless-react';
8
import { useComposedCssClasses } from '../hooks';
6✔
9
import { executeGenerativeDirectAnswer } from '../utils/search-operations';
6✔
10
import ReactMarkdown from "react-markdown";
6✔
11
import React from 'react';
6✔
12

13
/**
14
 * The CSS class interface used for {@link GenerativeDirectAnswer}.
15
 *
16
 * @public
17
 */
18
export interface GenerativeDirectAnswerCssClasses {
19
  container?: string,
20
  header?: string,
21
  answerText?: string,
22
  divider?: string,
23
  citationsContainer?: string,
24
  citation?: string,
25
  citationTitle?: string,
26
  citationSnippet?: string
27
}
28

29
const builtInCssClasses: Readonly<GenerativeDirectAnswerCssClasses> = {
65✔
30
  container: 'p-6 border border-gray-200 rounded-lg shadow-sm',
31
  header: 'text-xl',
32
  answerText: 'mt-4',
33
  divider: 'border-b border-gray-200 w-full pb-6 mb-6',
34
  citationsContainer: 'mt-4 flex overflow-x-auto gap-4',
35
  citation: 'p-4 border border-gray-200 rounded-lg shadow-sm bg-slate-100 flex flex-col grow-0 shrink-0 basis-64 text-sm text-neutral overflow-x-auto cursor-pointer hover:border-indigo-500',
36
  citationTitle: 'font-bold',
37
  citationSnippet: 'line-clamp-2 text-ellipsis break-words'
38
};
39

40
/**
41
 * Props for {@link GenerativeDirectAnswer}.
42
 *
43
 * @public
44
 */
45
export interface GenerativeDirectAnswerProps {
46
  /** CSS classes for customizing the component styling. */
47
  customCssClasses?: GenerativeDirectAnswerCssClasses,
48
  /** The header for the answer section of the generative direct answer. */
49
  answerHeader?: string | JSX.Element,
50
  /** The header for the citations section of the generative direct answer. */
51
  citationsHeader?: string | JSX.Element,
52
  /** The component for citation card */
53
  CitationCard?: (props: CitationProps) => JSX.Element | null
54
  /** The component for citations container */
55
  CitationsContainer?: (props: CitationsProps) => JSX.Element | null
56
}
57

58
/**
59
 * Displays the AI generated answer of a generative direct answer.
60
 *
61
 * @public
62
 *
63
 * @param props - {@link GenerativeDirectAnswerProps}
64
 * @returns A React element for the generative direct answer, or null if there is no generated answer
65
 */
66
export function GenerativeDirectAnswer({
6✔
67
  customCssClasses,
68
  answerHeader,
69
  citationsHeader,
70
  CitationCard,
71
  CitationsContainer = Citations,
2✔
72
}: GenerativeDirectAnswerProps): JSX.Element | null {
73
  const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);
3✔
74

75
  const isUniversal = useSearchState(state => state.meta.searchType) === SearchTypeEnum.Universal;
3✔
76
  const universalResults = useSearchState(state => state.universal);
3✔
77
  const verticalResults = useSearchState(state => state.vertical);
3✔
78
  const searchId = useSearchState(state => state.meta.uuid);
3✔
79

80
  const searchResults: Result[] | undefined = React.useMemo(() => {
3✔
81
    if (isUniversal) {
2!
82
      return universalResults.verticals?.flatMap(v => v.results);
9!
83
    } else {
84
      return verticalResults.results;
×
85
    }
86
  }, [isUniversal, universalResults, verticalResults]);
87

88
  const searchActions = useSearchActions();
3✔
89
  const gdaResponse = useSearchState(state => state.generativeDirectAnswer?.response);
3!
90
  const isLoading = useSearchState(state => state.generativeDirectAnswer?.isLoading);
3!
91

92
  React.useEffect(() => {
3✔
93
    if (!searchResults?.length || !searchId) {
2!
94
      return;
3✔
95
    }
UNCOV
96
    executeGenerativeDirectAnswer(searchActions);
×
97
  }, [searchResults, searchId]);
98

99
  if (!searchResults?.length || isLoading || !gdaResponse || gdaResponse.resultStatus !== 'SUCCESS') {
2!
100
    return null;
×
101
  }
102

103
  return (
3✔
104
    <div className={cssClasses.container}>
105
      <Answer gdaResponse={gdaResponse} cssClasses={cssClasses} answerHeader={answerHeader}/>
106
      <div className={cssClasses.divider} />
107
      <CitationsContainer
108
        gdaResponse={gdaResponse}
109
        cssClasses={cssClasses}
110
        searchResults={searchResults}
111
        citationsHeader={citationsHeader}
112
        CitationCard={CitationCard}
113
      />
114
    </div>
115
  );
116
}
117

118
interface AnswerProps {
119
  gdaResponse: GenerativeDirectAnswerResponse,
120
  cssClasses: GenerativeDirectAnswerCssClasses,
121
  answerHeader?: string | JSX.Element
122
}
123

124
/**
125
 * The answer section of the Generative Direct Answer.
126
 */
127
function Answer(props: AnswerProps) {
128
  const { 
129
    gdaResponse, 
130
    cssClasses,
131
    answerHeader = 'AI Generated Answer'
2✔
132
  } = props;
3✔
133
  return <>
3✔
134
    <div className={cssClasses.header}>
135
      {answerHeader}
136
    </div>
137
    <ReactMarkdown className={cssClasses.answerText} children={gdaResponse.directAnswer} />
138
  </>;
139
}
140

141
/**
142
 * Props for citations component.
143
 *
144
 * @public
145
 */
146
export interface CitationsProps {
147
  /** Response object containing generative direct answer info. */
148
  gdaResponse: GenerativeDirectAnswerResponse,
149
  /** CSS classes for customizing the component styling. */
150
  cssClasses: GenerativeDirectAnswerCssClasses,
151
  /** Returned results relevant to the users' query to be used in Citations. */
152
  searchResults: Result[],
153
  /** The header for the citations section generative direct answer. */
154
  citationsHeader?: string | JSX.Element,
155
  /** The component for citation card */
156
  CitationCard?: (props: CitationProps) => JSX.Element | null
157
}
158

159
/**
160
 * The citations section of the Generative Direct Answer.
161
 */
162
function Citations(props: CitationsProps) {
163
  const { 
164
    gdaResponse, 
165
    cssClasses,
166
    searchResults,
167
    citationsHeader = `Sources (${gdaResponse.citations.length})`,
1✔
168
    CitationCard = Citation
2✔
169
  } = props;
2✔
170
  if (!gdaResponse.citations.length) {
1!
171
    return null;
×
172
  }
173
  const citationCards: JSX.Element[] = [];
2✔
174
  gdaResponse.citations.forEach(
2✔
175
    citation => {
176
      const result: Result | undefined = searchResults.find(r => r.rawData.uid === citation);
10✔
177
      if (result) {
2!
178
        citationCards.push(
2✔
179
          <CitationCard 
180
            key={citation} 
181
            searchResult={result} 
182
            cssClasses={cssClasses}
183
          />
184
        )
185
      }
186
    });
187

188
  return <>
2✔
189
    <div className={cssClasses.header}>
190
      {citationsHeader}
191
    </div>
192
    <div className={cssClasses.citationsContainer}>
193
      {citationCards}
194
    </div>
195
  </>;
196
}
197

198
/**
199
 * Props for citation card.
200
 *
201
 * @public
202
 */
203
export interface CitationProps {
204
  searchResult: Result,
205
  cssClasses: GenerativeDirectAnswerCssClasses
206
}
207

208
function Citation(props: CitationProps) {
209
  const {
210
    searchResult,
211
    cssClasses
212
  } = props;
4✔
213
  return (
4✔
214
    <a className={cssClasses.citation} href={typeof searchResult.rawData.link === 'string' ? searchResult.rawData.link : undefined}>
4!
215
      <div className={cssClasses.citationTitle}>{searchResult.rawData.name}</div>
216
      <div className={cssClasses.citationSnippet}>{searchResult.rawData.description}</div>
217
    </a>
218
  );
219
}
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

© 2025 Coveralls, Inc