From ef2efeb31c7c044f5967654ad55d09a4c58f5b9c Mon Sep 17 00:00:00 2001 From: Alexander J Sheehan Date: Tue, 11 Apr 2023 20:17:16 +0000 Subject: [PATCH 1/2] feat: adding enterprise catalog client --- .../ProvisioningCatalogCurationContainer.jsx | 34 ++++++++++++++++--- .../ProvisioningFormAccountDetails.jsx | 2 +- src/data/services/EnterpriseCatalogApiService | 17 ++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/data/services/EnterpriseCatalogApiService diff --git a/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningCatalogCurationContainer.jsx b/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningCatalogCurationContainer.jsx index 2428f4880..a20d906f0 100644 --- a/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningCatalogCurationContainer.jsx +++ b/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningCatalogCurationContainer.jsx @@ -1,10 +1,36 @@ import CatalogCurationContextProvider from '../CatalogCuration/CatalogCurationContext'; import CatalogCurationSearch from '../CatalogCuration/CatalogCurationSearch'; +import { Button } from '@edx/paragon'; -const ProvisioningCatalogCurationContainer = () => ( - +import EnterpriseCatalogApiService from '../../../../data/services/EnterpriseCatalogApiService'; +import { selectProvisioningContext } from '../../data/utils'; +import { logError } from '@edx/frontend-platform/logging'; + + +const ProvisioningCatalogCurationContainer = () => { + const [formData] = selectProvisioningContext('formData'); + const handleClick = () => { + EnterpriseCatalogApiService.generateNewCustomerCatalog( + formData.enterpriseUUID, + { 'data': 'foobar' }, + ) + .then((response) => { + console.log(response); + }) + .catch((err) => { + console.log("sad"); + }); + console.log("wassup"); + }; + + return ( - -); + + ) +}; export default ProvisioningCatalogCurationContainer; diff --git a/src/Configuration/Provisioning/ProvisioningForm/PolicyForm/ProvisioningFormAccountDetails.jsx b/src/Configuration/Provisioning/ProvisioningForm/PolicyForm/ProvisioningFormAccountDetails.jsx index 0e03ba929..93a0d3d3d 100644 --- a/src/Configuration/Provisioning/ProvisioningForm/PolicyForm/ProvisioningFormAccountDetails.jsx +++ b/src/Configuration/Provisioning/ProvisioningForm/PolicyForm/ProvisioningFormAccountDetails.jsx @@ -47,7 +47,7 @@ const ProvisioningFormAccountDetails = ({ index }) => { data-testid="account-value" /> - {formFeedbackText} + {formFeedbackText || ""} diff --git a/src/data/services/EnterpriseCatalogApiService b/src/data/services/EnterpriseCatalogApiService new file mode 100644 index 000000000..ce2e6ab73 --- /dev/null +++ b/src/data/services/EnterpriseCatalogApiService @@ -0,0 +1,17 @@ +import qs from 'query-string'; + +import { getHttpClient, getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +class EnterpriseCatalogApiService { + static enterpriseCatalogServiceApiUrl = `${process.env.CATALOG_SERVICE_BASE_URL}/api/v1`; + + static apiClient = getAuthenticatedHttpClient; + + static generateNewCustomerCatalog(enterprise_uuid, options, query) { + const enterpriseCatalogQueryUrl = `${EnterpriseCatalogApiService.enterpriseCatalogServiceApiUrl + }/enterprise-customer/${enterprise_uuid}/catalog_query`; + return EnterpriseCatalogApiService.apiClient().post(enterpriseCatalogQueryUrl, query); + } +} + +export default EnterpriseCatalogApiService; From dfd5e12fd9478cbb40d24c447b1e4d6343d3acb2 Mon Sep 17 00:00:00 2001 From: Alexander J Sheehan Date: Wed, 12 Apr 2023 17:04:23 +0000 Subject: [PATCH 2/2] iterations --- .../CatalogCuration/CatalogCurationSearch.jsx | 13 ++- .../CatalogCurationSelectContentDataTable.jsx | 47 +++++++---- .../ProvisioningCatalogCurationContainer.jsx | 84 ++++++++++++++++--- .../ProvisioningFormCustomCatalog.jsx | 29 ++++--- .../Provisioning/data/constants.js | 1 + src/data/services/EnterpriseCatalogApiService | 4 +- src/overrides.scss | 4 + 7 files changed, 133 insertions(+), 49 deletions(-) diff --git a/src/Configuration/Provisioning/ProvisioningForm/CatalogCuration/CatalogCurationSearch.jsx b/src/Configuration/Provisioning/ProvisioningForm/CatalogCuration/CatalogCurationSearch.jsx index 21ebd2abb..01eb7f064 100644 --- a/src/Configuration/Provisioning/ProvisioningForm/CatalogCuration/CatalogCurationSearch.jsx +++ b/src/Configuration/Provisioning/ProvisioningForm/CatalogCuration/CatalogCurationSearch.jsx @@ -2,11 +2,9 @@ import { Configure, InstantSearch } from 'react-instantsearch-dom'; import { SearchData, SearchHeader } from '@edx/frontend-enterprise-catalog-search'; import { useContextSelector } from 'use-context-selector'; -import { Button } from '@edx/paragon'; import { useEffect, useState } from 'react'; import { configuration } from './data/config'; import { CatalogCurationContext } from './CatalogCurationContext'; -import { MAX_PAGE_SIZE } from './data/constants'; import CatalogCurationDateSelection from './CatalogCurationDateSelection'; import CatalogCurationSelectContentDataTable from './CatalogCurationSelectContentDataTable'; import useCatalogCurationContext from './data/hooks'; @@ -27,18 +25,17 @@ const CatalogCurationSearch = () => { // check to see if one date is not null if (startDate !== '' || endDate !== '') { setCurrentSearchFilter({ currentSearchFilter: { content_type: 'course' } }); - let placeholderString = ''; - for (let [key, value] of Object.entries(currentSearchFilter)) { + let placeholderString = ``; + for (let [key, value] of Object.entries({ content_type: 'course' })) { placeholderString = placeholderString + ` AND ${key}:${value}`; }; setCurrentSearchFilterValue(defaultSearchFilter + placeholderString); - // set to have contentType = course + // set to have contentType = course } else { - setCurrentSearchFilter({}); + setCurrentSearchFilter({ currentSearchFilter: {} }); setCurrentSearchFilterValue(defaultSearchFilter); } }, [startDate, endDate]); - return ( { > diff --git a/src/Configuration/Provisioning/ProvisioningForm/CatalogCuration/CatalogCurationSelectContentDataTable.jsx b/src/Configuration/Provisioning/ProvisioningForm/CatalogCuration/CatalogCurationSelectContentDataTable.jsx index 052b650eb..98eb05dd0 100644 --- a/src/Configuration/Provisioning/ProvisioningForm/CatalogCuration/CatalogCurationSelectContentDataTable.jsx +++ b/src/Configuration/Provisioning/ProvisioningForm/CatalogCuration/CatalogCurationSelectContentDataTable.jsx @@ -4,7 +4,7 @@ import { } from '@edx/paragon'; import { connectStateResults } from 'react-instantsearch-dom'; import PropTypes from 'prop-types'; -import { useState, useMemo } from 'react'; +import { useState, useMemo, useEffect } from 'react'; import { camelCaseObject } from '@edx/frontend-platform'; import { useContextSelector } from 'use-context-selector'; import { FOOTER_TEXT_BY_CONTENT_TYPE } from './data/utils'; @@ -53,21 +53,38 @@ const BaseHighlightStepperSelectContentDataTable = ({ const [currentView, setCurrentView] = useState(defaultActiveStateValue); // TODO: searchResults contain all information before its populated into the datatable (do manual filtering here) const { startDate, endDate } = useContextSelector(CatalogCurationContext, v => v[0]); - const filteredHits = searchResults?.hits.filter((hit) => { - const courseStartDate = hit.advertised_course_run?.start ? new Date(hit.advertised_course_run.start) : null; - const courseEndDate = hit.advertised_course_run?.end ? new Date(hit.advertised_course_run.end) : null; + const [filteredHits, setFilteredHits] = useState([]); + const page_size = 12; + const page_index = searchResults?.page || 0; + let current_index = 1; + const window_start_index = (page_index * page_size) + 1; - if (startDate && courseStartDate < new Date(startDate)) { - return false; - } + useEffect(() => { + setFilteredHits(searchResults?.hits.filter((hit) => { + if (current_index < window_start_index + page_size) { + const courseStartDate = hit.advertised_course_run?.start ? new Date(hit.advertised_course_run.start) : null; + const courseEndDate = hit.advertised_course_run?.end ? new Date(hit.advertised_course_run.end) : null; + if (startDate && courseStartDate < new Date(startDate)) { + return false; + } - if (endDate && courseEndDate > new Date(endDate)) { - return false; - } - return true; - }); + if (endDate && courseEndDate > new Date(endDate)) { + return false; + } - const tableData = useMemo(() => camelCaseObject(filteredHits || []), [searchResults]); + if (current_index >= window_start_index && current_index < window_start_index + page_size || (!startDate && !endDate)) { + current_index += 1; + return true; + } + current_index += 1; + return false; + } else { + return false; + } + })); + }, [searchResults, startDate, endDate]); + + const tableData = useMemo(() => camelCaseObject(filteredHits || []), [filteredHits, searchResults?.hits]); const searchResultsItemCount = searchResults?.nbHits || 0; const searchResultsPageCount = searchResults?.nbPages || 0; return ( @@ -85,7 +102,7 @@ const BaseHighlightStepperSelectContentDataTable = ({ manualPagination initialState={{ pageIndex: 0, - pageSize: MAX_PAGE_SIZE, + pageSize: 12, selectedRowIds, }} pageCount={searchResultsPageCount} @@ -128,7 +145,7 @@ const BaseHighlightStepperSelectContentDataTable = ({ CardComponent={ContentSearchResultCard} /> )} - {currentView === 'list' && } + {currentView === 'list' && } diff --git a/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningCatalogCurationContainer.jsx b/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningCatalogCurationContainer.jsx index a20d906f0..0133e3085 100644 --- a/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningCatalogCurationContainer.jsx +++ b/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningCatalogCurationContainer.jsx @@ -5,14 +5,68 @@ import { Button } from '@edx/paragon'; import EnterpriseCatalogApiService from '../../../../data/services/EnterpriseCatalogApiService'; import { selectProvisioningContext } from '../../data/utils'; import { logError } from '@edx/frontend-platform/logging'; +import { useContextSelector } from 'use-context-selector'; +import { CatalogCurationContext } from '../CatalogCuration/CatalogCurationContext'; +import { useEffect, useState } from 'react'; +import { Form } from '@edx/paragon'; +import PROVISIONING_PAGE_TEXT from '../../data/constants'; const ProvisioningCatalogCurationContainer = () => { + const { CUSTOM_CATALOG } = PROVISIONING_PAGE_TEXT.FORM; + const { startDate, endDate } = useContextSelector(CatalogCurationContext, v => v[0]); + let searchParams = new URLSearchParams(window.location.search); + + const [catalogQuery, setCatalogQuery] = useState({}); + useEffect(() => { + let baseQuery = { + "content_type": [ + "learnerpathway", + "course" + ], + "availability": [ + "Current", + "Starting Soon", + "Upcoming" + ], + "partner": "edx", + "level_type": [ + "Introductory", + "Intermediate", + "Advanced" + ], + "status": [ + "published", + "active" + ], + "org__exclude": [ + "StanfordOnline", + "PennX" + ] + }; + if (startDate !== '') { + baseQuery['start'] = startDate; + } + if (endDate !== '') { + baseQuery['end'] = endDate; + } + if (searchParams) { + searchParams.forEach((value, key) => { + baseQuery[key] = value; + }); + } + setCatalogQuery(baseQuery) + }, [startDate, endDate, window.location.search]); + + // const [catalogQueryContentFilter, setCatalogQueryContentFilter] = useState( + + // ); + const [formData] = selectProvisioningContext('formData'); const handleClick = () => { EnterpriseCatalogApiService.generateNewCustomerCatalog( formData.enterpriseUUID, - { 'data': 'foobar' }, + catalogQuery, ) .then((response) => { console.log(response); @@ -20,17 +74,25 @@ const ProvisioningCatalogCurationContainer = () => { .catch((err) => { console.log("sad"); }); - console.log("wassup"); }; - - return ( - - - ) + return ( + <> + + + + + ) }; export default ProvisioningCatalogCurationContainer; diff --git a/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningFormCustomCatalog.jsx b/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningFormCustomCatalog.jsx index beb67998d..bcd9c4906 100644 --- a/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningFormCustomCatalog.jsx +++ b/src/Configuration/Provisioning/ProvisioningForm/CustomCatalog/ProvisioningFormCustomCatalog.jsx @@ -7,6 +7,7 @@ import ProvisioningFormEnterpriseCustomerCatalog from './ProvisioningFormEnterpr import { indexOnlyPropType, selectProvisioningContext } from '../../data/utils'; import ProvisioningFormCustomCatalogExecEdBoolean from './ProvisioningFormCustomCatalogExecEdBoolean'; import ProvisioningCatalogCurationContainer from './ProvisioningCatalogCurationContainer'; +import CatalogCurationContextProvider from '../CatalogCuration/CatalogCurationContext'; const ProvisioningFormCustomCatalog = ({ index }) => { const [formData] = selectProvisioningContext('formData'); @@ -33,19 +34,21 @@ const ProvisioningFormCustomCatalog = ({ index }) => { } if (customerCatalogBoolean === false) { return ( -
- - {showCatalogCuration && } - {!showCatalogCuration && } - {!showCatalogCuration && policyData.catalogQuery.title && } - {!showCatalogCuration && policyData.catalogQuery.contentFilter && } - {!showCatalogCuration && (policyData.catalogQuery.includeExecEd2UCourses !== undefined) - && } -
+ +
+ + {showCatalogCuration && } + {!showCatalogCuration && } + {!showCatalogCuration && policyData.catalogQuery.title && } + {!showCatalogCuration && policyData.catalogQuery.contentFilter && } + {!showCatalogCuration && (policyData.catalogQuery.includeExecEd2UCourses !== undefined) + && } +
+
); } return null; diff --git a/src/Configuration/Provisioning/data/constants.js b/src/Configuration/Provisioning/data/constants.js index cfbe99370..6d91e3bc0 100644 --- a/src/Configuration/Provisioning/data/constants.js +++ b/src/Configuration/Provisioning/data/constants.js @@ -101,6 +101,7 @@ const PROVISIONING_PAGE_TEXT = { }, catalogTitle: 'Catalog title', contentFilter: 'Content filter', + queryPreview: 'What your query is looking like now:', includeExecEd2UCourses: 'Includes Executive Education courses', courseModes: 'Enabled course modes', }, diff --git a/src/data/services/EnterpriseCatalogApiService b/src/data/services/EnterpriseCatalogApiService index ce2e6ab73..4c823e9c9 100644 --- a/src/data/services/EnterpriseCatalogApiService +++ b/src/data/services/EnterpriseCatalogApiService @@ -1,4 +1,3 @@ -import qs from 'query-string'; import { getHttpClient, getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; @@ -7,7 +6,8 @@ class EnterpriseCatalogApiService { static apiClient = getAuthenticatedHttpClient; - static generateNewCustomerCatalog(enterprise_uuid, options, query) { + static generateNewCustomerCatalog(enterprise_uuid, query) { + console.log("hello!", query); const enterpriseCatalogQueryUrl = `${EnterpriseCatalogApiService.enterpriseCatalogServiceApiUrl }/enterprise-customer/${enterprise_uuid}/catalog_query`; return EnterpriseCatalogApiService.apiClient().post(enterpriseCatalogQueryUrl, query); diff --git a/src/overrides.scss b/src/overrides.scss index 58e965df5..d363ead00 100644 --- a/src/overrides.scss +++ b/src/overrides.scss @@ -196,4 +196,8 @@ div.fe__searchfield>div.mb-4 { div.fe__searchbox-col--default>div.mb-4 { display: none !important; +} + +textarea.has-value { + background-color: #c2c3c4 !important; } \ No newline at end of file