Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Error messages for generating schedules #423

Merged
merged 32 commits into from Nov 29, 2020
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
961ba2b
Added message to create_schedules on failure
rachelconn Oct 23, 2020
cca1f33
Frontend expects new generate schedules formatting
rachelconn Oct 23, 2020
a001ad2
Error handling for generate schedules
rachelconn Oct 23, 2020
60e9e03
Created type for scheduler/generate API calls
rachelconn Oct 23, 2020
d921daa
Changed section filters to enum
rachelconn Oct 23, 2020
40b61d1
Added availability checking to create_schedules
rachelconn Oct 24, 2020
e1e77a2
Update existing backend tests to pass
rachelconn Oct 25, 2020
24cab0a
Created tests for create_schedules errors
rachelconn Oct 25, 2020
54a2eec
squash
rachelconn Oct 25, 2020
41670c6
Made NoSchedulesError messages modular
rachelconn Oct 25, 2020
ceb32e6
Merge remote-tracking branch 'origin/master' into generate-schedules-…
rachelconn Oct 25, 2020
74a03b3
squash
rachelconn Oct 25, 2020
72cd9ea
Made no seats message more helpful
rachelconn Oct 25, 2020
72f04b2
Use django test assertions
rachelconn Oct 25, 2020
4e045bb
Merge remote-tracking branch 'origin/master' into generate-schedules-…
rachelconn Oct 27, 2020
44eec0c
Fix tests after merge
rachelconn Oct 27, 2020
d6908ca
Add comment to async filter
rachelconn Oct 27, 2020
1608ce1
Add error message for too restrictive basic filters
rachelconn Oct 27, 2020
50a1488
Add error message for no course cards
rachelconn Oct 27, 2020
f416467
Added tests for new error messages
rachelconn Oct 27, 2020
8272914
Fix linter errors
rachelconn Oct 27, 2020
3136e77
Merge remote-tracking branch 'origin/master' into generate-schedules-…
rachelconn Oct 30, 2020
ed8a1d0
Merge remote-tracking branch 'origin/master' into generate-schedules-…
rachelconn Oct 30, 2020
2a3222f
Update generic no schedules error message
rachelconn Oct 31, 2020
d6631b3
lint fix
rachelconn Oct 31, 2020
b230f13
Error messages show course before the error message
rachelconn Nov 2, 2020
41f2542
Merge remote-tracking branch 'origin/master' into generate-schedules-…
rachelconn Nov 4, 2020
b61cf45
Formatting fixes
rachelconn Nov 8, 2020
152e2fd
Merge remote-tracking branch 'origin/master' into generate-schedules-…
rachelconn Nov 8, 2020
e04608e
Override snackbar wrapping
rachelconn Nov 15, 2020
f0f7bd6
Merge remote-tracking branch 'origin/master' into generate-schedules-…
rachelconn Nov 15, 2020
4ed0c4f
Test fix
rachelconn Nov 15, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../../../../../../redux/reducer';
import { updateCourseCard } from '../../../../../../redux/actions/courseCards';
import * as styles from './BasicSelect.css';
import { SectionFilter } from '../../../../../../types/CourseCardOptions';

interface BasicOptionRowProps {
id: number;
Expand Down Expand Up @@ -37,9 +38,9 @@ const BasicOptionRow: React.FC<BasicOptionRowProps> = ({ id, value, label }) =>
dispatch(updateCourseCard(id, { [value]: evt.target.value as string }));
}}
>
<MenuItem value="no_preference">No Preference</MenuItem>
<MenuItem value="exclude">Exclude</MenuItem>
<MenuItem value="only">Only</MenuItem>
<MenuItem value={SectionFilter.NO_PREFERENCE}>No Preference</MenuItem>
<MenuItem value={SectionFilter.EXCLUDE}>Exclude</MenuItem>
<MenuItem value={SectionFilter.ONLY}>Only</MenuItem>
</Select>
</td>
</tr>
Expand Down
15 changes: 12 additions & 3 deletions autoscheduler/frontend/src/redux/actions/courseCards.ts
@@ -1,6 +1,7 @@
import { ThunkAction } from 'redux-thunk';
import {
CourseCardOptions, SectionSelected, CustomizationLevel, SerializedCourseCardOptions,
SectionFilter,
} from '../../types/CourseCardOptions';
import {
AddCourseAction, ADD_COURSE_CARD, RemoveCourseAction, REMOVE_COURSE_CARD, UpdateCourseAction,
Expand All @@ -17,9 +18,9 @@ function createEmptyCourseCard(): CourseCardOptions {
course: '',
customizationLevel: CustomizationLevel.BASIC,
sections: [],
web: 'no_preference',
honors: 'exclude',
asynchronous: 'no_preference',
web: SectionFilter.NO_PREFERENCE,
honors: SectionFilter.EXCLUDE,
asynchronous: SectionFilter.NO_PREFERENCE,
collapsed: false,
};
}
Expand Down Expand Up @@ -192,12 +193,20 @@ async function fetchCourseCardFrom(
const hasHonors = sections.some((section) => section.section.honors);
const hasWeb = sections.some((section) => section.section.web);
const hasAsynchronous = sections.some((section) => section.section.asynchronous);
// Update honors and web based on whether the old selection is still possible
const honors = hasHonors ? courseCard.honors : SectionFilter.NO_PREFERENCE;
const web = hasWeb ? courseCard.web : SectionFilter.NO_PREFERENCE;
gannonprudhomme marked this conversation as resolved.
Show resolved Hide resolved
const asynchronous = hasAsynchronous ? courseCard.asynchronous : SectionFilter.NO_PREFERENCE;

return {
...courseCard,
sections,
hasHonors,
hasWeb,
hasAsynchronous,
honors,
web,
asynchronous,
};
})
.catch(() => undefined);
Expand Down
30 changes: 23 additions & 7 deletions autoscheduler/frontend/src/redux/actions/schedules.ts
Expand Up @@ -12,6 +12,7 @@ import { CustomizationLevel } from '../../types/CourseCardOptions';
import { parseAllMeetings } from './courseCards';
import { SelectScheduleAction } from '../reducers/selectedSchedule';
import selectSchedule from './selectedSchedule';
import { GenerateSchedulesResponse } from '../../types/APIResponses';

export function addSchedule(meetings: Meeting[]): AddScheduleAction {
return {
Expand Down Expand Up @@ -56,6 +57,10 @@ export function renameSchedule(index: number, name: string): RenameScheduleActio
};
}

export const errorGeneratingSchedulesMessage = 'There was an error generating schedules, please try again.';

// Fetches scheduler/generate. If something goes wrong or no schedules can be generated,
// throws an error with a message indicating what happened.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Fetches scheduler/generate. If something goes wrong or no schedules can be generated,
// throws an error with a message indicating what happened.
/** Fetches scheduler/generate. If something goes wrong or no schedules can be generated,
throws an error with a message indicating what happened. */

So it shows up as a doc when you hover over the function

export function generateSchedules(includeFull: boolean):
ThunkAction<Promise<void>, RootState, undefined, ReplaceSchedulesAction | SelectScheduleAction> {
return async (dispatch, getState): Promise<void> => {
Expand Down Expand Up @@ -114,13 +119,24 @@ ThunkAction<Promise<void>, RootState, undefined, ReplaceSchedulesAction | Select
courses,
availabilities,
}),
}).then(
(res) => res.json(),
).then(
(generatedSchedules: any[][]) => generatedSchedules.map(parseAllMeetings),
).then(
checkIfEmpty,
)
})
.then((res) => res.json())
.catch(() => {
// If something went wrong generating schedules, catch the error here before
// handling messages from no schedules being generated.
throw new Error(errorGeneratingSchedulesMessage);
})
.then((json) => {
const { schedules, message }: GenerateSchedulesResponse = json;
if (message) throw new Error(message);
if (!schedules?.length) {
throw new Error(errorGeneratingSchedulesMessage);
}
return schedules.map(parseAllMeetings);
})
.then(
checkIfEmpty,
)
gannonprudhomme marked this conversation as resolved.
Show resolved Hide resolved
.then((schedules: Meeting[][]) => {
dispatch(replaceSchedules(schedules));
dispatch(selectSchedule(0));
Expand Down
10 changes: 6 additions & 4 deletions autoscheduler/frontend/src/redux/reducers/courseCards.ts
Expand Up @@ -2,7 +2,9 @@
* Stores all course cards that the user uses to select courses to include
* in generated schedules
*/
import { CourseCardOptions, CourseCardArray, CustomizationLevel } from '../../types/CourseCardOptions';
import {
CourseCardOptions, CourseCardArray, CustomizationLevel, SectionFilter,
} from '../../types/CourseCardOptions';

// action type strings
export const ADD_COURSE_CARD = 'ADD_COURSE_CARD';
Expand Down Expand Up @@ -38,9 +40,9 @@ const initialCourseCardArray: CourseCardArray = {
0: {
course: '',
customizationLevel: CustomizationLevel.BASIC,
web: 'no_preference',
honors: 'exclude',
asynchronous: 'no_preference',
web: SectionFilter.NO_PREFERENCE,
honors: SectionFilter.EXCLUDE,
asynchronous: SectionFilter.NO_PREFERENCE,
sections: [],
loading: true,
collapsed: false,
Expand Down
22 changes: 12 additions & 10 deletions autoscheduler/frontend/src/tests/redux/CourseCards.test.ts
Expand Up @@ -16,7 +16,9 @@ import Meeting, { MeetingType } from '../../types/Meeting';
import Section from '../../types/Section';
import Instructor from '../../types/Instructor';
import Grades from '../../types/Grades';
import { CustomizationLevel, CourseCardArray, SerializedCourseCardOptions } from '../../types/CourseCardOptions';
import {
CustomizationLevel, CourseCardArray, SerializedCourseCardOptions, SectionFilter,
} from '../../types/CourseCardOptions';

// The input from the backend use snake_case, so disable camelcase errors for this file
/* eslint-disable @typescript-eslint/camelcase */
Expand Down Expand Up @@ -323,9 +325,9 @@ describe('Course Cards Redux', () => {
0: {
course: '',
customizationLevel: CustomizationLevel.BASIC,
web: 'no_preference',
honors: 'exclude',
asynchronous: 'no_preference',
web: SectionFilter.NO_PREFERENCE,
honors: SectionFilter.EXCLUDE,
asynchronous: SectionFilter.NO_PREFERENCE,
sections: [],
},
};
Expand All @@ -349,19 +351,19 @@ describe('Course Cards Redux', () => {
0: {
course: 'MATH 151',
customizationLevel: CustomizationLevel.BASIC,
web: 'no_preference',
honors: 'exclude',
asynchronous: 'no_preference',
web: SectionFilter.NO_PREFERENCE,
honors: SectionFilter.EXCLUDE,
asynchronous: SectionFilter.NO_PREFERENCE,
},
numCardsCreated: 1,
};
const courseCards = [
{
course: 'MATH 151',
customizationLevel: CustomizationLevel.BASIC,
web: 'no_preference',
honors: 'exclude',
asynchronous: 'no_preference',
web: SectionFilter.NO_PREFERENCE,
honors: SectionFilter.EXCLUDE,
asynchronous: SectionFilter.NO_PREFERENCE,
},
];
fetchMock.mockImplementationOnce(testFetch);
Expand Down
14 changes: 10 additions & 4 deletions autoscheduler/frontend/src/tests/testData.ts
@@ -1,5 +1,7 @@
/* eslint-disable @typescript-eslint/camelcase */

import { GenerateSchedulesResponse } from '../types/APIResponses';

/**
* Mocks the fetch call made to the API to retrieve all sections of a given course. Should return
* 3 sections with 5 meetings total for CSCE classes and 1 section with 1 meeting total for MATH
Expand Down Expand Up @@ -247,8 +249,12 @@ export async function mockFetchSchedulerGenerate(): Promise<Response> {
}],
};

return new Response(JSON.stringify([
[testSection1, testSection2],
[testSection1, testSection3],
]));
const response: GenerateSchedulesResponse = {
schedules: [
[testSection1, testSection2],
[testSection1, testSection3],
],
message: '',
};
return new Response(JSON.stringify(response));
}
16 changes: 11 additions & 5 deletions autoscheduler/frontend/src/tests/ui/ConfigureCard.test.tsx
Expand Up @@ -11,8 +11,10 @@ import thunk from 'redux-thunk';
import ConfigureCard from '../../components/SchedulingPage/ConfigureCard/ConfigureCard';
import autoSchedulerReducer from '../../redux/reducer';
import { updateCourseCard } from '../../redux/actions/courseCards';
import { CustomizationLevel, SectionSelected } from '../../types/CourseCardOptions';
import { CustomizationLevel, SectionFilter, SectionSelected } from '../../types/CourseCardOptions';
import testFetch from '../testData';
import { GenerateSchedulesResponse } from '../../types/APIResponses';
import { errorGeneratingSchedulesMessage } from '../../redux/actions/schedules';

describe('ConfigureCard component', () => {
beforeEach(fetchMock.mockReset);
Expand Down Expand Up @@ -153,8 +155,8 @@ describe('ConfigureCard component', () => {

// assert
// no_preference is the default value
expect(web).toEqual('no_preference');
expect(honors).toEqual('no_preference');
expect(web).toEqual(SectionFilter.NO_PREFERENCE);
expect(honors).toEqual(SectionFilter.NO_PREFERENCE);
});

test('Does not send sections when "BASIC" customization level is selected', () => {
Expand Down Expand Up @@ -216,7 +218,7 @@ describe('ConfigureCard component', () => {

// act
fireEvent.click(getByText('Generate Schedules'));
const errorMessage = await findByText('No schedules found. Try widening your criteria.');
const errorMessage = await findByText(errorGeneratingSchedulesMessage);

// assert
expect(errorMessage).toBeInTheDocument();
Expand All @@ -233,7 +235,11 @@ describe('ConfigureCard component', () => {
</Provider>,
);

fetchMock.mockResponseOnce(JSON.stringify([[], []]));
const mockedResponse: GenerateSchedulesResponse = {
schedules: [[], []],
message: '',
};
fetchMock.mockResponseOnce(JSON.stringify(mockedResponse));

// act
fireEvent.click(queryByText('Generate Schedules'));
Expand Down
6 changes: 6 additions & 0 deletions autoscheduler/frontend/src/types/APIResponses.ts
@@ -0,0 +1,6 @@
// Module containing type definitions for API responses

export interface GenerateSchedulesResponse {
schedules: any[][];
message: string;
}
6 changes: 6 additions & 0 deletions autoscheduler/frontend/src/types/CourseCardOptions.ts
Expand Up @@ -6,6 +6,12 @@ export enum CustomizationLevel {
BASIC, SECTION
}

export enum SectionFilter {
NO_PREFERENCE = 'no_preference',
EXCLUDE = 'exclude',
ONLY = 'only',
}

/**
* Represents a section, its meetings, and whether or not the section
* is curently selected
Expand Down