@windward/integrations 0.0.1 → 0.0.3
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/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +13 -0
- package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumers.vue +13 -0
- package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +374 -0
- package/components/ExternalIntegration/Driver/Lti1p1/ManageProviders.vue +220 -0
- package/components/ExternalIntegration/Driver/ManageLti1p1.vue +45 -0
- package/components/Integration/Driver/ManageBase.vue +1 -1
- package/components/SecretField.vue +50 -0
- package/i18n/en-US/components/external_integration/driver/lti1p1.ts +26 -0
- package/i18n/en-US/components/external_integration/index.ts +5 -0
- package/i18n/en-US/components/index.ts +2 -0
- package/i18n/en-US/pages/course/external_integration/index.ts +6 -0
- package/i18n/en-US/pages/course/index.ts +5 -0
- package/i18n/en-US/pages/index.ts +4 -0
- package/i18n/en-US/pages/login/index.ts +5 -0
- package/i18n/en-US/pages/login/lti.ts +21 -0
- package/i18n/en-US/shared/permission.ts +4 -0
- package/models/ExternalIntegration/Lti1p1Consumer.ts +13 -0
- package/models/ExternalIntegration/Lti1p1Provider.ts +13 -0
- package/package.json +1 -1
- package/pages/admin/vendors.vue +1 -1
- package/pages/course/externalIntegration/index.vue +72 -0
- package/pages/login/lti/error.vue +98 -0
- package/plugin.js +28 -0
- package/test/Components/ExternalIntegration/Lti1p1/ManageConsumer.spec.js +19 -0
- package/test/Components/ExternalIntegration/Lti1p1/ManageConsumers.spec.js +19 -0
- package/test/Components/ExternalIntegration/Lti1p1/ManageProvider.spec.js +19 -0
- package/test/Components/ExternalIntegration/Lti1p1/ManageProviders.spec.js +19 -0
- package/test/Components/ExternalIntegration/ManageLti1p1.spec.js +19 -0
- package/test/Pages/Course/ExternalIntegration/index.spec.js +19 -0
- package/test/Pages/Login/Lti/error.spec.js +19 -0
- package/test/__mocks__/modelMock.js +4 -1
- package/test/mocks.js +1 -0
|
@@ -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>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-text-field :value="value" readonly :type="fieldType">
|
|
3
|
+
<template #append>
|
|
4
|
+
<v-btn icon @click="toggleClear">
|
|
5
|
+
<v-icon>{{ showClear ? 'mdi-eye-off' : 'mdi-eye' }}</v-icon>
|
|
6
|
+
</v-btn>
|
|
7
|
+
<v-btn v-if="copy" icon @click="copyText(value)">
|
|
8
|
+
<v-icon>mdi-content-copy</v-icon>
|
|
9
|
+
</v-btn>
|
|
10
|
+
</template>
|
|
11
|
+
</v-text-field>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
export default {
|
|
16
|
+
name: 'SecretField',
|
|
17
|
+
props: {
|
|
18
|
+
value: { type: String, required: false, default: '' },
|
|
19
|
+
hidden: { type: Boolean, required: false, default: true },
|
|
20
|
+
copy: { type: Boolean, required: false, default: true },
|
|
21
|
+
},
|
|
22
|
+
data() {
|
|
23
|
+
return {
|
|
24
|
+
showClear: false,
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
computed: {
|
|
29
|
+
fieldType() {
|
|
30
|
+
if (this.showClear) {
|
|
31
|
+
return 'text'
|
|
32
|
+
} else {
|
|
33
|
+
return 'password'
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
mounted() {
|
|
38
|
+
this.showClear = !this.hidden
|
|
39
|
+
},
|
|
40
|
+
methods: {
|
|
41
|
+
toggleClear() {
|
|
42
|
+
this.showClear = !this.showClear
|
|
43
|
+
},
|
|
44
|
+
copyText(text) {
|
|
45
|
+
navigator.clipboard.writeText(text || '')
|
|
46
|
+
this.$dialog.show(this.$t('shared.forms.copied'))
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
key: 'Key',
|
|
3
|
+
secret: 'Secret',
|
|
4
|
+
auto_key: 'Autogenerated Key',
|
|
5
|
+
auto_secret: 'Autogenerated Secret',
|
|
6
|
+
target_url: 'Target Url',
|
|
7
|
+
launch_url: 'Launch Url',
|
|
8
|
+
new: 'New LTI Link',
|
|
9
|
+
edit: 'Edit LTI Link',
|
|
10
|
+
enabled: 'Enabled',
|
|
11
|
+
provider: 'Provider',
|
|
12
|
+
consumer: 'Consumer',
|
|
13
|
+
provider_panel: 'Provider (Students incoming to Windward)',
|
|
14
|
+
consumer_panel: 'Consumer (Students outgoing from Windward)',
|
|
15
|
+
role_map_panel: 'Role map',
|
|
16
|
+
role_in_host: 'Role in Host LMS',
|
|
17
|
+
role_in_local: 'Role in Windward',
|
|
18
|
+
remote_role: 'Remote Role name',
|
|
19
|
+
map_to_role: 'Map to Windward Role',
|
|
20
|
+
delete_role_map_item: 'Delete role map item',
|
|
21
|
+
add_role_map_item: 'Add role map item',
|
|
22
|
+
role_map_instructions:
|
|
23
|
+
'Only add roles to the below map if the host LMS uses different role names that you want to elevated permissions. Eg "Mentor" that you want to elevate to "External Instructor"',
|
|
24
|
+
role_map_warning:
|
|
25
|
+
'Only add roles to the below map if the host LMS uses different role names that you want to elevated permissions. Eg "Mentor" that you want to elevate to "External Instructor"',
|
|
26
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import vendor from './vendor'
|
|
2
2
|
import importCourse from './importCourse'
|
|
3
3
|
import importContent from './importContent'
|
|
4
|
+
import login from './login'
|
|
5
|
+
import course from './course'
|
|
4
6
|
|
|
5
7
|
export default {
|
|
6
8
|
vendor,
|
|
7
9
|
import_course: importCourse,
|
|
8
10
|
import_content: importContent,
|
|
11
|
+
login,
|
|
12
|
+
course,
|
|
9
13
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
lti_error: 'LTI Errors',
|
|
3
|
+
launch_error: 'LTI link failed to launch',
|
|
4
|
+
missing_title: 'The below fields are missing:',
|
|
5
|
+
error_title: 'The below errors occurred:',
|
|
6
|
+
error: {
|
|
7
|
+
unknown: 'An unknown error occurred',
|
|
8
|
+
organization: 'Invalid organization',
|
|
9
|
+
tool: 'Invalid tool id',
|
|
10
|
+
tool_organization: 'This tool does not belong to this organization',
|
|
11
|
+
oauth: 'Invalid OAuth. Check your key and secret.',
|
|
12
|
+
},
|
|
13
|
+
missing: {
|
|
14
|
+
context_label:
|
|
15
|
+
'The context label field `context_label` is missing or invalid.',
|
|
16
|
+
resource_link_title:
|
|
17
|
+
'The resource link title `resource_link_title` is missing or invalid.',
|
|
18
|
+
lis_person_contact_email_primary:
|
|
19
|
+
'The users email is missing and an anonymous email could not be generated.',
|
|
20
|
+
},
|
|
21
|
+
}
|
|
@@ -8,6 +8,8 @@ export default {
|
|
|
8
8
|
'Organization Integrations',
|
|
9
9
|
'plugin->windward->integrations->course': 'Course Integrations',
|
|
10
10
|
'plugin->windward->integrations->content': 'Content Integrations',
|
|
11
|
+
'plugin->windward->integrations->course->externalIntegration':
|
|
12
|
+
'External Integrations (LTI / SCORM / etc)',
|
|
11
13
|
},
|
|
12
14
|
|
|
13
15
|
type_description: {
|
|
@@ -22,5 +24,7 @@ export default {
|
|
|
22
24
|
'Access and manage course imports',
|
|
23
25
|
'plugin->windward->integrations->content':
|
|
24
26
|
'Access and manage content imports',
|
|
27
|
+
'plugin->windward->integrations->course->externalIntegration':
|
|
28
|
+
'Access and manage external integrations such as LTI and SCORM',
|
|
25
29
|
},
|
|
26
30
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import Model from '~/models/Model'
|
|
3
|
+
|
|
4
|
+
export default class Lti1p1Consumer extends Model {
|
|
5
|
+
get required(): string[] {
|
|
6
|
+
return []
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Set the resource route of the model
|
|
10
|
+
resource() {
|
|
11
|
+
return 'external-integrations/lti/1.1/consumers'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import Model from '~/models/Model'
|
|
3
|
+
|
|
4
|
+
export default class Lti1p1Provider extends Model {
|
|
5
|
+
get required(): string[] {
|
|
6
|
+
return []
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Set the resource route of the model
|
|
10
|
+
resource() {
|
|
11
|
+
return 'external-integrations/lti/1.1/providers'
|
|
12
|
+
}
|
|
13
|
+
}
|
package/package.json
CHANGED
package/pages/admin/vendors.vue
CHANGED
|
@@ -151,9 +151,9 @@
|
|
|
151
151
|
import _ from 'lodash'
|
|
152
152
|
import { mapGetters } from 'vuex'
|
|
153
153
|
|
|
154
|
-
import Dialog from '~/components/Dialog.vue'
|
|
155
154
|
import Organization from '../../models/Organization'
|
|
156
155
|
import IntegrationJobTable from '../../components/Integration/JobTable.vue'
|
|
156
|
+
import Dialog from '~/components/Dialog.vue'
|
|
157
157
|
|
|
158
158
|
export default {
|
|
159
159
|
name: 'PluginIntegrationsAdminVendorsPage',
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<h1>
|
|
4
|
+
{{
|
|
5
|
+
$t(
|
|
6
|
+
'windward.integrations.pages.course.external_integration.title'
|
|
7
|
+
)
|
|
8
|
+
}}
|
|
9
|
+
</h1>
|
|
10
|
+
<v-tabs v-model="tab">
|
|
11
|
+
<v-tabs-slider></v-tabs-slider>
|
|
12
|
+
|
|
13
|
+
<v-tab>
|
|
14
|
+
{{
|
|
15
|
+
$t(
|
|
16
|
+
'windward.integrations.pages.course.external_integration.lti_1p1'
|
|
17
|
+
)
|
|
18
|
+
}}
|
|
19
|
+
</v-tab>
|
|
20
|
+
|
|
21
|
+
<v-tab>
|
|
22
|
+
{{
|
|
23
|
+
$t(
|
|
24
|
+
'windward.integrations.pages.course.external_integration.lti_1p3'
|
|
25
|
+
)
|
|
26
|
+
}}
|
|
27
|
+
</v-tab>
|
|
28
|
+
|
|
29
|
+
<v-tab>
|
|
30
|
+
{{
|
|
31
|
+
$t(
|
|
32
|
+
'windward.integrations.pages.course.external_integration.scorm_1p2'
|
|
33
|
+
)
|
|
34
|
+
}}
|
|
35
|
+
</v-tab>
|
|
36
|
+
</v-tabs>
|
|
37
|
+
|
|
38
|
+
<v-tabs-items v-model="tab">
|
|
39
|
+
<v-tab-item> <ManageLti1p1></ManageLti1p1> </v-tab-item>
|
|
40
|
+
<v-tab-item>
|
|
41
|
+
<p class="ma-5">LTI 1.3 Not yet implemented</p>
|
|
42
|
+
</v-tab-item>
|
|
43
|
+
<v-tab-item>
|
|
44
|
+
<p class="ma-5">SCORM 1.2 Not yet implemented</p>
|
|
45
|
+
</v-tab-item>
|
|
46
|
+
</v-tabs-items>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script>
|
|
51
|
+
import ManageLti1p1 from '../../../components/ExternalIntegration/Driver/ManageLti1p1.vue'
|
|
52
|
+
|
|
53
|
+
export default {
|
|
54
|
+
name: 'PluginIntegrationsExternalIntegrationIndexPage',
|
|
55
|
+
components: { ManageLti1p1 },
|
|
56
|
+
layout: 'authenticated',
|
|
57
|
+
meta: {
|
|
58
|
+
privilege: {
|
|
59
|
+
'plugin.windward.integrations.course.externalIntegration': {
|
|
60
|
+
readable: true,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
data() {
|
|
65
|
+
return {
|
|
66
|
+
tab: 0,
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async fetch() {},
|
|
70
|
+
computed: {},
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-container>
|
|
3
|
+
<v-divider />
|
|
4
|
+
<v-row>
|
|
5
|
+
<v-col cols="12" md="6" offset-md="3">
|
|
6
|
+
<h1 class="text-center mt-5">
|
|
7
|
+
{{
|
|
8
|
+
$t('windward.integrations.pages.login.lti.launch_error')
|
|
9
|
+
}}
|
|
10
|
+
</h1>
|
|
11
|
+
<v-alert
|
|
12
|
+
v-if="error.length === 0 && missing.length === 0"
|
|
13
|
+
type="error"
|
|
14
|
+
class="mt-5"
|
|
15
|
+
>
|
|
16
|
+
<strong>{{
|
|
17
|
+
$t(
|
|
18
|
+
'windward.integrations.pages.login.lti.error.unknown'
|
|
19
|
+
)
|
|
20
|
+
}}</strong>
|
|
21
|
+
</v-alert>
|
|
22
|
+
<v-alert v-if="missing.length" type="warning" class="mt-5">
|
|
23
|
+
<strong>{{
|
|
24
|
+
$t(
|
|
25
|
+
'windward.integrations.pages.login.lti.missing_title'
|
|
26
|
+
)
|
|
27
|
+
}}</strong>
|
|
28
|
+
<ul>
|
|
29
|
+
<li v-for="missingItem in missing" :key="missingItem">
|
|
30
|
+
{{
|
|
31
|
+
$t(
|
|
32
|
+
'windward.integrations.pages.login.lti.missing.' +
|
|
33
|
+
missingItem
|
|
34
|
+
)
|
|
35
|
+
}}
|
|
36
|
+
</li>
|
|
37
|
+
</ul>
|
|
38
|
+
</v-alert>
|
|
39
|
+
<v-alert v-if="error.length" type="error" class="mt-5">
|
|
40
|
+
<strong>{{
|
|
41
|
+
$t('windward.integrations.pages.login.lti.error_title')
|
|
42
|
+
}}</strong>
|
|
43
|
+
<ul>
|
|
44
|
+
<li v-for="errorItem in error" :key="errorItem">
|
|
45
|
+
{{
|
|
46
|
+
$t(
|
|
47
|
+
'windward.integrations.pages.login.lti.error.' +
|
|
48
|
+
errorItem
|
|
49
|
+
)
|
|
50
|
+
}}
|
|
51
|
+
</li>
|
|
52
|
+
</ul>
|
|
53
|
+
</v-alert>
|
|
54
|
+
</v-col>
|
|
55
|
+
</v-row>
|
|
56
|
+
</v-container>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
import _ from 'lodash'
|
|
61
|
+
export default {
|
|
62
|
+
name: 'PluginIntegrationsLoginLtiErrorPage',
|
|
63
|
+
layout: 'default',
|
|
64
|
+
data() {
|
|
65
|
+
return {
|
|
66
|
+
missing: [],
|
|
67
|
+
error: [],
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
async fetch() {},
|
|
71
|
+
computed: {},
|
|
72
|
+
mounted() {
|
|
73
|
+
// Get the missing url parameters to display
|
|
74
|
+
if (!_.isEmpty(_.get(this.$route, 'query.missing', ''))) {
|
|
75
|
+
this.missing = _.get(this.$route, 'query.missing', '')
|
|
76
|
+
.split(',')
|
|
77
|
+
.map((v) => v.trim())
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Get the error url parameters to display
|
|
81
|
+
if (!_.isEmpty(_.get(this.$route, 'query.error', ''))) {
|
|
82
|
+
this.error = _.get(this.$route, 'query.error', '')
|
|
83
|
+
.split(',')
|
|
84
|
+
.map((v) => v.trim())
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Set the appropriate locale
|
|
88
|
+
if (!_.isEmpty(_.get(this.$route, 'query.locale', ''))) {
|
|
89
|
+
const locale = _.get(this.$route, 'query.locale', '')
|
|
90
|
+
|
|
91
|
+
if (this.$i18n.locales.includes(locale)) {
|
|
92
|
+
this.$i18n.setLocale(locale)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
methods: {},
|
|
97
|
+
}
|
|
98
|
+
</script>
|
package/plugin.js
CHANGED
|
@@ -2,6 +2,8 @@ import enUS from './i18n/en-US'
|
|
|
2
2
|
import AdminVendorsPage from './pages/admin/vendors.vue'
|
|
3
3
|
import AdminImportCoursePage from './pages/admin/importCourse.vue'
|
|
4
4
|
import ImportContentPage from './pages/course/importContent.vue'
|
|
5
|
+
import ExternalIntegrationIndexPage from './pages/course/externalIntegration/index.vue'
|
|
6
|
+
import LoginLtiErrorPage from './pages/login/lti/error.vue'
|
|
5
7
|
import IntegrationHelper from './helpers/IntegrationHelper'
|
|
6
8
|
|
|
7
9
|
export default {
|
|
@@ -41,6 +43,21 @@ export default {
|
|
|
41
43
|
name: 'PluginIntegrationsImportContentPage',
|
|
42
44
|
template: ImportContentPage,
|
|
43
45
|
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
page: 'admin-integrations',
|
|
49
|
+
path: '/course/:course/integrations/external',
|
|
50
|
+
i18n: 'windward.integrations.pages.course.external_integration.title',
|
|
51
|
+
name: 'PluginIntegrationsExternalIntegrationIndexPage',
|
|
52
|
+
template: ExternalIntegrationIndexPage,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
page: 'lti-error',
|
|
56
|
+
path: '/login/lti/error',
|
|
57
|
+
i18n: 'windward.integrations.pages.login.lti.lti_error',
|
|
58
|
+
name: 'PluginIntegrationsLoginLtiErrorPage',
|
|
59
|
+
template: LoginLtiErrorPage,
|
|
60
|
+
},
|
|
44
61
|
],
|
|
45
62
|
components: {
|
|
46
63
|
menu: [
|
|
@@ -78,6 +95,17 @@ export default {
|
|
|
78
95
|
},
|
|
79
96
|
},
|
|
80
97
|
},
|
|
98
|
+
{
|
|
99
|
+
i18n: 'windward.integrations.pages.course.external_integration.title',
|
|
100
|
+
path: '/course/{course.id}/integrations/external',
|
|
101
|
+
context: ['course'],
|
|
102
|
+
permissions: {
|
|
103
|
+
'plugin.windward.integrations.course.externalIntegration':
|
|
104
|
+
{
|
|
105
|
+
readable: true,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
81
109
|
/*
|
|
82
110
|
{
|
|
83
111
|
i18n: 'windward.integrations.components.navigation.integrations.manage_lti',
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils'
|
|
2
|
+
|
|
3
|
+
import Vue from 'vue'
|
|
4
|
+
import Vuetify from 'vuetify'
|
|
5
|
+
import { defaultMocks } from '@/test/mocks'
|
|
6
|
+
|
|
7
|
+
import ManageConsumer from '@/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('ManageConsumer', () => {
|
|
12
|
+
test('ManageConsumer is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(ManageConsumer, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.vm).toBeTruthy()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils'
|
|
2
|
+
|
|
3
|
+
import Vue from 'vue'
|
|
4
|
+
import Vuetify from 'vuetify'
|
|
5
|
+
import { defaultMocks } from '@/test/mocks'
|
|
6
|
+
|
|
7
|
+
import ManageConsumers from '@/components/ExternalIntegration/Driver/Lti1p1/ManageConsumers.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('ManageConsumers', () => {
|
|
12
|
+
test('ManageConsumers is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(ManageConsumers, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.vm).toBeTruthy()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils'
|
|
2
|
+
|
|
3
|
+
import Vue from 'vue'
|
|
4
|
+
import Vuetify from 'vuetify'
|
|
5
|
+
import { defaultMocks } from '@/test/mocks'
|
|
6
|
+
|
|
7
|
+
import ManageProvider from '@/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('ManageProvider', () => {
|
|
12
|
+
test('ManageProvider is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(ManageProvider, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.vm).toBeTruthy()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils'
|
|
2
|
+
|
|
3
|
+
import Vue from 'vue'
|
|
4
|
+
import Vuetify from 'vuetify'
|
|
5
|
+
import { defaultMocks } from '@/test/mocks'
|
|
6
|
+
|
|
7
|
+
import ManageProviders from '@/components/ExternalIntegration/Driver/Lti1p1/ManageProviders.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('ManageProviders', () => {
|
|
12
|
+
test('ManageProviders is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(ManageProviders, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.vm).toBeTruthy()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils'
|
|
2
|
+
|
|
3
|
+
import Vue from 'vue'
|
|
4
|
+
import Vuetify from 'vuetify'
|
|
5
|
+
import { defaultMocks } from '@/test/mocks'
|
|
6
|
+
|
|
7
|
+
import ManageLti1p1 from '@/components/ExternalIntegration/Driver/ManageLti1p1.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('ManageLti1p1', () => {
|
|
12
|
+
test('ManageLti1p1 is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(ManageLti1p1, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.vm).toBeTruthy()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils'
|
|
2
|
+
|
|
3
|
+
import Vue from 'vue'
|
|
4
|
+
import Vuetify from 'vuetify'
|
|
5
|
+
import { defaultMocks } from '@/test/mocks'
|
|
6
|
+
|
|
7
|
+
import ExternalIntegrationIndexPage from '@/pages/course/externalIntegration/index.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('ExternalIntegrationIndexPage', () => {
|
|
12
|
+
test('ExternalIntegrationIndexPage is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(ExternalIntegrationIndexPage, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.vm).toBeTruthy()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils'
|
|
2
|
+
|
|
3
|
+
import Vue from 'vue'
|
|
4
|
+
import Vuetify from 'vuetify'
|
|
5
|
+
import { defaultMocks } from '@/test/mocks'
|
|
6
|
+
|
|
7
|
+
import LtiErrorPage from '@/pages/login/lti/error.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('LtiErrorPage', () => {
|
|
12
|
+
test('LtiErrorPage is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(LtiErrorPage, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.vm).toBeTruthy()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -7,7 +7,10 @@ mockModel.$http = axios
|
|
|
7
7
|
jest.mock('axios')
|
|
8
8
|
|
|
9
9
|
// Define any new model mocks here. The imports / mocks will be auto-generated below
|
|
10
|
-
const mockVirtualModels = [
|
|
10
|
+
const mockVirtualModels = [
|
|
11
|
+
{ path: '~/models/Role', resource: 'roles' },
|
|
12
|
+
{ path: '~/models/Organization', resource: 'organizations' },
|
|
13
|
+
]
|
|
11
14
|
|
|
12
15
|
const mockModels = [
|
|
13
16
|
{ path: '../../models/Organization', resource: 'organizations' },
|