Skip to content

Commit 6fdc49b

Browse files
authored
Merge pull request #219 from nrotstan/notify-if-virtual-challenge-expired
Notify user if virtual challenge has expired.
2 parents 68d29f9 + ed5fe46 commit 6fdc49b

File tree

8 files changed

+64
-9
lines changed

8 files changed

+64
-9
lines changed

src/components/HOCs/WithVirtualChallenge/WithVirtualChallenge.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import _isFinite from 'lodash/isFinite'
66
import _debounce from 'lodash/debounce'
77
import { fetchVirtualChallenge }
88
from '../../../services/VirtualChallenge/VirtualChallenge'
9+
import { addError } from '../../../services/Error/Error'
10+
import AppErrors from '../../../services/Error/AppErrors'
911

1012
const FRESHNESS_THRESHOLD = 5000 // 5 seconds
1113

@@ -64,10 +66,17 @@ export const mapDispatchToProps = (dispatch, ownProps) => {
6466
*
6567
* @private
6668
*/
67-
loadVirtualChallenge: _debounce(
68-
virtualChallengeId => dispatch(fetchVirtualChallenge(virtualChallengeId)),
69-
FRESHNESS_THRESHOLD
70-
),
69+
loadVirtualChallenge: _debounce(virtualChallengeId => {
70+
return dispatch(
71+
fetchVirtualChallenge(virtualChallengeId)
72+
).then(results => {
73+
if (!results.result ||
74+
results.entities.virtualChallenges[results.result].expiry < Date.now()) {
75+
dispatch(addError(AppErrors.virtualChallenge.expired))
76+
ownProps.history.push('/')
77+
}
78+
})
79+
}, FRESHNESS_THRESHOLD),
7180
}
7281
}
7382

src/components/TaskPane/TaskPane.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ export class TaskPane extends Component {
6060

6161
render() {
6262
if (!_isFinite(_get(this.props, 'task.id'))) {
63-
return <BusySpinner />
63+
return (
64+
<div className="pane-loading full-screen-height">
65+
<BusySpinner />
66+
</div>
67+
)
6468
}
6569

6670
return (

src/lang/en-US.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@
312312
"Errors.challenge.rebuildFailure": "Unable to rebuild challenge tasks",
313313
"Errors.virtualChallenge.fetchFailure": "Unable to retrieve latest virtual challenge data from server.",
314314
"Errors.virtualChallenge.createFailure": "Unable to create a virtual challenge{details}",
315+
"Errors.virtualChallenge.expired": "Virtual challenge has expired.",
315316
"Errors.project.saveFailure": "Unable to save your changes{details}",
316317
"Errors.project.fetchFailure": "Unable to retrieve latest project data from server.",
317318
"Errors.project.searchFailure": "Unable to search projects.",

src/services/Error/AppErrors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default {
4040
virtualChallenge: {
4141
fetchFailure: messages.virtualChallengeFetchFailure,
4242
createFailure: messages.virtualChallengeCreateFailure,
43+
expired: messages.virtualChallengeExpired,
4344
},
4445

4546
project: {

src/services/Error/Messages.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export default defineMessages({
7979
id: 'Errors.virtualChallenge.createFailure',
8080
defaultMessage: "Unable to create a virtual challenge{details}",
8181
},
82+
virtualChallengeExpired: {
83+
id: 'Errors.virtualChallenge.expired',
84+
defaultMessage: "Virtual challenge has expired.",
85+
},
8286

8387
projectSaveFailure: {
8488
id: 'Errors.project.saveFailure',

src/services/Server/GenericEntityReducer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ const entities = function(state = {}, action, entityName, reduceFurther) {
6868
const timestamp = Date.now()
6969

7070
_forOwn(action.entities[entityName], (entity, entityId) => {
71+
if (typeof entityId === 'undefined') {
72+
return
73+
}
74+
7175
// Add a _meta object to each entity where we can store some application
7276
// meta data about the entity. Right now we just store a timestamp of
7377
// when the data was fetched so that we can measure the freshness of the

src/services/VirtualChallenge/VirtualChallenge.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { schema } from 'normalizr'
22
import _get from 'lodash/get'
33
import _isFinite from 'lodash/isFinite'
4+
import addHours from 'date-fns/add_hours'
45
import { defaultRoutes as api } from '../Server/Server'
56
import Endpoint from '../Server/Endpoint'
67
import RequestStatus from '../Server/RequestStatus'
@@ -43,13 +44,12 @@ export const fetchVirtualChallenge = function(virtualChallengeId) {
4344
api.virtualChallenge.single,
4445
{schema: virtualChallengeSchema(), variables: {id: virtualChallengeId}}
4546
).execute().then(normalizedResults => {
46-
// Mark that the challenge is virtual.
4747
if (_isFinite(normalizedResults.result)) {
48+
// Mark that the challenge is virtual.
4849
normalizedResults.entities.virtualChallenges[normalizedResults.result].isVirtual = true
4950
}
5051

5152
dispatch(receiveVirtualChallenges(normalizedResults.entities))
52-
5353
return normalizedResults
5454
}).catch((error) => {
5555
dispatch(addError(AppErrors.virtualChallenge.fetchFailure))
@@ -61,11 +61,12 @@ export const fetchVirtualChallenge = function(virtualChallengeId) {
6161
/**
6262
* Creates a new virtual challenge with the given name and tasks.
6363
*/
64-
export const createVirtualChallenge = function(name, taskIds) {
64+
export const createVirtualChallenge = function(name, taskIds, expiration) {
6565
return function(dispatch) {
6666
const challengeData = {
6767
name,
6868
taskIdList: taskIds,
69+
expiry: expiration ? expiration : addHours(new Date(), 36).getTime()
6970
}
7071

7172
return new Endpoint(api.virtualChallenge.create, {
@@ -92,6 +93,27 @@ export const createVirtualChallenge = function(name, taskIds) {
9293
}
9394

9495
// redux reducers
96+
//
97+
/**
98+
* reduceVirtualChallengesFurther will be invoked by the genericEntityReducer function to
99+
* perform additional reduction on virtualChallenge entities.
100+
*
101+
* @private
102+
*/
103+
const reduceVirtualChallengesFurther = function(mergedState,
104+
oldState,
105+
virtualChallengeEntities) {
106+
const now = Date.now()
107+
virtualChallengeEntities.forEach(entity => {
108+
// Ignore deleted and expired virtual challenges
109+
if (entity.deleted || entity.expired < now) {
110+
delete mergedState[entity.id]
111+
return
112+
}
113+
})
114+
}
95115

96116
export const virtualChallengeEntities =
97-
genericEntityReducer([RECEIVE_VIRTUAL_CHALLENGES], 'virtualChallenges')
117+
genericEntityReducer([RECEIVE_VIRTUAL_CHALLENGES],
118+
'virtualChallenges',
119+
reduceVirtualChallengesFurther)

src/theme.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,16 @@ code {
303303
background-color: $white;
304304
}
305305

306+
.pane-loading {
307+
display: flex;
308+
align-items: center;
309+
font-size: $size-1;
310+
.busy-spinner {
311+
@include busy-spinner-color($primary);
312+
flex: auto;
313+
}
314+
}
315+
306316
// Add a short-and-wide variant of the rounded Bulma switch
307317
.switch[type="checkbox"].is-rounded.short-and-wide + label::before {
308318
width: 60px;

0 commit comments

Comments
 (0)