diff --git a/package-lock.json b/package-lock.json index 7c622128d..e9a357e9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "babel-polyfill": "6.26.0", "classnames": "2.2.6", "lodash.debounce": "4.0.8", + "lodash.snakecase": "^4.1.1", "moment": "2.29.1", "prop-types": "15.7.2", "react": "16.14.0", diff --git a/package.json b/package.json index 2918d2e91..fd8f1a0c2 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "babel-polyfill": "6.26.0", "classnames": "2.2.6", "lodash.debounce": "4.0.8", + "lodash.snakecase": "^4.1.1", "moment": "2.29.1", "prop-types": "15.7.2", "react": "16.14.0", diff --git a/src/Configuration/Provisioning/Dashboard.jsx b/src/Configuration/Provisioning/Dashboard.jsx index c9539b90e..7ccefcf8d 100644 --- a/src/Configuration/Provisioning/Dashboard.jsx +++ b/src/Configuration/Provisioning/Dashboard.jsx @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import DashboardHeader from './DashboardHeader'; import DashboardToast from './DashboardToast'; -import DashboardDatatable from './DashboardDatatable'; +import DashboardDataTable from './DashboardDataTable'; import { toastText } from './data/constants'; // TODO: Create a new item header, search box and datatable @@ -29,7 +29,7 @@ const Dashboard = () => { return ( <> - + {toasts.map(({ text, uuid }) => ())} ); diff --git a/src/Configuration/Provisioning/DashboardDatatable/DashboardDatatable.jsx b/src/Configuration/Provisioning/DashboardDataTable/DashboardDataTable.jsx similarity index 90% rename from src/Configuration/Provisioning/DashboardDatatable/DashboardDatatable.jsx rename to src/Configuration/Provisioning/DashboardDataTable/DashboardDataTable.jsx index 00f0b2d8e..3ce995bb7 100644 --- a/src/Configuration/Provisioning/DashboardDatatable/DashboardDatatable.jsx +++ b/src/Configuration/Provisioning/DashboardDataTable/DashboardDataTable.jsx @@ -1,10 +1,5 @@ -import { - DataTable, - TextFilter, -} from '@edx/paragon'; -import React, { - useCallback, useMemo, useState, -} from 'react'; +import { DataTable, TextFilter } from '@edx/paragon'; +import React, { useCallback, useMemo, useState } from 'react'; import { useContextSelector } from 'use-context-selector'; import debounce from 'lodash.debounce'; import { logError } from '@edx/frontend-platform/logging'; @@ -13,9 +8,9 @@ import { MAX_PAGE_SIZE } from '../data/constants'; import { useDashboardContext } from '../data/hooks'; import DashboardTableActions from './DashboardTableActions'; import DashboardTableBadges from './DashboardTableBadges'; -import { filterDatatableData, sortDatatableData, transformDatatableDate } from '../data/utils'; +import { filterDatatableData, sortDataTableData, transformDatatableDate } from '../data/utils'; -const DashboardDatatable = () => { +const DashboardDataTable = () => { const { enterpriseSubsidies } = useContextSelector(DashboardContext, v => v[0]); const { hydrateEnterpriseSubsidies } = useDashboardContext(); const [isLoading, setIsLoading] = useState(true); @@ -25,10 +20,11 @@ const DashboardDatatable = () => { const fetchData = useCallback(async (datatableProps) => { setIsLoading(true); + console.log(datatableProps.sortBy); try { await hydrateEnterpriseSubsidies({ pageIndex: datatableProps.pageIndex + 1, - sortBy: sortDatatableData(datatableProps), + sortBy: sortDataTableData(datatableProps), filterBy: filterDatatableData(datatableProps), }); } catch (e) { @@ -111,4 +107,4 @@ const DashboardDatatable = () => { ); }; -export default DashboardDatatable; +export default DashboardDataTable; diff --git a/src/Configuration/Provisioning/DashboardDatatable/DashboardTableActions.jsx b/src/Configuration/Provisioning/DashboardDataTable/DashboardTableActions.jsx similarity index 57% rename from src/Configuration/Provisioning/DashboardDatatable/DashboardTableActions.jsx rename to src/Configuration/Provisioning/DashboardDataTable/DashboardTableActions.jsx index ee6106f2b..a37daac0f 100644 --- a/src/Configuration/Provisioning/DashboardDatatable/DashboardTableActions.jsx +++ b/src/Configuration/Provisioning/DashboardDataTable/DashboardTableActions.jsx @@ -17,8 +17,8 @@ const DashboardTableActions = ({ row }) => { const { DJANGO_ADMIN_SUBSIDY_BASE_URL } = getConfig(); const history = useHistory(); const { HOME } = ROUTES.CONFIGURATION.SUB_DIRECTORY.PROVISIONING; - return [ - getConfig().FEATURE_CONFIGURATION_EDIT_ENTERPRISE_PROVISION && ( + const enabledActionArray = [{ + action: ( { data-testid={`Edit-${rowUuid}`} /> ), - - - , - ]; + enabled: getConfig().FEATURE_CONFIGURATION_EDIT_ENTERPRISE_PROVISION, + }, { + action: ( + + + ), + enabled: true, + }].filter(interaction => interaction.enabled); + + return enabledActionArray.map(interaction => interaction.action); }; DashboardTableActions.propTypes = { diff --git a/src/Configuration/Provisioning/DashboardDatatable/DashboardTableBadges.jsx b/src/Configuration/Provisioning/DashboardDataTable/DashboardTableBadges.jsx similarity index 100% rename from src/Configuration/Provisioning/DashboardDatatable/DashboardTableBadges.jsx rename to src/Configuration/Provisioning/DashboardDataTable/DashboardTableBadges.jsx diff --git a/src/Configuration/Provisioning/DashboardDataTable/index.js b/src/Configuration/Provisioning/DashboardDataTable/index.js new file mode 100644 index 000000000..582d9be22 --- /dev/null +++ b/src/Configuration/Provisioning/DashboardDataTable/index.js @@ -0,0 +1,3 @@ +import DashboardDataTable from './DashboardDataTable'; + +export default DashboardDataTable; diff --git a/src/Configuration/Provisioning/DashboardDatatable/tests/DashboardDatatable.test.jsx b/src/Configuration/Provisioning/DashboardDataTable/tests/DashboardDataTable.test.jsx similarity index 95% rename from src/Configuration/Provisioning/DashboardDatatable/tests/DashboardDatatable.test.jsx rename to src/Configuration/Provisioning/DashboardDataTable/tests/DashboardDataTable.test.jsx index d1a5d0ef9..c7884c763 100644 --- a/src/Configuration/Provisioning/DashboardDatatable/tests/DashboardDatatable.test.jsx +++ b/src/Configuration/Provisioning/DashboardDataTable/tests/DashboardDataTable.test.jsx @@ -3,7 +3,7 @@ import { renderWithRouter } from '@edx/frontend-enterprise-utils'; import { screen, waitFor } from '@testing-library/react'; import { camelCaseObject } from '@edx/frontend-platform'; import { DashboardContext, initialStateValue } from '../../../testData/Dashboard'; -import DashboardDatatable from '../DashboardDatatable'; +import DashboardDataTable from '../DashboardDataTable'; import { sampleDataTableData } from '../../../testData/constants'; // Mock the debounce function @@ -32,7 +32,7 @@ const DashboardDatatableWrapper = ({ value = initialStateValue, }) => ( - + ); diff --git a/src/Configuration/Provisioning/DashboardDatatable/tests/DashboardTableActions.test.jsx b/src/Configuration/Provisioning/DashboardDataTable/tests/DashboardTableActions.test.jsx similarity index 100% rename from src/Configuration/Provisioning/DashboardDatatable/tests/DashboardTableActions.test.jsx rename to src/Configuration/Provisioning/DashboardDataTable/tests/DashboardTableActions.test.jsx diff --git a/src/Configuration/Provisioning/DashboardDatatable/tests/DashboardTableBadges.test.jsx b/src/Configuration/Provisioning/DashboardDataTable/tests/DashboardTableBadges.test.jsx similarity index 100% rename from src/Configuration/Provisioning/DashboardDatatable/tests/DashboardTableBadges.test.jsx rename to src/Configuration/Provisioning/DashboardDataTable/tests/DashboardTableBadges.test.jsx diff --git a/src/Configuration/Provisioning/DashboardDatatable/index.js b/src/Configuration/Provisioning/DashboardDatatable/index.js deleted file mode 100644 index c9fbd2630..000000000 --- a/src/Configuration/Provisioning/DashboardDatatable/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import DashboardDatatable from './DashboardDatatable'; - -export default DashboardDatatable; diff --git a/src/Configuration/Provisioning/ProvisioningForm/ProvisioningFormCustomer.jsx b/src/Configuration/Provisioning/ProvisioningForm/ProvisioningFormCustomer.jsx index 8df0a8503..374c658e7 100644 --- a/src/Configuration/Provisioning/ProvisioningForm/ProvisioningFormCustomer.jsx +++ b/src/Configuration/Provisioning/ProvisioningForm/ProvisioningFormCustomer.jsx @@ -54,9 +54,9 @@ const ProvisioningFormCustomer = () => { data-testid="customer-financial-identifier" /> {isOpportunityProduct && ( - - {financialIdentifier.length}/{CUSTOMER.FINANCIAL_IDENTIFIER.MAX_LENGTH} - + + {financialIdentifier.length}/{CUSTOMER.FINANCIAL_IDENTIFIER.MAX_LENGTH} + )} {!isOpportunityProduct && ( { describe('sortDatatableData', () => { it('returns null if no data is passed', () => { const output = null; - expect(sortDatatableData({ sortBy: {} })).toEqual(output); + expect(sortDataTableData({ sortBy: {} })).toEqual(output); }); it('returns a sort by expirationDateTime if isActive is passed as the id', () => { const output = 'expirationDatetime'; // desc is true - expect(sortDatatableData({ + expect(sortDataTableData({ sortBy: [{ id: 'isActive', @@ -387,7 +387,7 @@ describe('sortDatatableData', () => { })).toEqual(`-${output}`); // desc is false - expect(sortDatatableData({ + expect(sortDataTableData({ sortBy: [{ id: 'isActive', @@ -399,7 +399,7 @@ describe('sortDatatableData', () => { const output = 'title'; // desc is true - expect(sortDatatableData({ + expect(sortDataTableData({ sortBy: [{ id: 'title', @@ -408,7 +408,7 @@ describe('sortDatatableData', () => { })).toEqual(`-${output}`); // desc is false - expect(sortDatatableData({ + expect(sortDataTableData({ sortBy: [{ id: 'title', diff --git a/src/Configuration/Provisioning/data/utils.js b/src/Configuration/Provisioning/data/utils.js index d9d314aff..1133d112a 100644 --- a/src/Configuration/Provisioning/data/utils.js +++ b/src/Configuration/Provisioning/data/utils.js @@ -246,10 +246,7 @@ export function getCamelCasedConfigAttribute(attribute) { */ export function normalizeSubsidyDataTableData({ fetchedSubsidyData, fetchedCustomerData }) { if (fetchedSubsidyData.count === 0) { - return { - ...fetchedSubsidyData, - results: [], - }; + return fetchedSubsidyData; } const normalizedData = fetchedSubsidyData.results.map((item) => { const { @@ -453,10 +450,10 @@ export function generatePolicyName(formData, index) { * @returns - Returns a date string in the format of MM-DD-YYYY */ export function transformDatatableDate(date) { - if (date) { - return new Date(date).toLocaleDateString().replace(/\//g, '-'); + if (!date) { + return null; } - return null; + return new Date(date).toLocaleDateString().replace(/\//g, '-'); } /** @@ -479,14 +476,15 @@ export function filterDatatableData({ filters }) { * @param {Object} sortBy - The sort object from the datatable * @returns - Returns a string that can be used to sort the API response */ -export function sortDatatableData({ sortBy }) { - if (sortBy[0]?.id) { - if (sortBy[0].id === 'isActive') { - return sortBy[0].desc ? '-expirationDatetime' : 'expirationDatetime'; - } - return sortBy[0].desc ? `-${sortBy[0].id}` : sortBy[0].id; +export function sortDataTableData({ sortBy }) { + const sortByObject = sortBy[0]; + if (!sortByObject) { + return null; } - return null; + if (sortByObject.id === 'isActive') { + return sortByObject.desc ? '-expirationDatetime' : 'expirationDatetime'; + } + return sortByObject.desc ? `-${sortByObject.id}` : sortByObject.id; } /** diff --git a/src/data/services/SubsidyApiService.js b/src/data/services/SubsidyApiService.js index d22146ebd..26dc0bb75 100644 --- a/src/data/services/SubsidyApiService.js +++ b/src/data/services/SubsidyApiService.js @@ -1,25 +1,23 @@ -import { getConfig } from '@edx/frontend-platform'; +import { getConfig, snakeCaseObject } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { snakeCaseWord } from '../../utils'; +import snakeCase from 'lodash.snakecase'; class SubsidyApiService { static apiClient = getAuthenticatedHttpClient; static getAllSubsidies = ({ - paginatedURL, + pageIndex, pageSize, sortBy, filteredData, }) => { const subsidiesURL = `${getConfig().SUBSIDY_BASE_URL}/api/v1/subsidies/`; - let optionalUrlParams = ''; - - optionalUrlParams += pageSize ? `&page_size=${pageSize}` : ''; - optionalUrlParams += sortBy ? `&sort_by=${snakeCaseWord(sortBy)}` : ''; - Object.keys(filteredData).forEach((key) => { - optionalUrlParams += `&${snakeCaseWord(key)}=${filteredData[key]}`; - }); - return SubsidyApiService.apiClient().get(`${subsidiesURL}?page=${paginatedURL}${optionalUrlParams}`); + const optionalUrlParams = new URLSearchParams(snakeCaseObject({ + pageSize, + sortBy: sortBy ? snakeCase(sortBy) : 'uuid', + ...filteredData, + })).toString(); + return SubsidyApiService.apiClient().get(`${subsidiesURL}?page=${pageIndex}&${optionalUrlParams}`); }; static postSubsidy = ( diff --git a/src/utils/index.js b/src/utils/index.js index 62b24a4a4..c2d921d44 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -44,16 +44,16 @@ export const isWholeDollarAmount = (value) => Boolean(value && value.match(DIGIT // Opportunity Product must begin with 00k and be 18 alphanumeric characters long export const isValidOpportunityProduct = value => { + if (!value) { + return false; + } if (value?.length <= 2) { return Boolean(value && value.match(/^0{1,2}$/)); } if (value?.length === 3) { return Boolean(value && value.match(/^0{2}k$/)); } - if (value?.length > 3 || value?.length < 19) { - return Boolean(value && value.match(/^0{2}k([0-9A-Za-z]{1,15})$/)); - } - return false; + return Boolean(value && value.match(/^0{2}k([0-9A-Za-z]{1,15})$/)); }; export function sort(firstElement, secondElement, key, direction) { @@ -73,16 +73,6 @@ export function titleCase(str) { return str.toLowerCase().replace(/_/g, ' ').replace(/\b(\w)/g, s => s.toUpperCase()); } -/** - * Convert a string containing camelCase into snake_case. - * @param {String} word - word to be converted to snake_case - * @returns - snake_case word - */ -export function snakeCaseWord(word) { - const result = word.replace(/([A-Z])/g, ' $1'); - return result.split(' ').join('_').toLowerCase(); -} - /** Compare dates function for array.sort() */ export function sortedCompareDates(x, y, asc) { const a = new Date(x); diff --git a/src/utils/index.test.js b/src/utils/index.test.js index 972aa8f46..8ed686690 100644 --- a/src/utils/index.test.js +++ b/src/utils/index.test.js @@ -1,20 +1,19 @@ import { - isEmail, - isValidUsername, - isValidLMSUserID, + extractMessageTuple, + extractParams, formatBoolean, formatDate, formatUnixTimestamp, - sort, - titleCase, - sortedCompareDates, + isEmail, isValidCourseID, - extractMessageTuple, - extractParams, isValidDateString, - isWholeDollarAmount, + isValidLMSUserID, isValidOpportunityProduct, - snakeCaseWord, + isValidUsername, + isWholeDollarAmount, + sort, + sortedCompareDates, + titleCase, } from './index'; describe('Test Utils', () => { @@ -276,10 +275,3 @@ describe('isValidOpportunityProduct', () => { expect(isValidOpportunityProduct()).toEqual(false); }); }); -describe('snakeCaseWord', () => { - it('returns snake case word', () => { - const camelCasedWord = 'helloWorld'; - const snakeCasedWord = 'hello_world'; - expect(snakeCaseWord(camelCasedWord)).toEqual(snakeCasedWord); - }); -});