Skip to content

Commit

Permalink
Task/WP-72: Highlight matching search terms (#873)
Browse files Browse the repository at this point in the history
* task/WP-72 highlighted search queries/term in job history infinitescrolltable

* fixed formatting

* removed highlight and added bold

* remove css properties and replaced <mark> with <b>

* used local css modules instead of global

* Changed implementation of HighlightSearchTerm component to be used by Jobs.jsx

* added useMemo hook and improved HighlightSearchTerm perf

* removed useMemo hook

* added unit tests for HighlightSearchTerm component

* updated comment and fixed linting

---------

Co-authored-by: Shayan Khan <shayanaijaz@gmail.com>
Co-authored-by: Chandra Y <cyemparala@tacc.utexas.edu>
  • Loading branch information
3 people authored Oct 24, 2023
1 parent 31af29e commit f3e7f18
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 15 deletions.
49 changes: 34 additions & 15 deletions client/src/components/Jobs/Jobs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Link, useLocation } from 'react-router-dom';
import {
AppIcon,
InfiniteScrollTable,
HighlightSearchTerm,
Message,
SectionMessage,
Section,
Expand Down Expand Up @@ -90,8 +91,6 @@ function JobsView({
original: { id, uuid, name },
},
}) => {
const query = queryStringParser.parse(useLocation().search);

// TODOv3: dropV2Jobs
const jobsPathname = uuid ? `/jobs/${uuid}` : `/jobsv2/${id}`;
return (
Expand All @@ -105,11 +104,11 @@ function JobsView({
}}
className="wb-link"
>
View Details
{query.query_string ? <b>View Details</b> : 'View Details'}
</Link>
);
},
[]
[query]
);

if (error) {
Expand All @@ -133,15 +132,24 @@ function JobsView({
{
Header: 'Job Name',
accessor: 'name',
Cell: (el) => (
<span
title={el.value}
id={`jobID${el.row.index}`}
className="job__name"
>
{el.value}
</span>
),
Cell: (el) => {
return (
<span
title={el.value}
id={`jobID${el.row.index}`}
className="job__name"
>
{query.query_string ? (
<HighlightSearchTerm
searchTerm={query.query_string}
content={el.value}
/>
) : (
el.value
)}
</span>
);
},
},
{
Header: 'Job Status',
Expand Down Expand Up @@ -183,12 +191,20 @@ function JobsView({
// TODOv3: dropV2Jobs
if (el.row.original.uuid) {
const outputLocation = getOutputPath(el.row.original);

return outputLocation && !hideDataFiles ? (
<Link
to={`${ROUTES.WORKBENCH}${ROUTES.DATA}/tapis/private/${outputLocation}`}
className="wb-link job__path"
>
{outputLocation}
{query.query_string ? (
<HighlightSearchTerm
searchTerm={query.query_string}
content={outputLocation}
/>
) : (
outputLocation
)}
</Link>
) : null;
} else {
Expand Down Expand Up @@ -232,7 +248,10 @@ function JobsView({
</Section>
}
getRowProps={rowProps}
columnMemoProps={[version]} /* TODOv3: dropV2Jobs. */
columnMemoProps={[
version,
query,
]} /* TODOv3: dropV2Jobs. Refactor version prop. */
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './HighlightSearchTerm.module.scss';

const HighlightSearchTerm = ({ searchTerm, content }) => {
if (!searchTerm) {
return <>{content}</>;
}

const searchTermRegex = new RegExp(`(${searchTerm})`, 'gi');

const highlightParts = () => {
const parts = content.split(searchTermRegex);
return parts.map((part, i) => {
const isSearchTerm = part.match(searchTermRegex);
return isSearchTerm ? (
<b className={styles['highlight']} key={i}>
{part}
</b>
) : (
part
);
});
};

return <>{highlightParts()}</>;
};

HighlightSearchTerm.propTypes = {
searchTerm: PropTypes.string,
content: PropTypes.string,
};

HighlightSearchTerm.defaultProps = {
searchTerm: '',
content: '',
};

export default HighlightSearchTerm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.highlight {
font-weight: bold;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { render } from '@testing-library/react';
import {
toBeInTheDocument,
toHaveClass,
toBeNull,
} from '@testing-library/jest-dom';

import HighlightSearchTerm from './HighlightSearchTerm';

describe('HighlightSearchTerm Component', () => {
it('renders content when searchTerm is not provided', () => {
const { getByText } = render(<HighlightSearchTerm content="Lorem ipsum" />);
expect(getByText('Lorem ipsum')).toBeInTheDocument();
});

it('renders without highlighting when searchTerm in content do not match', () => {
const { getByText } = render(
<HighlightSearchTerm
searchTerm="minim"
content="Lorem ipsum dolor sit amet"
/>
);
expect(getByText('Lorem ipsum dolor sit amet')).toBeInTheDocument();
expect(document.querySelector('.highlight')).toBeNull();
});

it('renders content when searchTerm is not provided', () => {
const { getByText } = render(<HighlightSearchTerm content="Lorem ipsum" />);
expect(getByText('Lorem ipsum')).toBeInTheDocument();
});

it('renders content with searchTerm highlighted', () => {
const { getByText } = render(
<HighlightSearchTerm
searchTerm="ipsum"
content="Lorem ipsum dolor sit amet"
/>
);
const highlightedText = getByText('ipsum');
expect(highlightedText).toHaveClass('highlight');
});

it('renders content with multiple searchTerm occurrences highlighted', () => {
const { getAllByText } = render(
<HighlightSearchTerm
searchTerm="ipsum"
content="Lorem ipsum ipsum Loremipsum ipsumipsum dolor sit amet"
/>
);
const highlightedText = getAllByText('ipsum');
expect(highlightedText.length).toBe(5);
highlightedText.forEach((element) => {
expect(element).toHaveClass('highlight');
});
});
});
3 changes: 3 additions & 0 deletions client/src/components/_common/HighlightSearchTerm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import HighlightSearchTerm from './HighlightSearchTerm';

export default HighlightSearchTerm;
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ InfiniteScrollTable.propTypes = {
noDataText: rowContentPropType,
getRowProps: PropTypes.func,
columnMemoProps: PropTypes.arrayOf(PropTypes.any),
cell: PropTypes.object,
};
InfiniteScrollTable.defaultProps = {
onInfiniteScroll: (offset) => {},
Expand Down
1 change: 1 addition & 0 deletions client/src/components/_common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export { default as Icon } from './Icon';
export { default as Message } from './Message';
export { default as InlineMessage } from './InlineMessage';
export { default as SectionMessage } from './SectionMessage';
export { default as HighlightSearchTerm } from './HighlightSearchTerm';
export { default as Sidebar } from './Sidebar';
export { default as DescriptionList } from './DescriptionList';
export { default as DropdownSelector } from './DropdownSelector';
Expand Down

0 comments on commit f3e7f18

Please sign in to comment.