Skip to content

Commit

Permalink
feat: change instructors filters
Browse files Browse the repository at this point in the history
  • Loading branch information
AuraAlba committed Dec 4, 2023
1 parent 61c27f3 commit 2d83e03
Show file tree
Hide file tree
Showing 16 changed files with 297 additions and 117 deletions.
35 changes: 35 additions & 0 deletions src/features/Common/data/_test_/api.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getCoursesByInstitution } from 'features/Common/data/api';

jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedHttpClient: jest.fn(),
}));

jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(() => ({
LMS_BASE_URL: 'http://localhost:18000',
COURSE_OPERATIONS_API_V2_BASE_URL: 'http://localhost:18000/pearson_course_operation/api/v2',
})),
}));

describe('getCoursesByInstitution', () => {
test('should call getAuthenticatedHttpClient with the correct parameters', () => {
const httpClientMock = {
get: jest.fn(),
};

const institutionId = 1;

getAuthenticatedHttpClient.mockReturnValue(httpClientMock);

getCoursesByInstitution(institutionId);

expect(getAuthenticatedHttpClient).toHaveBeenCalledTimes(1);
expect(getAuthenticatedHttpClient).toHaveBeenCalledWith();

expect(httpClientMock.get).toHaveBeenCalledTimes(1);
expect(httpClientMock.get).toHaveBeenCalledWith(
'http://localhost:18000/pearson_course_operation/api/v2/courses/?institution_id=1',
);
});
});
12 changes: 12 additions & 0 deletions src/features/Common/data/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getConfig } from '@edx/frontend-platform';

function getCoursesByInstitution(institutionId) {
return getAuthenticatedHttpClient().get(
`${getConfig().COURSE_OPERATIONS_API_V2_BASE_URL}/courses/?institution_id=${institutionId}`,
);
}

export {
getCoursesByInstitution,
};
5 changes: 3 additions & 2 deletions src/features/Instructors/AddInstructors/index.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState, useReducer, useEffect } from 'react';
import {
Button, FormGroup, ModalDialog, useToggle, Form,
FormGroup, ModalDialog, useToggle, Form,
} from '@edx/paragon';
import { Button } from 'react-paragon-topaz';
import { getCCXList, handleInstructorsEnrollment } from 'features/Instructors/data/api';
import reducer from 'features/Instructors/AddInstructors/reducer';
import { logError } from '@edx/frontend-platform/logging';
Expand Down Expand Up @@ -62,7 +63,7 @@ const AddInstructors = () => {

return (
<>
<Button variant="primary" onClick={open} size="sm">
<Button variant="outline-primary" onClick={open} size="sm">
Add Instructor
</Button>
<ModalDialog
Expand Down
23 changes: 2 additions & 21 deletions src/features/Instructors/InstructorsFilters/_test_/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,18 @@ describe('InstructorsFilters Component', () => {
<InstructorsFilters fetchData={fetchData} resetPagination={resetPagination} />,
);

const button = getByText('Filters');
await act(async () => {
fireEvent.click(button);
});

const nameInput = getByPlaceholderText('Enter Instructor Name');
const emailInput = getByPlaceholderText('Enter Instructor Email');
const classNameInput = getByPlaceholderText('Enter Class Name');
const buttonApplyFilters = getByText('Apply Filters');
const buttonApplyFilters = getByText('Apply');

expect(nameInput).toBeInTheDocument();
expect(emailInput).toBeInTheDocument();
expect(classNameInput).toBeInTheDocument();

fireEvent.change(nameInput, { target: { value: 'Name' } });
fireEvent.change(emailInput, { target: { value: '[email protected]' } });
fireEvent.change(classNameInput, { target: { value: 'CCX01' } });

expect(nameInput).toHaveValue('Name');
expect(emailInput).toHaveValue('[email protected]');
expect(classNameInput).toHaveValue('CCX01');

await act(async () => {
fireEvent.click(buttonApplyFilters);
Expand All @@ -47,35 +38,25 @@ describe('InstructorsFilters Component', () => {
<InstructorsFilters fetchData={fetchData} resetPagination={resetPagination} />,
);

const button = getByText('Filters');
await act(async () => {
fireEvent.click(button);
});

const nameInput = getByPlaceholderText('Enter Instructor Name');
const emailInput = getByPlaceholderText('Enter Instructor Email');
const classNameInput = getByPlaceholderText('Enter Class Name');
const buttonClearFilters = getByText('Clear');
const buttonClearFilters = getByText('Reset');

expect(nameInput).toBeInTheDocument();
expect(emailInput).toBeInTheDocument();
expect(classNameInput).toBeInTheDocument();

fireEvent.change(nameInput, { target: { value: 'Name' } });
fireEvent.change(emailInput, { target: { value: '[email protected]' } });
fireEvent.change(classNameInput, { target: { value: 'CCX01' } });

expect(nameInput).toHaveValue('Name');
expect(emailInput).toHaveValue('[email protected]');
expect(classNameInput).toHaveValue('CCX01');

await act(async () => {
fireEvent.click(buttonClearFilters);
});

expect(nameInput).toHaveValue('');
expect(emailInput).toHaveValue('');
expect(classNameInput).toHaveValue('');
expect(resetPagination).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
FETCH_COURSES_DATA_FAILURE,
FETCH_COURSES_DATA_REQUEST,
FETCH_COURSES_DATA_SUCCESS,
} from 'features/Instructors/actionTypes';
import { RequestStatus } from 'features/constants';
import reducer from 'features/Students/StudentsFilters/reducer';

describe('Student filter reducers', () => {
const initialState = {
courses: {
data: [],
status: RequestStatus.SUCCESS,
error: null,
},
};

test('should handle FETCH_COURSES_DATA_REQUEST', () => {
const state = {
...initialState,
courses: {
...initialState.courses,
status: RequestStatus.LOADING,
},
};
const action = {
type: FETCH_COURSES_DATA_REQUEST,
};
expect(reducer(state, action)).toEqual(state);
});

test('should handle FETCH_COURSES_DATA_SUCCESS', () => {
const state = {
...initialState,
courses: {
...initialState.courses,
status: RequestStatus.SUCCESS,
data: [],
},
};
const action = {
type: FETCH_COURSES_DATA_SUCCESS,
payload: [],
};
expect(reducer(state, action)).toEqual(state);
});

test('should handle FETCH_COURSES_DATA_FAILURE', () => {
const state = {
...initialState,
courses: {
...initialState.courses,
status: RequestStatus.ERROR,
error: '',
},
};
const action = {
type: FETCH_COURSES_DATA_FAILURE,
payload: '',
};
expect(reducer(state, action)).toEqual(state);
});
});
173 changes: 121 additions & 52 deletions src/features/Instructors/InstructorsFilters/index.jsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,142 @@
import React, { useState } from 'react';
import React, {
useState, useReducer, useContext, useEffect,
} from 'react';
import {
DropdownButton, Form, Col, Button,
Form, Col,
} from '@edx/paragon';
import { camelCaseObject } from '@edx/frontend-platform';
import { Select, Button } from 'react-paragon-topaz';
import { logError } from '@edx/frontend-platform/logging';
import { InstitutionContext } from 'features/Main/institutionContext';
import reducer from 'features/Instructors/InstructorsFilters/reducer';
import { getCoursesByInstitution } from 'features/Common/data/api';
import {
FETCH_COURSES_DATA_REQUEST,
FETCH_COURSES_DATA_SUCCESS,
FETCH_COURSES_DATA_FAILURE,
} from 'features/Instructors/actionTypes';
import { RequestStatus } from 'features/constants';
import PropTypes from 'prop-types';

const initialFilterFormValues = {
instructorName: '',
instructorEmail: '',
ccxId: '',
const initialState = {
courses: {
data: [],
status: RequestStatus.SUCCESS,
error: null,
},
};

const InstructorsFilters = ({ fetchData, resetPagination }) => {
const [filters, setFilters] = useState(initialFilterFormValues);
const stateInstitution = useContext(InstitutionContext);
const [state, dispatch] = useReducer(reducer, initialState);
const [courseOptions, setCourseOptions] = useState([]);
const [instructorName, setInstructorName] = useState('');
const [instructorEmail, setInstructorEmail] = useState('');
const [courseSelected, setCourseSelected] = useState(null);
// check this after implementation of selector institution
let id = 0;
if (stateInstitution.length > 0) {
id = stateInstitution[0].id;
}

const handleInputChange = (e) => {
setFilters({
...filters,
[e.target.name]: e.target.value.trim(),
});
};
const fetchCoursesData = async () => {
dispatch({ type: FETCH_COURSES_DATA_REQUEST });

const handleApplyFilters = async () => {
fetchData(filters);
try {
const response = camelCaseObject(await getCoursesByInstitution(id));
dispatch({ type: FETCH_COURSES_DATA_SUCCESS, payload: response });
} catch (error) {
dispatch({ type: FETCH_COURSES_DATA_FAILURE, payload: error });
logError(error);
}
};

const handleCleanFilters = () => {
setFilters(initialFilterFormValues);
fetchData();
resetPagination();
setInstructorName('');
setInstructorEmail('');
setCourseSelected(null);
};

const handleInstructorsFilter = async (e) => {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
const formJson = Object.fromEntries(formData.entries());
try {
fetchData(formJson);
} catch (error) {
logError(error);
}
};

useEffect(() => {
if (id > 0) {
fetchCoursesData();
} // eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);

useEffect(() => {
if (state.courses.data.length > 0) {
const options = state.courses.data.map(course => ({
...course,
label: course.masterCourseName,
value: course.masterCourseName,
}));
setCourseOptions(options);
}
}, [state.courses]);

return (
<DropdownButton title="Filters" variant="outline-primary" size="sm" className="mr-3">
<Form className="row justify-content-center px-3 py-2">
<Form.Group as={Col} className="mb-0">
<Form.Control
type="text"
floatingLabel="Name"
name="instructorName"
placeholder="Enter Instructor Name"
value={filters.instructorName}
onChange={handleInputChange}
className="mb-3 mr-0"
/>
<Form.Control
type="text"
floatingLabel="Email"
name="instructorEmail"
placeholder="Enter Instructor Email"
value={filters.instructorEmail}
onChange={handleInputChange}
className="mb-3 mr-0"
/>
<Form.Control
type="text"
floatingLabel="Class Name"
name="ccxId"
placeholder="Enter Class Name"
value={filters.ccxId}
onChange={handleInputChange}
className="mb-4 mr-0"
/>
<div className="d-flex justify-content-between">
<Button onClick={handleApplyFilters}>Apply Filters</Button>
<Button onClick={handleCleanFilters} variant="outline-primary">Clear</Button>
<div className="filter-container justify-content-center row">
<div className="col-11">
<h3>Search</h3>
<Form className="row justify-content-center" onSubmit={handleInstructorsFilter}>
<Form.Row className="col-12">
<Form.Group as={Col}>
<Form.Control
type="text"
floatingLabel="Instructor Name"
name="instructor_name"
placeholder="Enter Instructor Name"
onChange={(e) => setInstructorName(e.target.value)}
value={instructorName}
/>
</Form.Group>
<Form.Group as={Col}>
<Form.Control
type="email"
floatingLabel="Instructor Email"
name="instructor_email"
placeholder="Enter Instructor Email"
onChange={(e) => setInstructorEmail(e.target.value)}
value={instructorEmail}
/>
</Form.Group>
</Form.Row>
<div className="col-12 px-1">
<Form.Row className="col-4">
<Form.Group as={Col}>
<Select
placeholder="Course"
name="course_name"
className="mr-2"
options={courseOptions}
onChange={option => setCourseSelected(option)}
value={courseSelected}
/>
</Form.Group>
</Form.Row>
</div>
</Form.Group>
</Form>
</DropdownButton>
<div className="d-flex col-12 justify-content-end mr-3">
<Button onClick={handleCleanFilters} variant="tertiary" text className="mr-2">Reset</Button>
<Button type="submit">Apply</Button>
</div>
</Form>
</div>

</div>
);
};

Expand Down
Loading

0 comments on commit 2d83e03

Please sign in to comment.