@instructure/quiz-core 21.0.1-rc.5 → 21.0.1-rc.7
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.
- package/es/common/actions/taking.js +3 -2
- package/es/common/reducers/taking.js +5 -1
- package/es/common/selectors/taking.js +3 -0
- package/es/common/util/getClientIpAddress.js +35 -0
- package/es/common/util/serializeEvent.js +4 -2
- package/es/index.js +1 -2
- package/es/moderating/components/events/Event.js +27 -10
- package/es/moderating/components/events/ResponseEvent.js +24 -7
- package/es/moderating/components/events/SessionStartEvent.js +16 -15
- package/es/moderating/components/events/SessionSubmitEvent.js +42 -0
- package/es/moderating/components/resources/EventDetails.js +43 -0
- package/es/moderating/components/resources/SessionBreachAlert.js +10 -0
- package/es/moderating/components/resources/util/checkIsBreach.js +28 -0
- package/es/moderating/components/resources/util/extractSessionData.js +20 -0
- package/es/reporting/components/resources/common/propTypes.js +14 -0
- package/es/taking/api/taking.js +26 -3
- package/lib/common/actions/taking.js +3 -2
- package/lib/common/reducers/taking.js +5 -1
- package/lib/common/selectors/taking.js +5 -1
- package/lib/common/util/getClientIpAddress.js +43 -0
- package/lib/common/util/serializeEvent.js +3 -1
- package/lib/index.js +8 -0
- package/lib/moderating/components/events/Event.js +26 -9
- package/lib/moderating/components/events/ResponseEvent.js +25 -7
- package/lib/moderating/components/events/SessionStartEvent.js +16 -15
- package/lib/moderating/components/events/SessionSubmitEvent.js +52 -0
- package/lib/moderating/components/resources/EventDetails.js +51 -0
- package/lib/moderating/components/resources/SessionBreachAlert.js +18 -0
- package/lib/moderating/components/resources/util/checkIsBreach.js +36 -0
- package/lib/moderating/components/resources/util/extractSessionData.js +28 -0
- package/lib/reporting/components/resources/common/propTypes.js +18 -2
- package/lib/taking/api/taking.js +26 -3
- package/package.json +9 -9
|
@@ -104,12 +104,13 @@ export var loadResumeData = function loadResumeData(resumeData) {
|
|
|
104
104
|
payload: resumeData
|
|
105
105
|
};
|
|
106
106
|
};
|
|
107
|
-
export var sessionStartOrResumeEvent = function sessionStartOrResumeEvent(userAgent) {
|
|
107
|
+
export var sessionStartOrResumeEvent = function sessionStartOrResumeEvent(userAgent, ipAddress) {
|
|
108
108
|
return {
|
|
109
109
|
type: SESSION_STARTED_OR_RESUMED_EVENT,
|
|
110
110
|
payload: {
|
|
111
111
|
userAgent: userAgent,
|
|
112
|
-
browserSessionId: uuid()
|
|
112
|
+
browserSessionId: uuid(),
|
|
113
|
+
ipAddress: ipAddress
|
|
113
114
|
}
|
|
114
115
|
};
|
|
115
116
|
};
|
|
@@ -67,7 +67,11 @@ var loadResponses = function loadResponses(state, payload) {
|
|
|
67
67
|
return payload.reduce(modifyResponse, state);
|
|
68
68
|
};
|
|
69
69
|
var markSessionStartedIdentifier = function markSessionStartedIdentifier(state, payload) {
|
|
70
|
-
|
|
70
|
+
var needsUpdate = state.get('browserSessionId') !== payload.browserSessionId || state.get('ipAddress') !== payload.ipAddress;
|
|
71
|
+
return needsUpdate ? state.merge({
|
|
72
|
+
browserSessionId: payload.browserSessionId,
|
|
73
|
+
ipAddress: payload.ipAddress
|
|
74
|
+
}) : state;
|
|
71
75
|
};
|
|
72
76
|
var updateQuizSubmitFlag = function updateQuizSubmitFlag(state, payload) {
|
|
73
77
|
return state.set('submitFlag', payload.value);
|
|
@@ -7,6 +7,9 @@ export var getCurrentSessionItemPosition = function getCurrentSessionItemPositio
|
|
|
7
7
|
export var getBrowserSessionId = function getBrowserSessionId(state) {
|
|
8
8
|
return state.getIn([path, 'browserSessionId']);
|
|
9
9
|
};
|
|
10
|
+
export var getIpAddress = function getIpAddress(state) {
|
|
11
|
+
return state.getIn([path, 'ipAddress']);
|
|
12
|
+
};
|
|
10
13
|
export var getTimerData = function getTimerData(state) {
|
|
11
14
|
return state.getIn([path, 'timerData'], Map());
|
|
12
15
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
2
|
+
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
|
|
3
|
+
import Fetcher from "./Fetcher.js";
|
|
4
|
+
export var getClientIpAddress = /*#__PURE__*/function () {
|
|
5
|
+
var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(quizSessionId) {
|
|
6
|
+
var url, fetcher, _yield$fetcher$get, ip;
|
|
7
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
8
|
+
while (1) {
|
|
9
|
+
switch (_context.prev = _context.next) {
|
|
10
|
+
case 0:
|
|
11
|
+
_context.prev = 0;
|
|
12
|
+
url = "/api/quiz_sessions/".concat(quizSessionId, "/get_ip_address");
|
|
13
|
+
fetcher = new Fetcher();
|
|
14
|
+
_context.next = 5;
|
|
15
|
+
return fetcher.get(url);
|
|
16
|
+
case 5:
|
|
17
|
+
_yield$fetcher$get = _context.sent;
|
|
18
|
+
ip = _yield$fetcher$get.ip;
|
|
19
|
+
return _context.abrupt("return", ip);
|
|
20
|
+
case 10:
|
|
21
|
+
_context.prev = 10;
|
|
22
|
+
_context.t0 = _context["catch"](0);
|
|
23
|
+
console.error(_context.t0);
|
|
24
|
+
return _context.abrupt("return", null);
|
|
25
|
+
case 14:
|
|
26
|
+
case "end":
|
|
27
|
+
return _context.stop();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}, _callee, null, [[0, 10]]);
|
|
31
|
+
}));
|
|
32
|
+
return function getClientIpAddress(_x) {
|
|
33
|
+
return _ref.apply(this, arguments);
|
|
34
|
+
};
|
|
35
|
+
}();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { QUIZ_SUBMISSION_EVENT, RESPONSE, SESSION_STARTED_OR_RESUMED_EVENT, RCE_EVENT, PAGE_BLURRED_EVENT, PAGE_FOCUSED_EVENT } from '@instructure/quiz-common';
|
|
2
|
-
import { getBrowserSessionId } from "../selectors/taking.js";
|
|
2
|
+
import { getBrowserSessionId, getIpAddress } from "../selectors/taking.js";
|
|
3
3
|
import { getActiveQuizSession } from "../selectors/quizSessions.js";
|
|
4
4
|
var whitelist = [QUIZ_SUBMISSION_EVENT, RESPONSE, SESSION_STARTED_OR_RESUMED_EVENT, RCE_EVENT, PAGE_BLURRED_EVENT, PAGE_FOCUSED_EVENT];
|
|
5
5
|
export function serializeEvent(action, store) {
|
|
@@ -10,8 +10,10 @@ export function serializeEvent(action, store) {
|
|
|
10
10
|
var state = store.getState();
|
|
11
11
|
var quizSession = getActiveQuizSession(state);
|
|
12
12
|
var defaultEventData = {
|
|
13
|
+
userAgent: navigator.userAgent,
|
|
13
14
|
browserSessionId: getBrowserSessionId(state),
|
|
14
|
-
clientTimestamp: new Date()
|
|
15
|
+
clientTimestamp: new Date(),
|
|
16
|
+
ipAddress: getIpAddress(state)
|
|
15
17
|
};
|
|
16
18
|
var authoritativeData = {
|
|
17
19
|
accountUuid: quizSession.accountUuid,
|
package/es/index.js
CHANGED
|
@@ -107,8 +107,7 @@ export { sessionStore } from "./common/util/sessionStore.js";
|
|
|
107
107
|
export { printWithCss, ensureImagesLoaded } from "./common/util/printUtils.js";
|
|
108
108
|
export { ensureRCEContentIsLoaded } from "./common/util/rceChecker.js";
|
|
109
109
|
export { applyComponentStyleOverride, withStyleOverrides, getThemeByKey, setThemeByKey, getActiveTheme, setActiveTheme } from '@instructure/quiz-common';
|
|
110
|
-
|
|
111
|
-
// APIs
|
|
110
|
+
export { getClientIpAddress } from "./common/util/getClientIpAddress.js"; // APIs
|
|
112
111
|
export { createQuizEntryRegrade } from "./grading/api/quizEntryRegrades.js";
|
|
113
112
|
export { getQuizEntry } from "./building/api/quizEntries.js";
|
|
114
113
|
export { getItem } from "./building/api/items.js";
|
|
@@ -6,14 +6,17 @@ var _Event;
|
|
|
6
6
|
import React, { Component } from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
9
|
-
import { RESPONSE, SESSION_STARTED_OR_RESUMED_EVENT, RCE_EVENT, PAGE_BLURRED_EVENT, PAGE_FOCUSED_EVENT } from '@instructure/quiz-common';
|
|
9
|
+
import { RESPONSE, SESSION_STARTED_OR_RESUMED_EVENT, RCE_EVENT, PAGE_BLURRED_EVENT, PAGE_FOCUSED_EVENT, QUIZ_SUBMISSION_EVENT } from '@instructure/quiz-common';
|
|
10
10
|
import { Table } from '@instructure/ui-table';
|
|
11
11
|
import ElapsedTime from "../resources/ElapsedTime.js";
|
|
12
12
|
import ResponseEvent from "./ResponseEvent.js";
|
|
13
13
|
import SessionStartEvent from "./SessionStartEvent.js";
|
|
14
|
+
import { SessionSubmitEvent } from "./SessionSubmitEvent.js";
|
|
14
15
|
import RCEEvent from "./RCEEvent.js";
|
|
15
16
|
import PageBlurredEvent from "./PageBlurredEvent.js";
|
|
16
17
|
import PageFocusedEvent from "./PageFocusedEvent.js";
|
|
18
|
+
import { extractSessionData } from "../resources/util/extractSessionData.js";
|
|
19
|
+
import { checkIsBreach } from "../resources/util/checkIsBreach.js";
|
|
17
20
|
var _ref = /*#__PURE__*/React.createElement(PageBlurredEvent, null);
|
|
18
21
|
var _ref2 = /*#__PURE__*/React.createElement(PageFocusedEvent, null);
|
|
19
22
|
export var Event = /*#__PURE__*/function (_Component) {
|
|
@@ -25,26 +28,34 @@ export var Event = /*#__PURE__*/function (_Component) {
|
|
|
25
28
|
}
|
|
26
29
|
_createClass(Event, [{
|
|
27
30
|
key: "renderResponseEvent",
|
|
28
|
-
value: function renderResponseEvent(event) {
|
|
31
|
+
value: function renderResponseEvent(event, validationErrors, currentEventData) {
|
|
29
32
|
var item = event.getItem();
|
|
30
33
|
if (!item) {
|
|
31
34
|
return null;
|
|
32
35
|
} else {
|
|
33
36
|
return /*#__PURE__*/React.createElement(ResponseEvent, {
|
|
34
37
|
event: event,
|
|
35
|
-
item: item
|
|
38
|
+
item: item,
|
|
39
|
+
validationErrors: validationErrors,
|
|
40
|
+
currentEventData: currentEventData
|
|
36
41
|
});
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
44
|
}, {
|
|
40
45
|
key: "renderPayload",
|
|
41
|
-
value: function renderPayload(event) {
|
|
46
|
+
value: function renderPayload(event, validationErrors, currentEventData) {
|
|
42
47
|
switch (event.eventType) {
|
|
43
48
|
case RESPONSE:
|
|
44
|
-
return this.renderResponseEvent(event);
|
|
49
|
+
return this.renderResponseEvent(event, validationErrors, currentEventData);
|
|
45
50
|
case SESSION_STARTED_OR_RESUMED_EVENT:
|
|
46
51
|
return /*#__PURE__*/React.createElement(SessionStartEvent, {
|
|
47
|
-
|
|
52
|
+
validationErrors: validationErrors,
|
|
53
|
+
currentEventData: currentEventData
|
|
54
|
+
});
|
|
55
|
+
case QUIZ_SUBMISSION_EVENT:
|
|
56
|
+
return /*#__PURE__*/React.createElement(SessionSubmitEvent, {
|
|
57
|
+
validationErrors: validationErrors,
|
|
58
|
+
currentEventData: currentEventData
|
|
48
59
|
});
|
|
49
60
|
case RCE_EVENT:
|
|
50
61
|
return /*#__PURE__*/React.createElement(RCEEvent, {
|
|
@@ -64,8 +75,12 @@ export var Event = /*#__PURE__*/function (_Component) {
|
|
|
64
75
|
value: function render() {
|
|
65
76
|
var _this$props = this.props,
|
|
66
77
|
event = _this$props.event,
|
|
67
|
-
startTime = _this$props.startTime
|
|
68
|
-
|
|
78
|
+
startTime = _this$props.startTime,
|
|
79
|
+
firstEvent = _this$props.firstEvent;
|
|
80
|
+
var currentEventData = extractSessionData(event);
|
|
81
|
+
var firstEventData = extractSessionData(firstEvent);
|
|
82
|
+
var validationErrors = checkIsBreach(firstEventData, currentEventData);
|
|
83
|
+
var payloadNode = this.renderPayload(event, validationErrors, currentEventData);
|
|
69
84
|
if (!payloadNode) return null;
|
|
70
85
|
return /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.Cell, null, /*#__PURE__*/React.createElement(ElapsedTime, {
|
|
71
86
|
eventTime: event.createdDate(),
|
|
@@ -82,10 +97,12 @@ Event.displayName = 'Row';
|
|
|
82
97
|
Event.componentId = "Quizzes".concat(_Event.displayName);
|
|
83
98
|
Event.propTypes = {
|
|
84
99
|
event: ImmutablePropTypes.record,
|
|
85
|
-
startTime: PropTypes.instanceOf(Date)
|
|
100
|
+
startTime: PropTypes.instanceOf(Date),
|
|
101
|
+
firstEvent: ImmutablePropTypes.record
|
|
86
102
|
};
|
|
87
103
|
Event.defaultProps = {
|
|
88
104
|
event: void 0,
|
|
89
|
-
startTime: void 0
|
|
105
|
+
startTime: void 0,
|
|
106
|
+
firstEvent: void 0
|
|
90
107
|
};
|
|
91
108
|
export default Event;
|
|
@@ -4,7 +4,7 @@ import _inherits from "@babel/runtime/helpers/esm/inherits";
|
|
|
4
4
|
import _createSuper from "@babel/runtime/helpers/esm/createSuper";
|
|
5
5
|
var _dec, _class, _ResponseEvent;
|
|
6
6
|
/** @jsx jsx */
|
|
7
|
-
import { Component } from 'react';
|
|
7
|
+
import React, { Component } from 'react';
|
|
8
8
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
9
9
|
import PropTypes from 'prop-types';
|
|
10
10
|
import { IconImageSolid } from '@instructure/ui-icons';
|
|
@@ -14,9 +14,11 @@ import { ToggleDetails } from '@instructure/ui-toggle-details';
|
|
|
14
14
|
import { jsx } from '@instructure/emotion';
|
|
15
15
|
import t from '@instructure/quiz-i18n/es/format-message';
|
|
16
16
|
import { extractTextFromHtml } from '@instructure/quiz-interactions';
|
|
17
|
+
import { EventDetails } from "../resources/EventDetails.js";
|
|
17
18
|
import generateStyle from "./styles.js";
|
|
18
19
|
import generateComponentTheme from "./theme.js";
|
|
19
20
|
import { withStyleOverrides } from '@instructure/quiz-common';
|
|
21
|
+
import { sessionValidationErrorsPropType, eventDataPropType } from "../../../reporting/components/resources/common/propTypes.js";
|
|
20
22
|
var imgRegex = /<img.*?src=['"](.+)['"]/g;
|
|
21
23
|
export var ResponseEvent = (_dec = withStyleOverrides(generateStyle, generateComponentTheme), _dec(_class = (_ResponseEvent = /*#__PURE__*/function (_Component) {
|
|
22
24
|
_inherits(ResponseEvent, _Component);
|
|
@@ -44,13 +46,16 @@ export var ResponseEvent = (_dec = withStyleOverrides(generateStyle, generateCom
|
|
|
44
46
|
}, {
|
|
45
47
|
key: "renderTextSummary",
|
|
46
48
|
value: function renderTextSummary() {
|
|
49
|
+
var isBreach = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : false;
|
|
47
50
|
var strippedItemBody = extractTextFromHtml(this.itemBody);
|
|
48
51
|
return jsx("span", null, this.hasMedia() && jsx(IconImageSolid, {
|
|
49
52
|
css: this.props.styles.answerMediaIcon
|
|
50
|
-
}),
|
|
53
|
+
}), jsx(Text, {
|
|
54
|
+
color: isBreach ? 'danger' : 'primary'
|
|
55
|
+
}, t('Answered question {position}: {question}', {
|
|
51
56
|
position: this.props.event.getIn(['eventData', 'position']),
|
|
52
57
|
question: strippedItemBody
|
|
53
|
-
}));
|
|
58
|
+
})));
|
|
54
59
|
}
|
|
55
60
|
}, {
|
|
56
61
|
key: "renderMedia",
|
|
@@ -78,16 +83,26 @@ export var ResponseEvent = (_dec = withStyleOverrides(generateStyle, generateCom
|
|
|
78
83
|
}, {
|
|
79
84
|
key: "render",
|
|
80
85
|
value: function render() {
|
|
86
|
+
var _this$props = this.props,
|
|
87
|
+
validationErrors = _this$props.validationErrors,
|
|
88
|
+
currentEventData = _this$props.currentEventData;
|
|
81
89
|
var responseText = this.getReadableResponseText();
|
|
82
90
|
if (responseText) {
|
|
83
91
|
// If this returns falsey, then we know to just display the stem
|
|
84
92
|
return jsx(ToggleDetails, {
|
|
85
|
-
summary: this.renderTextSummary()
|
|
86
|
-
}, this.renderMedia(),
|
|
93
|
+
summary: this.renderTextSummary(validationErrors.isBreach)
|
|
94
|
+
}, this.renderMedia(), jsx(EventDetails, {
|
|
95
|
+
currentEventData: currentEventData,
|
|
96
|
+
validationErrors: validationErrors,
|
|
97
|
+
answerValue: responseText
|
|
98
|
+
}));
|
|
87
99
|
} else {
|
|
88
100
|
return jsx(Text, {
|
|
89
101
|
size: "small"
|
|
90
|
-
}, this.renderTextSummary(),
|
|
102
|
+
}, this.renderTextSummary(validationErrors.isBreach), jsx(EventDetails, {
|
|
103
|
+
currentEventData: currentEventData,
|
|
104
|
+
validationErrors: validationErrors
|
|
105
|
+
}), this.renderMedia());
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
}, {
|
|
@@ -101,6 +116,8 @@ export var ResponseEvent = (_dec = withStyleOverrides(generateStyle, generateCom
|
|
|
101
116
|
}(Component), _ResponseEvent.displayName = 'ResponseEvent', _ResponseEvent.componentId = "Quizzes".concat(_ResponseEvent.displayName), _ResponseEvent.propTypes = {
|
|
102
117
|
event: ImmutablePropTypes.record.isRequired,
|
|
103
118
|
item: ImmutablePropTypes.record.isRequired,
|
|
104
|
-
styles: PropTypes.object
|
|
119
|
+
styles: PropTypes.object,
|
|
120
|
+
validationErrors: sessionValidationErrorsPropType,
|
|
121
|
+
currentEventData: eventDataPropType
|
|
105
122
|
}, _ResponseEvent)) || _class);
|
|
106
123
|
export default ResponseEvent;
|
|
@@ -3,11 +3,11 @@ import _createClass from "@babel/runtime/helpers/esm/createClass";
|
|
|
3
3
|
import _inherits from "@babel/runtime/helpers/esm/inherits";
|
|
4
4
|
import _createSuper from "@babel/runtime/helpers/esm/createSuper";
|
|
5
5
|
import React, { Component } from 'react';
|
|
6
|
-
import
|
|
7
|
-
import platform from 'platform';
|
|
6
|
+
import { EventDetails } from "../resources/EventDetails.js";
|
|
8
7
|
import { ToggleDetails } from '@instructure/ui-toggle-details';
|
|
9
|
-
import { Table } from '@instructure/ui-table';
|
|
10
8
|
import t from '@instructure/quiz-i18n/es/format-message';
|
|
9
|
+
import { Text } from '@instructure/ui-text';
|
|
10
|
+
import { sessionValidationErrorsPropType, eventDataPropType } from "../../../reporting/components/resources/common/propTypes.js";
|
|
11
11
|
export var SessionStartEvent = /*#__PURE__*/function (_Component) {
|
|
12
12
|
_inherits(SessionStartEvent, _Component);
|
|
13
13
|
var _super = _createSuper(SessionStartEvent);
|
|
@@ -18,24 +18,25 @@ export var SessionStartEvent = /*#__PURE__*/function (_Component) {
|
|
|
18
18
|
_createClass(SessionStartEvent, [{
|
|
19
19
|
key: "render",
|
|
20
20
|
value: function render() {
|
|
21
|
-
var
|
|
22
|
-
|
|
21
|
+
var _this$props = this.props,
|
|
22
|
+
validationErrors = _this$props.validationErrors,
|
|
23
|
+
currentEventData = _this$props.currentEventData;
|
|
24
|
+
var summary = /*#__PURE__*/React.createElement(Text, {
|
|
25
|
+
color: validationErrors.isBreach ? 'danger' : 'primary'
|
|
26
|
+
}, t('Session started'));
|
|
23
27
|
return /*#__PURE__*/React.createElement(ToggleDetails, {
|
|
24
|
-
summary:
|
|
25
|
-
}, /*#__PURE__*/React.createElement(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}, t('attribute')), /*#__PURE__*/React.createElement(Table.ColHeader, {
|
|
30
|
-
id: "system-details-value"
|
|
31
|
-
}, t('value')))), /*#__PURE__*/React.createElement(Table.Body, null, /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.Cell, null, t('Browser')), /*#__PURE__*/React.createElement(Table.Cell, null, // eslint-disable-next-line react/jsx-no-literals
|
|
32
|
-
"".concat(userPlatform.name, " ").concat(userPlatform.version))), /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.Cell, null, t('Operating System')), /*#__PURE__*/React.createElement(Table.Cell, null, userPlatform.os.toString())))));
|
|
28
|
+
summary: summary
|
|
29
|
+
}, /*#__PURE__*/React.createElement(EventDetails, {
|
|
30
|
+
currentEventData: currentEventData,
|
|
31
|
+
validationErrors: validationErrors
|
|
32
|
+
}));
|
|
33
33
|
}
|
|
34
34
|
}]);
|
|
35
35
|
SessionStartEvent.displayName = "SessionStartEvent";
|
|
36
36
|
return SessionStartEvent;
|
|
37
37
|
}(Component);
|
|
38
38
|
SessionStartEvent.propTypes = {
|
|
39
|
-
|
|
39
|
+
validationErrors: sessionValidationErrorsPropType,
|
|
40
|
+
currentEventData: eventDataPropType
|
|
40
41
|
};
|
|
41
42
|
export default SessionStartEvent;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
|
|
2
|
+
import _createClass from "@babel/runtime/helpers/esm/createClass";
|
|
3
|
+
import _inherits from "@babel/runtime/helpers/esm/inherits";
|
|
4
|
+
import _createSuper from "@babel/runtime/helpers/esm/createSuper";
|
|
5
|
+
import React, { Component } from 'react';
|
|
6
|
+
import t from '@instructure/quiz-i18n/es/format-message';
|
|
7
|
+
import { EventDetails } from "../resources/EventDetails.js";
|
|
8
|
+
import { ToggleDetails } from '@instructure/ui-toggle-details';
|
|
9
|
+
import { Text } from '@instructure/ui-text';
|
|
10
|
+
import { sessionValidationErrorsPropType, eventDataPropType } from "../../../reporting/components/resources/common/propTypes.js";
|
|
11
|
+
export var SessionSubmitEvent = /*#__PURE__*/function (_Component) {
|
|
12
|
+
_inherits(SessionSubmitEvent, _Component);
|
|
13
|
+
var _super = _createSuper(SessionSubmitEvent);
|
|
14
|
+
function SessionSubmitEvent() {
|
|
15
|
+
_classCallCheck(this, SessionSubmitEvent);
|
|
16
|
+
return _super.apply(this, arguments);
|
|
17
|
+
}
|
|
18
|
+
_createClass(SessionSubmitEvent, [{
|
|
19
|
+
key: "render",
|
|
20
|
+
value: function render() {
|
|
21
|
+
var _this$props = this.props,
|
|
22
|
+
validationErrors = _this$props.validationErrors,
|
|
23
|
+
currentEventData = _this$props.currentEventData;
|
|
24
|
+
var summary = /*#__PURE__*/React.createElement(Text, {
|
|
25
|
+
color: validationErrors.isBreach ? 'danger' : 'primary'
|
|
26
|
+
}, t('Session submitted'));
|
|
27
|
+
return /*#__PURE__*/React.createElement(ToggleDetails, {
|
|
28
|
+
summary: summary
|
|
29
|
+
}, /*#__PURE__*/React.createElement(EventDetails, {
|
|
30
|
+
currentEventData: currentEventData,
|
|
31
|
+
validationErrors: validationErrors
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
}]);
|
|
35
|
+
SessionSubmitEvent.displayName = "SessionSubmitEvent";
|
|
36
|
+
return SessionSubmitEvent;
|
|
37
|
+
}(Component);
|
|
38
|
+
SessionSubmitEvent.propTypes = {
|
|
39
|
+
validationErrors: sessionValidationErrorsPropType,
|
|
40
|
+
currentEventData: eventDataPropType
|
|
41
|
+
};
|
|
42
|
+
export default SessionSubmitEvent;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Table } from '@instructure/ui-table';
|
|
2
|
+
import { Text } from '@instructure/ui-text';
|
|
3
|
+
import { IconButton } from '@instructure/ui-buttons';
|
|
4
|
+
import t from '@instructure/quiz-i18n/es/format-message';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { SessionBreachAlert } from "./SessionBreachAlert.js";
|
|
7
|
+
import { IconInfoLine } from '@instructure/ui-icons';
|
|
8
|
+
import { Tooltip } from '@instructure/ui-tooltip';
|
|
9
|
+
var _ref2 = /*#__PURE__*/React.createElement(SessionBreachAlert, null);
|
|
10
|
+
export var EventDetails = function EventDetails(_ref) {
|
|
11
|
+
var currentEventData = _ref.currentEventData,
|
|
12
|
+
validationErrors = _ref.validationErrors,
|
|
13
|
+
_ref$answerValue = _ref.answerValue,
|
|
14
|
+
answerValue = _ref$answerValue === void 0 ? null : _ref$answerValue;
|
|
15
|
+
var eventTimeString = "".concat(t('Event time'), ": ").concat(currentEventData.eventDate);
|
|
16
|
+
var renderTooltip = function renderTooltip(message) {
|
|
17
|
+
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
18
|
+
renderTip: message,
|
|
19
|
+
placement: "end",
|
|
20
|
+
on: ['click', 'hover', 'focus']
|
|
21
|
+
}, /*#__PURE__*/React.createElement(IconButton, {
|
|
22
|
+
screenReaderLabel: message,
|
|
23
|
+
renderIcon: IconInfoLine,
|
|
24
|
+
withBackground: false,
|
|
25
|
+
withBorder: false
|
|
26
|
+
}));
|
|
27
|
+
};
|
|
28
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, validationErrors.isBreach && _ref2, /*#__PURE__*/React.createElement("h4", null, eventTimeString), /*#__PURE__*/React.createElement(Table, {
|
|
29
|
+
caption: t('System details')
|
|
30
|
+
}, /*#__PURE__*/React.createElement(Table.Head, null, /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.ColHeader, {
|
|
31
|
+
id: "system-details-attribute"
|
|
32
|
+
}, t('Attribute')), /*#__PURE__*/React.createElement(Table.ColHeader, {
|
|
33
|
+
id: "system-details-value"
|
|
34
|
+
}, t('Value')))), /*#__PURE__*/React.createElement(Table.Body, null, /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.Cell, null, t('Client IP address')), /*#__PURE__*/React.createElement(Table.Cell, null, /*#__PURE__*/React.createElement(Text, {
|
|
35
|
+
color: validationErrors.ipAddress ? 'danger' : 'primary'
|
|
36
|
+
}, currentEventData.ipAddress), validationErrors.ipAddress && renderTooltip(validationErrors.ipAddress))), /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.Cell, null, t('Browser session ID')), /*#__PURE__*/React.createElement(Table.Cell, null, /*#__PURE__*/React.createElement(Text, {
|
|
37
|
+
color: validationErrors.browserSessionID ? 'danger' : 'primary'
|
|
38
|
+
}, currentEventData.browserSessionID), validationErrors.browserSessionID && renderTooltip(validationErrors.browserSessionID))), /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.Cell, null, t('Browser')), /*#__PURE__*/React.createElement(Table.Cell, null, /*#__PURE__*/React.createElement(Text, {
|
|
39
|
+
color: validationErrors.browserString ? 'danger' : 'primary'
|
|
40
|
+
}, currentEventData.browserString), validationErrors.browserString && renderTooltip(validationErrors.browserString))), /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.Cell, null, t('Operating System')), /*#__PURE__*/React.createElement(Table.Cell, null, /*#__PURE__*/React.createElement(Text, {
|
|
41
|
+
color: validationErrors.userPlatform ? 'danger' : 'primary'
|
|
42
|
+
}, currentEventData.userPlatform), validationErrors.userPlatform && renderTooltip(validationErrors.userPlatform))), answerValue && /*#__PURE__*/React.createElement(Table.Row, null, /*#__PURE__*/React.createElement(Table.Cell, null, t('Answer value')), /*#__PURE__*/React.createElement(Table.Cell, null, answerValue)))));
|
|
43
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Alert } from '@instructure/ui-alerts';
|
|
3
|
+
import t from '@instructure/quiz-i18n/es/format-message';
|
|
4
|
+
var _ref = /*#__PURE__*/React.createElement("br", null);
|
|
5
|
+
export var SessionBreachAlert = function SessionBreachAlert() {
|
|
6
|
+
return /*#__PURE__*/React.createElement(Alert, {
|
|
7
|
+
variant: "warning",
|
|
8
|
+
margin: "small"
|
|
9
|
+
}, t('Potential breach: This Quiz appears to have been accessed from multiple devices or browsers.'), _ref, t('Review the activity log and take appropriate action.'));
|
|
10
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import t from '@instructure/quiz-i18n/es/format-message';
|
|
2
|
+
export var checkIsBreach = function checkIsBreach(firstEventData, currentEventData) {
|
|
3
|
+
var result = {
|
|
4
|
+
isBreach: false
|
|
5
|
+
};
|
|
6
|
+
var checks = [{
|
|
7
|
+
key: 'ipAddress',
|
|
8
|
+
message: t('Client IP address is different from initial address')
|
|
9
|
+
}, {
|
|
10
|
+
key: 'browserSessionID',
|
|
11
|
+
message: t('Browser session ID is different from initial id')
|
|
12
|
+
}, {
|
|
13
|
+
key: 'browserString',
|
|
14
|
+
message: t('Browser is different from initial browser')
|
|
15
|
+
}, {
|
|
16
|
+
key: 'userPlatform',
|
|
17
|
+
message: t('Operating system is different from initial system')
|
|
18
|
+
}];
|
|
19
|
+
checks.forEach(function (_ref) {
|
|
20
|
+
var key = _ref.key,
|
|
21
|
+
message = _ref.message;
|
|
22
|
+
if (currentEventData[key] !== firstEventData[key]) {
|
|
23
|
+
result.isBreach = true;
|
|
24
|
+
result[key] = message;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import platform from 'platform';
|
|
2
|
+
import { formatDateTimeSeconds } from '@instructure/quiz-i18n';
|
|
3
|
+
export var extractSessionData = function extractSessionData(sourceData) {
|
|
4
|
+
if (!sourceData) return {
|
|
5
|
+
ipAddress: '',
|
|
6
|
+
browserSessionID: '',
|
|
7
|
+
eventDate: '',
|
|
8
|
+
browserString: '',
|
|
9
|
+
userPlatform: ''
|
|
10
|
+
};
|
|
11
|
+
var clientTimestamp = sourceData.getIn(['eventData', 'clientTimestamp']);
|
|
12
|
+
var userPlatform = platform.parse(sourceData.getIn(['eventData', 'userAgent']));
|
|
13
|
+
return {
|
|
14
|
+
ipAddress: sourceData.getIn(['eventData', 'ipAddress']),
|
|
15
|
+
browserSessionID: sourceData.getIn(['eventData', 'browserSessionId']),
|
|
16
|
+
eventDate: clientTimestamp ? formatDateTimeSeconds(clientTimestamp) : '',
|
|
17
|
+
browserString: "".concat(userPlatform.name, " ").concat(userPlatform.version),
|
|
18
|
+
userPlatform: userPlatform.os.toString()
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -163,4 +163,18 @@ export var afsFlags = PropTypes.shape({
|
|
|
163
163
|
numericEnabled: PropTypes.bool.isRequired,
|
|
164
164
|
essayEnabled: PropTypes.bool.isRequired,
|
|
165
165
|
fileUploadEnabled: PropTypes.bool.isRequired
|
|
166
|
+
});
|
|
167
|
+
export var sessionValidationErrorsPropType = PropTypes.shape({
|
|
168
|
+
isBreach: PropTypes.bool,
|
|
169
|
+
ipAddress: PropTypes.string,
|
|
170
|
+
browserSessionID: PropTypes.string,
|
|
171
|
+
browserString: PropTypes.string,
|
|
172
|
+
userPlatform: PropTypes.string
|
|
173
|
+
});
|
|
174
|
+
export var eventDataPropType = PropTypes.shape({
|
|
175
|
+
ipAddress: PropTypes.string,
|
|
176
|
+
browserSessionID: PropTypes.string,
|
|
177
|
+
eventDate: PropTypes.string,
|
|
178
|
+
browserString: PropTypes.string,
|
|
179
|
+
userPlatform: PropTypes.string
|
|
166
180
|
});
|
package/es/taking/api/taking.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
2
|
+
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
|
|
1
3
|
// =============================================
|
|
2
4
|
// =============================================
|
|
3
5
|
|
|
@@ -25,6 +27,7 @@ import { getSessionItems, handleQuizSessionFetch } from "../../common/actions/qu
|
|
|
25
27
|
import { handleSessionItemsResponse, handleResumeDataResponse, handleQuizSessionResponse } from "../../common/api/callHandlers.js";
|
|
26
28
|
import * as modalActions from "../../common/actions/modal.js";
|
|
27
29
|
import { backtrackingNextQuestion, sessionStartOrResumeEvent, setCurrentSessionItemPosition, submitQuizSessionEvent, updateQuizSubmitFlag, updateQuizSessionStatusOnPageLoad, updateTimerInfo } from "../../common/actions/taking.js";
|
|
30
|
+
import { getClientIpAddress } from "../../common/util/getClientIpAddress.js";
|
|
28
31
|
var getQuizSessionConfig = function getQuizSessionConfig(quizSession) {
|
|
29
32
|
// these should be handled by existing functions
|
|
30
33
|
var oneQuestionAtATime = quizSession.isOneQuestionAtATime();
|
|
@@ -144,9 +147,29 @@ export function newQuizTakingSession(quizSessionId) {
|
|
|
144
147
|
};
|
|
145
148
|
}
|
|
146
149
|
export function onQuizSessionReadyForTaking(quizSession) {
|
|
147
|
-
return function (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
return /*#__PURE__*/function () {
|
|
151
|
+
var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(dispatch) {
|
|
152
|
+
var ipAddress;
|
|
153
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
154
|
+
while (1) {
|
|
155
|
+
switch (_context.prev = _context.next) {
|
|
156
|
+
case 0:
|
|
157
|
+
_context.next = 2;
|
|
158
|
+
return getClientIpAddress(quizSession.id);
|
|
159
|
+
case 2:
|
|
160
|
+
ipAddress = _context.sent;
|
|
161
|
+
dispatch([makeSessionItemCall(quizSession), getResumeData(quizSession.id), sessionStartOrResumeEvent(navigator.userAgent, ipAddress), updateQuizSessionStatusOnPageLoad(quizSession.status)]);
|
|
162
|
+
case 4:
|
|
163
|
+
case "end":
|
|
164
|
+
return _context.stop();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}, _callee);
|
|
168
|
+
}));
|
|
169
|
+
return function (_x) {
|
|
170
|
+
return _ref.apply(this, arguments);
|
|
171
|
+
};
|
|
172
|
+
}();
|
|
150
173
|
}
|
|
151
174
|
|
|
152
175
|
// ====================================
|
|
@@ -122,12 +122,13 @@ var loadResumeData = function loadResumeData(resumeData) {
|
|
|
122
122
|
};
|
|
123
123
|
};
|
|
124
124
|
exports.loadResumeData = loadResumeData;
|
|
125
|
-
var sessionStartOrResumeEvent = function sessionStartOrResumeEvent(userAgent) {
|
|
125
|
+
var sessionStartOrResumeEvent = function sessionStartOrResumeEvent(userAgent, ipAddress) {
|
|
126
126
|
return {
|
|
127
127
|
type: _quizCommon.SESSION_STARTED_OR_RESUMED_EVENT,
|
|
128
128
|
payload: {
|
|
129
129
|
userAgent: userAgent,
|
|
130
|
-
browserSessionId: (0, _uuid.v4)()
|
|
130
|
+
browserSessionId: (0, _uuid.v4)(),
|
|
131
|
+
ipAddress: ipAddress
|
|
131
132
|
}
|
|
132
133
|
};
|
|
133
134
|
};
|
|
@@ -73,7 +73,11 @@ var loadResponses = function loadResponses(state, payload) {
|
|
|
73
73
|
return payload.reduce(modifyResponse, state);
|
|
74
74
|
};
|
|
75
75
|
var markSessionStartedIdentifier = function markSessionStartedIdentifier(state, payload) {
|
|
76
|
-
|
|
76
|
+
var needsUpdate = state.get('browserSessionId') !== payload.browserSessionId || state.get('ipAddress') !== payload.ipAddress;
|
|
77
|
+
return needsUpdate ? state.merge({
|
|
78
|
+
browserSessionId: payload.browserSessionId,
|
|
79
|
+
ipAddress: payload.ipAddress
|
|
80
|
+
}) : state;
|
|
77
81
|
};
|
|
78
82
|
var updateQuizSubmitFlag = function updateQuizSubmitFlag(state, payload) {
|
|
79
83
|
return state.set('submitFlag', payload.value);
|
|
@@ -8,7 +8,7 @@ exports.isTimeUp = isTimeUp;
|
|
|
8
8
|
exports.getResponsesByIds = getResponsesByIds;
|
|
9
9
|
exports.getResponseById = getResponseById;
|
|
10
10
|
exports.getResponseDataByPosition = getResponseDataByPosition;
|
|
11
|
-
exports.getIsTaking = exports.getTimerData = exports.getBrowserSessionId = exports.getCurrentSessionItemPosition = void 0;
|
|
11
|
+
exports.getIsTaking = exports.getTimerData = exports.getIpAddress = exports.getBrowserSessionId = exports.getCurrentSessionItemPosition = void 0;
|
|
12
12
|
var _immutable = require("immutable");
|
|
13
13
|
var _Response = _interopRequireDefault(require("../records/Response.js"));
|
|
14
14
|
var path = 'taking';
|
|
@@ -20,6 +20,10 @@ var getBrowserSessionId = function getBrowserSessionId(state) {
|
|
|
20
20
|
return state.getIn([path, 'browserSessionId']);
|
|
21
21
|
};
|
|
22
22
|
exports.getBrowserSessionId = getBrowserSessionId;
|
|
23
|
+
var getIpAddress = function getIpAddress(state) {
|
|
24
|
+
return state.getIn([path, 'ipAddress']);
|
|
25
|
+
};
|
|
26
|
+
exports.getIpAddress = getIpAddress;
|
|
23
27
|
var getTimerData = function getTimerData(state) {
|
|
24
28
|
return state.getIn([path, 'timerData'], (0, _immutable.Map)());
|
|
25
29
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.getClientIpAddress = void 0;
|
|
8
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
9
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
10
|
+
var _Fetcher = _interopRequireDefault(require("./Fetcher.js"));
|
|
11
|
+
var getClientIpAddress = /*#__PURE__*/function () {
|
|
12
|
+
var _ref = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee(quizSessionId) {
|
|
13
|
+
var url, fetcher, _yield$fetcher$get, ip;
|
|
14
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
15
|
+
while (1) {
|
|
16
|
+
switch (_context.prev = _context.next) {
|
|
17
|
+
case 0:
|
|
18
|
+
_context.prev = 0;
|
|
19
|
+
url = "/api/quiz_sessions/".concat(quizSessionId, "/get_ip_address");
|
|
20
|
+
fetcher = new _Fetcher.default();
|
|
21
|
+
_context.next = 5;
|
|
22
|
+
return fetcher.get(url);
|
|
23
|
+
case 5:
|
|
24
|
+
_yield$fetcher$get = _context.sent;
|
|
25
|
+
ip = _yield$fetcher$get.ip;
|
|
26
|
+
return _context.abrupt("return", ip);
|
|
27
|
+
case 10:
|
|
28
|
+
_context.prev = 10;
|
|
29
|
+
_context.t0 = _context["catch"](0);
|
|
30
|
+
console.error(_context.t0);
|
|
31
|
+
return _context.abrupt("return", null);
|
|
32
|
+
case 14:
|
|
33
|
+
case "end":
|
|
34
|
+
return _context.stop();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, _callee, null, [[0, 10]]);
|
|
38
|
+
}));
|
|
39
|
+
return function getClientIpAddress(_x) {
|
|
40
|
+
return _ref.apply(this, arguments);
|
|
41
|
+
};
|
|
42
|
+
}();
|
|
43
|
+
exports.getClientIpAddress = getClientIpAddress;
|
|
@@ -17,8 +17,10 @@ function serializeEvent(action, store) {
|
|
|
17
17
|
var state = store.getState();
|
|
18
18
|
var quizSession = (0, _quizSessions.getActiveQuizSession)(state);
|
|
19
19
|
var defaultEventData = {
|
|
20
|
+
userAgent: navigator.userAgent,
|
|
20
21
|
browserSessionId: (0, _taking.getBrowserSessionId)(state),
|
|
21
|
-
clientTimestamp: new Date()
|
|
22
|
+
clientTimestamp: new Date(),
|
|
23
|
+
ipAddress: (0, _taking.getIpAddress)(state)
|
|
22
24
|
};
|
|
23
25
|
var authoritativeData = {
|
|
24
26
|
accountUuid: quizSession.accountUuid,
|
package/lib/index.js
CHANGED
|
@@ -118,6 +118,7 @@ var _exportNames = {
|
|
|
118
118
|
setThemeByKey: true,
|
|
119
119
|
getActiveTheme: true,
|
|
120
120
|
setActiveTheme: true,
|
|
121
|
+
getClientIpAddress: true,
|
|
121
122
|
createQuizEntryRegrade: true,
|
|
122
123
|
getQuizEntry: true,
|
|
123
124
|
moveQuizEntry: true,
|
|
@@ -892,6 +893,12 @@ Object.defineProperty(exports, "setActiveTheme", {
|
|
|
892
893
|
return _quizCommon.setActiveTheme;
|
|
893
894
|
}
|
|
894
895
|
});
|
|
896
|
+
Object.defineProperty(exports, "getClientIpAddress", {
|
|
897
|
+
enumerable: true,
|
|
898
|
+
get: function get() {
|
|
899
|
+
return _getClientIpAddress.getClientIpAddress;
|
|
900
|
+
}
|
|
901
|
+
});
|
|
895
902
|
Object.defineProperty(exports, "createQuizEntryRegrade", {
|
|
896
903
|
enumerable: true,
|
|
897
904
|
get: function get() {
|
|
@@ -1578,6 +1585,7 @@ var _sessionStore = require("./common/util/sessionStore.js");
|
|
|
1578
1585
|
var _printUtils = require("./common/util/printUtils.js");
|
|
1579
1586
|
var _rceChecker = require("./common/util/rceChecker.js");
|
|
1580
1587
|
var _quizCommon = require("@instructure/quiz-common");
|
|
1588
|
+
var _getClientIpAddress = require("./common/util/getClientIpAddress.js");
|
|
1581
1589
|
var _quizEntryRegrades = require("./grading/api/quizEntryRegrades.js");
|
|
1582
1590
|
var _quizEntries = require("./building/api/quizEntries.js");
|
|
1583
1591
|
var _items = require("./building/api/items.js");
|
|
@@ -18,9 +18,12 @@ var _uiTable = require("@instructure/ui-table");
|
|
|
18
18
|
var _ElapsedTime = _interopRequireDefault(require("../resources/ElapsedTime.js"));
|
|
19
19
|
var _ResponseEvent = _interopRequireDefault(require("./ResponseEvent.js"));
|
|
20
20
|
var _SessionStartEvent = _interopRequireDefault(require("./SessionStartEvent.js"));
|
|
21
|
+
var _SessionSubmitEvent = require("./SessionSubmitEvent.js");
|
|
21
22
|
var _RCEEvent = _interopRequireDefault(require("./RCEEvent.js"));
|
|
22
23
|
var _PageBlurredEvent = _interopRequireDefault(require("./PageBlurredEvent.js"));
|
|
23
24
|
var _PageFocusedEvent = _interopRequireDefault(require("./PageFocusedEvent.js"));
|
|
25
|
+
var _extractSessionData = require("../resources/util/extractSessionData.js");
|
|
26
|
+
var _checkIsBreach = require("../resources/util/checkIsBreach.js");
|
|
24
27
|
var _Event;
|
|
25
28
|
var _ref = /*#__PURE__*/_react.default.createElement(_PageBlurredEvent.default, null);
|
|
26
29
|
var _ref2 = /*#__PURE__*/_react.default.createElement(_PageFocusedEvent.default, null);
|
|
@@ -33,26 +36,34 @@ var Event = /*#__PURE__*/function (_Component) {
|
|
|
33
36
|
}
|
|
34
37
|
(0, _createClass2.default)(Event, [{
|
|
35
38
|
key: "renderResponseEvent",
|
|
36
|
-
value: function renderResponseEvent(event) {
|
|
39
|
+
value: function renderResponseEvent(event, validationErrors, currentEventData) {
|
|
37
40
|
var item = event.getItem();
|
|
38
41
|
if (!item) {
|
|
39
42
|
return null;
|
|
40
43
|
} else {
|
|
41
44
|
return /*#__PURE__*/_react.default.createElement(_ResponseEvent.default, {
|
|
42
45
|
event: event,
|
|
43
|
-
item: item
|
|
46
|
+
item: item,
|
|
47
|
+
validationErrors: validationErrors,
|
|
48
|
+
currentEventData: currentEventData
|
|
44
49
|
});
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
}, {
|
|
48
53
|
key: "renderPayload",
|
|
49
|
-
value: function renderPayload(event) {
|
|
54
|
+
value: function renderPayload(event, validationErrors, currentEventData) {
|
|
50
55
|
switch (event.eventType) {
|
|
51
56
|
case _quizCommon.RESPONSE:
|
|
52
|
-
return this.renderResponseEvent(event);
|
|
57
|
+
return this.renderResponseEvent(event, validationErrors, currentEventData);
|
|
53
58
|
case _quizCommon.SESSION_STARTED_OR_RESUMED_EVENT:
|
|
54
59
|
return /*#__PURE__*/_react.default.createElement(_SessionStartEvent.default, {
|
|
55
|
-
|
|
60
|
+
validationErrors: validationErrors,
|
|
61
|
+
currentEventData: currentEventData
|
|
62
|
+
});
|
|
63
|
+
case _quizCommon.QUIZ_SUBMISSION_EVENT:
|
|
64
|
+
return /*#__PURE__*/_react.default.createElement(_SessionSubmitEvent.SessionSubmitEvent, {
|
|
65
|
+
validationErrors: validationErrors,
|
|
66
|
+
currentEventData: currentEventData
|
|
56
67
|
});
|
|
57
68
|
case _quizCommon.RCE_EVENT:
|
|
58
69
|
return /*#__PURE__*/_react.default.createElement(_RCEEvent.default, {
|
|
@@ -72,8 +83,12 @@ var Event = /*#__PURE__*/function (_Component) {
|
|
|
72
83
|
value: function render() {
|
|
73
84
|
var _this$props = this.props,
|
|
74
85
|
event = _this$props.event,
|
|
75
|
-
startTime = _this$props.startTime
|
|
76
|
-
|
|
86
|
+
startTime = _this$props.startTime,
|
|
87
|
+
firstEvent = _this$props.firstEvent;
|
|
88
|
+
var currentEventData = (0, _extractSessionData.extractSessionData)(event);
|
|
89
|
+
var firstEventData = (0, _extractSessionData.extractSessionData)(firstEvent);
|
|
90
|
+
var validationErrors = (0, _checkIsBreach.checkIsBreach)(firstEventData, currentEventData);
|
|
91
|
+
var payloadNode = this.renderPayload(event, validationErrors, currentEventData);
|
|
77
92
|
if (!payloadNode) return null;
|
|
78
93
|
return /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, /*#__PURE__*/_react.default.createElement(_ElapsedTime.default, {
|
|
79
94
|
eventTime: event.createdDate(),
|
|
@@ -91,11 +106,13 @@ Event.displayName = 'Row';
|
|
|
91
106
|
Event.componentId = "Quizzes".concat(_Event.displayName);
|
|
92
107
|
Event.propTypes = {
|
|
93
108
|
event: _reactImmutableProptypes.default.record,
|
|
94
|
-
startTime: _propTypes.default.instanceOf(Date)
|
|
109
|
+
startTime: _propTypes.default.instanceOf(Date),
|
|
110
|
+
firstEvent: _reactImmutableProptypes.default.record
|
|
95
111
|
};
|
|
96
112
|
Event.defaultProps = {
|
|
97
113
|
event: void 0,
|
|
98
|
-
startTime: void 0
|
|
114
|
+
startTime: void 0,
|
|
115
|
+
firstEvent: void 0
|
|
99
116
|
};
|
|
100
117
|
var _default = Event;
|
|
101
118
|
exports.default = _default;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
|
|
4
5
|
Object.defineProperty(exports, "__esModule", {
|
|
5
6
|
value: true
|
|
6
7
|
});
|
|
@@ -9,7 +10,7 @@ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/cl
|
|
|
9
10
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
11
|
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
|
|
11
12
|
var _createSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/createSuper"));
|
|
12
|
-
var _react = require("react");
|
|
13
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
13
14
|
var _reactImmutableProptypes = _interopRequireDefault(require("react-immutable-proptypes"));
|
|
14
15
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
15
16
|
var _uiIcons = require("@instructure/ui-icons");
|
|
@@ -19,9 +20,11 @@ var _uiToggleDetails = require("@instructure/ui-toggle-details");
|
|
|
19
20
|
var _emotion = require("@instructure/emotion");
|
|
20
21
|
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/format-message"));
|
|
21
22
|
var _quizInteractions = require("@instructure/quiz-interactions");
|
|
23
|
+
var _EventDetails = require("../resources/EventDetails.js");
|
|
22
24
|
var _styles = _interopRequireDefault(require("./styles.js"));
|
|
23
25
|
var _theme = _interopRequireDefault(require("./theme.js"));
|
|
24
26
|
var _quizCommon = require("@instructure/quiz-common");
|
|
27
|
+
var _propTypes2 = require("../../../reporting/components/resources/common/propTypes.js");
|
|
25
28
|
var _dec, _class, _ResponseEvent;
|
|
26
29
|
/** @jsx jsx */
|
|
27
30
|
var imgRegex = /<img.*?src=['"](.+)['"]/g;
|
|
@@ -51,13 +54,16 @@ var ResponseEvent = (_dec = (0, _quizCommon.withStyleOverrides)(_styles.default,
|
|
|
51
54
|
}, {
|
|
52
55
|
key: "renderTextSummary",
|
|
53
56
|
value: function renderTextSummary() {
|
|
57
|
+
var isBreach = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : false;
|
|
54
58
|
var strippedItemBody = (0, _quizInteractions.extractTextFromHtml)(this.itemBody);
|
|
55
59
|
return (0, _emotion.jsx)("span", null, this.hasMedia() && (0, _emotion.jsx)(_uiIcons.IconImageSolid, {
|
|
56
60
|
css: this.props.styles.answerMediaIcon
|
|
57
|
-
}), (0,
|
|
61
|
+
}), (0, _emotion.jsx)(_uiText.Text, {
|
|
62
|
+
color: isBreach ? 'danger' : 'primary'
|
|
63
|
+
}, (0, _formatMessage.default)('Answered question {position}: {question}', {
|
|
58
64
|
position: this.props.event.getIn(['eventData', 'position']),
|
|
59
65
|
question: strippedItemBody
|
|
60
|
-
}));
|
|
66
|
+
})));
|
|
61
67
|
}
|
|
62
68
|
}, {
|
|
63
69
|
key: "renderMedia",
|
|
@@ -85,16 +91,26 @@ var ResponseEvent = (_dec = (0, _quizCommon.withStyleOverrides)(_styles.default,
|
|
|
85
91
|
}, {
|
|
86
92
|
key: "render",
|
|
87
93
|
value: function render() {
|
|
94
|
+
var _this$props = this.props,
|
|
95
|
+
validationErrors = _this$props.validationErrors,
|
|
96
|
+
currentEventData = _this$props.currentEventData;
|
|
88
97
|
var responseText = this.getReadableResponseText();
|
|
89
98
|
if (responseText) {
|
|
90
99
|
// If this returns falsey, then we know to just display the stem
|
|
91
100
|
return (0, _emotion.jsx)(_uiToggleDetails.ToggleDetails, {
|
|
92
|
-
summary: this.renderTextSummary()
|
|
93
|
-
}, this.renderMedia(),
|
|
101
|
+
summary: this.renderTextSummary(validationErrors.isBreach)
|
|
102
|
+
}, this.renderMedia(), (0, _emotion.jsx)(_EventDetails.EventDetails, {
|
|
103
|
+
currentEventData: currentEventData,
|
|
104
|
+
validationErrors: validationErrors,
|
|
105
|
+
answerValue: responseText
|
|
106
|
+
}));
|
|
94
107
|
} else {
|
|
95
108
|
return (0, _emotion.jsx)(_uiText.Text, {
|
|
96
109
|
size: "small"
|
|
97
|
-
}, this.renderTextSummary(),
|
|
110
|
+
}, this.renderTextSummary(validationErrors.isBreach), (0, _emotion.jsx)(_EventDetails.EventDetails, {
|
|
111
|
+
currentEventData: currentEventData,
|
|
112
|
+
validationErrors: validationErrors
|
|
113
|
+
}), this.renderMedia());
|
|
98
114
|
}
|
|
99
115
|
}
|
|
100
116
|
}, {
|
|
@@ -108,7 +124,9 @@ var ResponseEvent = (_dec = (0, _quizCommon.withStyleOverrides)(_styles.default,
|
|
|
108
124
|
}(_react.Component), _ResponseEvent.displayName = 'ResponseEvent', _ResponseEvent.componentId = "Quizzes".concat(_ResponseEvent.displayName), _ResponseEvent.propTypes = {
|
|
109
125
|
event: _reactImmutableProptypes.default.record.isRequired,
|
|
110
126
|
item: _reactImmutableProptypes.default.record.isRequired,
|
|
111
|
-
styles: _propTypes.default.object
|
|
127
|
+
styles: _propTypes.default.object,
|
|
128
|
+
validationErrors: _propTypes2.sessionValidationErrorsPropType,
|
|
129
|
+
currentEventData: _propTypes2.eventDataPropType
|
|
112
130
|
}, _ResponseEvent)) || _class);
|
|
113
131
|
exports.ResponseEvent = ResponseEvent;
|
|
114
132
|
var _default = ResponseEvent;
|
|
@@ -11,11 +11,11 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
|
|
|
11
11
|
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
|
|
12
12
|
var _createSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/createSuper"));
|
|
13
13
|
var _react = _interopRequireWildcard(require("react"));
|
|
14
|
-
var
|
|
15
|
-
var _platform = _interopRequireDefault(require("platform"));
|
|
14
|
+
var _EventDetails = require("../resources/EventDetails.js");
|
|
16
15
|
var _uiToggleDetails = require("@instructure/ui-toggle-details");
|
|
17
|
-
var _uiTable = require("@instructure/ui-table");
|
|
18
16
|
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/format-message"));
|
|
17
|
+
var _uiText = require("@instructure/ui-text");
|
|
18
|
+
var _propTypes = require("../../../reporting/components/resources/common/propTypes.js");
|
|
19
19
|
var SessionStartEvent = /*#__PURE__*/function (_Component) {
|
|
20
20
|
(0, _inherits2.default)(SessionStartEvent, _Component);
|
|
21
21
|
var _super = (0, _createSuper2.default)(SessionStartEvent);
|
|
@@ -26,18 +26,18 @@ var SessionStartEvent = /*#__PURE__*/function (_Component) {
|
|
|
26
26
|
(0, _createClass2.default)(SessionStartEvent, [{
|
|
27
27
|
key: "render",
|
|
28
28
|
value: function render() {
|
|
29
|
-
var
|
|
30
|
-
|
|
29
|
+
var _this$props = this.props,
|
|
30
|
+
validationErrors = _this$props.validationErrors,
|
|
31
|
+
currentEventData = _this$props.currentEventData;
|
|
32
|
+
var summary = /*#__PURE__*/_react.default.createElement(_uiText.Text, {
|
|
33
|
+
color: validationErrors.isBreach ? 'danger' : 'primary'
|
|
34
|
+
}, (0, _formatMessage.default)('Session started'));
|
|
31
35
|
return /*#__PURE__*/_react.default.createElement(_uiToggleDetails.ToggleDetails, {
|
|
32
|
-
summary:
|
|
33
|
-
}, /*#__PURE__*/_react.default.createElement(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}, (0, _formatMessage.default)('attribute')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.ColHeader, {
|
|
38
|
-
id: "system-details-value"
|
|
39
|
-
}, (0, _formatMessage.default)('value')))), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Body, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, (0, _formatMessage.default)('Browser')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, // eslint-disable-next-line react/jsx-no-literals
|
|
40
|
-
"".concat(userPlatform.name, " ").concat(userPlatform.version))), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, (0, _formatMessage.default)('Operating System')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, userPlatform.os.toString())))));
|
|
36
|
+
summary: summary
|
|
37
|
+
}, /*#__PURE__*/_react.default.createElement(_EventDetails.EventDetails, {
|
|
38
|
+
currentEventData: currentEventData,
|
|
39
|
+
validationErrors: validationErrors
|
|
40
|
+
}));
|
|
41
41
|
}
|
|
42
42
|
}]);
|
|
43
43
|
SessionStartEvent.displayName = "SessionStartEvent";
|
|
@@ -45,7 +45,8 @@ var SessionStartEvent = /*#__PURE__*/function (_Component) {
|
|
|
45
45
|
}(_react.Component);
|
|
46
46
|
exports.SessionStartEvent = SessionStartEvent;
|
|
47
47
|
SessionStartEvent.propTypes = {
|
|
48
|
-
|
|
48
|
+
validationErrors: _propTypes.sessionValidationErrorsPropType,
|
|
49
|
+
currentEventData: _propTypes.eventDataPropType
|
|
49
50
|
};
|
|
50
51
|
var _default = SessionStartEvent;
|
|
51
52
|
exports.default = _default;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.default = exports.SessionSubmitEvent = void 0;
|
|
9
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
10
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
11
|
+
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
|
|
12
|
+
var _createSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/createSuper"));
|
|
13
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
14
|
+
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/format-message"));
|
|
15
|
+
var _EventDetails = require("../resources/EventDetails.js");
|
|
16
|
+
var _uiToggleDetails = require("@instructure/ui-toggle-details");
|
|
17
|
+
var _uiText = require("@instructure/ui-text");
|
|
18
|
+
var _propTypes = require("../../../reporting/components/resources/common/propTypes.js");
|
|
19
|
+
var SessionSubmitEvent = /*#__PURE__*/function (_Component) {
|
|
20
|
+
(0, _inherits2.default)(SessionSubmitEvent, _Component);
|
|
21
|
+
var _super = (0, _createSuper2.default)(SessionSubmitEvent);
|
|
22
|
+
function SessionSubmitEvent() {
|
|
23
|
+
(0, _classCallCheck2.default)(this, SessionSubmitEvent);
|
|
24
|
+
return _super.apply(this, arguments);
|
|
25
|
+
}
|
|
26
|
+
(0, _createClass2.default)(SessionSubmitEvent, [{
|
|
27
|
+
key: "render",
|
|
28
|
+
value: function render() {
|
|
29
|
+
var _this$props = this.props,
|
|
30
|
+
validationErrors = _this$props.validationErrors,
|
|
31
|
+
currentEventData = _this$props.currentEventData;
|
|
32
|
+
var summary = /*#__PURE__*/_react.default.createElement(_uiText.Text, {
|
|
33
|
+
color: validationErrors.isBreach ? 'danger' : 'primary'
|
|
34
|
+
}, (0, _formatMessage.default)('Session submitted'));
|
|
35
|
+
return /*#__PURE__*/_react.default.createElement(_uiToggleDetails.ToggleDetails, {
|
|
36
|
+
summary: summary
|
|
37
|
+
}, /*#__PURE__*/_react.default.createElement(_EventDetails.EventDetails, {
|
|
38
|
+
currentEventData: currentEventData,
|
|
39
|
+
validationErrors: validationErrors
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
}]);
|
|
43
|
+
SessionSubmitEvent.displayName = "SessionSubmitEvent";
|
|
44
|
+
return SessionSubmitEvent;
|
|
45
|
+
}(_react.Component);
|
|
46
|
+
exports.SessionSubmitEvent = SessionSubmitEvent;
|
|
47
|
+
SessionSubmitEvent.propTypes = {
|
|
48
|
+
validationErrors: _propTypes.sessionValidationErrorsPropType,
|
|
49
|
+
currentEventData: _propTypes.eventDataPropType
|
|
50
|
+
};
|
|
51
|
+
var _default = SessionSubmitEvent;
|
|
52
|
+
exports.default = _default;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.EventDetails = void 0;
|
|
8
|
+
var _uiTable = require("@instructure/ui-table");
|
|
9
|
+
var _uiText = require("@instructure/ui-text");
|
|
10
|
+
var _uiButtons = require("@instructure/ui-buttons");
|
|
11
|
+
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/format-message"));
|
|
12
|
+
var _react = _interopRequireDefault(require("react"));
|
|
13
|
+
var _SessionBreachAlert = require("./SessionBreachAlert.js");
|
|
14
|
+
var _uiIcons = require("@instructure/ui-icons");
|
|
15
|
+
var _uiTooltip = require("@instructure/ui-tooltip");
|
|
16
|
+
var _ref2 = /*#__PURE__*/_react.default.createElement(_SessionBreachAlert.SessionBreachAlert, null);
|
|
17
|
+
var EventDetails = function EventDetails(_ref) {
|
|
18
|
+
var currentEventData = _ref.currentEventData,
|
|
19
|
+
validationErrors = _ref.validationErrors,
|
|
20
|
+
_ref$answerValue = _ref.answerValue,
|
|
21
|
+
answerValue = _ref$answerValue === void 0 ? null : _ref$answerValue;
|
|
22
|
+
var eventTimeString = "".concat((0, _formatMessage.default)('Event time'), ": ").concat(currentEventData.eventDate);
|
|
23
|
+
var renderTooltip = function renderTooltip(message) {
|
|
24
|
+
return /*#__PURE__*/_react.default.createElement(_uiTooltip.Tooltip, {
|
|
25
|
+
renderTip: message,
|
|
26
|
+
placement: "end",
|
|
27
|
+
on: ['click', 'hover', 'focus']
|
|
28
|
+
}, /*#__PURE__*/_react.default.createElement(_uiButtons.IconButton, {
|
|
29
|
+
screenReaderLabel: message,
|
|
30
|
+
renderIcon: _uiIcons.IconInfoLine,
|
|
31
|
+
withBackground: false,
|
|
32
|
+
withBorder: false
|
|
33
|
+
}));
|
|
34
|
+
};
|
|
35
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, validationErrors.isBreach && _ref2, /*#__PURE__*/_react.default.createElement("h4", null, eventTimeString), /*#__PURE__*/_react.default.createElement(_uiTable.Table, {
|
|
36
|
+
caption: (0, _formatMessage.default)('System details')
|
|
37
|
+
}, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Head, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.ColHeader, {
|
|
38
|
+
id: "system-details-attribute"
|
|
39
|
+
}, (0, _formatMessage.default)('Attribute')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.ColHeader, {
|
|
40
|
+
id: "system-details-value"
|
|
41
|
+
}, (0, _formatMessage.default)('Value')))), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Body, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, (0, _formatMessage.default)('Client IP address')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, /*#__PURE__*/_react.default.createElement(_uiText.Text, {
|
|
42
|
+
color: validationErrors.ipAddress ? 'danger' : 'primary'
|
|
43
|
+
}, currentEventData.ipAddress), validationErrors.ipAddress && renderTooltip(validationErrors.ipAddress))), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, (0, _formatMessage.default)('Browser session ID')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, /*#__PURE__*/_react.default.createElement(_uiText.Text, {
|
|
44
|
+
color: validationErrors.browserSessionID ? 'danger' : 'primary'
|
|
45
|
+
}, currentEventData.browserSessionID), validationErrors.browserSessionID && renderTooltip(validationErrors.browserSessionID))), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, (0, _formatMessage.default)('Browser')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, /*#__PURE__*/_react.default.createElement(_uiText.Text, {
|
|
46
|
+
color: validationErrors.browserString ? 'danger' : 'primary'
|
|
47
|
+
}, currentEventData.browserString), validationErrors.browserString && renderTooltip(validationErrors.browserString))), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, (0, _formatMessage.default)('Operating System')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, /*#__PURE__*/_react.default.createElement(_uiText.Text, {
|
|
48
|
+
color: validationErrors.userPlatform ? 'danger' : 'primary'
|
|
49
|
+
}, currentEventData.userPlatform), validationErrors.userPlatform && renderTooltip(validationErrors.userPlatform))), answerValue && /*#__PURE__*/_react.default.createElement(_uiTable.Table.Row, null, /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, (0, _formatMessage.default)('Answer value')), /*#__PURE__*/_react.default.createElement(_uiTable.Table.Cell, null, answerValue)))));
|
|
50
|
+
};
|
|
51
|
+
exports.EventDetails = EventDetails;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.SessionBreachAlert = void 0;
|
|
8
|
+
var _react = _interopRequireDefault(require("react"));
|
|
9
|
+
var _uiAlerts = require("@instructure/ui-alerts");
|
|
10
|
+
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/format-message"));
|
|
11
|
+
var _ref = /*#__PURE__*/_react.default.createElement("br", null);
|
|
12
|
+
var SessionBreachAlert = function SessionBreachAlert() {
|
|
13
|
+
return /*#__PURE__*/_react.default.createElement(_uiAlerts.Alert, {
|
|
14
|
+
variant: "warning",
|
|
15
|
+
margin: "small"
|
|
16
|
+
}, (0, _formatMessage.default)('Potential breach: This Quiz appears to have been accessed from multiple devices or browsers.'), _ref, (0, _formatMessage.default)('Review the activity log and take appropriate action.'));
|
|
17
|
+
};
|
|
18
|
+
exports.SessionBreachAlert = SessionBreachAlert;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.checkIsBreach = void 0;
|
|
8
|
+
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/format-message"));
|
|
9
|
+
var checkIsBreach = function checkIsBreach(firstEventData, currentEventData) {
|
|
10
|
+
var result = {
|
|
11
|
+
isBreach: false
|
|
12
|
+
};
|
|
13
|
+
var checks = [{
|
|
14
|
+
key: 'ipAddress',
|
|
15
|
+
message: (0, _formatMessage.default)('Client IP address is different from initial address')
|
|
16
|
+
}, {
|
|
17
|
+
key: 'browserSessionID',
|
|
18
|
+
message: (0, _formatMessage.default)('Browser session ID is different from initial id')
|
|
19
|
+
}, {
|
|
20
|
+
key: 'browserString',
|
|
21
|
+
message: (0, _formatMessage.default)('Browser is different from initial browser')
|
|
22
|
+
}, {
|
|
23
|
+
key: 'userPlatform',
|
|
24
|
+
message: (0, _formatMessage.default)('Operating system is different from initial system')
|
|
25
|
+
}];
|
|
26
|
+
checks.forEach(function (_ref) {
|
|
27
|
+
var key = _ref.key,
|
|
28
|
+
message = _ref.message;
|
|
29
|
+
if (currentEventData[key] !== firstEventData[key]) {
|
|
30
|
+
result.isBreach = true;
|
|
31
|
+
result[key] = message;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
exports.checkIsBreach = checkIsBreach;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.extractSessionData = void 0;
|
|
8
|
+
var _platform = _interopRequireDefault(require("platform"));
|
|
9
|
+
var _quizI18n = require("@instructure/quiz-i18n");
|
|
10
|
+
var extractSessionData = function extractSessionData(sourceData) {
|
|
11
|
+
if (!sourceData) return {
|
|
12
|
+
ipAddress: '',
|
|
13
|
+
browserSessionID: '',
|
|
14
|
+
eventDate: '',
|
|
15
|
+
browserString: '',
|
|
16
|
+
userPlatform: ''
|
|
17
|
+
};
|
|
18
|
+
var clientTimestamp = sourceData.getIn(['eventData', 'clientTimestamp']);
|
|
19
|
+
var userPlatform = _platform.default.parse(sourceData.getIn(['eventData', 'userAgent']));
|
|
20
|
+
return {
|
|
21
|
+
ipAddress: sourceData.getIn(['eventData', 'ipAddress']),
|
|
22
|
+
browserSessionID: sourceData.getIn(['eventData', 'browserSessionId']),
|
|
23
|
+
eventDate: clientTimestamp ? (0, _quizI18n.formatDateTimeSeconds)(clientTimestamp) : '',
|
|
24
|
+
browserString: "".concat(userPlatform.name, " ").concat(userPlatform.version),
|
|
25
|
+
userPlatform: userPlatform.os.toString()
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
exports.extractSessionData = extractSessionData;
|
|
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.afsFlags = exports.itemAnalysisPropType = exports.answerSummaryPropType = exports.numericTypeAnswerSummaryPropType = exports.richFillBlankTypeAnswerSummaryPropType = exports.matchingTypeAnswerSummaryPropType = exports.scoreDistributionType = exports.scoreDistributionTypeAnswerSummaryPropType = exports.choiceTypeAnswerSummaryPropType = exports.choiceTypeObject = exports.onlyAggregatePropType = exports.aggregatePropType = exports.aggregateDetailPropType = exports.studentAnalysesPropType = exports.quizAnalysisPropType = exports.quizAnalysisMetadata = void 0;
|
|
7
|
+
exports.eventDataPropType = exports.sessionValidationErrorsPropType = exports.afsFlags = exports.itemAnalysisPropType = exports.answerSummaryPropType = exports.numericTypeAnswerSummaryPropType = exports.richFillBlankTypeAnswerSummaryPropType = exports.matchingTypeAnswerSummaryPropType = exports.scoreDistributionType = exports.scoreDistributionTypeAnswerSummaryPropType = exports.choiceTypeAnswerSummaryPropType = exports.choiceTypeObject = exports.onlyAggregatePropType = exports.aggregatePropType = exports.aggregateDetailPropType = exports.studentAnalysesPropType = exports.quizAnalysisPropType = exports.quizAnalysisMetadata = void 0;
|
|
8
8
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
9
|
var quizAnalysisMetadata = _propTypes.default.shape({
|
|
10
10
|
quiz_title: _propTypes.default.string,
|
|
@@ -186,4 +186,20 @@ var afsFlags = _propTypes.default.shape({
|
|
|
186
186
|
essayEnabled: _propTypes.default.bool.isRequired,
|
|
187
187
|
fileUploadEnabled: _propTypes.default.bool.isRequired
|
|
188
188
|
});
|
|
189
|
-
exports.afsFlags = afsFlags;
|
|
189
|
+
exports.afsFlags = afsFlags;
|
|
190
|
+
var sessionValidationErrorsPropType = _propTypes.default.shape({
|
|
191
|
+
isBreach: _propTypes.default.bool,
|
|
192
|
+
ipAddress: _propTypes.default.string,
|
|
193
|
+
browserSessionID: _propTypes.default.string,
|
|
194
|
+
browserString: _propTypes.default.string,
|
|
195
|
+
userPlatform: _propTypes.default.string
|
|
196
|
+
});
|
|
197
|
+
exports.sessionValidationErrorsPropType = sessionValidationErrorsPropType;
|
|
198
|
+
var eventDataPropType = _propTypes.default.shape({
|
|
199
|
+
ipAddress: _propTypes.default.string,
|
|
200
|
+
browserSessionID: _propTypes.default.string,
|
|
201
|
+
eventDate: _propTypes.default.string,
|
|
202
|
+
browserString: _propTypes.default.string,
|
|
203
|
+
userPlatform: _propTypes.default.string
|
|
204
|
+
});
|
|
205
|
+
exports.eventDataPropType = eventDataPropType;
|
package/lib/taking/api/taking.js
CHANGED
|
@@ -17,6 +17,8 @@ exports.postQuizSession = postQuizSession;
|
|
|
17
17
|
exports.getTimeRemaining = getTimeRemaining;
|
|
18
18
|
exports.recordQuizTime = recordQuizTime;
|
|
19
19
|
exports.messageForSubmitModal = exports.nextQuestion = exports.submitStudentAccessCode = exports.makeSessionItemCall = exports.getResumeData = void 0;
|
|
20
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
21
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
20
22
|
var _react = _interopRequireDefault(require("react"));
|
|
21
23
|
var _partial = _interopRequireDefault(require("lodash/partial"));
|
|
22
24
|
var _immutable = require("immutable");
|
|
@@ -34,6 +36,7 @@ var _quizSessions2 = require("../../common/actions/quizSessions.js");
|
|
|
34
36
|
var _callHandlers = require("../../common/api/callHandlers.js");
|
|
35
37
|
var modalActions = _interopRequireWildcard(require("../../common/actions/modal.js"));
|
|
36
38
|
var _taking = require("../../common/actions/taking.js");
|
|
39
|
+
var _getClientIpAddress = require("../../common/util/getClientIpAddress.js");
|
|
37
40
|
// =============================================
|
|
38
41
|
// =============================================
|
|
39
42
|
|
|
@@ -167,9 +170,29 @@ function newQuizTakingSession(quizSessionId) {
|
|
|
167
170
|
};
|
|
168
171
|
}
|
|
169
172
|
function onQuizSessionReadyForTaking(quizSession) {
|
|
170
|
-
return function (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
return /*#__PURE__*/function () {
|
|
174
|
+
var _ref = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee(dispatch) {
|
|
175
|
+
var ipAddress;
|
|
176
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
177
|
+
while (1) {
|
|
178
|
+
switch (_context.prev = _context.next) {
|
|
179
|
+
case 0:
|
|
180
|
+
_context.next = 2;
|
|
181
|
+
return (0, _getClientIpAddress.getClientIpAddress)(quizSession.id);
|
|
182
|
+
case 2:
|
|
183
|
+
ipAddress = _context.sent;
|
|
184
|
+
dispatch([makeSessionItemCall(quizSession), getResumeData(quizSession.id), (0, _taking.sessionStartOrResumeEvent)(navigator.userAgent, ipAddress), (0, _taking.updateQuizSessionStatusOnPageLoad)(quizSession.status)]);
|
|
185
|
+
case 4:
|
|
186
|
+
case "end":
|
|
187
|
+
return _context.stop();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}, _callee);
|
|
191
|
+
}));
|
|
192
|
+
return function (_x) {
|
|
193
|
+
return _ref.apply(this, arguments);
|
|
194
|
+
};
|
|
195
|
+
}();
|
|
173
196
|
}
|
|
174
197
|
|
|
175
198
|
// ====================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/quiz-core",
|
|
3
|
-
"version": "21.0.1-rc.
|
|
3
|
+
"version": "21.0.1-rc.7+5563d7637",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "The Quiz React SDK by Instructure Inc.",
|
|
6
6
|
"author": "Instructure, Inc. Engineering and Product Design",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"@instructure/emotion": "^9.11.1",
|
|
47
47
|
"@instructure/grading-utils": "^1.0.0",
|
|
48
48
|
"@instructure/outcomes-ui": "^3.2.2",
|
|
49
|
-
"@instructure/quiz-common": "21.0.1-rc.
|
|
50
|
-
"@instructure/quiz-i18n": "21.0.1-rc.
|
|
51
|
-
"@instructure/quiz-interactions": "21.0.1-rc.
|
|
52
|
-
"@instructure/quiz-number-input": "21.0.1-rc.
|
|
53
|
-
"@instructure/quiz-rce": "21.0.1-rc.
|
|
49
|
+
"@instructure/quiz-common": "21.0.1-rc.7+5563d7637",
|
|
50
|
+
"@instructure/quiz-i18n": "21.0.1-rc.7+5563d7637",
|
|
51
|
+
"@instructure/quiz-interactions": "21.0.1-rc.7+5563d7637",
|
|
52
|
+
"@instructure/quiz-number-input": "21.0.1-rc.7+5563d7637",
|
|
53
|
+
"@instructure/quiz-rce": "21.0.1-rc.7+5563d7637",
|
|
54
54
|
"@instructure/ui-a11y-content": "^9.11.1",
|
|
55
55
|
"@instructure/ui-alerts": "^9.11.1",
|
|
56
56
|
"@instructure/ui-avatar": "^9.11.1",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"file-saver": "~2.0.5",
|
|
110
110
|
"humps": "^2.0.0",
|
|
111
111
|
"immutable": "^3.8.1",
|
|
112
|
-
"instructure-validations": "21.0.1-rc.
|
|
112
|
+
"instructure-validations": "21.0.1-rc.7+5563d7637",
|
|
113
113
|
"ipaddr.js": "^1.5.4",
|
|
114
114
|
"isomorphic-fetch": "^2.2.0",
|
|
115
115
|
"isuuid": "^0.1.0",
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
"jquery": "^2.2.3",
|
|
160
160
|
"karma-junit-reporter": "^2.0.1",
|
|
161
161
|
"most-subject": "^5.3.0",
|
|
162
|
-
"quiz-presets": "21.0.1-rc.
|
|
162
|
+
"quiz-presets": "21.0.1-rc.7+5563d7637",
|
|
163
163
|
"react": "^16.8.6",
|
|
164
164
|
"react-addons-test-utils": "^15.6.2",
|
|
165
165
|
"react-dom": "^16.8.6",
|
|
@@ -175,5 +175,5 @@
|
|
|
175
175
|
"publishConfig": {
|
|
176
176
|
"access": "public"
|
|
177
177
|
},
|
|
178
|
-
"gitHead": "
|
|
178
|
+
"gitHead": "5563d76375a3820d02aa7b7698023d9f985beb59"
|
|
179
179
|
}
|