@@ -6,28 +6,24 @@ import { ArrowBack } from '@edx/paragon/icons';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import { locationId } from '../../data/constants/app';
|
||||
import { locationId } from 'data/constants/app';
|
||||
import urls from 'data/services/lms/urls';
|
||||
|
||||
/**
|
||||
* <ListViewBreadcrumb />
|
||||
*/
|
||||
export const ListViewBreadcrumb = ({ courseId, oraName }) => {
|
||||
const openResponseUrl = `${process.env.LMS_BASE_URL}/courses/${courseId}/instructor#view-open_response_assessment`;
|
||||
const oraUrl = `${process.env.LMS_BASE_URL}/courses/${courseId}/jump_to/${locationId}`;
|
||||
return (
|
||||
<>
|
||||
<Hyperlink className="py-4" destination={openResponseUrl}>
|
||||
<ArrowBack className="mr-3" />
|
||||
Back to all open responses
|
||||
</Hyperlink>
|
||||
<p className="h3 py-4">{oraName}<Hyperlink
|
||||
destination={oraUrl}
|
||||
target="_blank"
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const ListViewBreadcrumb = ({ courseId, oraName }) => (
|
||||
<>
|
||||
<Hyperlink className="py-4" destination={urls.openResponse(courseId)}>
|
||||
<ArrowBack className="mr-3" />
|
||||
Back to all open responses
|
||||
</Hyperlink>
|
||||
<p className="h3 py-4">
|
||||
{oraName}
|
||||
<Hyperlink destination={urls.ora(courseId, locationId)} target="_blank" />
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
ListViewBreadcrumb.defaultProps = {
|
||||
courseId: '',
|
||||
oraName: '',
|
||||
@@ -42,7 +38,6 @@ export const mapStateToProps = (state) => ({
|
||||
oraName: selectors.app.ora.name(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
};
|
||||
export const mapDispatchToProps = {};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ListViewBreadcrumb);
|
||||
|
||||
79
src/containers/ListView/ListViewBreadcrumb.test.jsx
Normal file
79
src/containers/ListView/ListViewBreadcrumb.test.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import {
|
||||
Hyperlink,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import * as constants from 'data/constants/app';
|
||||
import urls from 'data/services/lms/urls';
|
||||
import selectors from 'data/selectors';
|
||||
|
||||
import {
|
||||
ListViewBreadcrumb,
|
||||
mapStateToProps,
|
||||
} from './ListViewBreadcrumb';
|
||||
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
Hyperlink: () => 'Hyperlink',
|
||||
}));
|
||||
|
||||
jest.mock('data/selectors', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
app: {
|
||||
courseId: (...args) => ({ courseId: args }),
|
||||
ora: {
|
||||
name: (...args) => ({ oraName: args }),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('data/constants/app', () => ({
|
||||
locationId: 'fake-location-id',
|
||||
}));
|
||||
jest.mock('data/services/lms/urls', () => ({
|
||||
openResponse: (courseId) => `openResponseUrl(${courseId})`,
|
||||
ora: (courseId, locationId) => `oraUrl(${courseId}, ${locationId})`,
|
||||
}));
|
||||
|
||||
let el;
|
||||
|
||||
describe('ListViewBreadcrumb component', () => {
|
||||
describe('component', () => {
|
||||
const props = {
|
||||
courseId: 'test-course-id',
|
||||
oraName: 'fake-ora-name',
|
||||
};
|
||||
beforeEach(() => {
|
||||
el = shallow(<ListViewBreadcrumb {...props} />);
|
||||
});
|
||||
test('snapshot: empty (no list data)', () => {
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('openResponse destination', () => {
|
||||
expect(
|
||||
el.find(Hyperlink).at(0).props().destination,
|
||||
).toEqual(urls.openResponse(props.courseId));
|
||||
});
|
||||
test('ora destination', () => {
|
||||
expect(
|
||||
el.find(Hyperlink).at(1).props().destination,
|
||||
).toEqual(urls.ora(props.courseId, constants.locationId));
|
||||
});
|
||||
});
|
||||
describe('mapStateToProps', () => {
|
||||
let mapped;
|
||||
const testState = { some: 'test-state' };
|
||||
beforeEach(() => {
|
||||
mapped = mapStateToProps(testState);
|
||||
});
|
||||
test('courseId loads from app.courseId', () => {
|
||||
expect(mapped.courseId).toEqual(selectors.app.courseId(testState));
|
||||
});
|
||||
test('oraName loads from app.ora.name', () => {
|
||||
expect(mapped.oraName).toEqual(selectors.app.ora.name(testState));
|
||||
});
|
||||
});
|
||||
});
|
||||
20
src/containers/ListView/TableControls.jsx
Normal file
20
src/containers/ListView/TableControls.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
DataTable,
|
||||
} from '@edx/paragon';
|
||||
|
||||
/**
|
||||
* <TableControls />
|
||||
*/
|
||||
export const TableControls = () => (
|
||||
<>
|
||||
<DataTable.TableControlBar />
|
||||
<DataTable.Table />
|
||||
<DataTable.EmptyTable content="No results found" />
|
||||
<DataTable.TableFooter />
|
||||
</>
|
||||
);
|
||||
TableControls.propTypes = {};
|
||||
|
||||
export default TableControls;
|
||||
21
src/containers/ListView/TableControls.test.jsx
Normal file
21
src/containers/ListView/TableControls.test.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import TableControls from './TableControls';
|
||||
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
DataTable: {
|
||||
TableControlBar: () => 'DataTable.TableControlBar',
|
||||
Table: () => 'DataTable.Table',
|
||||
EmptyTable: () => 'DataTable.EmptyTable',
|
||||
TableFooter: () => 'DataTable.TableFooter',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('ListView TableControls component', () => {
|
||||
describe('component', () => {
|
||||
test('snapshot', () => {
|
||||
expect(shallow(<TableControls />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ListViewBreadcrumb component component snapshot: empty (no list data) 1`] = `
|
||||
<Fragment>
|
||||
<Hyperlink
|
||||
className="py-4"
|
||||
destination="openResponseUrl(test-course-id)"
|
||||
>
|
||||
<SvgArrowBack
|
||||
className="mr-3"
|
||||
/>
|
||||
Back to all open responses
|
||||
</Hyperlink>
|
||||
<p
|
||||
className="h3 py-4"
|
||||
>
|
||||
fake-ora-name
|
||||
<Hyperlink
|
||||
destination="oraUrl(test-course-id, fake-location-id)"
|
||||
target="_blank"
|
||||
/>
|
||||
</p>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ListView TableControls component component snapshot 1`] = `
|
||||
<Fragment>
|
||||
<TableControlBar />
|
||||
<Table />
|
||||
<EmptyTable
|
||||
content="No results found"
|
||||
/>
|
||||
<TableFooter />
|
||||
</Fragment>
|
||||
`;
|
||||
123
src/containers/ListView/__snapshots__/index.test.jsx.snap
Normal file
123
src/containers/ListView/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,123 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ListView component component render tests snapshots snapshot: empty (no list data) 1`] = `""`;
|
||||
|
||||
exports[`ListView component component render tests snapshots snapshot: happy path 1`] = `
|
||||
<Container
|
||||
className="py-4"
|
||||
>
|
||||
<ListViewBreadcrumb />
|
||||
<DataTable
|
||||
bulkActions={
|
||||
Array [
|
||||
[MockFunction this.selectedBulkAction],
|
||||
]
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Username",
|
||||
"accessor": "username",
|
||||
},
|
||||
Object {
|
||||
"Cell": [MockFunction this.formatDate],
|
||||
"Header": "Learner submission date",
|
||||
"accessor": "dateSubmitted",
|
||||
"disableFilters": true,
|
||||
},
|
||||
Object {
|
||||
"Cell": [MockFunction this.formatGrade],
|
||||
"Header": "Grade",
|
||||
"accessor": "score",
|
||||
"disableFilters": true,
|
||||
},
|
||||
Object {
|
||||
"Cell": [MockFunction this.formatStatus],
|
||||
"Filter": "MultiSelectDropdownFilter",
|
||||
"Header": "Grading Status",
|
||||
"accessor": "gradingStatus",
|
||||
"filter": "includesValue",
|
||||
"filterChoices": Array [
|
||||
Object {
|
||||
"name": "Ungraded",
|
||||
"value": "ungraded",
|
||||
},
|
||||
Object {
|
||||
"name": "Currently being graded by someone else",
|
||||
"value": "locked",
|
||||
},
|
||||
Object {
|
||||
"name": "Grading Complete",
|
||||
"value": "graded",
|
||||
},
|
||||
Object {
|
||||
"name": "You are currently grading this response",
|
||||
"value": "in-progress",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"dateSubmitted": 16131215154955,
|
||||
"grade": Object {
|
||||
"pointsEarned": 1,
|
||||
"pointsPossible": 10,
|
||||
},
|
||||
"gradingStatus": "ungraded",
|
||||
"username": "username-1",
|
||||
},
|
||||
Object {
|
||||
"dateSubmitted": 16131225154955,
|
||||
"grade": Object {
|
||||
"pointsEarned": 2,
|
||||
"pointsPossible": 10,
|
||||
},
|
||||
"gradingStatus": "graded",
|
||||
"username": "username-2",
|
||||
},
|
||||
Object {
|
||||
"dateSubmitted": 16131215250955,
|
||||
"grade": Object {
|
||||
"pointsEarned": 3,
|
||||
"pointsPossible": 10,
|
||||
},
|
||||
"gradingStatus": "in-progress",
|
||||
"username": "username-3",
|
||||
},
|
||||
]
|
||||
}
|
||||
defaultColumnValues={
|
||||
Object {
|
||||
"Filter": "TextFilter",
|
||||
}
|
||||
}
|
||||
initialState={
|
||||
Object {
|
||||
"pageIndex": 0,
|
||||
"pageSize": 10,
|
||||
}
|
||||
}
|
||||
isFilterable={true}
|
||||
isPaginated={true}
|
||||
isSelectable={true}
|
||||
isSortable={true}
|
||||
itemCount={3}
|
||||
numBreakoutFilters={2}
|
||||
tableActions={
|
||||
Array [
|
||||
Object {
|
||||
"buttonText": "View all responses",
|
||||
"handleClick": [MockFunction this.handleViewAllResponsesClick],
|
||||
"variant": "primary",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<TableControls />
|
||||
</DataTable>
|
||||
<ReviewModal />
|
||||
</Container>
|
||||
`;
|
||||
@@ -19,9 +19,10 @@ import thunkActions from 'data/thunkActions';
|
||||
import StatusBadge from 'components/StatusBadge';
|
||||
import ReviewModal from 'containers/ReviewModal';
|
||||
import ListViewBreadcrumb from './ListViewBreadcrumb';
|
||||
import TableControls from './TableControls';
|
||||
import './ListView.scss';
|
||||
|
||||
const gradeStatusOptions = Object.keys(gradingStatusDisplay).map(key => ({
|
||||
export const gradeStatusOptions = Object.keys(gradingStatusDisplay).map(key => ({
|
||||
name: gradingStatusDisplay[key],
|
||||
value: key,
|
||||
}));
|
||||
@@ -118,10 +119,7 @@ export class ListView extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<DataTable.TableControlBar />
|
||||
<DataTable.Table />
|
||||
<DataTable.EmptyTable content="No results found" />
|
||||
<DataTable.TableFooter />
|
||||
<TableControls />
|
||||
</DataTable>
|
||||
<ReviewModal />
|
||||
</Container>
|
||||
|
||||
256
src/containers/ListView/index.test.jsx
Normal file
256
src/containers/ListView/index.test.jsx
Normal file
@@ -0,0 +1,256 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import {
|
||||
DataTable,
|
||||
MultiSelectDropdownFilter,
|
||||
TextFilter,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import thunkActions from 'data/thunkActions';
|
||||
import { gradingStatuses as statuses } from 'data/services/lms/constants';
|
||||
|
||||
import StatusBadge from 'components/StatusBadge';
|
||||
import {
|
||||
ListView,
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
gradeStatusOptions,
|
||||
} from '.';
|
||||
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
DataTable: () => 'DataTable',
|
||||
TextFilter: 'TextFilter',
|
||||
MultiSelectDropdownFilter: 'MultiSelectDropdownFilter',
|
||||
Container: () => 'Container',
|
||||
}));
|
||||
jest.mock('components/StatusBadge', () => 'StatusBadge');
|
||||
jest.mock('containers/ReviewModal', () => 'ReviewModal');
|
||||
jest.mock('./ListViewBreadcrumb', () => 'ListViewBreadcrumb');
|
||||
jest.mock('./TableControls', () => 'TableControls');
|
||||
|
||||
jest.mock('data/selectors', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
submissions: {
|
||||
listData: (...args) => ({ listData: args }),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
let el;
|
||||
jest.useFakeTimers('modern');
|
||||
|
||||
describe('ListView component', () => {
|
||||
describe('component', () => {
|
||||
const props = {
|
||||
listData: [
|
||||
{
|
||||
username: 'username-1',
|
||||
dateSubmitted: 16131215154955,
|
||||
gradingStatus: statuses.ungraded,
|
||||
grade: {
|
||||
pointsEarned: 1,
|
||||
pointsPossible: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
username: 'username-2',
|
||||
dateSubmitted: 16131225154955,
|
||||
gradingStatus: statuses.graded,
|
||||
grade: {
|
||||
pointsEarned: 2,
|
||||
pointsPossible: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
username: 'username-3',
|
||||
dateSubmitted: 16131215250955,
|
||||
gradingStatus: statuses.inProgress,
|
||||
grade: {
|
||||
pointsEarned: 3,
|
||||
pointsPossible: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
beforeEach(() => {
|
||||
props.initializeApp = jest.fn();
|
||||
props.loadSelectionForReview = jest.fn();
|
||||
});
|
||||
describe('render tests', () => {
|
||||
const mockMethod = (methodName) => {
|
||||
el.instance()[methodName] = jest.fn().mockName(`this.${methodName}`);
|
||||
};
|
||||
beforeEach(() => {
|
||||
el = shallow(<ListView {...props} />);
|
||||
});
|
||||
describe('snapshots', () => {
|
||||
beforeEach(() => {
|
||||
mockMethod('handleViewAllResponsesClick');
|
||||
mockMethod('selectedBulkAction');
|
||||
mockMethod('formatDate');
|
||||
mockMethod('formatGrade');
|
||||
mockMethod('formatStatus');
|
||||
});
|
||||
test('snapshot: empty (no list data)', () => {
|
||||
el = shallow(<ListView {...props} listData={[]} />);
|
||||
expect(el).toMatchSnapshot();
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
test('snapshot: happy path', () => {
|
||||
expect(el.instance().render()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('DataTable', () => {
|
||||
let table;
|
||||
let tableProps;
|
||||
beforeEach(() => {
|
||||
table = el.find(DataTable);
|
||||
tableProps = table.props();
|
||||
});
|
||||
test.each([
|
||||
'isFilterable',
|
||||
'isSelectable',
|
||||
'isSortable',
|
||||
'isPaginated',
|
||||
])('%s', key => expect(tableProps[key]).toEqual(true));
|
||||
test.each([
|
||||
['numBreakoutFilters', 2],
|
||||
['defaultColumnValues', { Filter: TextFilter }],
|
||||
['itemCount', 3],
|
||||
['initialState', { pageSize: 10, pageIndex: 0 }],
|
||||
])('%s = %p', (key, value) => expect(tableProps[key]).toEqual(value));
|
||||
test('bulkActions linked to selectedBulkAction', () => {
|
||||
expect(tableProps.bulkActions).toEqual([el.instance().selectedBulkAction]);
|
||||
});
|
||||
describe('columns', () => {
|
||||
let columns;
|
||||
beforeEach(() => {
|
||||
columns = tableProps.columns;
|
||||
});
|
||||
test('username column', () => {
|
||||
expect(columns[0]).toEqual({
|
||||
Header: 'Username',
|
||||
accessor: 'username',
|
||||
});
|
||||
});
|
||||
test('submission date column', () => {
|
||||
expect(columns[1]).toEqual({
|
||||
Header: 'Learner submission date',
|
||||
accessor: 'dateSubmitted',
|
||||
Cell: el.instance().formatDate,
|
||||
disableFilters: true,
|
||||
});
|
||||
});
|
||||
test('grade column', () => {
|
||||
expect(columns[2]).toEqual({
|
||||
Header: 'Grade',
|
||||
accessor: 'score',
|
||||
Cell: el.instance().formatGrade,
|
||||
disableFilters: true,
|
||||
});
|
||||
});
|
||||
test('grading status column', () => {
|
||||
expect(columns[3]).toEqual({
|
||||
Header: 'Grading Status',
|
||||
accessor: 'gradingStatus',
|
||||
Cell: el.instance().formatStatus,
|
||||
Filter: MultiSelectDropdownFilter,
|
||||
filter: 'includesValue',
|
||||
filterChoices: gradeStatusOptions,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('behavior', () => {
|
||||
describe('formatDate method', () => {
|
||||
it('returns the date in locale time string', () => {
|
||||
const fakeDate = 16131215154955;
|
||||
const fakeDateString = 'test-date-string';
|
||||
const mock = jest.spyOn(Date.prototype, 'toLocaleString').mockReturnValue(fakeDateString);
|
||||
expect(el.instance().formatDate({ value: fakeDate })).toEqual(fakeDateString);
|
||||
mock.mockRestore();
|
||||
});
|
||||
});
|
||||
describe('formatGrade method', () => {
|
||||
it('returns "-" if grade is null', () => {
|
||||
expect(el.instance().formatGrade({ value: null })).toEqual('-');
|
||||
});
|
||||
it('returns <pointsEarned>/<pointsPossible> if grade exists', () => {
|
||||
expect(
|
||||
el.instance().formatGrade({ value: { pointsEarned: 1, pointsPossible: 10 } }),
|
||||
).toEqual('1/10');
|
||||
});
|
||||
});
|
||||
describe('formatStatus method', () => {
|
||||
it('returns a StatusBadge with the given status', () => {
|
||||
const status = 'graded';
|
||||
expect(el.instance().formatStatus({ value: 'graded' })).toEqual(
|
||||
<StatusBadge status={status} />,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('handleViewAllResponsesClick', () => {
|
||||
it('calls loadSelectionForReview with submissionId from all rows if there are no selectedRows', () => {
|
||||
const data = {
|
||||
selectedRows: [
|
||||
],
|
||||
tableInstance: {
|
||||
rows: [
|
||||
{ original: { submissionId: '123' } },
|
||||
{ original: { submissionId: '456' } },
|
||||
{ original: { submissionId: '789' } },
|
||||
],
|
||||
},
|
||||
};
|
||||
el.instance().handleViewAllResponsesClick(data);
|
||||
expect(el.instance().props.loadSelectionForReview).toHaveBeenCalledWith(['123', '456', '789']);
|
||||
});
|
||||
it('calls loadSelectionForReview with submissionId from selected rows if there are any', () => {
|
||||
const data = {
|
||||
selectedRows: [
|
||||
{ original: { submissionId: '123' } },
|
||||
{ original: { submissionId: '456' } },
|
||||
{ original: { submissionId: '789' } },
|
||||
],
|
||||
};
|
||||
el.instance().handleViewAllResponsesClick(data);
|
||||
expect(
|
||||
el.instance().props.loadSelectionForReview,
|
||||
).toHaveBeenCalledWith(['123', '456', '789']);
|
||||
});
|
||||
});
|
||||
describe('selectedBulkAction', () => {
|
||||
it('includes selection length and triggers handleViewAllResponsesClick', () => {
|
||||
const rows = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
const action = el.instance().selectedBulkAction(rows);
|
||||
expect(action.buttonText.includes(rows.length)).toEqual(true);
|
||||
expect(action.handleClick).toEqual(el.instance().handleViewAllResponsesClick);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('mapStateToProps', () => {
|
||||
let mapped;
|
||||
const testState = { some: 'test-state' };
|
||||
beforeEach(() => {
|
||||
mapped = mapStateToProps(testState);
|
||||
});
|
||||
test('listData loads from submissions.listData', () => {
|
||||
expect(mapped.listData).toEqual(selectors.submissions.listData(testState));
|
||||
});
|
||||
});
|
||||
describe('mapDispatchToProps', () => {
|
||||
it('loads initializeApp from thunkActions.app.initialize', () => {
|
||||
expect(mapDispatchToProps.initializeApp).toEqual(thunkActions.app.initialize);
|
||||
});
|
||||
it('loads loadSelectionForReview from thunkActions.grading.loadSelectionForReview', () => {
|
||||
expect(
|
||||
mapDispatchToProps.loadSelectionForReview,
|
||||
).toEqual(thunkActions.grading.loadSelectionForReview);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { feedbackRequirement } from 'data/services/lms/constants';
|
||||
|
||||
// import * in order to mock in-file references
|
||||
|
||||
@@ -4,8 +4,17 @@ import { configuration } from 'config';
|
||||
const baseUrl = `${configuration.LMS_BASE_URL}`;
|
||||
|
||||
const api = `${baseUrl}/api/`;
|
||||
const course = (courseId) => `${baseUrl}/courses/${courseId}`;
|
||||
|
||||
const openResponse = (courseId) => (
|
||||
`${course(courseId)}/instructor#view-open_response_assessment`
|
||||
);
|
||||
const ora = (courseId, locationId) => `${course(courseId)}/jump_to/${locationId}`;
|
||||
|
||||
export default StrictDict({
|
||||
api,
|
||||
baseUrl,
|
||||
course,
|
||||
openResponse,
|
||||
ora,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user