Skip to content

Commit 97268b8

Browse files
ning-yremo5000
authored andcommitted
Fetch assessments (#204)
* Abstract functions making backend calls * Add property attempted to AssessmentOverviews * Fetch and update assessmentOverviews on login * Replace mock calls in ContentDisplay * Replace mock calls in update assessment * Add mockBackendSaga for dev * Use .env variable as flag instead of NODE_ENV * Move apiFetchSaga actions to mockBackendSaga - apiFetchSaga is removed - deprecated some mock userAPI enums
1 parent 30daca9 commit 97268b8

File tree

8 files changed

+121
-59
lines changed

8 files changed

+121
-59
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
REACT_APP_IVLE_KEY=your_ivle_key_here
22
REACT_APP_VERSION=$npm_package_version
33
REACT_APP_BACKEND_URL=http://something.com
4+
REACT_APP_USE_BACKEND=

src/components/assessment/assessmentShape.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Used to display information regarding an assessment in the UI.
33
*/
44
export interface IAssessmentOverview {
5+
attempted: boolean
56
category: AssessmentCategory
67
closeAt: string
78
coverImage: string

src/mocks/assessmentAPI.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99

1010
const mockOpenAssessmentsOverviews: IAssessmentOverview[] = [
1111
{
12+
attempted: true,
1213
category: AssessmentCategories.Mission,
1314
closeAt: '2048-06-18T05:24:26.026Z',
1415
coverImage: 'www.imgur.com',
@@ -20,6 +21,7 @@ const mockOpenAssessmentsOverviews: IAssessmentOverview[] = [
2021
title: 'An Odessey to Runes'
2122
},
2223
{
24+
attempted: false,
2325
category: AssessmentCategories.Mission,
2426
closeAt: '2048-06-18T05:24:26.026Z',
2527
coverImage: 'www.imgur.com',
@@ -31,6 +33,7 @@ const mockOpenAssessmentsOverviews: IAssessmentOverview[] = [
3133
title: 'The Secret to Streams'
3234
},
3335
{
36+
attempted: true,
3437
category: AssessmentCategories.Sidequest,
3538
closeAt: '2048-06-18T05:24:26.026Z',
3639
coverImage: 'www.imgur.com',
@@ -45,6 +48,7 @@ const mockOpenAssessmentsOverviews: IAssessmentOverview[] = [
4548

4649
const mockClosedAssessmentOverviews: IAssessmentOverview[] = [
4750
{
51+
attempted: true,
4852
category: AssessmentCategories.Mission,
4953
closeAt: '2008-06-18T05:24:26.026Z',
5054
coverImage: 'www.imgur.com',
@@ -56,6 +60,7 @@ const mockClosedAssessmentOverviews: IAssessmentOverview[] = [
5660
title: 'A closed Mission'
5761
},
5862
{
63+
attempted: false,
5964
category: AssessmentCategories.Sidequest,
6065
closeAt: '2008-06-18T05:24:26.026Z',
6166
coverImage: 'www.imgur.com',

src/mocks/backend.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { delay, SagaIterator } from 'redux-saga'
2+
import { call, put, select, takeEvery } from 'redux-saga/effects'
3+
4+
import * as actions from '../actions'
5+
import * as actionTypes from '../actions/actionTypes'
6+
import { IState } from '../reducers/states'
7+
import { history } from '../utils/history'
8+
import { mockAssessmentOverviews, mockAssessments } from './assessmentAPI'
9+
import { mockFetchGrading, mockFetchGradingOverview } from './gradingAPI'
10+
11+
export function* mockBackendSaga(): SagaIterator {
12+
yield takeEvery(actionTypes.FETCH_AUTH, function*(action) {
13+
const tokens = {
14+
accessToken: 'accessToken',
15+
refreshToken: 'refreshToken'
16+
}
17+
const user = {
18+
name: 'DevStaff',
19+
role: 'staff'
20+
}
21+
yield put(actions.setTokens(tokens))
22+
yield put(actions.setRole(user.role))
23+
yield put(actions.setUsername(user.name))
24+
yield delay(2000)
25+
yield history.push('/academy')
26+
})
27+
28+
yield takeEvery(actionTypes.FETCH_ASSESSMENT_OVERVIEWS, function*() {
29+
yield put(actions.updateAssessmentOverviews(mockAssessmentOverviews))
30+
})
31+
32+
yield takeEvery(actionTypes.FETCH_ASSESSMENT, function*(action) {
33+
const id = (action as actionTypes.IAction).payload
34+
const assessment = mockAssessments[id]
35+
yield put(actions.updateAssessment(assessment))
36+
})
37+
38+
yield takeEvery(actionTypes.FETCH_GRADING_OVERVIEWS, function*() {
39+
const accessToken = yield select((state: IState) => state.session.accessToken)
40+
const gradingOverviews = yield call(() => mockFetchGradingOverview(accessToken))
41+
if (gradingOverviews !== null) {
42+
yield put(actions.updateGradingOverviews(gradingOverviews))
43+
}
44+
})
45+
46+
yield takeEvery(actionTypes.FETCH_GRADING, function*(action) {
47+
const submissionId = (action as actionTypes.IAction).payload
48+
const accessToken = yield select((state: IState) => state.session.accessToken)
49+
const grading = yield call(() => mockFetchGrading(accessToken, submissionId))
50+
if (grading !== null) {
51+
yield put(actions.updateGrading(submissionId, grading))
52+
}
53+
})
54+
}

src/mocks/userAPI.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* Deprecated, check backend for roles
3+
*/
14
export enum Roles {
25
student = 'student',
36
trainer = 'trainer',

src/sagas/backend.ts

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,85 @@
11
import { delay, SagaIterator } from 'redux-saga'
2-
import { call, put, takeEvery } from 'redux-saga/effects'
2+
import { call, put, select, takeEvery } from 'redux-saga/effects'
33

44
import * as actions from '../actions'
55
import * as actionTypes from '../actions/actionTypes'
6+
import { IAssessmentOverview } from '../components/assessment/assessmentShape'
7+
import { IState } from '../reducers/states'
68
import { BACKEND_URL } from '../utils/constants'
79
import { history } from '../utils/history'
810

911
function* backendSaga(): SagaIterator {
1012
yield takeEvery(actionTypes.FETCH_AUTH, function*(action) {
1113
const ivleToken = (action as actionTypes.IAction).payload
12-
const resp = yield call(request, 'auth', {
13-
method: 'POST',
14-
body: JSON.stringify({ login: { ivle_token: ivleToken } }),
15-
headers: new Headers({
16-
Accept: 'application/json',
17-
'Content-Type': 'application/json'
18-
})
19-
})
14+
const resp = yield call(callAuth, ivleToken)
2015
const tokens = {
2116
accessToken: resp.access_token,
2217
refreshToken: resp.refresh_token
2318
}
24-
const user = yield getUser(tokens.accessToken)
19+
const user = yield call(authorizedGet, 'user', tokens.accessToken)
2520
yield put(actions.setTokens(tokens))
2621
yield put(actions.setRole(user.role))
2722
yield put(actions.setUsername(user.name))
2823
yield delay(2000)
2924
yield history.push('/academy')
3025
})
26+
27+
yield takeEvery(actionTypes.FETCH_ASSESSMENT_OVERVIEWS, function*() {
28+
const accessToken = yield select((state: IState) => state.session.accessToken)
29+
const assessmentOverviews = yield call(callAssessments, accessToken)
30+
yield put(actions.updateAssessmentOverviews(assessmentOverviews))
31+
})
32+
33+
yield takeEvery(actionTypes.FETCH_ASSESSMENT, function*(action) {
34+
const accessToken = yield select((state: IState) => state.session.accessToken)
35+
const id = (action as actionTypes.IAction).payload
36+
const assessment = yield call(callAssessmentsId, id, accessToken)
37+
yield put(actions.updateAssessment(assessment))
38+
})
3139
}
3240

33-
function* getUser(accessToken: string) {
34-
const resp = yield call(request, 'user', {
41+
const callAuth = (ivleToken: string) =>
42+
request('auth', {
43+
method: 'POST',
44+
body: JSON.stringify({ login: { ivle_token: ivleToken } }),
45+
headers: new Headers({
46+
Accept: 'application/json',
47+
'Content-Type': 'application/json'
48+
})
49+
})
50+
51+
const callAssessments = async (accessToken: string) => {
52+
const assessmentOverviews: any = await authorizedGet('assessments', accessToken)
53+
return assessmentOverviews.map((overview: any) => {
54+
// backend has property -> type: 'mission' | 'sidequest' | 'path' | 'contest'
55+
// we have -> category: 'Mission' | 'Sidequest' | 'Path' | 'Contest'
56+
overview.category = capitalise(overview.type)
57+
delete overview.type
58+
return overview as IAssessmentOverview
59+
})
60+
}
61+
62+
const callAssessmentsId = async (id: number, accessToken: string) => {
63+
const assessment: any = await authorizedGet(`assessments/${id}`, accessToken)
64+
assessment.category = capitalise(assessment.type)
65+
delete assessment.type
66+
return assessment
67+
}
68+
69+
const authorizedGet = (path: string, accessToken: string) =>
70+
request(path, {
3571
method: 'GET',
3672
headers: new Headers({
3773
Authorization: `Bearer ${accessToken}`,
3874
Accept: 'application/json'
3975
})
4076
})
41-
return resp
42-
}
4377

44-
function request(path: string, opts: {}) {
45-
return fetch(`${BACKEND_URL}/v1/${path}`, opts)
78+
const request = (path: string, opts: {}) =>
79+
fetch(`${BACKEND_URL}/v1/${path}`, opts)
4680
.then(data => data.json())
47-
.catch(err => err)
48-
}
81+
.catch(_ => null)
82+
83+
const capitalise = (text: string) => text.charAt(0).toUpperCase() + text.slice(1)
4984

5085
export default backendSaga

src/sagas/index.ts

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,57 +8,19 @@ import { call, put, race, select, take, takeEvery } from 'redux-saga/effects'
88
import * as actions from '../actions'
99
import * as actionTypes from '../actions/actionTypes'
1010
import { WorkspaceLocation } from '../actions/workspaces'
11-
import { mockAssessmentOverviews, mockAssessments } from '../mocks/assessmentAPI'
12-
import { mockFetchGrading, mockFetchGradingOverview } from '../mocks/gradingAPI'
11+
import { mockBackendSaga } from '../mocks/backend'
1312
import { defaultEditorValue, externalLibraries, IState } from '../reducers/states'
14-
import { IVLE_KEY } from '../utils/constants'
13+
import { IVLE_KEY, USE_BACKEND } from '../utils/constants'
1514
import { showSuccessMessage, showWarningMessage } from '../utils/notification'
1615
import backendSaga from './backend'
1716

1817
function* mainSaga() {
19-
yield* apiFetchSaga()
20-
yield* backendSaga()
18+
yield* USE_BACKEND ? backendSaga() : mockBackendSaga()
2119
yield* workspaceSaga()
2220
yield* loginSaga()
2321
yield* playgroundSaga()
2422
}
2523

26-
function* apiFetchSaga(): SagaIterator {
27-
yield takeEvery(actionTypes.FETCH_ASSESSMENT_OVERVIEWS, function*() {
28-
const newContent = mockAssessmentOverviews
29-
const oldContent = yield select((state: IState) => state.session.assessmentOverviews)
30-
if (newContent !== oldContent) {
31-
yield put(actions.updateAssessmentOverviews(newContent))
32-
}
33-
})
34-
35-
yield takeEvery(actionTypes.FETCH_ASSESSMENT, function*(action) {
36-
const id = (action as actionTypes.IAction).payload
37-
const newContent = mockAssessments[id]
38-
const oldContent = yield select((state: IState) => state.session.assessments[id])
39-
if (newContent !== oldContent) {
40-
yield put(actions.updateAssessment(newContent))
41-
}
42-
})
43-
44-
yield takeEvery(actionTypes.FETCH_GRADING_OVERVIEWS, function*() {
45-
const accessToken = yield select((state: IState) => state.session.accessToken)
46-
const gradingOverviews = yield call(() => mockFetchGradingOverview(accessToken))
47-
if (gradingOverviews !== null) {
48-
yield put(actions.updateGradingOverviews(gradingOverviews))
49-
}
50-
})
51-
52-
yield takeEvery(actionTypes.FETCH_GRADING, function*(action) {
53-
const submissionId = (action as actionTypes.IAction).payload
54-
const accessToken = yield select((state: IState) => state.session.accessToken)
55-
const grading = yield call(() => mockFetchGrading(accessToken, submissionId))
56-
if (grading !== null) {
57-
yield put(actions.updateGrading(submissionId, grading))
58-
}
59-
})
60-
}
61-
6224
function* workspaceSaga(): SagaIterator {
6325
let context: Context
6426

src/utils/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dotenv.config()
55
export const IVLE_KEY = process.env.REACT_APP_IVLE_KEY
66
export const VERSION = process.env.REACT_APP_VERSION
77
export const BACKEND_URL = process.env.REACT_APP_BACKEND_URL
8+
export const USE_BACKEND = process.env.REACT_APP_USE_BACKEND

0 commit comments

Comments
 (0)