@windward/integrations 0.17.0 → 0.19.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 (80) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/components/Content/Blocks/ExternalIntegration/LtiConsumer.vue +3 -3
  3. package/components/Content/Blocks/ExternalIntegration/ScormConsumer.vue +34 -0
  4. package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +10 -8
  5. package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumers.vue +2 -2
  6. package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +8 -5
  7. package/components/ExternalIntegration/Driver/Lti1p1/ManageProviders.vue +3 -2
  8. package/components/ExternalIntegration/Driver/Lti1p3/ManageConsumer.vue +8 -6
  9. package/components/ExternalIntegration/Driver/Lti1p3/ManageConsumers.vue +2 -2
  10. package/components/ExternalIntegration/Driver/Lti1p3/ManageProvider.vue +27 -5
  11. package/components/ExternalIntegration/Driver/Lti1p3/ManageProviders.vue +4 -3
  12. package/components/ExternalIntegration/Driver/Lti1p3/ViewConsumer.vue +6 -5
  13. package/components/ExternalIntegration/Driver/ManageScorm.vue +45 -0
  14. package/components/ExternalIntegration/Driver/Scorm/ManageConsumer.vue +76 -0
  15. package/components/ExternalIntegration/Driver/Scorm/ManageConsumers.vue +233 -0
  16. package/components/ExternalIntegration/Driver/Scorm/ManageProvider.vue +475 -0
  17. package/components/ExternalIntegration/Driver/Scorm/ManageProviders.vue +299 -0
  18. package/components/Integration/Driver/LoginSamlButton.vue +119 -0
  19. package/components/Integration/Driver/ManageSaml.vue +327 -0
  20. package/components/LLM/GenerateContent/BlockQuestionGenerateButton.vue +34 -3
  21. package/components/SecretField.vue +99 -19
  22. package/components/Settings/ExternalIntegration/LtiConsumerSettings.vue +2 -2
  23. package/components/Settings/ExternalIntegration/ManageCourseIntegrationSettings.vue +6 -6
  24. package/components/Settings/ExternalIntegration/ScormConsumerSettings.vue +42 -0
  25. package/config/integration.config.js +2 -0
  26. package/helpers/Driver/SamlSso.ts +12 -0
  27. package/helpers/ExternalIntegration/ScormHelper.ts +155 -0
  28. package/i18n/en-US/components/external_integration/driver/lti1p3.ts +4 -1
  29. package/i18n/en-US/components/external_integration/driver/scorm.ts +14 -0
  30. package/i18n/en-US/components/external_integration/index.ts +3 -1
  31. package/i18n/en-US/components/integration/driver.ts +23 -0
  32. package/i18n/en-US/components/llm/generate_content/generate_questions.ts +7 -0
  33. package/i18n/en-US/pages/course/external_integration/index.ts +1 -1
  34. package/i18n/en-US/pages/login/index.ts +4 -0
  35. package/i18n/en-US/pages/login/lti.ts +2 -0
  36. package/i18n/en-US/pages/login/saml.ts +7 -0
  37. package/i18n/en-US/pages/login/scorm.ts +28 -0
  38. package/i18n/en-US/shared/content_blocks.ts +1 -0
  39. package/i18n/en-US/shared/settings.ts +1 -0
  40. package/i18n/es-ES/components/external_integration/driver/lti1p3.ts +4 -1
  41. package/i18n/es-ES/components/external_integration/driver/scorm.ts +15 -0
  42. package/i18n/es-ES/components/external_integration/index.ts +3 -1
  43. package/i18n/es-ES/components/integration/driver.ts +23 -0
  44. package/i18n/es-ES/components/llm/generate_content/generate_questions.ts +7 -0
  45. package/i18n/es-ES/pages/course/external_integration/index.ts +1 -1
  46. package/i18n/es-ES/pages/login/index.ts +4 -0
  47. package/i18n/es-ES/pages/login/lti.ts +2 -0
  48. package/i18n/es-ES/pages/login/saml.ts +7 -0
  49. package/i18n/es-ES/pages/login/scorm.ts +29 -0
  50. package/i18n/es-ES/shared/content_blocks.ts +1 -0
  51. package/i18n/es-ES/shared/settings.ts +1 -0
  52. package/i18n/sv-SE/components/external_integration/driver/lti1p3.ts +4 -1
  53. package/i18n/sv-SE/components/external_integration/driver/scorm.ts +14 -0
  54. package/i18n/sv-SE/components/external_integration/index.ts +3 -1
  55. package/i18n/sv-SE/components/integration/driver.ts +23 -0
  56. package/i18n/sv-SE/components/llm/generate_content/generate_questions.ts +7 -0
  57. package/i18n/sv-SE/pages/course/external_integration/index.ts +1 -1
  58. package/i18n/sv-SE/pages/login/index.ts +4 -0
  59. package/i18n/sv-SE/pages/login/lti.ts +2 -0
  60. package/i18n/sv-SE/pages/login/saml.ts +7 -0
  61. package/i18n/sv-SE/pages/login/scorm.ts +29 -0
  62. package/i18n/sv-SE/shared/content_blocks.ts +2 -1
  63. package/i18n/sv-SE/shared/settings.ts +1 -0
  64. package/jest.config.js +3 -0
  65. package/models/Auth/Saml.ts +21 -0
  66. package/models/ExternalIntegration/{LtiConsumer.ts → Consumer.ts} +2 -2
  67. package/models/ExternalIntegration/{LtiProvider.ts → Provider.ts} +2 -2
  68. package/package.json +2 -1
  69. package/pages/course/externalIntegration/index.vue +4 -0
  70. package/pages/login/scorm/error.vue +102 -0
  71. package/pages/login/scorm/promptEmail.vue +180 -0
  72. package/plugin.js +128 -7
  73. package/test/Components/ExternalIntegration/ManageScorm.spec.js +19 -0
  74. package/test/Components/ExternalIntegration/Scorm/ManageConsumer.spec.js +19 -0
  75. package/test/Components/ExternalIntegration/Scorm/ManageConsumers.spec.js +19 -0
  76. package/test/Components/ExternalIntegration/Scorm/ManageProvider.spec.js +19 -0
  77. package/test/Components/ExternalIntegration/Scorm/ManageProviders.spec.js +19 -0
  78. package/test/__mocks__/componentsMock.js +81 -1
  79. package/test/mocks.js +12 -0
  80. package/test/setup.js +1 -0
@@ -0,0 +1,475 @@
1
+ <template>
2
+ <div>
3
+ <div v-if="!render" class="integration-loading">
4
+ <v-progress-circular size="128" indeterminate />
5
+ </div>
6
+ <div v-if="render">
7
+ <v-form
8
+ :id="formId"
9
+ ref="form"
10
+ :key="updateKey"
11
+ v-model="formValid"
12
+ @submit="onFormSubmit"
13
+ >
14
+ <v-row justify="center" align="center">
15
+ <v-col cols="12">
16
+ <ProviderTargetPicker
17
+ v-model="provider"
18
+ ></ProviderTargetPicker>
19
+ <v-text-field
20
+ id="target-url"
21
+ v-model="provider.target"
22
+ disabled
23
+ :label="
24
+ $t(
25
+ 'windward.integrations.components.external_integration.target_url'
26
+ )
27
+ "
28
+ :hint="
29
+ $t(
30
+ 'windward.integrations.components.external_integration.target_url'
31
+ )
32
+ "
33
+ :rules="$Validation.getRule('exists')"
34
+ ></v-text-field>
35
+
36
+ <v-select
37
+ v-model="provider.version"
38
+ :disabled="!isNew"
39
+ :items="versions"
40
+ item-text="name"
41
+ item-value="value"
42
+ :label="
43
+ $t(
44
+ 'windward.integrations.components.external_integration.driver.scorm.version'
45
+ )
46
+ "
47
+ outlined
48
+ :rules="$Validation.getRule('exists')"
49
+ ></v-select>
50
+
51
+ <v-switch
52
+ v-model="provider.enabled"
53
+ :label="$t('shared.forms.enabled')"
54
+ />
55
+
56
+ <v-switch
57
+ v-model="provider.metadata.anonymize_users"
58
+ :disabled="provider.metadata.email_prompt"
59
+ :label="
60
+ $t(
61
+ 'windward.integrations.components.external_integration.driver.scorm.anonymize_users'
62
+ )
63
+ "
64
+ />
65
+
66
+ <v-switch
67
+ v-model="provider.metadata.email_prompt"
68
+ :disabled="provider.metadata.anonymize_users"
69
+ :label="
70
+ $t(
71
+ 'windward.integrations.components.external_integration.driver.scorm.email_prompt'
72
+ )
73
+ "
74
+ />
75
+
76
+ <v-switch
77
+ v-model="provider.metadata.incomplete_until_passed"
78
+ :label="
79
+ $t(
80
+ 'windward.integrations.components.external_integration.driver.scorm.incomplete_until_passed'
81
+ )
82
+ "
83
+ />
84
+
85
+ <v-switch
86
+ v-model="provider.metadata.has_mastery_score"
87
+ :label="
88
+ $t(
89
+ 'windward.integrations.components.external_integration.driver.scorm.has_mastery_score'
90
+ )
91
+ "
92
+ />
93
+
94
+ <v-text-field
95
+ id="scorm-mastery-score"
96
+ v-model="provider.metadata.mastery_score"
97
+ type="number"
98
+ :disabled="!provider.metadata.has_mastery_score"
99
+ :placeholder="
100
+ $t(
101
+ 'windward.integrations.components.external_integration.driver.scorm.mastery_score'
102
+ )
103
+ "
104
+ :label="
105
+ $t(
106
+ 'windward.integrations.components.external_integration.driver.scorm.mastery_score'
107
+ )
108
+ "
109
+ :hint="
110
+ $t(
111
+ 'windward.integrations.components.external_integration.driver.scorm.mastery_score'
112
+ )
113
+ "
114
+ :rules="[
115
+ ...$Validation.getRule('wholePositiveNumber'),
116
+ ...$Validation.getRule(
117
+ 'lessThanOrEqualTo',
118
+ 100
119
+ ),
120
+ ]"
121
+ ></v-text-field>
122
+ </v-col>
123
+ </v-row>
124
+ <v-row>
125
+ <v-col cols="12">
126
+ <v-expansion-panels v-model="rolePanel">
127
+ <v-expansion-panel>
128
+ <v-expansion-panel-header>
129
+ {{
130
+ $t(
131
+ 'windward.integrations.components.external_integration.role_map_panel'
132
+ )
133
+ }}
134
+ </v-expansion-panel-header>
135
+ <v-expansion-panel-content>
136
+ <p>
137
+ <strong>
138
+ {{
139
+ $t(
140
+ 'windward.integrations.components.external_integration.role_map_instructions'
141
+ )
142
+ }}
143
+ </strong>
144
+ </p>
145
+ <p>
146
+ {{
147
+ $t(
148
+ 'windward.integrations.components.external_integration.role_map_warning'
149
+ )
150
+ }}
151
+ </p>
152
+ <v-simple-table>
153
+ <template #default>
154
+ <thead>
155
+ <tr>
156
+ <th>
157
+ {{
158
+ $t(
159
+ 'windward.integrations.components.external_integration.role_in_host'
160
+ )
161
+ }}
162
+ </th>
163
+ <th>
164
+ {{
165
+ $t(
166
+ 'windward.integrations.components.external_integration.role_in_local'
167
+ )
168
+ }}
169
+ </th>
170
+ </tr>
171
+ </thead>
172
+ <tbody>
173
+ <tr
174
+ v-for="(
175
+ id, externalName
176
+ ) in provider.role_metadata"
177
+ :key="externalName"
178
+ >
179
+ <td>
180
+ {{
181
+ uppercase(
182
+ externalName
183
+ )
184
+ }}
185
+ </td>
186
+ <td>
187
+ <v-select
188
+ v-model="
189
+ provider
190
+ .role_metadata[
191
+ externalName
192
+ ]
193
+ "
194
+ :items="roles"
195
+ item-value="id"
196
+ item-text="name"
197
+ :label="
198
+ $t(
199
+ 'windward.integrations.components.external_integration.map_to_role'
200
+ )
201
+ "
202
+ ></v-select>
203
+ </td>
204
+ <td>
205
+ <v-btn
206
+ icon
207
+ elevation="0"
208
+ @click="
209
+ deleteMapItem(
210
+ externalName
211
+ )
212
+ "
213
+ >
214
+ <v-icon>
215
+ mdi-delete
216
+ </v-icon>
217
+ <span
218
+ class="sr-only"
219
+ >
220
+ {{
221
+ $t(
222
+ 'windward.integrations.components.external_integration.delete_role_map_item'
223
+ )
224
+ }}
225
+ </span>
226
+ </v-btn>
227
+ </td>
228
+ </tr>
229
+ <tr>
230
+ <td>
231
+ <v-text-field
232
+ v-model="
233
+ newMap.name
234
+ "
235
+ :label="
236
+ $t(
237
+ 'windward.integrations.components.external_integration.remote_role'
238
+ )
239
+ "
240
+ ></v-text-field>
241
+ </td>
242
+ <td>
243
+ <v-select
244
+ v-model="newMap.id"
245
+ :items="roles"
246
+ :label="
247
+ $t(
248
+ 'windward.integrations.components.external_integration.map_to_role'
249
+ )
250
+ "
251
+ item-value="id"
252
+ item-text="name"
253
+ ></v-select>
254
+ </td>
255
+ <td>
256
+ <v-btn
257
+ icon
258
+ elevation="0"
259
+ :disabled="
260
+ !newMap.id ||
261
+ !newMap.name
262
+ "
263
+ @click="addMapItem"
264
+ >
265
+ <v-icon>
266
+ mdi-plus
267
+ </v-icon>
268
+ <span
269
+ class="sr-only"
270
+ >
271
+ {{
272
+ $t(
273
+ 'windward.integrations.components.external_integration.add_role_map_item'
274
+ )
275
+ }}
276
+ </span>
277
+ </v-btn>
278
+ </td>
279
+ </tr>
280
+ </tbody>
281
+ </template>
282
+ </v-simple-table>
283
+ </v-expansion-panel-content>
284
+ </v-expansion-panel>
285
+ </v-expansion-panels>
286
+ </v-col>
287
+ </v-row>
288
+ </v-form>
289
+ </div>
290
+ </div>
291
+ </template>
292
+
293
+ <script>
294
+ import _ from 'lodash'
295
+ import { mapGetters } from 'vuex'
296
+ import ProviderTargetPicker from '../../ProviderTargetPicker.vue'
297
+ import Provider from '../../../../models/ExternalIntegration/Provider'
298
+ import FormVue from '~/components/Core/Form.vue'
299
+ import Role from '~/models/Role'
300
+ import Organization from '~/models/Organization'
301
+ import Course from '~/models/Course'
302
+ import Crypto from '~/helpers/Crypto'
303
+ import Uuid from '~/helpers/Uuid'
304
+
305
+ export default {
306
+ name: 'ManageScormProviderDriver',
307
+ components: { ProviderTargetPicker },
308
+ extends: FormVue,
309
+ props: {
310
+ value: {
311
+ type: [Provider, null],
312
+ required: false,
313
+ default: null,
314
+ },
315
+ },
316
+ emits: ['update:provider'],
317
+ meta: {
318
+ privilege: {
319
+ '': {
320
+ writable: true,
321
+ },
322
+ },
323
+ },
324
+ data() {
325
+ return {
326
+ render: false,
327
+ provider: {
328
+ role_metadata: {},
329
+ metadata: {},
330
+ version: '2004v4',
331
+ type: 'scorm',
332
+ },
333
+ versions: [
334
+ { name: '1.2', value: '1.2' },
335
+ { name: '2004v4', value: '2004v4' },
336
+ // { name: 'Custom', value: 'custom' }, // TODO: When this is selected provide direct access to check off LTI fields
337
+ ],
338
+ rolePanel: false,
339
+ roles: [],
340
+ newMap: {
341
+ name: null,
342
+ id: null,
343
+ },
344
+ updateKey: Crypto.id(),
345
+ }
346
+ },
347
+ async fetch() {
348
+ // Get available roles but filter out default high level roles since they'll be ignore anyways from the api
349
+ this.roles = await Role.whereIn('name,ne', [
350
+ 'admin',
351
+ 'editor',
352
+ 'instructor',
353
+ 'student',
354
+ ]).all()
355
+ },
356
+ computed: {
357
+ ...mapGetters({
358
+ organization: 'organization/get',
359
+ course: 'course/get',
360
+ contentTree: 'content/getTree',
361
+ }),
362
+ isNew() {
363
+ return !Uuid.test(this.provider.id)
364
+ },
365
+ },
366
+ created() {
367
+ if (_.isEmpty(this.value)) {
368
+ this.provider = new Provider({
369
+ role_metadata: {},
370
+ metadata: {},
371
+ version: '2004v4',
372
+ type: 'scorm',
373
+ })
374
+ } else {
375
+ this.provider = _.cloneDeep(this.value)
376
+ }
377
+
378
+ // Set the default mastery to disabled
379
+ if (typeof this.provider.metadata.has_mastery_score === 'undefined') {
380
+ this.provider.metadata.has_mastery_score = false
381
+ }
382
+ // Set the default score to 70
383
+ if (typeof this.provider.metadata.mastery_score === 'undefined') {
384
+ this.provider.metadata.mastery_score = 70
385
+ }
386
+ },
387
+ mounted() {
388
+ if (
389
+ !this.$PermissionService.userHasAccessTo(
390
+ 'plugin.windward.integrations.course.externalIntegration',
391
+ 'writable'
392
+ )
393
+ ) {
394
+ // Display an angry error that they can't view this driver
395
+ this.$dialog.error(this.$t('shared.error.description_401'), {
396
+ duration: null,
397
+ action: {
398
+ text: this.$t('shared.forms.close'),
399
+ onClick: (_e, toastObject) => {
400
+ toastObject.goAway(0)
401
+ },
402
+ },
403
+ })
404
+ // eslint-disable-next-line no-console
405
+ console.error('You do not have access to this provider!')
406
+
407
+ // Return so we don't even attempt loading
408
+ return false
409
+ }
410
+
411
+ this.render = true
412
+ },
413
+ methods: {
414
+ uppercase(value) {
415
+ return _.startCase(value)
416
+ },
417
+ deleteMapItem(key) {
418
+ this.$delete(this.provider.role_metadata, key)
419
+ },
420
+ addMapItem() {
421
+ this.provider.role_metadata[this.newMap.name] = this.newMap.id
422
+ this.newMap.name = null
423
+ this.newMap.id = null
424
+ },
425
+ async save() {
426
+ let provider = new Provider(this.provider).for(
427
+ new Organization({ id: this.organization.id }),
428
+ new Course({ id: this.course.id })
429
+ )
430
+
431
+ try {
432
+ console.log('SAVE PROVIDER', provider)
433
+ provider = await provider.save()
434
+ this.provider = provider
435
+
436
+ // Clone and delete the metadata since we don't need to share that around
437
+ // Also include the vendor going back for easier mapping
438
+ const providerEvent = _.cloneDeep(provider)
439
+
440
+ this.$dialog.success(this.$t('shared.forms.saved'))
441
+ this.$emit('update:provider', providerEvent)
442
+ this.$emit('input', provider)
443
+ } catch (e) {
444
+ this.$dialog.error(
445
+ this.$t('windward.integrations.shared.error.save_failed')
446
+ )
447
+ }
448
+
449
+ // Create a new provider to clear the form if the original value is empty aka new
450
+ if (_.isEmpty(this.value)) {
451
+ this.provider = new Provider({
452
+ role_metadata: {},
453
+ metadata: {},
454
+ version: '2004v4',
455
+ type: 'scorm',
456
+ })
457
+ }
458
+ this.updateKey = Crypto.id()
459
+ },
460
+ async onSave() {
461
+ this.$refs.form.validate()
462
+ if (this.formValid) {
463
+ await this.save()
464
+ }
465
+ },
466
+
467
+ async onSaveAndNew() {
468
+ this.$refs.form.validate()
469
+ if (this.formValid) {
470
+ await this.save()
471
+ }
472
+ },
473
+ },
474
+ }
475
+ </script>