@windward/integrations 0.0.2 → 0.0.4

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 (39) hide show
  1. package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +13 -0
  2. package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumers.vue +13 -0
  3. package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +374 -0
  4. package/components/ExternalIntegration/Driver/Lti1p1/ManageProviders.vue +220 -0
  5. package/components/ExternalIntegration/Driver/ManageLti1p1.vue +45 -0
  6. package/components/Integration/Driver/Disabled.vue +29 -0
  7. package/components/SecretField.vue +50 -0
  8. package/config/integration.config.js +11 -0
  9. package/helpers/Driver/BlackboardUltra.ts +15 -0
  10. package/helpers/Driver/Canvas.ts +12 -0
  11. package/helpers/Driver/Desire2Learn.ts +15 -0
  12. package/helpers/Driver/GoogleClassroom.ts +15 -0
  13. package/helpers/Driver/Moodle.ts +12 -0
  14. package/i18n/en-US/components/external_integration/driver/lti1p1.ts +26 -0
  15. package/i18n/en-US/components/external_integration/index.ts +5 -0
  16. package/i18n/en-US/components/index.ts +2 -0
  17. package/i18n/en-US/components/integration/driver.ts +19 -1
  18. package/i18n/en-US/pages/course/external_integration/index.ts +6 -0
  19. package/i18n/en-US/pages/course/index.ts +5 -0
  20. package/i18n/en-US/pages/index.ts +4 -0
  21. package/i18n/en-US/pages/login/index.ts +5 -0
  22. package/i18n/en-US/pages/login/lti.ts +21 -0
  23. package/i18n/en-US/shared/permission.ts +4 -0
  24. package/models/ExternalIntegration/Lti1p1Consumer.ts +13 -0
  25. package/models/ExternalIntegration/Lti1p1Provider.ts +13 -0
  26. package/package.json +1 -1
  27. package/pages/admin/vendors.vue +1 -1
  28. package/pages/course/externalIntegration/index.vue +72 -0
  29. package/pages/login/lti/error.vue +98 -0
  30. package/plugin.js +28 -0
  31. package/test/Components/ExternalIntegration/Lti1p1/ManageConsumer.spec.js +19 -0
  32. package/test/Components/ExternalIntegration/Lti1p1/ManageConsumers.spec.js +19 -0
  33. package/test/Components/ExternalIntegration/Lti1p1/ManageProvider.spec.js +19 -0
  34. package/test/Components/ExternalIntegration/Lti1p1/ManageProviders.spec.js +19 -0
  35. package/test/Components/ExternalIntegration/ManageLti1p1.spec.js +19 -0
  36. package/test/Pages/Course/ExternalIntegration/index.spec.js +19 -0
  37. package/test/Pages/Login/Lti/error.spec.js +19 -0
  38. package/test/__mocks__/modelMock.js +4 -1
  39. package/test/mocks.js +1 -0
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div>LTI 1.1 Consumers Not Implemented yet</div>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'ManageLti1p1ConsumerDriver',
8
+ data() {
9
+ return {}
10
+ },
11
+ methods: {},
12
+ }
13
+ </script>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div>LTI 1.1 Consumers Not Implemented yet</div>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'ManageLti1p1ConsumersDriver',
8
+ data() {
9
+ return {}
10
+ },
11
+ methods: {},
12
+ }
13
+ </script>
@@ -0,0 +1,374 @@
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 v-model="formValid" @submit.prevent>
8
+ <v-row justify="center" align="center" class="mt-5">
9
+ <v-col cols="12">
10
+ <v-text-field
11
+ v-model="provider.target"
12
+ :label="
13
+ $t(
14
+ 'windward.integrations.components.external_integration.driver.lti1p1.target_url'
15
+ )
16
+ "
17
+ :hint="
18
+ $t(
19
+ 'windward.integrations.components.external_integration.driver.lti1p1.target_url'
20
+ )
21
+ "
22
+ ></v-text-field>
23
+
24
+ <v-switch
25
+ v-model="provider.enabled"
26
+ :label="
27
+ $t(
28
+ 'windward.integrations.components.external_integration.driver.lti1p1.enabled'
29
+ )
30
+ "
31
+ />
32
+
33
+ <v-text-field
34
+ v-model="provider.metadata.key"
35
+ :placeholder="
36
+ $t(
37
+ 'windward.integrations.components.external_integration.driver.lti1p1.auto_key'
38
+ )
39
+ "
40
+ :label="
41
+ $t(
42
+ 'windward.integrations.components.external_integration.driver.lti1p1.key'
43
+ )
44
+ "
45
+ :hint="
46
+ $t(
47
+ 'windward.integrations.components.external_integration.driver.lti1p1.key'
48
+ )
49
+ "
50
+ ></v-text-field>
51
+
52
+ <v-text-field
53
+ v-model="provider.metadata.secret"
54
+ :placeholder="
55
+ $t(
56
+ 'windward.integrations.components.external_integration.driver.lti1p1.auto_secret'
57
+ )
58
+ "
59
+ :label="
60
+ $t(
61
+ 'windward.integrations.components.external_integration.driver.lti1p1.secret'
62
+ )
63
+ "
64
+ :hint="
65
+ $t(
66
+ 'windward.integrations.components.external_integration.driver.lti1p1.secret'
67
+ )
68
+ "
69
+ >
70
+ </v-text-field>
71
+ </v-col>
72
+ </v-row>
73
+ <v-row>
74
+ <v-col cols="12">
75
+ <v-expansion-panels v-model="rolePanel">
76
+ <v-expansion-panel>
77
+ <v-expansion-panel-header>
78
+ {{
79
+ $t(
80
+ 'windward.integrations.components.external_integration.driver.lti1p1.role_map_panel'
81
+ )
82
+ }}
83
+ </v-expansion-panel-header>
84
+ <v-expansion-panel-content>
85
+ <p>
86
+ <strong>
87
+ {{
88
+ $t(
89
+ 'windward.integrations.components.external_integration.driver.lti1p1.role_map_instructions'
90
+ )
91
+ }}
92
+ </strong>
93
+ </p>
94
+ <p>
95
+ {{
96
+ $t(
97
+ 'windward.integrations.components.external_integration.driver.lti1p1.role_map_warning'
98
+ )
99
+ }}
100
+ </p>
101
+ <v-simple-table>
102
+ <template #default>
103
+ <thead>
104
+ <tr>
105
+ <th>
106
+ {{
107
+ $t(
108
+ 'windward.integrations.components.external_integration.driver.lti1p1.role_in_host'
109
+ )
110
+ }}
111
+ </th>
112
+ <th>
113
+ {{
114
+ $t(
115
+ 'windward.integrations.components.external_integration.driver.lti1p1.role_in_local'
116
+ )
117
+ }}
118
+ </th>
119
+ </tr>
120
+ </thead>
121
+ <tbody>
122
+ <tr
123
+ v-for="(
124
+ id, externalName
125
+ ) in provider.role_metadata"
126
+ :key="externalName"
127
+ >
128
+ <td>
129
+ {{
130
+ uppercase(
131
+ externalName
132
+ )
133
+ }}
134
+ </td>
135
+ <td>
136
+ <v-select
137
+ v-model="
138
+ provider
139
+ .role_metadata[
140
+ externalName
141
+ ]
142
+ "
143
+ :items="roles"
144
+ item-value="id"
145
+ item-text="name"
146
+ :label="
147
+ $t(
148
+ 'windward.integrations.components.external_integration.driver.lti1p1.map_to_role'
149
+ )
150
+ "
151
+ ></v-select>
152
+ </td>
153
+ <td>
154
+ <v-btn
155
+ icon
156
+ @click="
157
+ deleteMapItem(
158
+ externalName
159
+ )
160
+ "
161
+ >
162
+ <v-icon>
163
+ mdi-delete
164
+ </v-icon>
165
+ <span
166
+ class="sr-only"
167
+ >
168
+ {{
169
+ $t(
170
+ 'windward.integrations.components.external_integration.driver.lti1p1.delete_role_map_item'
171
+ )
172
+ }}
173
+ </span>
174
+ </v-btn>
175
+ </td>
176
+ </tr>
177
+ <tr>
178
+ <td>
179
+ <v-text-field
180
+ v-model="
181
+ newMap.name
182
+ "
183
+ :label="
184
+ $t(
185
+ 'windward.integrations.components.external_integration.driver.lti1p1.remote_role'
186
+ )
187
+ "
188
+ ></v-text-field>
189
+ </td>
190
+ <td>
191
+ <v-select
192
+ v-model="newMap.id"
193
+ :items="roles"
194
+ :label="
195
+ $t(
196
+ 'windward.integrations.components.external_integration.driver.lti1p1.map_to_role'
197
+ )
198
+ "
199
+ item-value="id"
200
+ item-text="name"
201
+ ></v-select>
202
+ </td>
203
+ <td>
204
+ <v-btn
205
+ icon
206
+ :disabled="
207
+ !newMap.id ||
208
+ !newMap.name
209
+ "
210
+ @click="addMapItem"
211
+ >
212
+ <v-icon>
213
+ mdi-plus
214
+ </v-icon>
215
+ <span
216
+ class="sr-only"
217
+ >
218
+ {{
219
+ $t(
220
+ 'windward.integrations.components.external_integration.driver.lti1p1.add_role_map_item'
221
+ )
222
+ }}
223
+ </span>
224
+ </v-btn>
225
+ </td>
226
+ </tr>
227
+ </tbody>
228
+ </template>
229
+ </v-simple-table>
230
+ </v-expansion-panel-content>
231
+ </v-expansion-panel>
232
+ </v-expansion-panels>
233
+ </v-col>
234
+ </v-row>
235
+ </v-form>
236
+ </div>
237
+ </div>
238
+ </template>
239
+
240
+ <script>
241
+ import _ from 'lodash'
242
+ import { mapGetters } from 'vuex'
243
+ import Lti1p1Provider from '../../../../models/ExternalIntegration/Lti1p1Provider'
244
+ import FormVue from '~/components/Form'
245
+ import Role from '~/models/Role'
246
+ import Organization from '~/models/Organization'
247
+ import Course from '~/models/Course'
248
+
249
+ export default {
250
+ name: 'ManageLti1p1ProviderDriver',
251
+ components: {},
252
+ extends: FormVue,
253
+ props: {
254
+ value: {
255
+ type: [Lti1p1Provider, null],
256
+ required: false,
257
+ default: null,
258
+ },
259
+ },
260
+ emits: ['update:provider'],
261
+ meta: {
262
+ privilege: {
263
+ '': {
264
+ writable: true,
265
+ },
266
+ },
267
+ },
268
+ data() {
269
+ return {
270
+ render: false,
271
+ provider: {
272
+ role_metadata: {},
273
+ metadata: {},
274
+ },
275
+ rolePanel: false,
276
+ roles: [],
277
+ newMap: {
278
+ name: null,
279
+ id: null,
280
+ },
281
+ }
282
+ },
283
+ async fetch() {
284
+ // Get available roles but filter out default high level roles since they'll be ignore anyways from the api
285
+ this.roles = await Role.whereIn('name,ne', [
286
+ 'admin',
287
+ 'editor',
288
+ 'instructor',
289
+ 'student',
290
+ ]).all()
291
+ },
292
+ computed: {
293
+ ...mapGetters({
294
+ organization: 'organization/get',
295
+ course: 'course/get',
296
+ }),
297
+ },
298
+ created() {
299
+ if (_.isEmpty(this.value)) {
300
+ this.provider = new Lti1p1Provider({
301
+ role_metadata: {},
302
+ metadata: {},
303
+ })
304
+ } else {
305
+ this.provider = _.cloneDeep(this.value)
306
+ }
307
+ },
308
+ mounted() {
309
+ if (
310
+ !this.$PermissionService.userHasAccessTo(
311
+ 'plugin.windward.integrations.course.externalIntegration',
312
+ 'writable'
313
+ )
314
+ ) {
315
+ // Display an angry error that they can't view this driver
316
+ this.$dialog.error(this.$t('shared.error.description_401'), {
317
+ duration: null,
318
+ action: {
319
+ text: this.$t('shared.forms.close'),
320
+ onClick: (e, toastObject) => {
321
+ toastObject.goAway(0)
322
+ },
323
+ },
324
+ })
325
+ console.error('You do not have access to this provider!')
326
+
327
+ // Return so we don't even attempt loading
328
+ return false
329
+ }
330
+
331
+ this.render = true
332
+ },
333
+ methods: {
334
+ uppercase(value) {
335
+ return _.startCase(value)
336
+ },
337
+ deleteMapItem(key) {
338
+ this.$delete(this.provider.role_metadata, key)
339
+ },
340
+ addMapItem() {
341
+ this.provider.role_metadata[this.newMap.name] = this.newMap.id
342
+ this.newMap.name = null
343
+ this.newMap.id = null
344
+ },
345
+ async save() {
346
+ let provider = new Lti1p1Provider(this.provider).for(
347
+ new Organization({ id: this.organization.id }),
348
+ new Course({ id: this.course.id })
349
+ )
350
+
351
+ try {
352
+ provider = await provider.save()
353
+ this.provider = provider
354
+
355
+ // Clone and delete the metadata since we don't need to share that around
356
+ // Also include the vendor going back for easier mapping
357
+ const providerEvent = _.cloneDeep(provider)
358
+
359
+ this.$dialog.success(this.$t('shared.forms.saved'))
360
+ this.$emit('update:provider', providerEvent)
361
+ } catch (e) {
362
+ this.$dialog.error(
363
+ this.$t('windward.integrations.shared.error.save_failed')
364
+ )
365
+ }
366
+ },
367
+ async onSave() {
368
+ if (this.formValid) {
369
+ await this.save()
370
+ }
371
+ },
372
+ },
373
+ }
374
+ </script>
@@ -0,0 +1,220 @@
1
+ <template>
2
+ <div>
3
+ <Dialog
4
+ color="primary"
5
+ action-save
6
+ action-save-new
7
+ @click:save="onSaved"
8
+ >
9
+ <template #title>{{
10
+ $t(
11
+ 'windward.integrations.components.external_integration.driver.lti1p1.new'
12
+ )
13
+ }}</template>
14
+ <template #trigger>{{ $t('shared.forms.new') }}</template>
15
+ <template #form="{ on, attrs }"
16
+ ><ManageProvider v-bind="attrs" v-on="on"></ManageProvider
17
+ ></template>
18
+ </Dialog>
19
+
20
+ <v-data-table
21
+ :headers="headers"
22
+ :items="providers"
23
+ :items-per-page="10"
24
+ class="elevation-1"
25
+ >
26
+ <template #item.metadata.key="{ item }">
27
+ <SecretField v-model="item.metadata.key"></SecretField>
28
+ </template>
29
+ <template #item.metadata.secret="{ item }">
30
+ <SecretField v-model="item.metadata.secret"></SecretField>
31
+ </template>
32
+ <template #item.url="{ item }">
33
+ <SecretField v-model="item.url" :hidden="false"></SecretField>
34
+ </template>
35
+
36
+ <template #item.enabled="{ item }">
37
+ <v-icon :color="item.enabled ? 'success' : 'error'"
38
+ >{{ item.enabled ? 'mdi-check' : 'mdi-close' }}
39
+ </v-icon>
40
+ <span v-if="!item.enabled" class="sr-only">{{
41
+ $t(
42
+ 'windward.integrations.components.external_integration.driver.lti1p1.enabled'
43
+ )
44
+ }}</span>
45
+ </template>
46
+
47
+ <template #item.created_at="{ item }">
48
+ {{ $d(new Date(item.created_at), 'long') }}
49
+ </template>
50
+ <template #[`item.actions`]="{ index, item }">
51
+ <Dialog color="primary" action-save @click:save="onSaved">
52
+ <template #title>{{
53
+ $t(
54
+ 'windward.integrations.components.external_integration.driver.lti1p1.edit'
55
+ )
56
+ }}</template>
57
+ <template #trigger>
58
+ <v-icon small>mdi-pencil</v-icon>
59
+ <span class="sr-only">{{
60
+ $t(
61
+ 'windward.integrations.components.external_integration.driver.lti1p1.edit'
62
+ )
63
+ }}</span>
64
+ </template>
65
+ <template #form="{ on, attrs }"
66
+ ><ManageProvider
67
+ v-model="providers[index]"
68
+ v-bind="attrs"
69
+ v-on="on"
70
+ ></ManageProvider
71
+ ></template>
72
+ </Dialog>
73
+
74
+ <v-btn icon>
75
+ <v-icon @click="onConfirmDelete(item)"> mdi-delete </v-icon>
76
+ <span class="sr-only">{{ $t('shared.forms.delete') }}</span>
77
+ </v-btn>
78
+ </template>
79
+ </v-data-table>
80
+ </div>
81
+ </template>
82
+
83
+ <script>
84
+ import _ from 'lodash'
85
+ import { mapGetters } from 'vuex'
86
+ import Lti1p1Provider from '../../../../models/ExternalIntegration/Lti1p1Provider'
87
+ import SecretField from '../../../SecretField.vue'
88
+ import ManageProvider from './ManageProvider.vue'
89
+ import Course from '~/models/Course'
90
+ import Organization from '~/models/Organization'
91
+ import Dialog from '~/components/Dialog.vue'
92
+
93
+ export default {
94
+ name: 'ManageLti1p1ProvidersDriver',
95
+ components: { SecretField, Dialog, ManageProvider },
96
+ data() {
97
+ return {
98
+ providers: [],
99
+ headers: [
100
+ {
101
+ text: this.$t(
102
+ 'windward.integrations.components.external_integration.driver.lti1p1.target_url'
103
+ ),
104
+ value: 'target',
105
+ },
106
+ {
107
+ text: this.$t(
108
+ 'windward.integrations.components.external_integration.driver.lti1p1.key'
109
+ ),
110
+ value: 'metadata.key',
111
+ },
112
+ {
113
+ text: this.$t(
114
+ 'windward.integrations.components.external_integration.driver.lti1p1.secret'
115
+ ),
116
+ value: 'metadata.secret',
117
+ },
118
+ {
119
+ text: this.$t(
120
+ 'windward.integrations.components.external_integration.driver.lti1p1.launch_url'
121
+ ),
122
+ value: 'url',
123
+ },
124
+ {
125
+ text: this.$t(
126
+ 'windward.integrations.components.external_integration.driver.lti1p1.enabled'
127
+ ),
128
+ value: 'enabled',
129
+ },
130
+ { text: this.$t('shared.forms.created'), value: 'created_at' },
131
+ {
132
+ text: this.$t('shared.forms.actions'),
133
+ value: 'actions',
134
+ sortable: false,
135
+ },
136
+ ],
137
+ }
138
+ },
139
+
140
+ async fetch() {
141
+ if (
142
+ !this.$PermissionService.userHasAccessTo(
143
+ 'plugin.windward.integrations.course.externalIntegration',
144
+ 'readable'
145
+ ) ||
146
+ _.isEmpty(this.organization.id) ||
147
+ _.isEmpty(this.course.id)
148
+ ) {
149
+ // Display an angry error that they can't view this driver
150
+ this.$dialog.error(this.$t('shared.error.description_401'), {
151
+ duration: null,
152
+ action: {
153
+ text: this.$t('shared.forms.close'),
154
+ onClick: (e, toastObject) => {
155
+ toastObject.goAway(0)
156
+ },
157
+ },
158
+ })
159
+ if (_.isEmpty(this.organization.id) || _.isEmpty(this.course.id)) {
160
+ console.error(
161
+ 'Cannot load external integrations because organization or course is not set!'
162
+ )
163
+ } else {
164
+ console.error(
165
+ 'You do not have access to this external integration!'
166
+ )
167
+ }
168
+
169
+ // Return so we don't even attempt loading
170
+ return false
171
+ }
172
+
173
+ await this.loadProviders()
174
+ },
175
+ computed: {
176
+ ...mapGetters({
177
+ organization: 'organization/get',
178
+ course: 'course/get',
179
+ }),
180
+ },
181
+ methods: {
182
+ onSaved(e) {
183
+ this.loadProviders()
184
+ },
185
+ async loadProviders() {
186
+ this.providers = await new Lti1p1Provider()
187
+ .for(
188
+ new Organization({ id: this.organization.id }),
189
+ new Course({ id: this.course.id })
190
+ )
191
+ .get()
192
+ },
193
+ onConfirmDelete(provider) {
194
+ this.$dialog.show(this.$t('shared.forms.confirm_delete_text'), {
195
+ icon: 'mdi-help',
196
+ duration: null,
197
+ action: [
198
+ {
199
+ text: this.$t('shared.forms.cancel'),
200
+ onClick: (e, toastObject) => {
201
+ toastObject.goAway(0)
202
+ },
203
+ },
204
+ {
205
+ text: this.$t('shared.forms.confirm'),
206
+ // router navigation
207
+ onClick: (e, toastObject) => {
208
+ this.deleteProvider(provider)
209
+ toastObject.goAway(0)
210
+ },
211
+ },
212
+ ],
213
+ })
214
+ },
215
+ deleteProvider(provider) {
216
+ provider.delete()
217
+ },
218
+ },
219
+ }
220
+ </script>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <div>
3
+ <v-expansion-panels v-model="panel">
4
+ <v-expansion-panel>
5
+ <v-expansion-panel-header>
6
+ {{
7
+ $t(
8
+ 'windward.integrations.components.external_integration.driver.lti1p1.provider_panel'
9
+ )
10
+ }}
11
+ </v-expansion-panel-header>
12
+ <v-expansion-panel-content>
13
+ <ListProviders></ListProviders>
14
+ </v-expansion-panel-content>
15
+ </v-expansion-panel>
16
+ <v-expansion-panel>
17
+ <v-expansion-panel-header>
18
+ {{
19
+ $t(
20
+ 'windward.integrations.components.external_integration.driver.lti1p1.consumer_panel'
21
+ )
22
+ }}
23
+ </v-expansion-panel-header>
24
+ <v-expansion-panel-content>
25
+ <ListConsumers></ListConsumers>
26
+ </v-expansion-panel-content>
27
+ </v-expansion-panel>
28
+ </v-expansion-panels>
29
+ </div>
30
+ </template>
31
+
32
+ <script>
33
+ import ListProviders from './Lti1p1/ManageProviders.vue'
34
+ import ListConsumers from './Lti1p1/ManageConsumers.vue'
35
+
36
+ export default {
37
+ name: 'ManageLti1p1Driver',
38
+ components: { ListProviders, ListConsumers },
39
+ data() {
40
+ return {
41
+ panel: 0,
42
+ }
43
+ },
44
+ }
45
+ </script>