Skip to content

Commit

Permalink
feat: Add license table in home page (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
AuraAlba authored Jan 16, 2024
1 parent 4bb4079 commit d708d96
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 5 deletions.
26 changes: 23 additions & 3 deletions src/features/Common/data/_test_/api.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getCoursesByInstitution } from 'features/Common/data/api';
import { getCoursesByInstitution, getLicensesByInstitution } from 'features/Common/data/api';

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

describe('getCoursesByInstitution', () => {
test('should call getAuthenticatedHttpClient with the correct parameters', () => {
describe('Common api services', () => {
test('should call getCoursesByInstitution with the correct parameters', () => {
const httpClientMock = {
get: jest.fn(),
};
Expand All @@ -34,4 +34,24 @@ describe('getCoursesByInstitution', () => {
{ params: { page } },
);
});

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

const institutionId = 1;

getAuthenticatedHttpClient.mockReturnValue(httpClientMock);

getLicensesByInstitution(institutionId, true);

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

expect(httpClientMock.get).toHaveBeenCalledTimes(1);
expect(httpClientMock.get).toHaveBeenCalledWith(
'http://localhost:18000/pearson_course_operation/api/v2/license-pool/?limit=true&institution_id=1',
);
});
});
7 changes: 7 additions & 0 deletions src/features/Common/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ function getCoursesByInstitution(institutionId, limit, page, filters) {
);
}

function getLicensesByInstitution(institutionId, limit) {
return getAuthenticatedHttpClient().get(
`${getConfig().COURSE_OPERATIONS_API_V2_BASE_URL}/license-pool/?limit=${limit}&institution_id=${institutionId}`,
);
}

export {
getCoursesByInstitution,
getLicensesByInstitution,
};
7 changes: 7 additions & 0 deletions src/features/Dashboard/DashboardPage/_test_/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import { initializeStore } from 'store';

let store;

jest.mock('axios');

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

describe('DashboardPage component', () => {
beforeEach(() => {
store = initializeStore();
Expand All @@ -23,5 +29,6 @@ describe('DashboardPage component', () => {
expect(getByText('Next month')).toBeInTheDocument();
expect(getByText('New students registered')).toBeInTheDocument();
expect(getByText('Classes scheduled')).toBeInTheDocument();
expect(getByText('License inventory')).toBeInTheDocument();
});
});
41 changes: 39 additions & 2 deletions src/features/Dashboard/DashboardPage/index.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,54 @@
import React from 'react';
import { useSelector } from 'react-redux';
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';

import { Container } from '@edx/paragon';
import StudentsMetrics from 'features/Students/StudentsMetrics';
import LicensesTable from 'features/Licenses/LicensesTable';
import { Button } from 'react-paragon-topaz';

import { fetchLicensesData } from 'features/Dashboard/data';

import 'features/Dashboard/DashboardPage/index.scss';

const DashboardPage = () => {
const dispatch = useDispatch();
const stateInstitution = useSelector((state) => state.main.institution.data);
const licenseData = useSelector((state) => state.dashboard.tableLicense.data);
const [dataTableLicense, setDataTableLicense] = useState([]);

let idInstitution = '';
// eslint-disable-next-line no-unused-expressions
stateInstitution.length > 0 ? idInstitution = stateInstitution[0].id : idInstitution = '';

useEffect(() => {
if (licenseData.length > 5) {
// Return 5 licenses with fewest remaining seats
const arraySorted = licenseData.slice().sort((license1, license2) => {
if (license1.numberOfPendingStudents > license2.numberOfPendingStudents) { return 1; }
if (license1.numberOfPendingStudents < license2.numberOfPendingStudents) { return -1; }
return 0;
});
setDataTableLicense(arraySorted.slice(0, 5));
} else { setDataTableLicense(licenseData); }
}, [licenseData]);

useEffect(() => {
dispatch(fetchLicensesData(idInstitution));
}, [idInstitution]); // eslint-disable-line react-hooks/exhaustive-deps

return (
<Container size="xl" className="px-4">
<h2 className="title-page">
{stateInstitution.length === 1 ? `Welcome to ${stateInstitution[0].name}` : 'Select an institution'}
</h2>
<StudentsMetrics />
<div className="license-section">
<div className="d-flex justify-content-between">
<h3>License inventory</h3>
<Button variant="outline-primary">View All</Button>
</div>
<LicensesTable data={dataTableLicense} />
</div>
</Container>
);
};
Expand Down
6 changes: 6 additions & 0 deletions src/features/Dashboard/DashboardPage/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@import "assets/colors.scss";

.license-section {
background-color: $color-white;
padding: 2rem;
}
77 changes: 77 additions & 0 deletions src/features/Dashboard/data/_test_/redux.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform/testing';
import { fetchLicensesData } from 'features/Dashboard/data';
import { executeThunk } from 'test-utils';
import { initializeStore } from 'store';

let axiosMock;
let store;

describe('Dashboard redux tests', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 1,
username: 'testuser',
administrator: true,
roles: [],
},
});
axiosMock = new MockAdapter(getAuthenticatedHttpClient());

store = initializeStore();
});

afterEach(() => {
axiosMock.reset();
});

test('successful fetch licenses data', async () => {
const licensesApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/license-pool/?limit=false&institution_id=1`;
const mockResponse = [
{
licenseName: 'License Name 1',
purchasedSeats: 20,
numberOfStudents: 6,
numberOfPendingStudents: 11,
},
{
licenseName: 'License Name 2',
purchasedSeats: 10,
numberOfStudents: 1,
numberOfPendingStudents: 5,
},
];
axiosMock.onGet(licensesApiUrl)
.reply(200, mockResponse);

expect(store.getState().dashboard.tableLicense.status)
.toEqual('loading');

await executeThunk(fetchLicensesData(1), store.dispatch, store.getState);

expect(store.getState().dashboard.tableLicense.data)
.toEqual(mockResponse);

expect(store.getState().dashboard.tableLicense.status)
.toEqual('success');
});

test('failed fetch licenses data', async () => {
const licensesApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/license-pool/?limit=false&institution_id=1`;
axiosMock.onGet(licensesApiUrl)
.reply(500);

expect(store.getState().dashboard.tableLicense.status)
.toEqual('loading');

await executeThunk(fetchLicensesData(1), store.dispatch, store.getState);

expect(store.getState().dashboard.tableLicense.data)
.toEqual([]);

expect(store.getState().dashboard.tableLicense.status)
.toEqual('error');
});
});
2 changes: 2 additions & 0 deletions src/features/Dashboard/data/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { reducer } from 'features/Dashboard/data/slice';
export { fetchLicensesData } from 'features/Dashboard/data/thunks';
34 changes: 34 additions & 0 deletions src/features/Dashboard/data/slice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';
import { RequestStatus, initialStateService } from 'features/constants';

const initialState = {
tableLicense: {
...initialStateService,
},
};

export const dashboardSlice = createSlice({
name: 'dashboard',
initialState,
reducers: {
fetchLicensesDataRequest: (state) => {
state.tableLicense.status = RequestStatus.LOADING;
},
fetchLicensesDataSuccess: (state, { payload }) => {
state.tableLicense.status = RequestStatus.SUCCESS;
state.tableLicense.data = payload;
},
fetchLicensesDataFailed: (state) => {
state.tableLicense.status = RequestStatus.ERROR;
},
},
});

export const {
fetchLicensesDataRequest,
fetchLicensesDataSuccess,
fetchLicensesDataFailed,
} = dashboardSlice.actions;

export const { reducer } = dashboardSlice;
26 changes: 26 additions & 0 deletions src/features/Dashboard/data/thunks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { logError } from '@edx/frontend-platform/logging';
import { camelCaseObject } from '@edx/frontend-platform';

import { getLicensesByInstitution } from 'features/Common/data/api';
import {
fetchLicensesDataRequest,
fetchLicensesDataSuccess,
fetchLicensesDataFailed,
} from 'features/Dashboard/data/slice';

function fetchLicensesData(id) {
return async (dispatch) => {
dispatch(fetchLicensesDataRequest());
try {
const response = camelCaseObject(await getLicensesByInstitution(id, false));
dispatch(fetchLicensesDataSuccess(response.data));
} catch (error) {
dispatch(fetchLicensesDataFailed());
logError(error);
}
};
}

export {
fetchLicensesData,
};
27 changes: 27 additions & 0 deletions src/features/Licenses/LicensesTable/_test_/columns.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { columns } from 'features/Licenses/LicensesTable/columns';

describe('columns in license table', () => {
test('returns an array of columns with correct properties', () => {
expect(columns).toBeInstanceOf(Array);
expect(columns).toHaveLength(4);

const [
nameColumn,
purchasedColumn,
enrolledColumn,
remainingColumn,
] = columns;

expect(nameColumn).toHaveProperty('Header', 'License Pool');
expect(nameColumn).toHaveProperty('accessor', 'licenseName');

expect(purchasedColumn).toHaveProperty('Header', 'Purchased');
expect(purchasedColumn).toHaveProperty('accessor', 'purchasedSeats');

expect(enrolledColumn).toHaveProperty('Header', 'Enrolled');
expect(enrolledColumn).toHaveProperty('accessor', 'numberOfStudents');

expect(remainingColumn).toHaveProperty('Header', 'Remaining');
expect(remainingColumn).toHaveProperty('accessor', 'numberOfPendingStudents');
});
});
47 changes: 47 additions & 0 deletions src/features/Licenses/LicensesTable/_test_/index.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';

import LicensesTable from 'features/Licenses/LicensesTable';
import { columns } from 'features/Licenses/LicensesTable/columns';

describe('Licenses Table', () => {
test('renders Licenses table without data', () => {
render(<LicensesTable data={[]} count={0} columns={[]} />);
const emptyTableText = screen.getByText('No licenses found.');
expect(emptyTableText).toBeInTheDocument();
});

test('renders Licenses table with data', () => {
const data = [
{
licenseName: 'License Name 1',
purchasedSeats: 20,
numberOfStudents: 6,
numberOfPendingStudents: 11,
},
{
licenseName: 'License Name 2',
purchasedSeats: 10,
numberOfStudents: 1,
numberOfPendingStudents: 5,
},
];

const component = render(
<LicensesTable data={data} count={data.length} columns={columns} />,
);

// Check if the table rows are present
const tableRows = screen.getAllByRole('row');
expect(tableRows).toHaveLength(data.length + 1); // Data rows + 1 header row
expect(component.container).toHaveTextContent('License Name 1');
expect(component.container).toHaveTextContent('License Name 2');
expect(component.container).toHaveTextContent('20');
expect(component.container).toHaveTextContent('10');
expect(component.container).toHaveTextContent('6');
expect(component.container).toHaveTextContent('1');
expect(component.container).toHaveTextContent('11');
expect(component.container).toHaveTextContent('5');
});
});
20 changes: 20 additions & 0 deletions src/features/Licenses/LicensesTable/columns.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const columns = [
{
Header: 'License Pool',
accessor: 'licenseName',
},
{
Header: 'Purchased',
accessor: 'purchasedSeats',
},
{
Header: 'Enrolled',
accessor: 'numberOfStudents',
},
{
Header: 'Remaining',
accessor: 'numberOfPendingStudents',
},
];

export { columns };
Loading

0 comments on commit d708d96

Please sign in to comment.