Skip to content

Commit

Permalink
refactor: Convert js(x) -> ts(x) + add types
Browse files Browse the repository at this point in the history
  • Loading branch information
yusuf-musleh committed Jul 7, 2024
1 parent 4fd42b5 commit fd13676
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 134 deletions.
66 changes: 66 additions & 0 deletions src/library/data/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { camelCaseObject, snakeCaseObject, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;

export interface LibraryV2 {
id: string,

Check failure on line 7 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected tab character

Check failure on line 7 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 2 spaces but found 1 tab
type: string,

Check failure on line 8 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected tab character

Check failure on line 8 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 2 spaces but found 1 tab
org: string,

Check failure on line 9 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected tab character

Check failure on line 9 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 2 spaces but found 1 tab
slug: string,

Check failure on line 10 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected tab character

Check failure on line 10 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 2 spaces but found 1 tab
title: string,

Check failure on line 11 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Unexpected tab character

Check failure on line 11 in src/library/data/api.ts

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 2 spaces but found 1 tab
description: string,
numBlocks: number,
version: number,
lastPublished: string | null,
allowLti: boolean,
allowPublicLearning: boolean,
allowPublicRead: boolean,
hasUnpublishedChanges: boolean,
hasUnpublishedDeletes: boolean,
license: string,
};

export interface LibrariesV2Response {
next: string|null,
previous: string|null,
count: number,
numPages: number,
currentPage: number,
start: number,
results: LibraryV2[],
};

/* Additional custom parameters for the API request. */
export interface GetLibrariesV2CustomParams {
/* (optional) Library type, default `complex` */
type?: string,
/* (optional) Page number of results */
page?: number,
/* (optional) The number of results on each page, default `50` */
pageSize?: number,
/* (optional) Whether pagination is supported, default `true` */
pagination?: boolean,
/* (optional) Library field to order results by. Prefix with '-' for descending */
order?: string,
/* (optional) Search query to filter v2 Libraries by */
search?: string,
};

/**
* Get's studio home v2 Libraries.
*/
export async function getStudioHomeLibrariesV2(customParams: GetLibrariesV2CustomParams): Promise<LibrariesV2Response> {
// Set default params if not passed in
const customParamsDefaults = {
type: customParams.type || 'complex',
page: customParams.page || 1,
pageSize: customParams.pageSize || 50,
pagination: customParams.pagination !== undefined ? customParams.pagination : true,
order: customParams.order || 'title',
textSearch: customParams.search,
};
const customParamsFormat = snakeCaseObject(customParamsDefaults);
const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`, { params: customParamsFormat });
return camelCaseObject(data);
}
26 changes: 0 additions & 26 deletions src/studio-home/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,6 @@ export async function getStudioHomeLibraries() {
return camelCaseObject(data);
}

/**
* Get's studio home v2 Libraries.
* @param {object} customParams - Additional custom paramaters for the API request.
* @param {string} [customParams.type] - (optional) Library type, default `complex`
* @param {number} [customParams.page] - (optional) Page number of results
* @param {number} [customParams.pageSize] - (optional) The number of results on each page, default `50`
* @param {boolean} [customParams.pagination] - (optional) Whether pagination is supported, default `true`
* @param {string} [customParams.order] - (optional) Library field to order results by. Prefix with '-' for descending
* @param {string} [customParams.search] - (optional) Search query to filter v2 Libraries by
* @returns {Promise<import("./types.mjs").LibrariesV2Response>} - Promise that resolves to v2 libraries response data
*/
export async function getStudioHomeLibrariesV2(customParams) {
// Set default params if not passed in
const customParamsDefaults = {
type: customParams.type || 'complex',
page: customParams.page || 1,
pageSize: customParams.pageSize || 50,
pagination: customParams.pagination !== undefined ? customParams.pagination : true,
order: customParams.order || 'title',
textSearch: customParams.search,
};
const customParamsFormat = snakeCaseObject(customParamsDefaults);
const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`, { params: customParamsFormat });
return camelCaseObject(data);
}

/**
* Handle course notification requests.
* @param {string} url
Expand Down
13 changes: 2 additions & 11 deletions src/studio-home/data/apiHooks.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { useQuery } from '@tanstack/react-query';

import { getStudioHomeLibrariesV2 } from './api';

interface CustomParams {
type?: string,
page?: number,
pageSize?: number,
pagination?: boolean,
order?: string,
saerch?: string,
}
import { GetLibrariesV2CustomParams, getStudioHomeLibrariesV2 } from '../../library/data/api';

/**
* Builds the query to fetch list of V2 Libraries
*/
const useListStudioHomeV2Libraries = (customParams: CustomParams) => (
const useListStudioHomeV2Libraries = (customParams: GetLibrariesV2CustomParams) => (
useQuery({
queryKey: ['listV2Libraries', customParams],
queryFn: () => getStudioHomeLibrariesV2(customParams),
Expand Down
31 changes: 0 additions & 31 deletions src/studio-home/data/types.mjs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@ import {
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';

import LibrariesV2Filters from '.';
import LibrariesV2Filters, { LibrariesV2FiltersProps } from '.';

describe('LibrariesV2Filters', () => {
const setIsFilteredMock = jest.fn();
const setFilterParamsMock = jest.fn();
const setCurrentPageMock = jest.fn();

// eslint-disable-next-line react/prop-types
const IntlProviderWrapper = ({ children }) => (
const IntlProviderWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<IntlProvider locale="en" messages={{}}>
{children}
</IntlProvider>
);

const renderComponent = (overrideProps = {}) => render(
const renderComponent = (overrideProps: Partial<LibrariesV2FiltersProps> = {}) => render(
<IntlProviderWrapper>
<LibrariesV2Filters
setIsFiltered={setIsFilteredMock}
Expand All @@ -27,7 +26,6 @@ describe('LibrariesV2Filters', () => {
{...overrideProps}
/>
</IntlProviderWrapper>,

);

beforeEach(() => {
Expand Down Expand Up @@ -81,7 +79,7 @@ describe('LibrariesV2Filters', () => {
</IntlProviderWrapper>,
);

await waitFor(() => expect(screen.getByRole('searchbox').value).toBe(''));
await waitFor(() => expect((screen.getByRole('searchbox') as HTMLInputElement).value).toBe(''));
});

it('should update states with the correct parameters when a order menu item is selected', () => {
Expand Down Expand Up @@ -134,10 +132,16 @@ describe('LibrariesV2Filters', () => {

it('should clear the search input and call dispatch when the reset button is clicked', async () => {
renderComponent();
const searchInput = screen.getByRole('searchbox');
const searchInput = screen.getByRole('searchbox') as HTMLInputElement;
fireEvent.change(searchInput, { target: { value: 'test' } });
const form = searchInput.closest('form');
if (!form) {
throw new Error('Form not found');
}
const resetButton = form.querySelector('button[type="reset"]');
if (!resetButton || !(resetButton instanceof HTMLButtonElement)) {
throw new Error('Reset button not found');
}
fireEvent.click(resetButton);
expect(searchInput.value).toBe('');
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import React, { useState, useCallback, useEffect } from 'react';
import { SearchField } from '@openedx/paragon';
import { debounce } from 'lodash';

Expand All @@ -11,9 +10,17 @@ import LibrariesV2OrderFilterMenu from './libraries-v2-order-filter-menu';
*/
const regexOnlyWhiteSpaces = /^\s+$/;

const LibrariesV2Filters = ({
isLoading,
isFiltered,
export interface LibrariesV2FiltersProps {
isLoading?: boolean;
isFiltered?: boolean;
setIsFiltered: React.Dispatch<React.SetStateAction<boolean>>;
setFilterParams: React.Dispatch<React.SetStateAction<{ search: string | undefined, order: string }>>;
setCurrentPage: React.Dispatch<React.SetStateAction<number>>;
};

const LibrariesV2Filters: React.FC<LibrariesV2FiltersProps> = ({
isLoading = false,
isFiltered = false,
setIsFiltered,
setFilterParams,
setCurrentPage,
Expand All @@ -29,7 +36,7 @@ const LibrariesV2Filters = ({
}
}, [isFiltered, setSearch, setOrder]);

const getOrderFromFilterType = (filterType) => {
const getOrderFromFilterType = (filterType: string) => {
const orders = {
azLibrariesV2: 'title',
zaLibrariesV2: '-title',
Expand All @@ -41,14 +48,14 @@ const LibrariesV2Filters = ({
return orders[filterType] || 'title';
};

const getFilterTypeData = (baseFilters) => ({
const getFilterTypeData = (baseFilters: { search: string | undefined; order: string; }) => ({
azLibrariesV2: { ...baseFilters, order: 'title' },
zaLibrariesV2: { ...baseFilters, order: '-title' },
newestLibrariesV2: { ...baseFilters, order: '-created' },
oldestLibrariesV2: { ...baseFilters, order: 'created' },
});

const handleMenuFilterItemSelected = (filterType) => {
const handleMenuFilterItemSelected = (filterType: string) => {
setOrder(getOrderFromFilterType(filterType));
setIsFiltered(true);

Expand All @@ -64,7 +71,7 @@ const LibrariesV2Filters = ({
setCurrentPage(1);
};

const handleSearchLibrariesV2 = (searchValueDebounced) => {
const handleSearchLibrariesV2 = (searchValueDebounced: string) => {
const valueFormatted = searchValueDebounced.trim();
const filterParams = {
search: valueFormatted.length > 0 ? valueFormatted : undefined,
Expand All @@ -80,7 +87,7 @@ const LibrariesV2Filters = ({
};

const handleSearchLibrariesV2Debounced = useCallback(
debounce((value) => handleSearchLibrariesV2(value), 400),
debounce((value: string) => handleSearchLibrariesV2(value), 400),
[order, search],
);

Expand All @@ -107,17 +114,4 @@ const LibrariesV2Filters = ({
);
};

LibrariesV2Filters.defaultProps = {
isLoading: false,
isFiltered: false,
};

LibrariesV2Filters.propTypes = {
isLoading: PropTypes.bool,
isFiltered: PropTypes.bool,
setIsFiltered: PropTypes.func.isRequired,
setFilterParams: PropTypes.func.isRequired,
setCurrentPage: PropTypes.func.isRequired,
};

export default LibrariesV2Filters;
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import React, { useState, useEffect } from 'react';
import { Icon, Dropdown } from '@openedx/paragon';
import { Check } from '@openedx/paragon/icons';

const LibrariesV2FilterMenu = ({
const LibrariesV2FilterMenu: React.FC<{
id: string;
menuItems: { id: string, name: string, value: string }[];
onItemMenuSelected: (value: string) => void;
defaultItemSelectedText: string;
isFiltered: boolean;
}> = ({
id: idProp,
menuItems,
menuItems = [],
onItemMenuSelected,
defaultItemSelectedText,
defaultItemSelectedText = '',
isFiltered,
}) => {
const [itemMenuSelected, setItemMenuSelected] = useState(defaultItemSelectedText);
const handleCourseTypeSelected = (name, value) => {
const handleCourseTypeSelected = (name: string, value: string) => {
setItemMenuSelected(name);
onItemMenuSelected(value);
};

const libraryV2TypeSelectedIcon = (itemValue) => (itemValue === itemMenuSelected ? (
const libraryV2TypeSelectedIcon = (itemValue: string) => (itemValue === itemMenuSelected ? (
<Icon src={Check} className="ml-2" data-testid="menu-item-icon" />
) : null);

Expand Down Expand Up @@ -52,23 +57,4 @@ const LibrariesV2FilterMenu = ({
);
};

LibrariesV2FilterMenu.defaultProps = {
defaultItemSelectedText: '',
menuItems: [],
};

LibrariesV2FilterMenu.propTypes = {
onItemMenuSelected: PropTypes.func.isRequired,
defaultItemSelectedText: PropTypes.string,
id: PropTypes.string.isRequired,
menuItems: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
}),
),
isFiltered: PropTypes.bool.isRequired,
};

export default LibrariesV2FilterMenu;
Loading

0 comments on commit fd13676

Please sign in to comment.