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

CBIIT / INS-WebPortal / 21373810788

26 Jan 2026 09:01PM UTC coverage: 2.1% (-0.2%) from 2.314%
21373810788

Pull #491

github

web-flow
Merge 89c1b31bc into ca62eea5a
Pull Request #491: INS-1483 Removed FE highlighting logic

50 of 1797 branches covered (2.78%)

Branch coverage included in aggregate %.

30 of 31 new or added lines in 1 file covered. (96.77%)

1 existing line in 1 file now uncovered.

56 of 3251 relevant lines covered (1.72%)

5.73 hits per line

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

96.94
/src/pages/dataSets/SearchResult/SearchResult.js
1
/* eslint-disable max-len */
2
import React, { useEffect } from 'react';
3
import {
4
  useLocation,
5
  useHistory,
6
  Link,
7
} from 'react-router-dom';
8
import styled from 'styled-components';
9
import { Popover } from 'bootstrap';
10
import ReactHtmlParser from 'html-react-parser';
11
import databaseIcon from '../../../assets/icons/database.svg';
12
import dataResourceIcon from '../../../assets/img/DataResource.png';
13
import {
14
  externalLinkIcon,
15
} from '../../../bento/datasetDetailData';
16

17
const SearchResultContainer = styled.div`
2✔
18
  width: 100%;
19
  padding-top: 10px;
20
  
21
  .messageContainer {
22
    padding: 0 0 10px 0;
23
    font-weight: bold;
24
  }
25

26
  .tableMessageContainer {
27
    padding: 10px 0 0 0;
28
    font-weight: bold;
29
  }
30

31
  .container {
32
    margin: 12px  16px;
33
    padding: 12px 20px;
34
    border: 1px solid #D3D3D3;
35
    font-size: 14px;
36
    background-color: #F8FBFD;
37
  }
38

39
  .container:hover {
40
    background-color: #f7f8fa;
41
  }
42

43
  .container a{
44
    color: #004187;
45
    text-decoration: none;
46
  }
47

48
  .container .headerRow {
49
    margin: 5px 5px 5px 0;
50
  }
51

52
  .container .headerRow .resultTitle {
53
    font-family: Inter;
54
    font-size: 20px;
55
    font-weight: 600;
56
    line-height: 24px;
57
    text-align: left;
58
    color: #20506A;
59
  }
60

61
  .resultSubTitle{
62
    font-family: Nunito;
63
    font-size: 16px;
64
    font-weight: 600;
65
    line-height: 19px;
66
    text-align: left;
67
    color: #571AFF;
68
    padding-bottom: 10px !important;
69
    margin-bottom: 10px !important;
70
    border-bottom: 1px solid #D3D3D3;
71
  }
72

73
  .headerRow .piBlock {
74
    text-align: right;
75
    padding: 0;
76
  }
77

78
  .headerRow .typeBlock {
79
    background-color: #dcdcdc;
80
    border-radius: 20px;
81
    padding: 5px 10px;
82
    float: right;
83
    position: relative;
84
  }
85
  
86
  .headerRow .typeBlock .tooltiptext {
87
    visibility: hidden;
88
    color: white;
89
    background-color: rgb(80, 80, 80);
90
    width: 300px;
91
    border: 1px solid #004187;
92
    border-radius: 6px;
93
    padding: 5px 5px 5px 5px;
94
    
95
    text-align: left;
96
    text-transform: none;
97
    font-size: 12px;
98
    line-height: normal;
99
  
100
    /* Position the tooltip */
101
    position: absolute;
102
    z-index: 1;
103
    top: 100%;
104
    left: -100%;
105
    margin: 10px 0px 0px 0;
106
  }
107
  
108
  .headerRow .typeBlock:hover .tooltiptext {
109
    visibility: visible;
110
  }
111

112
  .headerRow .newtooltip {
113
    color: #212529;
114
    text-decoration: none;
115
  }
116

117
  .container .subHeaderRow {
118
    margin: 5px 5px 5px 0;
119
  }
120

121
  .subHeaderRow .col-sm {
122
    padding: 0;
123
  }
124

125
  .subHeaderRow .col-sm .img0 {
126
    vertical-align: middle;
127
    margin-right: 5px;
128
  }
129

130
  .subHeaderRow .col-sm .img1 {
131
    width: 14pt;
132
  }
133

134
  .subHeaderRow .col-sm .img2 {
135
    width: 16pt;
136
    height: 10pt;
137
    padding: 0 4px;
138
  }
139

140

141
  .subHeaderRow .col-sm a {
142
    font-weight: bold;
143
    color: #571AFF;
144
  }
145

146
  .subHeaderRow .fa-file {
147
    color: #6199d0;
148
  }
149

150
  .bodyRow span {
151
    font-weight: 600;
152
  }
153

154
  .bodyRow .itemSpan {
155
    font-family: Nunito;
156
    font-size: 16px;
157
    font-weight: 400;
158
    line-height: 19px;
159
    text-align: left;
160
    color: #212529;
161
  }
162

163
  .bodyRow b {
164
    margin: 0 3px;
165
    padding: 0px 5px;
166
    border: 1px solid #9EC1DB;
167
    border-radius: 5px;
168
    background-color: #DFEEF9;
169
    color: #004187;
170
    font-size: 14px;
171
  }
172

173
  b b,
174
  b b b,
175
  b b b b,
176
  b b b b b {
177
    border: none !important;
178
  }
179

180
  .footerRow .itemSpan {
181
    padding: 0 5px;
182
    display: inline-block;
183
    margin-bottom: 5px;
184
  }
185

186
  .footerRow .additionalItemSpan {
187
    margin-right: 5px;
188
  }
189

190
  .footerRow b {
191
    margin: 0 3px 0 3px;
192
    padding: 1px 5px 1px 5px;
193
    border: 1px solid #9EC1DB;
194
    border-radius: 5px;
195
    background-color: #DFEEF9;
196
    color: #004187;
197
  }
198

199
  .footerRow a {
200
    margin: 0 3px 0 3px;
201
    padding: 1px 5px 1px 5px;
202
    border: 1px solid #9EC1DB;
203
    border-radius: 5px;
204
    font-weight: bold;
205
    background-color: #DFEEF9;
206
    color: #004187;
207
  }
208

209
  .descLink {
210
    margin: 0 3px 0 3px;
211
    padding: 1px 5px 1px 5px;
212
    border: 1px solid #9EC1DB;
213
    border-radius: 5px;
214
    font-weight: bold;
215
    background-color: #DFEEF9;
216
    color: #004187;
217
    line-height: 2.5em;
218
  }
219

220
  a[target="_blank"] {
221
    color: #571AFF;
222
    background-size: 32px;
223
    padding: 1px 30px 1px 5px;
224
  }
225

226
  .labelDiv{
227
    font-family: Inter;
228
    font-size: 16px;
229
    font-weight: 600;
230
    line-height: 20px;
231
    text-align: left;
232
    color: #1C58A1;
233
    margin-bottom: 10px;
234
    padding-left: 20px;
235
  }
236

237
  .bodyRow .textSpan {
238
    font-family: Nunito;
239
    font-size: 16px;
240
    font-weight: 400;
241
    line-height: 19px;
242
    text-align: left;
243
    color: #212529;
244
  }
245

246
  .bodyRow .caseCountHighlight {
247
    font-family: Nunito;
248
    font-size: 16px;
249
    font-weight: 400;
250
    line-height: 19px;
251
    text-align: left;
252
    color: #212529;
253
  }
254

255
  .bodyRow .sampleCountHighlight {
256
    font-family: Nunito;
257
    font-size: 16px;
258
    font-weight: 400;
259
    line-height: 19px;
260
    text-align: left;
261
    color: #212529;
262
  }
263

264
  .bodyRow .itemContinued {
265
    margin-left: 5px;
266
    font-weight: 600;
267
  }
268

269
  .container .footerRow:last-child {
270
    margin-bottom: 5px;
271
  }
272

273
  .footerRow label {
274
    font-weight: 600;
275
  }
276

277
  .datasetTableRow a {
278
    color: #6199d0;
279
    font-weight: 600;
280
    text-decoration: none;
281
  }
282

283
  .datasetTableRow .typeBlock .newtooltip {
284
    color: #212529;
285
    font-weight: normal;
286
    text-decoration: none;
287
  }
288

289
  .datasetTableRow span .tooltiptext {
290
    visibility: hidden;
291
    color: white;
292
    background-color: rgb(80, 80, 80);
293
    width: 300px;
294
    border: 1px solid #004187;
295
    border-radius: 6px;
296
    padding: 5px 5px 5px 5px;
297

298
    text-align: left;
299
    text-transform: none;
300
    font-size: 12px;
301
    line-height: normal;
302

303
    position: absolute;
304
    z-index: 1;
305
    margin: 30px 0px 0px -150px;
306
  }
307

308
  .datasetTableRow span:hover .tooltiptext {
309
    visibility: visible;
310
  }
311

312
  .additionalMatches {
313
    line-height:26px;
314
    font-family: Nunito;
315
    font-size: 16px;
316
    font-weight: 400 !important;
317
    text-align: left;
318
    color: #212529;
319
    word-break: break-word;
320
  }
321

322
  .dataRepo {
323
    color: #004187;
324
    font-size: 16px;
325
    margin-right: 20px;
326
    margin-left: 0;
327
    font-weight: bold;
328
  }
329

330
`;
331

332
const SearchResult = ({
2✔
333
  resultList,
334
  search,
335
  glossaryTerms,
336
}) => {
337
  const initializePopover = () => {
222✔
338
    const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
222✔
339
    popoverTriggerList.map((popoverTriggerEl) => new Popover(popoverTriggerEl));
222✔
340
  };
341

342
  function removeHTMLTags(str) {
343
    if (!str) return '';
228✔
344
    return str.replace(/<\/?[a-z][\s\S]*?>/gi, '');
226✔
345
  }
346

347
  /**
348
   * Gets the highlighted value from backend if available, otherwise returns content value
349
   */
350
  function getHighlightedValue(resultItem, fieldName) {
351
    if (!resultItem || !resultItem.content) {
744!
NEW
UNCOV
352
      return '';
×
353
    }
354

355
    const highlightKey = `${fieldName}.search`;
744✔
356

357
    // Check if backend provided a highlight for this field
358
    if (resultItem.highlight
744✔
359
        && resultItem.highlight[highlightKey]
360
        && resultItem.highlight[highlightKey][0]) {
361
      return resultItem.highlight[highlightKey][0];
82✔
362
    }
363

364
    // Fallback to content value
365
    const contentValue = resultItem.content[fieldName];
662✔
366
    return contentValue !== null && contentValue !== undefined ? contentValue : '';
662✔
367
  }
368

369
  /**
370
   * Determines if a hidden field should be shown (backend found a match)
371
   */
372
  function shouldShowHiddenField(resultItem, fieldName) {
373
    const highlightKey = `${fieldName}.search`;
2,964✔
374
    return !!(
2,964✔
375
      resultItem.highlight
2,994✔
376
      && resultItem.highlight[highlightKey]
377
      && resultItem.highlight[highlightKey][0]
378
    );
379
  }
380

381
  /**
382
   * Removes all HTML tags EXCEPT <b> and </b> tags
383
   */
384
  function removeHTMLTagsExceptBold(str) {
385
    if (!str) return '';
14!
386
    // Remove all HTML tags except <b> and </b>
387
    return str.replace(/<\/?(?!b\b)[a-z][\s\S]*?>/gi, '');
14✔
388
  }
389

390
  /**
391
   * Gets description value with proper truncation logic
392
   * Backend provides full description; frontend truncates if no match
393
   */
394
  function getDescriptionValue(resultItem) {
395
    const rawDescription = resultItem.content.description || '';
228✔
396
    const cleanDescription = removeHTMLTags(rawDescription);
228✔
397

398
    // Check if backend highlighted the description
399
    const highlightKey = 'description.search';
228✔
400
    const hasHighlight = !!(
228✔
401
      resultItem.highlight
235✔
402
      && resultItem.highlight[highlightKey]
403
      && resultItem.highlight[highlightKey][0]
404
    );
405

406
    if (hasHighlight) {
228✔
407
      // Backend highlighted the description - show FULL description with highlights
408
      // Remove all HTML tags except <b> tags, but DON'T truncate
409
      const highlightedDesc = resultItem.highlight[highlightKey][0];
14✔
410
      return removeHTMLTagsExceptBold(highlightedDesc);
14✔
411
    }
412

413
    // No highlight - truncate if longer than 500 chars
414
    if (cleanDescription.length > 500) {
214✔
415
      return `${cleanDescription.substring(0, 500)}...`;
2✔
416
    }
417

418
    return cleanDescription;
212✔
419
  }
420

421
  useEffect(() => {
222✔
422
    window.scrollTo(0, 0);
222✔
423
  }, []);
424

425
  useEffect(() => {
222✔
426
    initializePopover();
222✔
427
  }, [resultList, glossaryTerms]);
428

429
  return (
222✔
430
    <>
431
      <SearchResultContainer>
432
        {
433
          resultList.length === 0 ? (
111✔
434
            <div className="messageContainer">No result found. Please refine your search.</div>
435
          ) : resultList.map((rst, idx) => {
436
            const keyName = `sr_${idx}`;
228✔
437

438
            // Get highlighted values from backend (or fallback to content)
439
            const highlightedPrimaryDisease = getHighlightedValue(rst, 'primary_disease');
228✔
440
            const highlightedDatasetSourceRepo = getHighlightedValue(rst, 'dataset_source_repo');
228✔
441
            const highlightedStudyType = getHighlightedValue(rst, 'study_type');
228✔
442
            const highlightedDesc = getDescriptionValue(rst);
228✔
443

444
            // Build list of hidden fields that have matches (backend highlighted them)
445
            const hiddenFieldsConfig = [
228✔
446
              { fieldName: 'dataset_source_url', displayName: 'study page' },
447
              { fieldName: 'PI_name', displayName: 'PI name' },
448
              { fieldName: 'dataset_pmid', displayName: 'dataset pmid' },
449
              { fieldName: 'funding_source', displayName: 'funding source' },
450
              { fieldName: 'related_diseases', displayName: 'related diseases' },
451
              { fieldName: 'related_terms', displayName: 'related terms' },
452
              { fieldName: 'study_links', displayName: 'study links' },
453
              { fieldName: 'related_genes', displayName: 'related genes' },
454
              { fieldName: 'assay_method', displayName: 'assay method' },
455
              { fieldName: 'limitations_for_reuse', displayName: 'limitations for reuse' },
456
              { fieldName: 'dataset_doc', displayName: 'NCI Division/Office/Center' },
457
              { fieldName: 'institute', displayName: 'institute' },
458
              { fieldName: 'experimental_approaches', displayName: 'experimental approaches' },
459
            ];
460

461
            const additionalMatches = [];
228✔
462
            hiddenFieldsConfig.forEach(({ fieldName, displayName }) => {
228✔
463
              if (shouldShowHiddenField(rst, fieldName)) {
2,964✔
464
                const highlightedValue = getHighlightedValue(rst, fieldName);
60✔
465
                additionalMatches.push({ [displayName]: highlightedValue });
60✔
466
              }
467
            });
468

469
            return (
228✔
470
              <div key={keyName} className="container">
471
                <div className="row align-items-start headerRow">
472
                  <div className="col-sm resultTitle">
473
                    <Link to={`/dataset/${rst.content.dataset_source_id}`}>
474
                      {rst.content.dataset_title}
475
                    </Link>
476
                  </div>
477
                </div>
478
                <div className="row align-items-start subHeaderRow">
479
                  <div className="col-sm resultSubTitle">
480
                    <span className="dataRepo" data-testid="dataset-source-repo">
481
                      <img src={databaseIcon} alt="database-icon" className="img0" />
482
                      {ReactHtmlParser(highlightedDatasetSourceRepo)}
483
                    </span>
484
                    <img src={dataResourceIcon} alt="data-resource" className="img1" />
485
                    {rst.content.dataset_source_url ? (
114✔
486
                      <a href={rst.content.dataset_source_url} target="_blank" rel="noopener noreferrer" className="link">
487
                        {rst.content.dataset_source_id}
488
                        <img
489
                          src={externalLinkIcon.src}
490
                          alt={externalLinkIcon.alt}
491
                          className="img2"
492
                        />
493
                      </a>
494
                    ) : (
495
                      <span className="link">
496
                        {rst.content.dataset_source_id}
497
                      </span>
498
                    )}
499
                  </div>
500
                </div>
501
                {
502
                  <div className="row align-items-start bodyRow">
503
                    <div className="col labelDiv">
504
                      <span>Primary Disease:&nbsp;&nbsp;&nbsp;</span>
505
                      <span className="itemSpan" data-testid="primary-disease">
506
                        {ReactHtmlParser(highlightedPrimaryDisease)}
507
                      </span>
508
                    </div>
509
                  </div>
510
                }
511
                {
512
                  rst.content.study_type != null && rst.content.study_type !== '' && (
332✔
513
                    <div className="row align-items-start bodyRow">
514
                      <div className="col labelDiv">
515
                        <span>Study Type:&nbsp;&nbsp;&nbsp;</span>
516
                        <span className="itemSpan" data-testid="study-type">
517
                          {ReactHtmlParser(highlightedStudyType)}
518
                        </span>
519
                      </div>
520
                    </div>
521
                  )
522
                }
523
                {
524
                  rst.content.sample_count != null && rst.content.sample_count !== '' && (
332✔
525
                    <div className="row align-items-start bodyRow">
526
                      <div className="col labelDiv">
527
                        <span>Sample Count:&nbsp;&nbsp;&nbsp;</span>
528
                        <span className="textSpan sampleCountHighlight" data-testid="sample-count">
529
                          {rst.content.sample_count}
530
                        </span>
531
                      </div>
532
                    </div>
533
                  )
534
                }
535
                {
536
                  highlightedDesc !== '' && (
227✔
537
                    <div className="row align-items-start bodyRow">
538
                      <div className="col labelDiv">
539
                        <span>Description:&nbsp;&nbsp;&nbsp;</span>
540
                        <span className="textSpan" data-testid="description">
541
                          {ReactHtmlParser(highlightedDesc)}
542
                        </span>
543
                      </div>
544
                    </div>
545
                  )
546
                }
547
                {
548
                  additionalMatches.length > 0 && additionalMatches.map((match, index) => (
143✔
549
                    <div className="row align-items-start bodyRow" key={index}>
60✔
550
                      <div className="col labelDiv">
551
                        <span>
552
                          Other Match in
553
                          {' '}
554
                          {Object.keys(match)[0]}
555
                          :&nbsp;&nbsp;&nbsp;
556
                        </span>
557
                        <span className="additionalMatches" data-testid="additional-match">
558
                          {ReactHtmlParser(Object.values(match)[0])}
559
                        </span>
560
                      </div>
561
                    </div>
562
                  ))
563
                }
564
              </div>
565
            );
566
          })
567
        }
568
      </SearchResultContainer>
569
    </>
570
  );
571
};
572

573
export default SearchResult;
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