@instructure/quiz-core 22.10.1 → 22.11.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 (41) hide show
  1. package/es/banks/api/banks.js +18 -97
  2. package/es/banks/components/SharingModal/index.js +6 -3
  3. package/es/banks/components/SharingModal/presenter.js +207 -131
  4. package/es/building/api/items.js +120 -24
  5. package/es/building/api/quizEntries.js +78 -15
  6. package/es/building/components/resources/quizEntry/QuizEntryShow/presenter.js +11 -2
  7. package/es/common/actions/sharedBanks.js +13 -1
  8. package/es/common/actions/sharingModal.js +7 -0
  9. package/es/common/components/resources/quiz/AddContent/Body/index.js +1 -1
  10. package/es/common/components/resources/quiz/AddContent/Body/presenter.js +129 -141
  11. package/es/common/components/resources/quiz/AddContent/Popover/presenter.js +126 -119
  12. package/es/common/components/shared/InteractionTypes/index.js +1 -4
  13. package/es/common/components/shared/InteractionTypes/presenter.js +1 -3
  14. package/es/common/middleware/sharedBanksMiddleware.js +243 -0
  15. package/es/common/records/Item.js +2 -1
  16. package/es/common/records/QuizEntry.js +7 -0
  17. package/es/common/reducers/sharingModal.js +17 -0
  18. package/es/common/util/useRemoteComponent.js +127 -0
  19. package/es/configureStore.js +4 -1
  20. package/es/index.js +3 -2
  21. package/lib/banks/api/banks.js +17 -96
  22. package/lib/banks/components/SharingModal/index.js +5 -2
  23. package/lib/banks/components/SharingModal/presenter.js +205 -129
  24. package/lib/building/api/items.js +120 -23
  25. package/lib/building/api/quizEntries.js +78 -14
  26. package/lib/building/components/resources/quizEntry/QuizEntryShow/presenter.js +10 -1
  27. package/lib/common/actions/sharedBanks.js +13 -1
  28. package/lib/common/actions/sharingModal.js +13 -0
  29. package/lib/common/components/resources/quiz/AddContent/Body/index.js +1 -1
  30. package/lib/common/components/resources/quiz/AddContent/Body/presenter.js +132 -141
  31. package/lib/common/components/resources/quiz/AddContent/Popover/presenter.js +127 -119
  32. package/lib/common/components/shared/InteractionTypes/index.js +1 -4
  33. package/lib/common/components/shared/InteractionTypes/presenter.js +1 -3
  34. package/lib/common/middleware/sharedBanksMiddleware.js +250 -0
  35. package/lib/common/records/Item.js +2 -1
  36. package/lib/common/records/QuizEntry.js +7 -0
  37. package/lib/common/reducers/sharingModal.js +23 -0
  38. package/lib/common/util/useRemoteComponent.js +134 -0
  39. package/lib/configureStore.js +4 -1
  40. package/lib/index.js +22 -0
  41. package/package.json +8 -8
@@ -11,7 +11,7 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
11
11
  // =========================================
12
12
 
13
13
  import first from 'lodash/first';
14
- import { CREATE_ITEM_CALL, UPDATE_ITEM_CALL, PARENT_TYPE_ADDING_TO_BANK } from '@instructure/quiz-common';
14
+ import { CREATE_ITEM_CALL, CREATE_BULK_ITEMS_CALL, UPDATE_ITEM_CALL, PARENT_TYPE_ADDING_TO_BANK } from '@instructure/quiz-common';
15
15
  import { addError } from '../../common/actions/alerts';
16
16
  import { handleError, errorsAllFromValidation, processApiError, getOnErrorWithDelayedValidationMessage } from '../../common/api/helpers';
17
17
  import Fetcher from '../../common/util/Fetcher';
@@ -122,6 +122,102 @@ export function createItem(item, parentId, parentType) {
122
122
  };
123
123
  }
124
124
 
125
+ /**
126
+ * Creates multiple items in bulk. The input data must consist of question groups and questions
127
+ * using a Learnosity format.
128
+ *
129
+ * @param {{
130
+ * question_type: string,
131
+ * questions: unknown[]
132
+ * }} items
133
+ * @param {object} options - Options for the bulk creation.
134
+ * @param {string} options.parentId - The ID of the parent resource (quiz or bank).
135
+ * @param {string} options.parentType - The type of the parent resource, either 'bank' or 'quiz'.
136
+ * @returns {function} - A thunk function that dispatches the action to create items.
137
+ */
138
+ export function createBulkItems(items, _ref3) {
139
+ var parentId = _ref3.parentId,
140
+ parentType = _ref3.parentType;
141
+ var resource = parentType === 'bank' ? 'banks' : 'quizzes';
142
+ var url = "/api/".concat(resource, "/").concat(parentId, "/items/bulk");
143
+ var body = {
144
+ body: JSON.stringify({
145
+ items: items
146
+ })
147
+ };
148
+ return /*#__PURE__*/function () {
149
+ var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3(dispatch) {
150
+ var fetcher;
151
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
152
+ while (1) switch (_context3.prev = _context3.next) {
153
+ case 0:
154
+ fetcher = new Fetcher({
155
+ callType: CREATE_BULK_ITEMS_CALL,
156
+ cloneResponseForError: true,
157
+ onError: function onError(_ref5) {
158
+ var response = _ref5.response;
159
+ return /*#__PURE__*/function () {
160
+ var _ref6 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(dispatch) {
161
+ var _yield$response$json2, errors;
162
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
163
+ while (1) switch (_context2.prev = _context2.next) {
164
+ case 0:
165
+ if (!(response && response.status === 422)) {
166
+ _context2.next = 12;
167
+ break;
168
+ }
169
+ _context2.prev = 1;
170
+ _context2.next = 4;
171
+ return response.json();
172
+ case 4:
173
+ _yield$response$json2 = _context2.sent;
174
+ errors = _yield$response$json2.errors;
175
+ if (!errorsAllFromValidation(errors)) {
176
+ _context2.next = 8;
177
+ break;
178
+ }
179
+ return _context2.abrupt("return");
180
+ case 8:
181
+ _context2.next = 12;
182
+ break;
183
+ case 10:
184
+ _context2.prev = 10;
185
+ _context2.t0 = _context2["catch"](1);
186
+ case 12:
187
+ return _context2.abrupt("return", dispatch(addError(t('Failed to create items.'))));
188
+ case 13:
189
+ case "end":
190
+ return _context2.stop();
191
+ }
192
+ }, _callee2, null, [[1, 10]]);
193
+ }));
194
+ return function (_x2) {
195
+ return _ref6.apply(this, arguments);
196
+ };
197
+ }();
198
+ }
199
+ });
200
+ return _context3.abrupt("return", fetcher.post(url, body).then(function (_ref7) {
201
+ var items = _ref7.items,
202
+ errors = _ref7.errors;
203
+ dispatch(handleItemsResponse(items));
204
+ return {
205
+ items: items,
206
+ errors: errors
207
+ };
208
+ })["catch"](processApiError));
209
+ case 2:
210
+ case "end":
211
+ return _context3.stop();
212
+ }
213
+ }, _callee3);
214
+ }));
215
+ return function (_x) {
216
+ return _ref4.apply(this, arguments);
217
+ };
218
+ }();
219
+ }
220
+
125
221
  // =====================
126
222
  // =====================
127
223
  // UPDATE
@@ -155,53 +251,53 @@ export function updateItem(item, parentId, parentType) {
155
251
  var fetcher = new Fetcher({
156
252
  callType: UPDATE_ITEM_CALL,
157
253
  cloneResponseForError: true,
158
- onError: function onError(_ref3) {
159
- var response = _ref3.response;
254
+ onError: function onError(_ref8) {
255
+ var response = _ref8.response;
160
256
  return /*#__PURE__*/function () {
161
- var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(dispatch) {
162
- var _yield$response$json2, errors;
163
- return _regeneratorRuntime.wrap(function _callee2$(_context2) {
164
- while (1) switch (_context2.prev = _context2.next) {
257
+ var _ref9 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4(dispatch) {
258
+ var _yield$response$json3, errors;
259
+ return _regeneratorRuntime.wrap(function _callee4$(_context4) {
260
+ while (1) switch (_context4.prev = _context4.next) {
165
261
  case 0:
166
262
  if (!(response && response.status === 422)) {
167
- _context2.next = 14;
263
+ _context4.next = 14;
168
264
  break;
169
265
  }
170
- _context2.prev = 1;
171
- _context2.next = 4;
266
+ _context4.prev = 1;
267
+ _context4.next = 4;
172
268
  return response.json();
173
269
  case 4:
174
- _yield$response$json2 = _context2.sent;
175
- errors = _yield$response$json2.errors;
270
+ _yield$response$json3 = _context4.sent;
271
+ errors = _yield$response$json3.errors;
176
272
  if (!(errors.base && errors.base.some(function (message) {
177
273
  return message.includes('cannot update an immutable item');
178
274
  }))) {
179
- _context2.next = 8;
275
+ _context4.next = 8;
180
276
  break;
181
277
  }
182
- return _context2.abrupt("return", dispatch(addError(t('This item has now been viewed by a student, so your edits were not saved. ' + 'Please refresh the page and try again.'))));
278
+ return _context4.abrupt("return", dispatch(addError(t('This item has now been viewed by a student, so your edits were not saved. ' + 'Please refresh the page and try again.'))));
183
279
  case 8:
184
280
  if (!errorsAllFromValidation(errors)) {
185
- _context2.next = 10;
281
+ _context4.next = 10;
186
282
  break;
187
283
  }
188
- return _context2.abrupt("return");
284
+ return _context4.abrupt("return");
189
285
  case 10:
190
- _context2.next = 14;
286
+ _context4.next = 14;
191
287
  break;
192
288
  case 12:
193
- _context2.prev = 12;
194
- _context2.t0 = _context2["catch"](1);
289
+ _context4.prev = 12;
290
+ _context4.t0 = _context4["catch"](1);
195
291
  case 14:
196
- return _context2.abrupt("return", dispatch(addError(t('Failed to update item.'))));
292
+ return _context4.abrupt("return", dispatch(addError(t('Failed to update item.'))));
197
293
  case 15:
198
294
  case "end":
199
- return _context2.stop();
295
+ return _context4.stop();
200
296
  }
201
- }, _callee2, null, [[1, 12]]);
297
+ }, _callee4, null, [[1, 12]]);
202
298
  }));
203
- return function (_x) {
204
- return _ref4.apply(this, arguments);
299
+ return function (_x3) {
300
+ return _ref9.apply(this, arguments);
205
301
  };
206
302
  }();
207
303
  }
@@ -1,4 +1,5 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
2
+ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
3
  import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
3
4
  import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
4
5
  import _regeneratorRuntime from "@babel/runtime/regenerator";
@@ -14,7 +15,7 @@ import { scrollToItem } from '../../common/actions/scrolling';
14
15
  import ThunkQueue from '../../common/util/ThunkQueue';
15
16
  import noop from '../../common/util/noop';
16
17
  import camelizedVals from '../../common/api/camelizeHelpers';
17
- import { CLONE_QUIZ_ENTRY_CALL, CREATE_QUIZ_ENTRY_CALL, DELETE_QUIZ_ENTRY_CALL, DO_NOTHING, UPDATE_QUIZ_ENTRY_CALL, DELETED_QUIZ_ENTRY, MOVED_QUIZ_ENTRY } from '@instructure/quiz-common';
18
+ import { CLONE_QUIZ_ENTRY_CALL, CREATE_QUIZ_ENTRY_CALL, CREATE_BULK_QUIZ_ENTRIES_CALL, DELETE_QUIZ_ENTRY_CALL, DO_NOTHING, UPDATE_QUIZ_ENTRY_CALL, DELETED_QUIZ_ENTRY, MOVED_QUIZ_ENTRY } from '@instructure/quiz-common';
18
19
  import { createItem } from './items';
19
20
  import { createStimulus } from './stimuli';
20
21
  import { addError } from '../../common/actions/alerts';
@@ -133,6 +134,68 @@ export function createQuizEntry(quizId, entryId, entryType, entryProperties) {
133
134
  });
134
135
  };
135
136
  }
137
+ export function createBulkQuizEntries(items, _ref) {
138
+ var quizId = _ref.quizId,
139
+ startPosition = _ref.startPosition,
140
+ _ref$pointsPossible = _ref.pointsPossible,
141
+ pointsPossible = _ref$pointsPossible === void 0 ? 1 : _ref$pointsPossible,
142
+ _ref$requireFocus = _ref.requireFocus,
143
+ requireFocus = _ref$requireFocus === void 0 ? true : _ref$requireFocus;
144
+ var url = "/api/quizzes/".concat(quizId, "/quiz_entries/bulk");
145
+ var quizEntries = items.map(function (item, index) {
146
+ return {
147
+ quizId: quizId,
148
+ entryId: item.id,
149
+ entryType: 'Item',
150
+ points_possible: pointsPossible,
151
+ position: Math.max(startPosition, 1) + index,
152
+ stimulus_quiz_entry_id: null
153
+ };
154
+ });
155
+ var body = {
156
+ body: JSON.stringify({
157
+ quiz_entries: quizEntries
158
+ })
159
+ };
160
+ return /*#__PURE__*/function () {
161
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(dispatch) {
162
+ var fetcher;
163
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
164
+ while (1) switch (_context.prev = _context.next) {
165
+ case 0:
166
+ fetcher = new Fetcher({
167
+ callType: CREATE_BULK_QUIZ_ENTRIES_CALL,
168
+ onError: function onError() {
169
+ return addError(t('Failed to create quiz entries.'));
170
+ }
171
+ });
172
+ return _context.abrupt("return", fetcher.post(url, body).then(function (response) {
173
+ var quizEntries = response.quiz_entries;
174
+ if (!Array.isArray(quizEntries)) {
175
+ throw new Error(t('Expected an array of quiz entries.'));
176
+ }
177
+ var _ref3 = quizEntries || [],
178
+ _ref4 = _slicedToArray(_ref3, 1),
179
+ firstQuizEntry = _ref4[0];
180
+ if (quizEntries.length === 0 || !(firstQuizEntry !== null && firstQuizEntry !== void 0 && firstQuizEntry.id)) {
181
+ return dispatch(getQuizEntries(quizId));
182
+ }
183
+ var scrollCallback = function scrollCallback() {
184
+ return dispatch(scrollToItem(String(firstQuizEntry.id), requireFocus));
185
+ };
186
+ return dispatch(getQuizEntries(quizId, scrollCallback));
187
+ }));
188
+ case 2:
189
+ case "end":
190
+ return _context.stop();
191
+ }
192
+ }, _callee);
193
+ }));
194
+ return function (_x) {
195
+ return _ref2.apply(this, arguments);
196
+ };
197
+ }();
198
+ }
136
199
 
137
200
  // =====================
138
201
  // =====================
@@ -168,9 +231,9 @@ function updateBody(updatedProperties) {
168
231
  // be updated with response data when it comes back. You may want to set
169
232
  // handleResponse to false for some optimistic update flows.
170
233
  export function updateQuizEntry(quizId, quizEntryId, updatedProperties) {
171
- var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
172
- _ref$handleResponse = _ref.handleResponse,
173
- handleResponse = _ref$handleResponse === void 0 ? true : _ref$handleResponse;
234
+ var _ref5 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
235
+ _ref5$handleResponse = _ref5.handleResponse,
236
+ handleResponse = _ref5$handleResponse === void 0 ? true : _ref5$handleResponse;
174
237
  var url = "/api/quizzes/".concat(quizId, "/quiz_entries/").concat(quizEntryId);
175
238
  var fetcher = new Fetcher({
176
239
  callType: UPDATE_QUIZ_ENTRY_CALL,
@@ -180,28 +243,28 @@ export function updateQuizEntry(quizId, quizEntryId, updatedProperties) {
180
243
  });
181
244
  var body = updateBody(updatedProperties);
182
245
  return /*#__PURE__*/function () {
183
- var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(dispatch, getState) {
246
+ var _ref6 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(dispatch, getState) {
184
247
  var response;
185
- return _regeneratorRuntime.wrap(function _callee$(_context) {
186
- while (1) switch (_context.prev = _context.next) {
248
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
249
+ while (1) switch (_context2.prev = _context2.next) {
187
250
  case 0:
188
- _context.next = 2;
251
+ _context2.next = 2;
189
252
  return fetcher.patch(url, {
190
253
  body: body
191
254
  });
192
255
  case 2:
193
- response = _context.sent;
256
+ response = _context2.sent;
194
257
  if (handleResponse) {
195
258
  dispatch(handleQuizEntryResponse(response));
196
259
  }
197
260
  case 4:
198
261
  case "end":
199
- return _context.stop();
262
+ return _context2.stop();
200
263
  }
201
- }, _callee);
264
+ }, _callee2);
202
265
  }));
203
- return function (_x, _x2) {
204
- return _ref2.apply(this, arguments);
266
+ return function (_x2, _x3) {
267
+ return _ref6.apply(this, arguments);
205
268
  };
206
269
  }();
207
270
  }
@@ -266,8 +329,8 @@ var createItemAndMoveQuizEntry = queue.wrapActionCreator(function (quizId, posit
266
329
  return function (dispatch, getState) {
267
330
  var quizEntry = QuizEntry.create(getState().getIn(['quizEntries', quizEntryId]));
268
331
  var workingItem = quizEntry.getEntry().getWorkingInstance();
269
- return dispatch(createItemAction(workingItem, quizId, 'quiz', workingItem.id)).then(function (_ref3) {
270
- var id = _ref3.id;
332
+ return dispatch(createItemAction(workingItem, quizId, 'quiz', workingItem.id)).then(function (_ref7) {
333
+ var id = _ref7.id;
271
334
  return dispatch(updateAction(quizId, quizEntryId, {
272
335
  entryId: id,
273
336
  position: position,
@@ -25,7 +25,7 @@ import PropTypes from 'prop-types';
25
25
  import ImmutablePropTypes from 'react-immutable-proptypes';
26
26
  import partial from 'lodash/partial';
27
27
  import { DragSource } from 'react-dnd';
28
- import { IconBankLine } from '@instructure/ui-icons';
28
+ import { IconAiLine, IconBankLine } from '@instructure/ui-icons';
29
29
  import { ScreenReaderContent } from '@instructure/ui-a11y-content';
30
30
  import { jsx } from '@instructure/emotion';
31
31
  import { FillBlankInteractionType } from '@instructure/quiz-interactions';
@@ -207,12 +207,21 @@ var QuizEntryShow = (_dec = withStyleOverrides(generateStyle, generateComponentT
207
207
  return jsx(IconBankLine, null);
208
208
  }
209
209
  }
210
+ }, {
211
+ key: "renderAiGeneratedContent",
212
+ value: function renderAiGeneratedContent() {
213
+ if (this.props.quizEntry.isAiGenerated) {
214
+ return jsx(IconAiLine, {
215
+ title: t('AI generated')
216
+ });
217
+ }
218
+ }
210
219
  }, {
211
220
  key: "renderLeftHeader",
212
221
  value: function renderLeftHeader() {
213
222
  return jsx("div", {
214
223
  css: this.props.styles.leftHeader
215
- }, this.renderBankedContent(), this.renderInteractionType(), this.renderPointsPossible(), jsx("div", {
224
+ }, this.renderBankedContent(), this.renderAiGeneratedContent(), this.renderInteractionType(), this.renderPointsPossible(), jsx("div", {
216
225
  css: this.props.styles.title,
217
226
  "data-automation": "sdk-quiz-entry-title-div"
218
227
  }, this.props.entry.title));
@@ -1,10 +1,22 @@
1
- import { ADD_SHARED_BANKS, REMOVE_SHARED_BANK, UPDATE_SHARED_BANK_PERMISSION } from '@instructure/quiz-common';
1
+ import { ADD_SHARED_BANKS, REMOVE_SHARED_BANK, UPDATE_SHARED_BANK_PERMISSION, GET_SHARED_BANKS, GET_ALL_SHARED_ITEMS } from '@instructure/quiz-common';
2
2
  export var addSharedBanks = function addSharedBanks(sharedBank) {
3
3
  return {
4
4
  type: ADD_SHARED_BANKS,
5
5
  payload: sharedBank
6
6
  };
7
7
  };
8
+ export var getSharedBanksAction = function getSharedBanksAction(props) {
9
+ return {
10
+ type: GET_SHARED_BANKS,
11
+ payload: props
12
+ };
13
+ };
14
+ export var getAllSharedItems = function getAllSharedItems(props) {
15
+ return {
16
+ type: GET_ALL_SHARED_ITEMS,
17
+ payload: props
18
+ };
19
+ };
8
20
  export var updateSharedBankPermission = function updateSharedBankPermission(sharedBankId, permission) {
9
21
  return {
10
22
  type: UPDATE_SHARED_BANK_PERMISSION,
@@ -0,0 +1,7 @@
1
+ import { CHANGE_MODAL_LOADING } from '@instructure/quiz-common';
2
+ export var sharedBankLoadingAction = function sharedBankLoadingAction(props) {
3
+ return {
4
+ type: CHANGE_MODAL_LOADING,
5
+ payload: props
6
+ };
7
+ };
@@ -5,7 +5,7 @@ import { screenreaderNotification } from '../../../../../actions/alerts';
5
5
  import AddContentBody from './presenter';
6
6
  import { getInteractionTypes } from '../../../../../selectors/interactionTypes';
7
7
  import { isEditingQuizEntry, getQuizEntryWithErrorsShowing } from '../../../../../selectors/quizzes';
8
- var mapStateToProps = function mapStateToProps(state, props) {
8
+ var mapStateToProps = function mapStateToProps(state) {
9
9
  return {
10
10
  editingQuizEntry: isEditingQuizEntry(state),
11
11
  interactionTypes: getInteractionTypes(state),