@instructure/quiz-core 20.17.3-rc.9 → 20.18.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/es/banks/api/bankEntries.js +45 -1
  3. package/es/banks/components/AddToBankModal/index.js +2 -1
  4. package/es/banks/components/AddToBankModal/presenter.js +13 -2
  5. package/es/banks/components/CopyMoveBankEntryModal/index.js +3 -1
  6. package/es/banks/components/CopyMoveBankEntryModal/presenter.js +26 -7
  7. package/es/building/components/resources/quizEntry/QuizEntryEdit/presenter.js +1 -0
  8. package/es/common/actions/taking.js +9 -1
  9. package/es/common/components/resources/stimulus/StimulusEdit/presenter.js +1 -0
  10. package/es/common/middleware/quizTimerWebSocketMiddleware.js +91 -0
  11. package/es/common/reducers/taking.js +8 -1
  12. package/es/common/util/TimerService.js +6 -1
  13. package/es/configureStore.js +4 -2
  14. package/es/index.js +8 -0
  15. package/es/taking/api/taking.js +18 -2
  16. package/lib/banks/api/bankEntries.js +57 -1
  17. package/lib/banks/components/AddToBankModal/index.js +1 -0
  18. package/lib/banks/components/AddToBankModal/presenter.js +14 -2
  19. package/lib/banks/components/CopyMoveBankEntryModal/index.js +2 -0
  20. package/lib/banks/components/CopyMoveBankEntryModal/presenter.js +27 -7
  21. package/lib/building/components/resources/quizEntry/QuizEntryEdit/presenter.js +1 -0
  22. package/lib/common/actions/taking.js +12 -1
  23. package/lib/common/components/resources/stimulus/StimulusEdit/presenter.js +1 -0
  24. package/lib/common/middleware/quizTimerWebSocketMiddleware.js +121 -0
  25. package/lib/common/reducers/taking.js +7 -0
  26. package/lib/common/util/TimerService.js +6 -1
  27. package/lib/configureStore.js +4 -1
  28. package/lib/taking/api/taking.js +20 -0
  29. package/package.json +10 -10
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [20.18.0](https://gerrit.instructure.com/quizzes-ui/compare/v20.17.2...v20.18.0) (2024-09-26)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **quizzes-ui:** Add data-automation tags for impact integration ([9b710b8](https://gerrit.instructure.com/quizzes-ui/commits/9b710b87929cc2d203de0f9f68e75a5e2b1a3150))
12
+
13
+
14
+ ### Features
15
+
16
+ * update remaining time in quiz session ([3a1a914](https://gerrit.instructure.com/quizzes-ui/commits/3a1a91424ba174b7f2a39cf2a8a4d669dae120ab))
17
+ * **quizzes-ui:** add 2-minute alert to timed quiz ([6bb83e9](https://gerrit.instructure.com/quizzes-ui/commits/6bb83e953ae989bc88975922051044bef9e4d7ce))
18
+ * **quizzes-ui:** add page_blurred/page_focused events ([2c6b692](https://gerrit.instructure.com/quizzes-ui/commits/2c6b6928b7a2f7926ab1757884ee63bcff6c4c37))
19
+ * add Cancel and Close button to ModerateTray ([e41ad88](https://gerrit.instructure.com/quizzes-ui/commits/e41ad88aaef2af576f53ff0e212c6d793db74bf4))
20
+ * refactor ingress for AccommodationModal and ModerateTray ([7c49355](https://gerrit.instructure.com/quizzes-ui/commits/7c49355a8b03cd20dabf480c2ba6c8c616a7eb65))
21
+
22
+
23
+
24
+
25
+
6
26
  ## [20.17.2](https://gerrit.instructure.com/quizzes-ui/compare/v20.17.1...v20.17.2) (2024-09-05)
7
27
 
8
28
 
@@ -9,7 +9,7 @@ import { archiveBankEntry } from "../../common/actions/bankEntries.js";
9
9
  import { getBank } from "./banks.js";
10
10
  import { handleError } from "../../common/api/helpers.js";
11
11
  import get from 'lodash/get';
12
- import { BANKS_BANK_ENTRY_PAGINATION, GET_BANK_ENTRIES_CALL, GET_BANK_ENTRY_CALL, UPDATE_BANK_ENTRY_CALL, CREATE_BANK_ENTRY_CALL, REMOVE_BANK_ENTRY_CALL } from '@instructure/quiz-common';
12
+ import { BANKS_BANK_ENTRY_PAGINATION, GET_BANK_ENTRIES_CALL, GET_BANK_ENTRY_CALL, UPDATE_BANK_ENTRY_CALL, CREATE_BANK_ENTRY_CALL, COPY_BANK_ENTRY_CALL, REMOVE_BANK_ENTRY_CALL } from '@instructure/quiz-common';
13
13
  import t from '@instructure/quiz-i18n/es/format-message';
14
14
  export var getBankEntries = function getBankEntries(bankId, useBankSearch) {
15
15
  var params = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
@@ -98,6 +98,50 @@ export var createEntryAndBankEntry = function createEntryAndBankEntry(bankId, en
98
98
  });
99
99
  };
100
100
  };
101
+ export var moveBankEntryFromQuizEntry = function moveBankEntryFromQuizEntry(sourceQuizId, sourceEntryId, sourceEntryType, destinationBankId) {
102
+ var body = {
103
+ source_entry_id: sourceEntryId,
104
+ source_entry_type: sourceEntryType
105
+ };
106
+ var query_params = {
107
+ source_quiz_id: sourceQuizId
108
+ };
109
+ return copyOrMoveBankEntry(body, destinationBankId, 'move_from_quiz_entry', query_params);
110
+ };
111
+ export var copyBankEntry = function copyBankEntry(sourceBankId, bankEntryId, destinationBankId) {
112
+ var body = {
113
+ source_bank_id: sourceBankId,
114
+ source_bank_entry_id: bankEntryId
115
+ };
116
+ return copyOrMoveBankEntry(body, destinationBankId, 'copy');
117
+ };
118
+ export var moveBankEntry = function moveBankEntry(sourceBankId, bankEntryId, destinationBankId) {
119
+ var body = {
120
+ source_bank_id: sourceBankId,
121
+ source_bank_entry_id: bankEntryId
122
+ };
123
+ return copyOrMoveBankEntry(body, destinationBankId, 'move');
124
+ };
125
+ export var copyOrMoveBankEntry = function copyOrMoveBankEntry(body, destinationBankId, operation, query_params) {
126
+ var url = "/api/banks/".concat(destinationBankId, "/bank_entries/").concat(operation);
127
+ return function (dispatch, getState) {
128
+ var fetcher = new Fetcher({
129
+ callType: COPY_BANK_ENTRY_CALL,
130
+ onError: handleError(dispatch, t('Failed to copy bank entry.'))
131
+ });
132
+
133
+ if (query_params) {
134
+ url = fetcher.appendQueryParams(url, query_params);
135
+ }
136
+
137
+ return fetcher.post(url, {
138
+ body: JSON.stringify(body)
139
+ }).then(function (response) {
140
+ dispatch([handleBankEntryResponse(response), associateEntries(destinationBankId, [response.id]), getBank(destinationBankId)]);
141
+ return response;
142
+ });
143
+ };
144
+ };
101
145
  export var createBankEntry = function createBankEntry(bankId, entryType, entryId) {
102
146
  var url = "/api/banks/".concat(bankId, "/bank_entries");
103
147
  return function (dispatch, getState) {
@@ -1,6 +1,6 @@
1
1
  import { connect } from "../../../common/react-redux.js";
2
2
  import { createBank } from "../../api/banks.js";
3
- import { createBankEntry } from "../../api/bankEntries.js";
3
+ import { createBankEntry, moveBankEntryFromQuizEntry } from "../../api/bankEntries.js";
4
4
  import { checkItemShareability } from "../../../building/api/items.js";
5
5
  import { checkStimulusShareability } from "../../../building/api/stimuli.js";
6
6
  import AddToBankModal from "./presenter.js";
@@ -27,6 +27,7 @@ var mapDispatchToProps = {
27
27
  closeModal: modalActions.closeModal,
28
28
  createBank: createBank,
29
29
  createBankEntry: createBankEntry,
30
+ moveBankEntryFromQuizEntry: moveBankEntryFromQuizEntry,
30
31
  disassociateBankEntry: disassociateEntry,
31
32
  uiSet: uiActions.set
32
33
  };
@@ -21,6 +21,7 @@ import { View } from '@instructure/ui-view';
21
21
  import { Flex } from '@instructure/ui-flex';
22
22
  import t from '@instructure/quiz-i18n/es/format-message';
23
23
  import { Modal, ModalHeader, ModalBody, ModalFooter, ADD_TO_BANK_MODAL_BANK_SELECTION, XSMALL_SIDE_MARGIN } from '@instructure/quiz-common';
24
+ import { featureOn } from "../../../common/util/featureCheck.js";
24
25
 
25
26
  var _ref2 = /*#__PURE__*/React.createElement(IconCheckLine, {
26
27
  color: "success"
@@ -60,9 +61,17 @@ export var AddToBankModal = /*#__PURE__*/function (_Component) {
60
61
  newBankTitle: ''
61
62
  };
62
63
 
64
+ _this.copyBankEntry = function (entryId, bankId) {
65
+ if (featureOn('instfs_bank_entry_movement_endpoints')) {
66
+ return _this.props.moveBankEntryFromQuizEntry(_this.props.quizId, entryId, _this.props.entryType, bankId);
67
+ } else {
68
+ return _this.props.createBankEntry(bankId, _this.props.entryType, entryId);
69
+ }
70
+ };
71
+
63
72
  _this.addToBank = function (bankId) {
64
73
  _this.props.getEntry().then(function (entry) {
65
- return _this.props.createBankEntry(bankId, _this.props.entryType, entry.id);
74
+ return _this.copyBankEntry(entry.id, bankId);
66
75
  }).then(_this.props.onAdd).catch(function (e) {
67
76
  if (e.validationErrorsFromApi) {
68
77
  // Close the modal so the user can view the input validation errors and correct them
@@ -301,6 +310,7 @@ AddToBankModal.propTypes = {
301
310
  checkContentShareability: PropTypes.func.isRequired,
302
311
  closeModal: PropTypes.func.isRequired,
303
312
  createBank: PropTypes.func.isRequired,
313
+ moveBankEntryFromQuizEntry: PropTypes.func.isRequired,
304
314
  createBankEntry: PropTypes.func.isRequired,
305
315
  // Create the bank entry's entry, or clone it if it already exists. Should
306
316
  // always be provided when a source is provided.
@@ -318,7 +328,8 @@ AddToBankModal.propTypes = {
318
328
  onContinue: PropTypes.func,
319
329
  selectedBank: ImmutablePropTypes.record,
320
330
  uiSet: PropTypes.func.isRequired,
321
- workingEntry: ImmutablePropTypes.record.isRequired
331
+ workingEntry: ImmutablePropTypes.record.isRequired,
332
+ quizId: PropTypes.string.isRequired
322
333
  };
323
334
  AddToBankModal.defaultProps = {
324
335
  canvasOrigin: '',
@@ -1,6 +1,6 @@
1
1
  import { connect } from "../../../common/react-redux.js";
2
2
  import { createBank } from "../../api/banks.js";
3
- import { createBankEntry, updateBankEntry } from "../../api/bankEntries.js";
3
+ import { createBankEntry, updateBankEntry, moveBankEntry, copyBankEntry } from "../../api/bankEntries.js";
4
4
  import CopyMoveBankModal from "./presenter.js";
5
5
  import * as modalActions from "../../../common/actions/modal.js";
6
6
  import { disassociateEntry } from "../../../common/actions/banks.js";
@@ -22,8 +22,10 @@ export function mapStateToProps(state, props) {
22
22
  var mapDispatchToProps = {
23
23
  closeModal: modalActions.closeModal,
24
24
  createBank: createBank,
25
+ copyBankEntry: copyBankEntry,
25
26
  createBankEntry: createBankEntry,
26
27
  updateBankEntry: updateBankEntry,
28
+ moveBankEntry: moveBankEntry,
27
29
  disassociateBankEntry: disassociateEntry,
28
30
  uiSet: uiActions.set
29
31
  };
@@ -15,6 +15,7 @@ import t from '@instructure/quiz-i18n/es/format-message';
15
15
  import Bank from "../../../common/records/Bank.js";
16
16
  import BankSelection from "../../../common/components/resources/BankSelection/index.js";
17
17
  import { Modal, ModalHeader, ModalBody, ModalFooter, COPY_MOVE_BANK_ENTRY_MODAL_BANK_SELECTION, COPY_MOVE_BANK_ENTRY_MODAL_KEEP_COPY, XSMALL_SIDE_MARGIN } from '@instructure/quiz-common';
18
+ import { featureOn } from "../../../common/util/featureCheck.js";
18
19
 
19
20
  var _ref = /*#__PURE__*/React.createElement("br", null);
20
21
 
@@ -65,20 +66,36 @@ export var CopyMoveBankEntryModal = /*#__PURE__*/function (_Component) {
65
66
  }
66
67
  };
67
68
 
69
+ _this.initiateMove = function (newBankId) {
70
+ if (featureOn('instfs_bank_entry_movement_endpoints')) {
71
+ return _this.props.moveBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId, newBankId);
72
+ } else {
73
+ return _this.props.updateBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId, {
74
+ bankId: newBankId
75
+ });
76
+ }
77
+ };
78
+
68
79
  _this.moveBankEntry = function (newBankId) {
69
- return _this.props.updateBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId, {
70
- bankId: newBankId
71
- }).then(function (bankEntry) {
80
+ return _this.initiateMove(newBankId).then(function (bankEntry) {
72
81
  _this.props.disassociateBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId);
73
82
 
74
83
  return bankEntry;
75
84
  }).then(_this.props.onAdd);
76
85
  };
77
86
 
87
+ _this.initiateCopy = function (newBankId) {
88
+ if (featureOn('instfs_bank_entry_movement_endpoints')) {
89
+ return _this.props.copyBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId, newBankId);
90
+ } else {
91
+ return _this.props.createEntry().then(function (entry) {
92
+ return _this.props.createBankEntry(newBankId, _this.props.entryType, entry.id);
93
+ });
94
+ }
95
+ };
96
+
78
97
  _this.copyBankEntry = function (newBankId) {
79
- return _this.props.createEntry().then(function (entry) {
80
- return _this.props.createBankEntry(newBankId, _this.props.entryType, entry.id);
81
- }).then(_this.props.onAdd);
98
+ return _this.initiateCopy(newBankId).then(_this.props.onAdd);
82
99
  };
83
100
 
84
101
  _this.closeAction = function () {
@@ -202,6 +219,7 @@ export var CopyMoveBankEntryModal = /*#__PURE__*/function (_Component) {
202
219
  CopyMoveBankEntryModal.propTypes = {
203
220
  closeModal: PropTypes.func.isRequired,
204
221
  createBank: PropTypes.func.isRequired,
222
+ copyBankEntry: PropTypes.func.isRequired,
205
223
  createBankEntry: PropTypes.func.isRequired,
206
224
  // Create the bank entry's entry, or clone it if it already exists. Should
207
225
  // always be provided when a source is provided.
@@ -221,7 +239,8 @@ CopyMoveBankEntryModal.propTypes = {
221
239
  sourceBankEntryId: PropTypes.string.isRequired,
222
240
  sourceBankId: PropTypes.string.isRequired,
223
241
  uiSet: PropTypes.func.isRequired,
224
- updateBankEntry: PropTypes.func.isRequired
242
+ updateBankEntry: PropTypes.func.isRequired,
243
+ moveBankEntry: PropTypes.func.isRequired
225
244
  };
226
245
  CopyMoveBankEntryModal.contextTypes = {
227
246
  locale: PropTypes.string
@@ -394,6 +394,7 @@ var QuizEntryEdit = (_dec = withStyle(generateStyle, generateComponentTheme), _d
394
394
  workingEntry: this.props.workingEntry,
395
395
  entryId: this.props.guid,
396
396
  entryType: this.props.quizEntry.entryType,
397
+ quizId: this.props.quizId,
397
398
  handleValidationErrorsFromApi: this.props.handleValidationErrorsFromApi
398
399
  });
399
400
  }
@@ -1,5 +1,5 @@
1
1
  import { v4 as uuid } from 'uuid';
2
- import { BACKTRACKING_NEXT_QUESTION, CLEAR_FOCUS_CURRENT_SESSION_ITEM, LOAD_RESUME_DATA, PREVIEW_RESPONSE, PREVIOUS_QUESTION, QUIZ_SUBMISSION_EVENT, UPDATE_QUIZ_SUBMIT_FLAG, RESPONSE, SESSION_STARTED_OR_RESUMED_EVENT, SET_CURRENT_SESSION_ITEM_POSITION, SET_FOCUS_TITLE_AND_INSTRUCTIONS, UPDATE_IS_TAKING, UPDATE_QUIZ_SESSION_STATUS_ON_PAGE_LOAD, UPDATE_TIMER_MINIMIZED, UPDATE_TIMER_INFO, PAGE_BLURRED_EVENT, PAGE_FOCUSED_EVENT } from '@instructure/quiz-common';
2
+ import { BACKTRACKING_NEXT_QUESTION, CLEAR_FOCUS_CURRENT_SESSION_ITEM, LOAD_RESUME_DATA, PREVIEW_RESPONSE, PREVIOUS_QUESTION, QUIZ_SUBMISSION_EVENT, UPDATE_QUIZ_SUBMIT_FLAG, RESPONSE, SESSION_STARTED_OR_RESUMED_EVENT, SET_CURRENT_SESSION_ITEM_POSITION, SET_FOCUS_TITLE_AND_INSTRUCTIONS, UPDATE_IS_TAKING, UPDATE_QUIZ_SESSION_STATUS_ON_PAGE_LOAD, UPDATE_TIMER_MINIMIZED, UPDATE_TIMER_INFO, SET_WS_TOKEN, PAGE_BLURRED_EVENT, PAGE_FOCUSED_EVENT } from '@instructure/quiz-common';
3
3
  export var setCurrentSessionItemPosition = function setCurrentSessionItemPosition(position) {
4
4
  return {
5
5
  type: SET_CURRENT_SESSION_ITEM_POSITION,
@@ -135,6 +135,14 @@ export var submitQuizSessionEvent = function submitQuizSessionEvent() {
135
135
  payload: {}
136
136
  };
137
137
  };
138
+ export var setWSToken = function setWSToken(wsToken) {
139
+ return {
140
+ type: SET_WS_TOKEN,
141
+ payload: {
142
+ wsToken: wsToken
143
+ }
144
+ };
145
+ };
138
146
  export var updateQuizSubmitFlag = function updateQuizSubmitFlag(value) {
139
147
  return {
140
148
  type: UPDATE_QUIZ_SUBMIT_FLAG,
@@ -382,6 +382,7 @@ export var StimulusEdit = (_dec = withStyle(generateStyle, generateComponentThem
382
382
  onContinue: this.handleAddToBankContinue,
383
383
  getEntry: this.getEntryForBank,
384
384
  workingEntry: this.props.workingStimulus,
385
+ quizId: this.props.quizId,
385
386
  entryId: this.props.guid,
386
387
  entryType: this.props.quizEntry.entryType
387
388
  });
@@ -0,0 +1,91 @@
1
+ import { SET_WS_TOKEN, QUIZ_SUBMISSION_EVENT } from '@instructure/quiz-common';
2
+ import { featureOn } from "../../common/util/featureCheck.js";
3
+ import { formatTime } from "../../common/util/formatTimespan.js";
4
+ import { updateTime, notifyTimeAdded } from "../../taking/api/taking.js";
5
+ import { bindActionCreators } from 'redux';
6
+ var takingApiActions = {
7
+ updateTime: updateTime,
8
+ notifyTimeAdded: notifyTimeAdded
9
+ };
10
+ export var createWebsocket = function createWebsocket(url, token) {
11
+ return new WebSocket("".concat(url, "?token=").concat(token));
12
+ };
13
+ export var handleExtraTime = function handleExtraTime(extraTimeInSeconds, endAt, updateTimeAction, notifyTimeAdded) {
14
+ var remainingTimeInMs = new Date(endAt) - Date.now();
15
+ var remainingTimeInSeconds = Math.floor(remainingTimeInMs / 1000);
16
+ var formattedExtraTimeText = formatTime(extraTimeInSeconds);
17
+ updateTimeAction(remainingTimeInSeconds);
18
+ notifyTimeAdded(formattedExtraTimeText);
19
+ };
20
+ export var parseJwt = function parseJwt(token) {
21
+ try {
22
+ return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
23
+ } catch (e) {
24
+ return null;
25
+ }
26
+ };
27
+ export var initializeWebSocket = function initializeWebSocket(token, createWebsocketFn) {
28
+ var jwt = parseJwt(token);
29
+
30
+ if (!jwt) {
31
+ return null;
32
+ }
33
+
34
+ return createWebsocketFn(jwt['aud'], token);
35
+ };
36
+ export var handleWebSocketMessage = function handleWebSocketMessage(webSocketMessage, boundTakingApiActions, handleExtraTimeFn) {
37
+ var _webSocketMessageData, _webSocketMessageData2, _webSocketMessageData3;
38
+
39
+ var webSocketMessageData = JSON.parse(webSocketMessage.data);
40
+ webSocketMessageData = ((_webSocketMessageData = webSocketMessageData) === null || _webSocketMessageData === void 0 ? void 0 : (_webSocketMessageData2 = _webSocketMessageData.message) === null || _webSocketMessageData2 === void 0 ? void 0 : _webSocketMessageData2.data) || ((_webSocketMessageData3 = webSocketMessageData) === null || _webSocketMessageData3 === void 0 ? void 0 : _webSocketMessageData3.data);
41
+
42
+ if (!webSocketMessageData) {
43
+ return;
44
+ }
45
+
46
+ var extra_time_in_seconds = webSocketMessageData.extra_time_in_seconds;
47
+ var end_at = webSocketMessageData.end_at;
48
+
49
+ if (!extra_time_in_seconds || !end_at) {
50
+ return;
51
+ }
52
+
53
+ handleExtraTimeFn(extra_time_in_seconds, end_at, boundTakingApiActions.updateTime, boundTakingApiActions.notifyTimeAdded);
54
+ };
55
+ export var createQuizTimerWebSocketMiddleware = function createQuizTimerWebSocketMiddleware() {
56
+ var createWebsocketFn = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : createWebsocket;
57
+ var featureOnFn = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : featureOn;
58
+ var socket = null;
59
+ return function (store) {
60
+ return function (next) {
61
+ return function (action) {
62
+ var addTimeToExistingQuizSessionsEnabled = featureOnFn('add_time_to_existing_quiz_sessions');
63
+
64
+ if (!addTimeToExistingQuizSessionsEnabled) {
65
+ return next(action);
66
+ }
67
+
68
+ if (action.type === SET_WS_TOKEN && socket === null) {
69
+ var token = action.payload.wsToken;
70
+ socket = initializeWebSocket(token, createWebsocketFn);
71
+
72
+ if (!socket) {
73
+ return next(action);
74
+ }
75
+
76
+ var boundTakingApiActions = bindActionCreators(takingApiActions, store.dispatch);
77
+ socket.addEventListener('message', function (webSocketMessage) {
78
+ handleWebSocketMessage(webSocketMessage, boundTakingApiActions, handleExtraTime);
79
+ });
80
+ }
81
+
82
+ if (action.type === QUIZ_SUBMISSION_EVENT && socket !== null) {
83
+ socket.close();
84
+ socket = null;
85
+ }
86
+
87
+ return next(action);
88
+ };
89
+ };
90
+ };
91
+ };
@@ -1,5 +1,5 @@
1
1
  import { Map } from 'immutable';
2
- import { ACTIVE_QUIZ_SESSION_CHANGE, UPDATE_TIMER_INFO, UPDATE_IS_TAKING, UPDATE_QUIZ_SESSION_STATUS_ON_PAGE_LOAD, UPDATE_TIMER_MINIMIZED, LOAD_RESUME_DATA, PREVIEW_RESPONSE, PREVIOUS_QUESTION, BACKTRACKING_NEXT_QUESTION, RESPONSE, SESSION_STARTED_OR_RESUMED_EVENT, SET_CURRENT_SESSION_ITEM_POSITION, CLEAR_FOCUS_CURRENT_SESSION_ITEM, SET_FOCUS_TITLE_AND_INSTRUCTIONS, UPDATE_QUIZ_SUBMIT_FLAG } from '@instructure/quiz-common';
2
+ import { ACTIVE_QUIZ_SESSION_CHANGE, UPDATE_TIMER_INFO, UPDATE_IS_TAKING, UPDATE_QUIZ_SESSION_STATUS_ON_PAGE_LOAD, UPDATE_TIMER_MINIMIZED, LOAD_RESUME_DATA, PREVIEW_RESPONSE, PREVIOUS_QUESTION, BACKTRACKING_NEXT_QUESTION, RESPONSE, SESSION_STARTED_OR_RESUMED_EVENT, SET_CURRENT_SESSION_ITEM_POSITION, CLEAR_FOCUS_CURRENT_SESSION_ITEM, SET_FOCUS_TITLE_AND_INSTRUCTIONS, UPDATE_QUIZ_SUBMIT_FLAG, SET_WS_TOKEN } from '@instructure/quiz-common';
3
3
 
4
4
  var defaultResponse = function defaultResponse(payload) {
5
5
  return Map({
@@ -53,6 +53,10 @@ var updateTimerInfo = function updateTimerInfo(state, payload) {
53
53
  return state.mergeIn(['timerData'], payload);
54
54
  };
55
55
 
56
+ var setWSToken = function setWSToken(state, payload) {
57
+ return state.set('wsToken', payload === null || payload === void 0 ? void 0 : payload.wsToken);
58
+ };
59
+
56
60
  var updateTimerMinimized = function updateTimerMinimized(state, payload) {
57
61
  return state.setIn(['timerData', 'timerMinimized'], payload.timerMinimized);
58
62
  };
@@ -126,6 +130,9 @@ export default (function () {
126
130
  case UPDATE_TIMER_INFO:
127
131
  return updateTimerInfo(state, action.payload);
128
132
 
133
+ case SET_WS_TOKEN:
134
+ return setWSToken(state, action.payload);
135
+
129
136
  case SET_CURRENT_SESSION_ITEM_POSITION:
130
137
  return setCurrentSessionItemPosition(state, action.payload);
131
138
 
@@ -5,7 +5,7 @@ import reduxStore from "../../reduxStore.js";
5
5
  export var MAX_SETTIMEOUT_IN_MILLISECONDS = 20 * 24 * 3600 * 1000; // ms of 20 days
6
6
 
7
7
  export var TimerService = /*#__PURE__*/function () {
8
- function TimerService(getTimeRemaining, updateTimeRemaining, getTimerData, submitQuiz, alert) {
8
+ function TimerService(getTimeRemaining, updateTimeRemaining, getTimerData, submitQuiz, alert, setWsToken) {
9
9
  var _this = this;
10
10
 
11
11
  _classCallCheck(this, TimerService);
@@ -23,6 +23,10 @@ export var TimerService = /*#__PURE__*/function () {
23
23
  _this.__onTimerChange(data);
24
24
 
25
25
  _this.__setApiCallIntervalTime(data.timeLeftInSeconds);
26
+
27
+ if (_this._setWsToken) {
28
+ _this._setWsToken(data.wsToken);
29
+ }
26
30
  });
27
31
  };
28
32
 
@@ -31,6 +35,7 @@ export var TimerService = /*#__PURE__*/function () {
31
35
  this._getTimerData = getTimerData;
32
36
  this._submitQuiz = submitQuiz;
33
37
  this._alert = alert;
38
+ this._setWsToken = setWsToken;
34
39
  this._stopped = false;
35
40
  this._apiInterval = null;
36
41
  this._clockInterval = null;
@@ -9,7 +9,8 @@ import multi from 'redux-multi';
9
9
  import thunkMiddleware from 'redux-thunk';
10
10
  import alertTimerMiddleware from "./common/middleware/alertTimerMiddleware.js";
11
11
  import crossDomainMessagingMiddleware from "./common/middleware/crossDomainMessagingMiddleware.js";
12
- import eventLoggerMiddlewareCreator from "./common/middleware/eventLoggerMiddleware.js"; // reducers
12
+ import eventLoggerMiddlewareCreator from "./common/middleware/eventLoggerMiddleware.js";
13
+ import { createQuizTimerWebSocketMiddleware } from "./common/middleware/quizTimerWebSocketMiddleware.js"; // reducers
13
14
 
14
15
  import alerts from "./common/reducers/alerts.js";
15
16
  import banks from "./common/reducers/banks.js";
@@ -69,7 +70,8 @@ export function configureStore(_ref) {
69
70
  }
70
71
  });
71
72
  var kinesisMiddleware = eventLoggerMiddlewareCreator();
72
- var baseMiddleware = [alertTimerMiddleware, crossDomainMessagingMiddleware, multi, kinesisMiddleware, thunkMiddleware];
73
+ var websocketMiddleware = createQuizTimerWebSocketMiddleware();
74
+ var baseMiddleware = [alertTimerMiddleware, crossDomainMessagingMiddleware, multi, kinesisMiddleware, thunkMiddleware, websocketMiddleware];
73
75
  var devMiddleware = [loggerMiddleware];
74
76
  var middlewareToApply = inDevelopment ? union(baseMiddleware, devMiddleware) : baseMiddleware;
75
77
  middlewareToApply = union(middlewareToApply, extraMiddleware);
package/es/index.js CHANGED
@@ -1,4 +1,12 @@
1
1
  /* eslint "import/newline-after-import": "warn" */
2
+
3
+ /**
4
+ *
5
+ * In g/356542 we tried importing takingApiActions from here, and it caused a weird karma issue.
6
+ * Nothing from this file should be imported from within quiz-core!
7
+ * These exports are for other packages to use.
8
+ * We should look into configuring an import/no-restricted-paths eslint rule to prevent this.
9
+ */
2
10
  // Components
3
11
  export { DragDroppableSDKApp as SDKApp } from "./common/components/SDKApp/index.js";
4
12
  export { ModerateTable } from "./moderating/components/resources/ModerateTable/index.js";
@@ -11,7 +11,7 @@ import { Map } from 'immutable';
11
11
  import { camelizeKeys } from 'humps';
12
12
  import t from '@instructure/quiz-i18n/es/format-message';
13
13
  import noop from "../../common/util/noop.js";
14
- import { screenreaderNotification } from "../../common/actions/alerts.js";
14
+ import { addInfo, screenreaderNotification } from "../../common/actions/alerts.js";
15
15
  import Item from "../../common/records/Item.js";
16
16
  import { featureOn } from "../../common/util/featureCheck.js";
17
17
  import { fetchLogger } from "../../common/kinesis/QuizEventsLogger.js";
@@ -21,7 +21,7 @@ import { fetchQuizSession, fetchResumeData, fetchTime, recordTime, singleRespons
21
21
  import { getSessionItems, handleQuizSessionFetch } from "../../common/actions/quizSessions.js";
22
22
  import { handleSessionItemsResponse, handleResumeDataResponse, handleQuizSessionResponse } from "../../common/api/callHandlers.js";
23
23
  import * as modalActions from "../../common/actions/modal.js";
24
- import { backtrackingNextQuestion, sessionStartOrResumeEvent, setCurrentSessionItemPosition, submitQuizSessionEvent, updateQuizSubmitFlag, updateQuizSessionStatusOnPageLoad } from "../../common/actions/taking.js";
24
+ import { backtrackingNextQuestion, sessionStartOrResumeEvent, setCurrentSessionItemPosition, submitQuizSessionEvent, updateQuizSubmitFlag, updateQuizSessionStatusOnPageLoad, updateTimerInfo } from "../../common/actions/taking.js";
25
25
 
26
26
  var getQuizSessionConfig = function getQuizSessionConfig(quizSession) {
27
27
  // these should be handled by existing functions
@@ -244,6 +244,22 @@ export function submitQuiz(quizSessionId) {
244
244
  }
245
245
  };
246
246
  }
247
+ export function notifyTimeAdded(extraTime) {
248
+ return function (dispatch) {
249
+ return dispatch(addInfo(t('Your original time limit has been extended by { extraTime }', {
250
+ extraTime: extraTime
251
+ })));
252
+ };
253
+ }
254
+ export function updateTime(timeInSeconds) {
255
+ return function (dispatch) {
256
+ return dispatch(updateTimerInfo({
257
+ timeLeftInSeconds: timeInSeconds,
258
+ timerRunning: true,
259
+ timeRecording: true
260
+ }));
261
+ };
262
+ }
247
263
 
248
264
  function getQuestionsNeedingAResponse(state, quizSessionId) {
249
265
  var responses = state.getIn(['taking', 'responses'], Map());
@@ -5,7 +5,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.removeBankEntry = exports.updateBankEntry = exports.createBankEntry = exports.createEntryAndBankEntry = exports.cloneBankEntry = exports.getBankEntry = exports.getBankEntries = void 0;
8
+ exports.removeBankEntry = exports.updateBankEntry = exports.createBankEntry = exports.copyOrMoveBankEntry = exports.moveBankEntry = exports.copyBankEntry = exports.moveBankEntryFromQuizEntry = exports.createEntryAndBankEntry = exports.cloneBankEntry = exports.getBankEntry = exports.getBankEntries = void 0;
9
9
 
10
10
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
11
11
 
@@ -132,6 +132,62 @@ var createEntryAndBankEntry = function createEntryAndBankEntry(bankId, entry, ba
132
132
 
133
133
  exports.createEntryAndBankEntry = createEntryAndBankEntry;
134
134
 
135
+ var moveBankEntryFromQuizEntry = function moveBankEntryFromQuizEntry(sourceQuizId, sourceEntryId, sourceEntryType, destinationBankId) {
136
+ var body = {
137
+ source_entry_id: sourceEntryId,
138
+ source_entry_type: sourceEntryType
139
+ };
140
+ var query_params = {
141
+ source_quiz_id: sourceQuizId
142
+ };
143
+ return copyOrMoveBankEntry(body, destinationBankId, 'move_from_quiz_entry', query_params);
144
+ };
145
+
146
+ exports.moveBankEntryFromQuizEntry = moveBankEntryFromQuizEntry;
147
+
148
+ var copyBankEntry = function copyBankEntry(sourceBankId, bankEntryId, destinationBankId) {
149
+ var body = {
150
+ source_bank_id: sourceBankId,
151
+ source_bank_entry_id: bankEntryId
152
+ };
153
+ return copyOrMoveBankEntry(body, destinationBankId, 'copy');
154
+ };
155
+
156
+ exports.copyBankEntry = copyBankEntry;
157
+
158
+ var moveBankEntry = function moveBankEntry(sourceBankId, bankEntryId, destinationBankId) {
159
+ var body = {
160
+ source_bank_id: sourceBankId,
161
+ source_bank_entry_id: bankEntryId
162
+ };
163
+ return copyOrMoveBankEntry(body, destinationBankId, 'move');
164
+ };
165
+
166
+ exports.moveBankEntry = moveBankEntry;
167
+
168
+ var copyOrMoveBankEntry = function copyOrMoveBankEntry(body, destinationBankId, operation, query_params) {
169
+ var url = "/api/banks/".concat(destinationBankId, "/bank_entries/").concat(operation);
170
+ return function (dispatch, getState) {
171
+ var fetcher = new _Fetcher.default({
172
+ callType: _quizCommon.COPY_BANK_ENTRY_CALL,
173
+ onError: (0, _helpers.handleError)(dispatch, (0, _formatMessage.default)('Failed to copy bank entry.'))
174
+ });
175
+
176
+ if (query_params) {
177
+ url = fetcher.appendQueryParams(url, query_params);
178
+ }
179
+
180
+ return fetcher.post(url, {
181
+ body: JSON.stringify(body)
182
+ }).then(function (response) {
183
+ dispatch([(0, _callHandlers.handleBankEntryResponse)(response), (0, _banks.associateEntries)(destinationBankId, [response.id]), (0, _banks2.getBank)(destinationBankId)]);
184
+ return response;
185
+ });
186
+ };
187
+ };
188
+
189
+ exports.copyOrMoveBankEntry = copyOrMoveBankEntry;
190
+
135
191
  var createBankEntry = function createBankEntry(bankId, entryType, entryId) {
136
192
  var url = "/api/banks/".concat(bankId, "/bank_entries");
137
193
  return function (dispatch, getState) {
@@ -53,6 +53,7 @@ var mapDispatchToProps = {
53
53
  closeModal: modalActions.closeModal,
54
54
  createBank: _banks.createBank,
55
55
  createBankEntry: _bankEntries.createBankEntry,
56
+ moveBankEntryFromQuizEntry: _bankEntries.moveBankEntryFromQuizEntry,
56
57
  disassociateBankEntry: _banks2.disassociateEntry,
57
58
  uiSet: uiActions.set
58
59
  };
@@ -55,6 +55,8 @@ var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/f
55
55
 
56
56
  var _quizCommon = require("@instructure/quiz-common");
57
57
 
58
+ var _featureCheck = require("../../../common/util/featureCheck.js");
59
+
58
60
  var _ref2 = /*#__PURE__*/_react.default.createElement(_uiIcons.IconCheckLine, {
59
61
  color: "success"
60
62
  });
@@ -93,9 +95,17 @@ var AddToBankModal = /*#__PURE__*/function (_Component) {
93
95
  newBankTitle: ''
94
96
  };
95
97
 
98
+ _this.copyBankEntry = function (entryId, bankId) {
99
+ if ((0, _featureCheck.featureOn)('instfs_bank_entry_movement_endpoints')) {
100
+ return _this.props.moveBankEntryFromQuizEntry(_this.props.quizId, entryId, _this.props.entryType, bankId);
101
+ } else {
102
+ return _this.props.createBankEntry(bankId, _this.props.entryType, entryId);
103
+ }
104
+ };
105
+
96
106
  _this.addToBank = function (bankId) {
97
107
  _this.props.getEntry().then(function (entry) {
98
- return _this.props.createBankEntry(bankId, _this.props.entryType, entry.id);
108
+ return _this.copyBankEntry(entry.id, bankId);
99
109
  }).then(_this.props.onAdd).catch(function (e) {
100
110
  if (e.validationErrorsFromApi) {
101
111
  // Close the modal so the user can view the input validation errors and correct them
@@ -335,6 +345,7 @@ AddToBankModal.propTypes = {
335
345
  checkContentShareability: _propTypes.default.func.isRequired,
336
346
  closeModal: _propTypes.default.func.isRequired,
337
347
  createBank: _propTypes.default.func.isRequired,
348
+ moveBankEntryFromQuizEntry: _propTypes.default.func.isRequired,
338
349
  createBankEntry: _propTypes.default.func.isRequired,
339
350
  // Create the bank entry's entry, or clone it if it already exists. Should
340
351
  // always be provided when a source is provided.
@@ -352,7 +363,8 @@ AddToBankModal.propTypes = {
352
363
  onContinue: _propTypes.default.func,
353
364
  selectedBank: _reactImmutableProptypes.default.record,
354
365
  uiSet: _propTypes.default.func.isRequired,
355
- workingEntry: _reactImmutableProptypes.default.record.isRequired
366
+ workingEntry: _reactImmutableProptypes.default.record.isRequired,
367
+ quizId: _propTypes.default.string.isRequired
356
368
  };
357
369
  AddToBankModal.defaultProps = {
358
370
  canvasOrigin: '',
@@ -45,8 +45,10 @@ function mapStateToProps(state, props) {
45
45
  var mapDispatchToProps = {
46
46
  closeModal: modalActions.closeModal,
47
47
  createBank: _banks.createBank,
48
+ copyBankEntry: _bankEntries.copyBankEntry,
48
49
  createBankEntry: _bankEntries.createBankEntry,
49
50
  updateBankEntry: _bankEntries.updateBankEntry,
51
+ moveBankEntry: _bankEntries.moveBankEntry,
50
52
  disassociateBankEntry: _banks2.disassociateEntry,
51
53
  uiSet: uiActions.set
52
54
  };
@@ -43,6 +43,8 @@ var _index = _interopRequireDefault(require("../../../common/components/resource
43
43
 
44
44
  var _quizCommon = require("@instructure/quiz-common");
45
45
 
46
+ var _featureCheck = require("../../../common/util/featureCheck.js");
47
+
46
48
  var _ref = /*#__PURE__*/_react.default.createElement("br", null);
47
49
 
48
50
  var _ref2 = /*#__PURE__*/_react.default.createElement("br", null);
@@ -92,20 +94,36 @@ var CopyMoveBankEntryModal = /*#__PURE__*/function (_Component) {
92
94
  }
93
95
  };
94
96
 
97
+ _this.initiateMove = function (newBankId) {
98
+ if ((0, _featureCheck.featureOn)('instfs_bank_entry_movement_endpoints')) {
99
+ return _this.props.moveBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId, newBankId);
100
+ } else {
101
+ return _this.props.updateBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId, {
102
+ bankId: newBankId
103
+ });
104
+ }
105
+ };
106
+
95
107
  _this.moveBankEntry = function (newBankId) {
96
- return _this.props.updateBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId, {
97
- bankId: newBankId
98
- }).then(function (bankEntry) {
108
+ return _this.initiateMove(newBankId).then(function (bankEntry) {
99
109
  _this.props.disassociateBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId);
100
110
 
101
111
  return bankEntry;
102
112
  }).then(_this.props.onAdd);
103
113
  };
104
114
 
115
+ _this.initiateCopy = function (newBankId) {
116
+ if ((0, _featureCheck.featureOn)('instfs_bank_entry_movement_endpoints')) {
117
+ return _this.props.copyBankEntry(_this.props.sourceBankId, _this.props.sourceBankEntryId, newBankId);
118
+ } else {
119
+ return _this.props.createEntry().then(function (entry) {
120
+ return _this.props.createBankEntry(newBankId, _this.props.entryType, entry.id);
121
+ });
122
+ }
123
+ };
124
+
105
125
  _this.copyBankEntry = function (newBankId) {
106
- return _this.props.createEntry().then(function (entry) {
107
- return _this.props.createBankEntry(newBankId, _this.props.entryType, entry.id);
108
- }).then(_this.props.onAdd);
126
+ return _this.initiateCopy(newBankId).then(_this.props.onAdd);
109
127
  };
110
128
 
111
129
  _this.closeAction = function () {
@@ -230,6 +248,7 @@ exports.CopyMoveBankEntryModal = CopyMoveBankEntryModal;
230
248
  CopyMoveBankEntryModal.propTypes = {
231
249
  closeModal: _propTypes.default.func.isRequired,
232
250
  createBank: _propTypes.default.func.isRequired,
251
+ copyBankEntry: _propTypes.default.func.isRequired,
233
252
  createBankEntry: _propTypes.default.func.isRequired,
234
253
  // Create the bank entry's entry, or clone it if it already exists. Should
235
254
  // always be provided when a source is provided.
@@ -249,7 +268,8 @@ CopyMoveBankEntryModal.propTypes = {
249
268
  sourceBankEntryId: _propTypes.default.string.isRequired,
250
269
  sourceBankId: _propTypes.default.string.isRequired,
251
270
  uiSet: _propTypes.default.func.isRequired,
252
- updateBankEntry: _propTypes.default.func.isRequired
271
+ updateBankEntry: _propTypes.default.func.isRequired,
272
+ moveBankEntry: _propTypes.default.func.isRequired
253
273
  };
254
274
  CopyMoveBankEntryModal.contextTypes = {
255
275
  locale: _propTypes.default.string
@@ -426,6 +426,7 @@ var QuizEntryEdit = (_dec = (0, _emotion.withStyle)(_styles.default, _theme.defa
426
426
  workingEntry: this.props.workingEntry,
427
427
  entryId: this.props.guid,
428
428
  entryType: this.props.quizEntry.entryType,
429
+ quizId: this.props.quizId,
429
430
  handleValidationErrorsFromApi: this.props.handleValidationErrorsFromApi
430
431
  });
431
432
  }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.updateQuizSubmitFlag = exports.submitQuizSessionEvent = exports.reportPageFocusedEvent = exports.reportPageBlurredEvent = exports.sessionStartOrResumeEvent = exports.loadResumeData = exports.editResponse = exports.previewResponse = exports.updateTimerInfo = exports.updateQuizSessionStatusOnPageLoad = exports.updateIsTaking = exports.updateTimerMinimized = exports.backtrackingNextQuestion = exports.previousQuestion = exports.setFocusTitleAndInstructions = exports.clearFocusCurrentSessionItem = exports.setCurrentSessionItemPosition = void 0;
6
+ exports.updateQuizSubmitFlag = exports.setWSToken = exports.submitQuizSessionEvent = exports.reportPageFocusedEvent = exports.reportPageBlurredEvent = exports.sessionStartOrResumeEvent = exports.loadResumeData = exports.editResponse = exports.previewResponse = exports.updateTimerInfo = exports.updateQuizSessionStatusOnPageLoad = exports.updateIsTaking = exports.updateTimerMinimized = exports.backtrackingNextQuestion = exports.previousQuestion = exports.setFocusTitleAndInstructions = exports.clearFocusCurrentSessionItem = exports.setCurrentSessionItemPosition = void 0;
7
7
 
8
8
  var _uuid = require("uuid");
9
9
 
@@ -192,6 +192,17 @@ var submitQuizSessionEvent = function submitQuizSessionEvent() {
192
192
 
193
193
  exports.submitQuizSessionEvent = submitQuizSessionEvent;
194
194
 
195
+ var setWSToken = function setWSToken(wsToken) {
196
+ return {
197
+ type: _quizCommon.SET_WS_TOKEN,
198
+ payload: {
199
+ wsToken: wsToken
200
+ }
201
+ };
202
+ };
203
+
204
+ exports.setWSToken = setWSToken;
205
+
195
206
  var updateQuizSubmitFlag = function updateQuizSubmitFlag(value) {
196
207
  return {
197
208
  type: _quizCommon.UPDATE_QUIZ_SUBMIT_FLAG,
@@ -419,6 +419,7 @@ var StimulusEdit = (_dec = (0, _emotion.withStyle)(_styles.default, _theme.defau
419
419
  onContinue: this.handleAddToBankContinue,
420
420
  getEntry: this.getEntryForBank,
421
421
  workingEntry: this.props.workingStimulus,
422
+ quizId: this.props.quizId,
422
423
  entryId: this.props.guid,
423
424
  entryType: this.props.quizEntry.entryType
424
425
  });
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createQuizTimerWebSocketMiddleware = exports.handleWebSocketMessage = exports.initializeWebSocket = exports.parseJwt = exports.handleExtraTime = exports.createWebsocket = void 0;
7
+
8
+ var _quizCommon = require("@instructure/quiz-common");
9
+
10
+ var _featureCheck = require("../../common/util/featureCheck.js");
11
+
12
+ var _formatTimespan = require("../../common/util/formatTimespan.js");
13
+
14
+ var _taking = require("../../taking/api/taking.js");
15
+
16
+ var _redux = require("redux");
17
+
18
+ var takingApiActions = {
19
+ updateTime: _taking.updateTime,
20
+ notifyTimeAdded: _taking.notifyTimeAdded
21
+ };
22
+
23
+ var createWebsocket = function createWebsocket(url, token) {
24
+ return new WebSocket("".concat(url, "?token=").concat(token));
25
+ };
26
+
27
+ exports.createWebsocket = createWebsocket;
28
+
29
+ var handleExtraTime = function handleExtraTime(extraTimeInSeconds, endAt, updateTimeAction, notifyTimeAdded) {
30
+ var remainingTimeInMs = new Date(endAt) - Date.now();
31
+ var remainingTimeInSeconds = Math.floor(remainingTimeInMs / 1000);
32
+ var formattedExtraTimeText = (0, _formatTimespan.formatTime)(extraTimeInSeconds);
33
+ updateTimeAction(remainingTimeInSeconds);
34
+ notifyTimeAdded(formattedExtraTimeText);
35
+ };
36
+
37
+ exports.handleExtraTime = handleExtraTime;
38
+
39
+ var parseJwt = function parseJwt(token) {
40
+ try {
41
+ return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
42
+ } catch (e) {
43
+ return null;
44
+ }
45
+ };
46
+
47
+ exports.parseJwt = parseJwt;
48
+
49
+ var initializeWebSocket = function initializeWebSocket(token, createWebsocketFn) {
50
+ var jwt = parseJwt(token);
51
+
52
+ if (!jwt) {
53
+ return null;
54
+ }
55
+
56
+ return createWebsocketFn(jwt['aud'], token);
57
+ };
58
+
59
+ exports.initializeWebSocket = initializeWebSocket;
60
+
61
+ var handleWebSocketMessage = function handleWebSocketMessage(webSocketMessage, boundTakingApiActions, handleExtraTimeFn) {
62
+ var _webSocketMessageData, _webSocketMessageData2, _webSocketMessageData3;
63
+
64
+ var webSocketMessageData = JSON.parse(webSocketMessage.data);
65
+ webSocketMessageData = ((_webSocketMessageData = webSocketMessageData) === null || _webSocketMessageData === void 0 ? void 0 : (_webSocketMessageData2 = _webSocketMessageData.message) === null || _webSocketMessageData2 === void 0 ? void 0 : _webSocketMessageData2.data) || ((_webSocketMessageData3 = webSocketMessageData) === null || _webSocketMessageData3 === void 0 ? void 0 : _webSocketMessageData3.data);
66
+
67
+ if (!webSocketMessageData) {
68
+ return;
69
+ }
70
+
71
+ var extra_time_in_seconds = webSocketMessageData.extra_time_in_seconds;
72
+ var end_at = webSocketMessageData.end_at;
73
+
74
+ if (!extra_time_in_seconds || !end_at) {
75
+ return;
76
+ }
77
+
78
+ handleExtraTimeFn(extra_time_in_seconds, end_at, boundTakingApiActions.updateTime, boundTakingApiActions.notifyTimeAdded);
79
+ };
80
+
81
+ exports.handleWebSocketMessage = handleWebSocketMessage;
82
+
83
+ var createQuizTimerWebSocketMiddleware = function createQuizTimerWebSocketMiddleware() {
84
+ var createWebsocketFn = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : createWebsocket;
85
+ var featureOnFn = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : _featureCheck.featureOn;
86
+ var socket = null;
87
+ return function (store) {
88
+ return function (next) {
89
+ return function (action) {
90
+ var addTimeToExistingQuizSessionsEnabled = featureOnFn('add_time_to_existing_quiz_sessions');
91
+
92
+ if (!addTimeToExistingQuizSessionsEnabled) {
93
+ return next(action);
94
+ }
95
+
96
+ if (action.type === _quizCommon.SET_WS_TOKEN && socket === null) {
97
+ var token = action.payload.wsToken;
98
+ socket = initializeWebSocket(token, createWebsocketFn);
99
+
100
+ if (!socket) {
101
+ return next(action);
102
+ }
103
+
104
+ var boundTakingApiActions = (0, _redux.bindActionCreators)(takingApiActions, store.dispatch);
105
+ socket.addEventListener('message', function (webSocketMessage) {
106
+ handleWebSocketMessage(webSocketMessage, boundTakingApiActions, handleExtraTime);
107
+ });
108
+ }
109
+
110
+ if (action.type === _quizCommon.QUIZ_SUBMISSION_EVENT && socket !== null) {
111
+ socket.close();
112
+ socket = null;
113
+ }
114
+
115
+ return next(action);
116
+ };
117
+ };
118
+ };
119
+ };
120
+
121
+ exports.createQuizTimerWebSocketMiddleware = createQuizTimerWebSocketMiddleware;
@@ -61,6 +61,10 @@ var updateTimerInfo = function updateTimerInfo(state, payload) {
61
61
  return state.mergeIn(['timerData'], payload);
62
62
  };
63
63
 
64
+ var setWSToken = function setWSToken(state, payload) {
65
+ return state.set('wsToken', payload === null || payload === void 0 ? void 0 : payload.wsToken);
66
+ };
67
+
64
68
  var updateTimerMinimized = function updateTimerMinimized(state, payload) {
65
69
  return state.setIn(['timerData', 'timerMinimized'], payload.timerMinimized);
66
70
  };
@@ -134,6 +138,9 @@ var _default = function _default() {
134
138
  case _quizCommon.UPDATE_TIMER_INFO:
135
139
  return updateTimerInfo(state, action.payload);
136
140
 
141
+ case _quizCommon.SET_WS_TOKEN:
142
+ return setWSToken(state, action.payload);
143
+
137
144
  case _quizCommon.SET_CURRENT_SESSION_ITEM_POSITION:
138
145
  return setCurrentSessionItemPosition(state, action.payload);
139
146
 
@@ -20,7 +20,7 @@ var MAX_SETTIMEOUT_IN_MILLISECONDS = 20 * 24 * 3600 * 1000; // ms of 20 days
20
20
  exports.MAX_SETTIMEOUT_IN_MILLISECONDS = MAX_SETTIMEOUT_IN_MILLISECONDS;
21
21
 
22
22
  var TimerService = /*#__PURE__*/function () {
23
- function TimerService(getTimeRemaining, updateTimeRemaining, getTimerData, submitQuiz, alert) {
23
+ function TimerService(getTimeRemaining, updateTimeRemaining, getTimerData, submitQuiz, alert, setWsToken) {
24
24
  var _this = this;
25
25
 
26
26
  (0, _classCallCheck2.default)(this, TimerService);
@@ -38,6 +38,10 @@ var TimerService = /*#__PURE__*/function () {
38
38
  _this.__onTimerChange(data);
39
39
 
40
40
  _this.__setApiCallIntervalTime(data.timeLeftInSeconds);
41
+
42
+ if (_this._setWsToken) {
43
+ _this._setWsToken(data.wsToken);
44
+ }
41
45
  });
42
46
  };
43
47
 
@@ -46,6 +50,7 @@ var TimerService = /*#__PURE__*/function () {
46
50
  this._getTimerData = getTimerData;
47
51
  this._submitQuiz = submitQuiz;
48
52
  this._alert = alert;
53
+ this._setWsToken = setWsToken;
49
54
  this._stopped = false;
50
55
  this._apiInterval = null;
51
56
  this._clockInterval = null;
@@ -30,6 +30,8 @@ var _crossDomainMessagingMiddleware = _interopRequireDefault(require("./common/m
30
30
 
31
31
  var _eventLoggerMiddleware = _interopRequireDefault(require("./common/middleware/eventLoggerMiddleware.js"));
32
32
 
33
+ var _quizTimerWebSocketMiddleware = require("./common/middleware/quizTimerWebSocketMiddleware.js");
34
+
33
35
  var _alerts = _interopRequireDefault(require("./common/reducers/alerts.js"));
34
36
 
35
37
  var _banks = _interopRequireDefault(require("./common/reducers/banks.js"));
@@ -129,7 +131,8 @@ function configureStore(_ref) {
129
131
  }
130
132
  });
131
133
  var kinesisMiddleware = (0, _eventLoggerMiddleware.default)();
132
- var baseMiddleware = [_alertTimerMiddleware.default, _crossDomainMessagingMiddleware.default, _reduxMulti.default, kinesisMiddleware, _reduxThunk.default];
134
+ var websocketMiddleware = (0, _quizTimerWebSocketMiddleware.createQuizTimerWebSocketMiddleware)();
135
+ var baseMiddleware = [_alertTimerMiddleware.default, _crossDomainMessagingMiddleware.default, _reduxMulti.default, kinesisMiddleware, _reduxThunk.default, websocketMiddleware];
133
136
  var devMiddleware = [loggerMiddleware];
134
137
  var middlewareToApply = inDevelopment ? (0, _union.default)(baseMiddleware, devMiddleware) : baseMiddleware;
135
138
  middlewareToApply = (0, _union.default)(middlewareToApply, extraMiddleware);
@@ -13,6 +13,8 @@ exports.onQuizSessionReadyForTaking = onQuizSessionReadyForTaking;
13
13
  exports.showSubmitConfirmModal = showSubmitConfirmModal;
14
14
  exports.closeSubmitConfirmModal = closeSubmitConfirmModal;
15
15
  exports.submitQuiz = submitQuiz;
16
+ exports.notifyTimeAdded = notifyTimeAdded;
17
+ exports.updateTime = updateTime;
16
18
  exports.postQuizSession = postQuizSession;
17
19
  exports.getTimeRemaining = getTimeRemaining;
18
20
  exports.recordQuizTime = recordQuizTime;
@@ -299,6 +301,24 @@ function submitQuiz(quizSessionId) {
299
301
  };
300
302
  }
301
303
 
304
+ function notifyTimeAdded(extraTime) {
305
+ return function (dispatch) {
306
+ return dispatch((0, _alerts.addInfo)((0, _formatMessage.default)('Your original time limit has been extended by { extraTime }', {
307
+ extraTime: extraTime
308
+ })));
309
+ };
310
+ }
311
+
312
+ function updateTime(timeInSeconds) {
313
+ return function (dispatch) {
314
+ return dispatch((0, _taking.updateTimerInfo)({
315
+ timeLeftInSeconds: timeInSeconds,
316
+ timerRunning: true,
317
+ timeRecording: true
318
+ }));
319
+ };
320
+ }
321
+
302
322
  function getQuestionsNeedingAResponse(state, quizSessionId) {
303
323
  var responses = state.getIn(['taking', 'responses'], (0, _immutable.Map)());
304
324
  var sessionItems = state.get('sessionItems', (0, _immutable.Map)()).filter(function (record) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/quiz-core",
3
- "version": "20.17.3-rc.9+04f56b67e",
3
+ "version": "20.18.1-rc.0+392e66e66",
4
4
  "license": "MIT",
5
5
  "description": "The Quiz React SDK by Instructure Inc.",
6
6
  "author": "Instructure, Inc. Engineering and Product Design",
@@ -44,11 +44,11 @@
44
44
  "@instructure/emotion": "^8.51.0",
45
45
  "@instructure/grading-utils": "^1.0.0",
46
46
  "@instructure/outcomes-ui": "^3.2.2",
47
- "@instructure/quiz-common": "20.17.3-rc.9+04f56b67e",
48
- "@instructure/quiz-i18n": "20.17.3-rc.9+04f56b67e",
49
- "@instructure/quiz-interactions": "20.17.3-rc.9+04f56b67e",
50
- "@instructure/quiz-number-input": "20.17.3-rc.9+04f56b67e",
51
- "@instructure/quiz-rce": "20.17.3-rc.9+04f56b67e",
47
+ "@instructure/quiz-common": "20.18.1-rc.0+392e66e66",
48
+ "@instructure/quiz-i18n": "20.18.1-rc.0+392e66e66",
49
+ "@instructure/quiz-interactions": "20.18.1-rc.0+392e66e66",
50
+ "@instructure/quiz-number-input": "20.18.1-rc.0+392e66e66",
51
+ "@instructure/quiz-rce": "20.18.1-rc.0+392e66e66",
52
52
  "@instructure/ui-a11y-content": "^8.51.0",
53
53
  "@instructure/ui-alerts": "^8.51.0",
54
54
  "@instructure/ui-avatar": "^8.51.0",
@@ -111,7 +111,7 @@
111
111
  "file-saver": "~2.0.5",
112
112
  "humps": "^2.0.0",
113
113
  "immutable": "^3.8.1",
114
- "instructure-validations": "20.17.3-rc.9+04f56b67e",
114
+ "instructure-validations": "20.18.1-rc.0+392e66e66",
115
115
  "ipaddr.js": "^1.5.4",
116
116
  "isomorphic-fetch": "^2.2.0",
117
117
  "isuuid": "^0.1.0",
@@ -142,7 +142,7 @@
142
142
  "uuid": "^3.2.1"
143
143
  },
144
144
  "devDependencies": {
145
- "@instructure/quiz-scripts": "20.17.2",
145
+ "@instructure/quiz-scripts": "20.18.0",
146
146
  "@instructure/ui-axe-check": "^8.51.0",
147
147
  "@instructure/ui-babel-preset": "^7.22.1",
148
148
  "@instructure/ui-scripts": "^7.22.1",
@@ -161,7 +161,7 @@
161
161
  "jquery": "^2.2.3",
162
162
  "karma-junit-reporter": "^2.0.1",
163
163
  "most-subject": "^5.3.0",
164
- "quiz-presets": "20.17.3-rc.9+04f56b67e",
164
+ "quiz-presets": "20.18.1-rc.0+392e66e66",
165
165
  "react": "^16.8.6",
166
166
  "react-addons-test-utils": "^15.6.2",
167
167
  "react-dom": "^16.8.6",
@@ -177,5 +177,5 @@
177
177
  "publishConfig": {
178
178
  "access": "public"
179
179
  },
180
- "gitHead": "04f56b67e3851ae350eef469d74d13ac657dd775"
180
+ "gitHead": "392e66e66be84e073171f29b85edaf54330b3b8f"
181
181
  }