Skip to content

Commit 43df5bb

Browse files
feat: migrate Learner Credit Mgmt to catalog query algolia filters behind feature flag (#1451)
1 parent cd80c50 commit 43df5bb

File tree

12 files changed

+310
-161
lines changed

12 files changed

+310
-161
lines changed

src/components/BulkEnrollmentPage/stepper/AddCoursesStep.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { BulkEnrollContext, BulkEnrollContextValue, Subscription } from '../Bulk
1313
import { TABLE_HEADERS } from '../CourseSearchResults';
1414

1515
import '../../../../__mocks__/react-instantsearch-dom';
16-
import { BaseAddCoursesStep } from './AddCoursesStep';
16+
import { AddCoursesStep } from './AddCoursesStep';
1717
import { renderWithRouter } from '../../test/testUtils';
1818
import type { SelectedRow } from '../data/types';
1919
import type { UseAlgoliaSearchResult } from '../../algolia-search';
@@ -90,7 +90,7 @@ const StepperWrapper = ({
9090
<IntlProvider locale="en">
9191
<Provider store={store || defaultMockStore}>
9292
<BulkEnrollContext.Provider value={value}>
93-
<BaseAddCoursesStep
93+
<AddCoursesStep
9494
{...props}
9595
algolia={algolia || defaultAlgoliaProps}
9696
subscription={subscription}

src/components/BulkEnrollmentPage/stepper/AddCoursesStep.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ interface AddCoursesStepProps {
108108
algolia: UseAlgoliaSearchResult;
109109
}
110110

111-
export const BaseAddCoursesStep: React.FC<AddCoursesStepProps> = ({
111+
export const AddCoursesStep: React.FC<AddCoursesStepProps> = ({
112112
subscription,
113113
algolia,
114114
}) => {
@@ -166,4 +166,4 @@ export const BaseAddCoursesStep: React.FC<AddCoursesStepProps> = ({
166166
);
167167
};
168168

169-
export default withAlgoliaSearch(BaseAddCoursesStep);
169+
export default withAlgoliaSearch(AddCoursesStep);

src/components/learner-credit-management/BudgetDetailCatalogTabContents.jsx

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import React, { useEffect, useRef } from 'react';
2-
import { InstantSearch } from 'react-instantsearch-dom';
3-
import algoliasearch from 'algoliasearch/lite';
1+
import { useEffect, useRef } from 'react';
42
import { Row, Col } from '@openedx/paragon';
53

64
import { SearchData, SEARCH_FACET_FILTERS } from '@edx/frontend-enterprise-catalog-search';
@@ -9,7 +7,6 @@ import CatalogSearch from './search/CatalogSearch';
97
import {
108
LANGUAGE_REFINEMENT, LEARNING_TYPE_REFINEMENT,
119
} from './data';
12-
import { configuration } from '../../config';
1310

1411
const BudgetDetailCatalogTabContents = () => {
1512
const navigate = useNavigate();
@@ -32,11 +29,6 @@ const BudgetDetailCatalogTabContents = () => {
3229
}
3330
});
3431

35-
const searchClient = algoliasearch(
36-
configuration.ALGOLIA.APP_ID,
37-
configuration.ALGOLIA.SEARCH_API_KEY,
38-
);
39-
4032
useEffect(() => {
4133
if (locationState?.budgetActivityScrollToKey === 'catalog') {
4234
catalogContainerRef.current?.scrollIntoView({ behavior: 'smooth' });
@@ -49,15 +41,8 @@ const BudgetDetailCatalogTabContents = () => {
4941
return (
5042
<Row data-testid="budget-detail-catalog-tab-contents" ref={catalogContainerRef}>
5143
<Col>
52-
<SearchData
53-
searchFacetFilters={[...SEARCH_FACET_FILTERS]}
54-
>
55-
<InstantSearch
56-
indexName={configuration.ALGOLIA.INDEX_NAME}
57-
searchClient={searchClient}
58-
>
59-
<CatalogSearch />
60-
</InstantSearch>
44+
<SearchData searchFacetFilters={[...SEARCH_FACET_FILTERS]}>
45+
<CatalogSearch />
6146
</SearchData>
6247
</Col>
6348
</Row>

src/components/learner-credit-management/data/hooks/useEnterpriseGroup.js renamed to src/components/learner-credit-management/data/hooks/useEnterpriseGroup.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,23 @@ import isEmpty from 'lodash/isEmpty';
33

44
import { learnerCreditManagementQueryKeys } from '../constants';
55
import LmsApiService from '../../../../data/services/LmsApiService';
6+
import { SubsidyAccessPolicy } from '../types';
67

78
/**
89
* Retrieves a enterprise group by the policy the from the API.
9-
*
10-
* @param {*} queryKey The queryKey from the associated `useQuery` call.
1110
* @returns The enterprise group object
1211
*/
13-
const getEnterpriseGroup = async ({ subsidyAccessPolicy }) => {
14-
if (isEmpty(subsidyAccessPolicy?.groupAssociations)) {
12+
const getEnterpriseGroup = async ({ subsidyAccessPolicy }: { subsidyAccessPolicy?: SubsidyAccessPolicy }) => {
13+
if (!subsidyAccessPolicy || isEmpty(subsidyAccessPolicy.groupAssociations)) {
1514
return null;
1615
}
1716
const response = await LmsApiService.fetchEnterpriseGroup(subsidyAccessPolicy.groupAssociations[0]);
1817
return response.data;
1918
};
2019

21-
const useEnterpriseGroup = (subsidyAccessPolicy, { queryOptions } = {}) => useQuery({
20+
const useEnterpriseGroup = (subsidyAccessPolicy: SubsidyAccessPolicy | undefined) => useQuery({
2221
queryKey: learnerCreditManagementQueryKeys.group(subsidyAccessPolicy?.uuid),
2322
queryFn: () => getEnterpriseGroup({ subsidyAccessPolicy }),
24-
...queryOptions,
2523
});
2624

2725
export default useEnterpriseGroup;
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useQuery } from '@tanstack/react-query';
22
import { camelCaseObject } from '@edx/frontend-platform/utils';
3+
import type { AxiosResponse } from 'axios';
34

45
import EnterpriseAccessApiService from '../../../../data/services/EnterpriseAccessApiService';
56
import { learnerCreditManagementQueryKeys } from '../constants';
67
import { isAssignableSubsidyAccessPolicyType } from '../../../../utils';
8+
import { SubsidyAccessPolicy } from '../types';
79

810
/**
911
* Retrieves a subsidy access policy by UUID from the API.
@@ -13,17 +15,16 @@ import { isAssignableSubsidyAccessPolicyType } from '../../../../utils';
1315
*/
1416
const getSubsidyAccessPolicy = async ({ queryKey }) => {
1517
const subsidyAccessPolicyUUID = queryKey[2];
16-
const response = await EnterpriseAccessApiService.retrieveSubsidyAccessPolicy(subsidyAccessPolicyUUID);
17-
const subsidyAccessPolicy = camelCaseObject(response.data);
18+
const response: AxiosResponse = await EnterpriseAccessApiService.retrieveSubsidyAccessPolicy(subsidyAccessPolicyUUID);
19+
const subsidyAccessPolicy: SubsidyAccessPolicy = camelCaseObject(response.data);
1820
subsidyAccessPolicy.isAssignable = isAssignableSubsidyAccessPolicyType(subsidyAccessPolicy);
1921
return subsidyAccessPolicy;
2022
};
2123

22-
const useSubsidyAccessPolicy = (subsidyAccessPolicyId, { queryOptions } = {}) => useQuery({
24+
const useSubsidyAccessPolicy = (subsidyAccessPolicyId) => useQuery({
2325
queryKey: learnerCreditManagementQueryKeys.budget(subsidyAccessPolicyId),
2426
queryFn: getSubsidyAccessPolicy,
2527
enabled: !!subsidyAccessPolicyId,
26-
...queryOptions,
2728
});
2829

2930
export default useSubsidyAccessPolicy;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export type SubsidyAccessPolicy = {
2+
uuid: string;
3+
displayName: string;
4+
isAssignable: boolean;
5+
policyType: string;
6+
assignmentConfiguration: Record<string, string>;
7+
catalogUuid: string;
8+
groupAssociations: string[];
9+
};

src/components/learner-credit-management/search/CatalogSearch.jsx

Lines changed: 0 additions & 66 deletions
This file was deleted.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { Skeleton } from '@openedx/paragon';
2+
import { Configure, InstantSearch } from 'react-instantsearch-dom';
3+
import { FormattedMessage } from '@edx/frontend-platform/i18n';
4+
import { SearchHeader } from '@edx/frontend-enterprise-catalog-search';
5+
6+
import { configuration } from '../../../config';
7+
import CatalogSearchResults from './CatalogSearchResults';
8+
import {
9+
SEARCH_RESULT_PAGE_SIZE, useBudgetId, useEnterpriseGroup, useSubsidyAccessPolicy,
10+
} from '../data';
11+
import { SearchUnavailableAlert, withAlgoliaSearch, type UseAlgoliaSearchResult } from '../../algolia-search';
12+
import { SubsidyAccessPolicy } from '../data/types';
13+
14+
interface CatalogSearchProps {
15+
algolia: UseAlgoliaSearchResult
16+
}
17+
18+
/**
19+
* Returns the filters to be used in the Algolia search.
20+
*
21+
* If the catalog query filters are enabled and we have the mappings of catalog<>query, the returned
22+
* filters use the catalog query UUIDs.
23+
*
24+
* Otherwise, the filters use the catalog UUIDs.
25+
*/
26+
function useAlgoliaFilters(subsidyAccessPolicy: SubsidyAccessPolicy, algolia: UseAlgoliaSearchResult) {
27+
let baseFilters = '';
28+
if (algolia.isCatalogQueryFiltersEnabled && algolia.catalogUuidsToCatalogQueryUuids) {
29+
baseFilters += `enterprise_catalog_query_uuids:${algolia.catalogUuidsToCatalogQueryUuids[subsidyAccessPolicy.catalogUuid]}`;
30+
} else {
31+
baseFilters += `enterprise_catalog_uuids:${subsidyAccessPolicy.catalogUuid}`;
32+
}
33+
baseFilters += ' AND content_type:course';
34+
return baseFilters;
35+
}
36+
37+
export const CatalogSearch = ({ algolia }: CatalogSearchProps) => {
38+
const { subsidyAccessPolicyId } = useBudgetId();
39+
const subsidyAccessPolicyResult = useSubsidyAccessPolicy(subsidyAccessPolicyId);
40+
const subsidyAccessPolicy = subsidyAccessPolicyResult.data!;
41+
const searchFilters = useAlgoliaFilters(subsidyAccessPolicy, algolia);
42+
43+
const { data: enterpriseGroup } = useEnterpriseGroup(subsidyAccessPolicy);
44+
45+
if (algolia.isCatalogQueryFiltersEnabled && algolia.isLoading) {
46+
return (
47+
<div data-testid="catalog-search-loading">
48+
<Skeleton height={360} />
49+
<span className="sr-only">
50+
<FormattedMessage
51+
id="catalogs.enterpriseCatalogs.loading"
52+
defaultMessage="Loading catalog..."
53+
description="Loading message for the catalog search."
54+
/>
55+
</span>
56+
</div>
57+
);
58+
}
59+
60+
if (!algolia.searchClient) {
61+
return <SearchUnavailableAlert />;
62+
}
63+
64+
const showSubtitle = subsidyAccessPolicy?.groupAssociations?.length > 0 && !enterpriseGroup?.appliesToAllContexts;
65+
66+
return (
67+
<section>
68+
{(
69+
subsidyAccessPolicy.displayName ? (
70+
<FormattedMessage
71+
id="catalogs.enterpriseCatalogs.header.subsidyAccessPolicyName"
72+
defaultMessage="{subsidyAccessPolicyName} catalog"
73+
description="Search dialogue message with subsidy access policy name."
74+
tagName="h3"
75+
values={{ subsidyAccessPolicyName: subsidyAccessPolicy.displayName }}
76+
/>
77+
) : (
78+
<FormattedMessage
79+
id="catalogs.enterpriseCatalogs.header"
80+
defaultMessage="Overview"
81+
description="Search dialogue."
82+
tagName="h3"
83+
/>
84+
)
85+
)}
86+
{showSubtitle && (
87+
<p>Members of this budget will be able to browse and enroll the content in this catalog.</p>
88+
)}
89+
<InstantSearch
90+
indexName={configuration.ALGOLIA.INDEX_NAME!}
91+
searchClient={algolia.searchClient}
92+
>
93+
<div className="enterprise-catalogs-header">
94+
<Configure
95+
filters={searchFilters}
96+
facetingAfterDistinct
97+
hitsPerPage={SEARCH_RESULT_PAGE_SIZE}
98+
/>
99+
<SearchHeader
100+
hideTitle
101+
variant="default"
102+
disableSuggestionRedirect
103+
/>
104+
</div>
105+
<CatalogSearchResults />
106+
</InstantSearch>
107+
</section>
108+
);
109+
};
110+
111+
export default withAlgoliaSearch(CatalogSearch);

src/components/learner-credit-management/search/tests/CatalogSearch.test.jsx

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)