Skip to content

Commit 9c0160d

Browse files
authored
UILD-688: Hub search - LoC source behavior (#287)
* feat: Add configurations for Library of Congress and Local hubs with complex lookup capabilities * feat: Update HubsModal and SearchView for new hubs lookup functionality and configuration * feat: Refactor Hubs components and add HubsLookupResultList with local check functionality * feat: Add local hubs endpoint and update hooks for local availability checks * feat: Implement local hub availability checks and enrich search results with local data * feat: Update HubSourceFormatter to differentiate between local and Library of Congress sources * feat: Refactor hub result formatting and add utility functions for source handling * feat: Add search operator and check query parameters for hub local checks * feat: Enhance local hub checks by adding loading state and error handling in hooks * feat: Add local availability enrichment for Library of Congress hub search results * feat: Enhance hub search results with local availability enrichment and update source labels * feat: Remove loading state handling from HubsResultList and simplify data formatting * feat: Implement local hub availability check service and enrich hub search results * feat: Refactor HubsLocalAvailabilityEnricher checks, adjust hubsTableConfig width, and update HubSourceFormatter for localization * feat: Update import button label to 'Import/Edit' for clarity in HubActionFormatter * feat: Add unit tests for hub local check service, hubs local availability enricher, and hub formatters * feat: Rename HubsResultList to HubsLookupResultList and update related tests; modify searchUIRegistry tests to check for limit property instead of searchableIndices * Update changelog
1 parent e0b9e1a commit 9c0160d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2248
-539
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
* Update Complex lookups to use the new Search feature. Refs [UILD-686].
6363
* Add Hub tab to landing page. Refs [UILD-687].
6464
* Add custom workspace profile settings application. Refs [UILD-684].
65+
* Add Hub search behavior for LoC source. Refs [UILD-688].
6566

6667
[UILD-552]:https://folio-org.atlassian.net/browse/UILD-552
6768
[UILD-544]:https://folio-org.atlassian.net/browse/UILD-544
@@ -124,6 +125,7 @@
124125
[UILD-686]:https://folio-org.atlassian.net/browse/UILD-686
125126
[UILD-687]:https://folio-org.atlassian.net/browse/UILD-687
126127
[UILD-684]:https://folio-org.atlassian.net/browse/UILD-684
128+
[UILD-688]:https://folio-org.atlassian.net/browse/UILD-688
127129

128130
## 1.0.5 (2025-04-30)
129131
* Fixed incorrect behavior when navigating between duplicated resources. Fixes [UILD-553].

src/common/constants/api.constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const SEARCH_API_ENDPOINT = {
1818
AUTHORITIES_SEARCH: '/search/authorities',
1919
AUTHORITIES_BROWSE: '/browse/authorities',
2020
HUBS_EXTERNAL: 'https://id.loc.gov/resources/hubs/suggest2/',
21+
HUBS_LOCAL: '/search/linked-data/hubs',
2122
};
2223

2324
export const MARC_PREVIEW_ENDPOINT = {

src/common/constants/search.constants.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,11 @@ export enum AuthRefType {
156156
}
157157

158158
export const SEARCH_QUERY_VALUE_PARAM = ':value';
159+
160+
export const SEARCH_OPERATOR = {
161+
OR: 'or',
162+
};
163+
164+
export const SEARCH_CHECK_QUERY_PARAM = {
165+
ORIGINAL_ID: 'originalId',
166+
};

src/features/complexLookup/components/modals/HubsModal/HubsModal.test.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ jest.mock('@/features/search/ui/components/Search', () => {
5151
});
5252

5353
jest.mock('@/features/search/ui', () => ({
54-
HubsResultList: ({
54+
HubsLookupResultList: ({
5555
context,
5656
onAssign,
5757
}: {
5858
context: string;
5959
onAssign: (record: ComplexLookupAssignRecordDTO) => void;
6060
}) => (
61-
<div data-testid="hubs-result-list">
61+
<div data-testid="hubs-lookup-result-list">
6262
<span>{context}</span>
6363
<button onClick={() => onAssign({ id: 'hub-1', title: 'Hub 1' })}>Assign Hub</button>
6464
</div>
@@ -97,8 +97,8 @@ describe('HubsModal', () => {
9797
expect(ComplexLookupHooks.useComplexLookupModalState).toHaveBeenCalledWith({
9898
isOpen: true,
9999
initialQuery: 'Test Hub',
100-
defaultSegment: 'hubs',
101-
defaultSource: 'external',
100+
defaultSegment: 'hubsLookup',
101+
defaultSource: 'libraryOfCongress',
102102
});
103103
});
104104

@@ -108,8 +108,8 @@ describe('HubsModal', () => {
108108
expect(ComplexLookupHooks.useComplexLookupModalState).toHaveBeenCalledWith({
109109
isOpen: true,
110110
initialQuery: undefined,
111-
defaultSegment: 'hubs',
112-
defaultSource: 'external',
111+
defaultSegment: 'hubsLookup',
112+
defaultSource: 'libraryOfCongress',
113113
});
114114
});
115115

@@ -136,15 +136,15 @@ describe('HubsModal', () => {
136136
expect(screen.getByTestId('complex-lookup-search-contents')).toBeInTheDocument();
137137
});
138138

139-
it('renders HubsResultList with complexLookup context', () => {
139+
it('renders HubsLookupResultList with complexLookup context', () => {
140140
render(<HubsModal isOpen={true} onClose={mockOnClose} onAssign={mockOnAssign} />);
141141

142-
const resultList = screen.getByTestId('hubs-result-list');
142+
const resultList = screen.getByTestId('hubs-lookup-result-list');
143143
expect(resultList).toBeInTheDocument();
144144
expect(resultList).toHaveTextContent('complexLookup');
145145
});
146146

147-
it('passes onAssign to HubsResultList', () => {
147+
it('passes onAssign to HubsLookupResultList', () => {
148148
render(<HubsModal isOpen={true} onClose={mockOnClose} onAssign={mockOnAssign} />);
149149

150150
const assignButton = screen.getByText('Assign Hub');

src/features/complexLookup/components/modals/HubsModal/HubsModal.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
33
import classNames from 'classnames';
44
import { Modal } from '@/components/Modal';
55
import { Search } from '@/features/search/ui/components/Search';
6-
import { HubsResultList } from '@/features/search/ui';
6+
import { HubsLookupResultList } from '@/features/search/ui';
77
import { useComplexLookupModalState } from '@/features/complexLookup/hooks';
88
import { IS_EMBEDDED_MODE } from '@/common/constants/build.constants';
99

@@ -22,8 +22,8 @@ export const HubsModal: FC<HubsModalProps> = ({ isOpen, onClose, initialQuery, o
2222
useComplexLookupModalState({
2323
isOpen,
2424
initialQuery,
25-
defaultSegment: 'hubs',
26-
defaultSource: 'external',
25+
defaultSegment: 'hubsLookup',
26+
defaultSource: 'libraryOfCongress',
2727
});
2828

2929
return (
@@ -40,7 +40,13 @@ export const HubsModal: FC<HubsModalProps> = ({ isOpen, onClose, initialQuery, o
4040
showModalControls={false}
4141
>
4242
<div className="complex-lookup-search-contents" data-testid="complex-lookup-search-contents">
43-
<Search segments={['hubs']} defaultSegment="hubs" defaultSource="external" flow="value" mode="custom">
43+
<Search
44+
segments={['hubsLookup']}
45+
defaultSegment="hubsLookup"
46+
defaultSource="libraryOfCongress"
47+
flow="value"
48+
mode="custom"
49+
>
4450
<Search.Controls>
4551
<Search.Controls.InputsWrapper />
4652
<Search.Controls.SubmitButton />
@@ -53,7 +59,7 @@ export const HubsModal: FC<HubsModalProps> = ({ isOpen, onClose, initialQuery, o
5359
<Search.ContentContainer>
5460
<Search.Results>
5561
{/* Existing component already supports complexLookup context! */}
56-
<HubsResultList context="complexLookup" onAssign={onAssign} />
62+
<HubsLookupResultList context="complexLookup" onAssign={onAssign} />
5763
<Search.Results.Pagination />
5864
</Search.Results>
5965
</Search.ContentContainer>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { authoritiesTableConfig } from '../../../search/ui/config/results/authoritiesTable.config';
2-
import { hubsTableConfig } from '../../../search/ui/config/results/hubsTable.config';
2+
import { hubsLookupTableConfig } from '../../../search/ui/config/results/hubsLookupTable.config';
33

44
export const SEARCH_RESULTS_TABLE_CONFIG: Record<string, SearchResultsTableConfig> = {
55
default: authoritiesTableConfig,
66
authorities: authoritiesTableConfig,
7-
hub: hubsTableConfig,
7+
hub: hubsLookupTableConfig,
88
};

src/features/search/core/config/hubsLibraryOfCongress.config.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import type { SearchTypeConfig } from '../types';
44
import { HubsLoCRequestBuilder } from '../strategies/requestBuilders';
55
import { HubResponseTransformer } from '../strategies/responseTransformers';
66
import { HubsResultFormatter } from '../strategies/resultFormatters';
7+
import { HubsLocalAvailabilityEnricher } from '../strategies/resultEnrichers';
78

89
/**
9-
* Hubs External Configuration (Atomic)
10+
* Hubs Library Of Congress Configuration for Search page (Atomic)
1011
*
11-
* Search in external hub services.
12+
* Search in Library Of Congress hub services.
1213
* Composite key: "hubs:libraryOfCongress"
1314
*/
1415
export const hubsLibraryOfCongressConfig: SearchTypeConfig = {
@@ -18,6 +19,7 @@ export const hubsLibraryOfCongressConfig: SearchTypeConfig = {
1819
requestBuilder: new HubsLoCRequestBuilder(HUB_SEARCHABLE_INDICES_MAP),
1920
responseTransformer: new HubResponseTransformer(),
2021
resultFormatter: new HubsResultFormatter(),
22+
resultEnricher: new HubsLocalAvailabilityEnricher(),
2123
},
2224

2325
capabilities: {
@@ -26,7 +28,7 @@ export const hubsLibraryOfCongressConfig: SearchTypeConfig = {
2628
},
2729

2830
defaults: {
29-
searchBy: SearchableIndexEnum.HubNameLeftAnchored,
31+
searchBy: SearchableIndexEnum.HubNameKeyword,
3032
query: '',
3133
limit: 100, // API fetches 100 results
3234
offset: 0,

src/features/search/core/config/hubsLocal.config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { HUB_SEARCHABLE_INDICES_MAP } from '@/features/complexLookup/configs';
33
import type { SearchTypeConfig } from '../types';
44
import { HubsLoCRequestBuilder } from '../strategies/requestBuilders';
55
import { ResourcesResponseTransformer } from '../strategies/responseTransformers';
6-
import { HubsResultFormatter } from '../strategies/resultFormatters';
6+
import { HubsLookupResultFormatter } from '../strategies/resultFormatters';
77

88
/**
9-
* Hubs Internal Configuration (Atomic)
9+
* Hubs Local Configuration for Search page (Atomic)
1010
*
1111
* Search in local hub registry.
1212
* Composite key: "hubs:local"
@@ -19,7 +19,7 @@ export const hubsLocalConfig: SearchTypeConfig = {
1919
// Can be replaced with HubsInternalRequestBuilder when implemented
2020
requestBuilder: new HubsLoCRequestBuilder(HUB_SEARCHABLE_INDICES_MAP),
2121
responseTransformer: new ResourcesResponseTransformer(),
22-
resultFormatter: new HubsResultFormatter(),
22+
resultFormatter: new HubsLookupResultFormatter(),
2323
},
2424

2525
capabilities: {
@@ -28,7 +28,7 @@ export const hubsLocalConfig: SearchTypeConfig = {
2828
},
2929

3030
defaults: {
31-
searchBy: SearchableIndexEnum.HubNameLeftAnchored,
31+
searchBy: SearchableIndexEnum.HubNameKeyword,
3232
query: '',
3333
limit: 100, // API fetches 100 results
3434
offset: 0,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { SearchableIndex as SearchableIndexEnum } from '@/common/constants/searchableIndex.constants';
2+
import { HUB_SEARCHABLE_INDICES_MAP } from '@/features/complexLookup/configs';
3+
import type { SearchTypeConfig } from '../types';
4+
import { HubsLoCRequestBuilder } from '../strategies/requestBuilders';
5+
import { HubResponseTransformer } from '../strategies/responseTransformers';
6+
import { HubsLookupResultFormatter } from '../strategies/resultFormatters';
7+
8+
/**
9+
* Hubs Library Of Congress Configuration for Complex Lookup modal (Atomic)
10+
*
11+
* Search in Library Of Congress hub services.
12+
* Composite key: "hubs:libraryOfCongress"
13+
*/
14+
export const hubsLookupLibraryOfCongressConfig: SearchTypeConfig = {
15+
id: 'hubs:libraryOfCongress',
16+
17+
strategies: {
18+
requestBuilder: new HubsLoCRequestBuilder(HUB_SEARCHABLE_INDICES_MAP),
19+
responseTransformer: new HubResponseTransformer(),
20+
resultFormatter: new HubsLookupResultFormatter(),
21+
},
22+
23+
capabilities: {
24+
defaultLimit: 100,
25+
maxLimit: 100,
26+
},
27+
28+
defaults: {
29+
searchBy: SearchableIndexEnum.HubNameLeftAnchored,
30+
query: '',
31+
limit: 100, // API fetches 100 results
32+
offset: 0,
33+
},
34+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { SearchableIndex as SearchableIndexEnum } from '@/common/constants/searchableIndex.constants';
2+
import { HUB_SEARCHABLE_INDICES_MAP } from '@/features/complexLookup/configs';
3+
import type { SearchTypeConfig } from '../types';
4+
import { HubsLoCRequestBuilder } from '../strategies/requestBuilders';
5+
import { ResourcesResponseTransformer } from '../strategies/responseTransformers';
6+
import { HubsLookupResultFormatter } from '../strategies/resultFormatters';
7+
8+
/**
9+
* Hubs Local Configuration for Complex lookups (Atomic)
10+
*
11+
* Search in local hub registry.
12+
* Composite key: "hubs:local"
13+
*/
14+
export const hubsLookupLocalConfig: SearchTypeConfig = {
15+
id: 'hubs:local',
16+
17+
strategies: {
18+
// Uses same request builder as external for now
19+
// Can be replaced with HubsInternalRequestBuilder when implemented
20+
requestBuilder: new HubsLoCRequestBuilder(HUB_SEARCHABLE_INDICES_MAP),
21+
responseTransformer: new ResourcesResponseTransformer(),
22+
resultFormatter: new HubsLookupResultFormatter(),
23+
},
24+
25+
capabilities: {
26+
defaultLimit: 100,
27+
maxLimit: 100,
28+
},
29+
30+
defaults: {
31+
searchBy: SearchableIndexEnum.HubNameLeftAnchored,
32+
query: '',
33+
limit: 100, // API fetches 100 results
34+
offset: 0,
35+
},
36+
};

0 commit comments

Comments
 (0)