@windward/integrations 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/components/Content/Blocks/ExternalIntegration/LtiConsumer.vue +205 -0
  2. package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +284 -3
  3. package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumers.vue +199 -3
  4. package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +36 -6
  5. package/components/ExternalIntegration/Driver/Lti1p1/ManageProviders.vue +36 -17
  6. package/components/ExternalIntegration/Driver/ManageLti1p1.vue +5 -5
  7. package/components/ExternalIntegration/ProviderTargetPicker.vue +234 -0
  8. package/components/ExternalIntegration/ProviderTargetViewer.vue +50 -0
  9. package/components/Integration/Driver/ManageBase.vue +3 -1
  10. package/components/Integration/JobTable.vue +2 -1
  11. package/components/SecretField.vue +1 -1
  12. package/components/Settings/ExternalIntegration/LtiConsumerSettings.vue +105 -0
  13. package/i18n/en-US/components/content/blocks/external_integration/index.ts +5 -0
  14. package/i18n/en-US/components/content/blocks/external_integration/lti_consumer.ts +8 -0
  15. package/i18n/en-US/components/content/blocks/index.ts +5 -0
  16. package/i18n/en-US/components/content/index.ts +5 -0
  17. package/i18n/en-US/components/external_integration/driver/lti1p1.ts +11 -2
  18. package/i18n/en-US/components/external_integration/index.ts +2 -1
  19. package/i18n/en-US/components/external_integration/provider_target.ts +9 -0
  20. package/i18n/en-US/components/index.ts +4 -0
  21. package/i18n/en-US/components/settings/external_integration/index.ts +5 -0
  22. package/i18n/en-US/components/settings/external_integration/lti_consumer.ts +7 -0
  23. package/i18n/en-US/components/settings/index.ts +5 -0
  24. package/i18n/en-US/shared/content_blocks.ts +8 -0
  25. package/i18n/en-US/shared/index.ts +2 -0
  26. package/i18n/en-US/shared/settings.ts +5 -1
  27. package/package.json +1 -1
  28. package/pages/admin/importCourse.vue +3 -0
  29. package/pages/admin/vendors.vue +2 -1
  30. package/plugin.js +27 -1
  31. package/test/Components/Content/Blocks/ExternalIntegration/LtiConsumer.spec.js +26 -0
  32. package/test/Components/ExternalIntegration/ProviderTargetPicker.spec.js +22 -0
  33. package/test/Components/ExternalIntegration/ProviderTargetViewer.spec.js +22 -0
  34. package/test/__mocks__/componentsMock.js +12 -0
  35. package/test/__mocks__/modelMock.js +1 -1
  36. package/test/mocks.js +1 -0
@@ -1,13 +1,209 @@
1
1
  <template>
2
- <div>LTI 1.1 Consumers Not Implemented yet</div>
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
+ ><ManageConsumer v-bind="attrs" v-on="on"></ManageConsumer
17
+ ></template>
18
+ </Dialog>
19
+
20
+ <v-data-table
21
+ :headers="headers"
22
+ :items="consumers"
23
+ :items-per-page="10"
24
+ class="elevation-1"
25
+ >
26
+ <template #[`item.description`]="{ item }">
27
+ {{ item.description.replace(/(<([^>]+)>)/gi, '').trim() }}
28
+ </template>
29
+
30
+ <template #[`item.enabled`]="{ item }">
31
+ <v-icon :color="item.enabled ? 'success' : 'error'"
32
+ >{{ item.enabled ? 'mdi-check' : 'mdi-close' }}
33
+ </v-icon>
34
+ <span v-if="!item.enabled" class="sr-only">{{
35
+ $t(
36
+ 'windward.integrations.components.external_integration.driver.lti1p1.enabled'
37
+ )
38
+ }}</span>
39
+ </template>
40
+
41
+ <template #[`item.created_at`]="{ item }">
42
+ {{ $d(new Date(item.created_at), 'long') }}
43
+ </template>
44
+ <template #[`item.actions`]="{ index, item }">
45
+ <Dialog color="primary" action-save @click:save="onSaved">
46
+ <template #title>{{
47
+ $t(
48
+ 'windward.integrations.components.external_integration.driver.lti1p1.edit'
49
+ )
50
+ }}</template>
51
+ <template #trigger>
52
+ <v-icon small>mdi-pencil</v-icon>
53
+ <span class="sr-only">{{
54
+ $t(
55
+ 'windward.integrations.components.external_integration.driver.lti1p1.edit'
56
+ )
57
+ }}</span>
58
+ </template>
59
+ <template #form="{ on, attrs }"
60
+ ><ManageConsumer
61
+ v-model="consumers[index]"
62
+ v-bind="attrs"
63
+ v-on="on"
64
+ ></ManageConsumer
65
+ ></template>
66
+ </Dialog>
67
+
68
+ <v-btn icon>
69
+ <v-icon @click="onConfirmDelete(item)"> mdi-delete </v-icon>
70
+ <span class="sr-only">{{ $t('shared.forms.delete') }}</span>
71
+ </v-btn>
72
+ </template>
73
+ </v-data-table>
74
+ </div>
3
75
  </template>
4
76
 
5
77
  <script>
78
+ import _ from 'lodash'
79
+ import { mapGetters } from 'vuex'
80
+ import Course from '~/models/Course'
81
+ import Organization from '~/models/Organization'
82
+ import Dialog from '~/components/Dialog.vue'
83
+ import Lti1p1Consumer from '../../../../models/ExternalIntegration/Lti1p1Consumer'
84
+ import ManageConsumer from './ManageConsumer.vue'
85
+
6
86
  export default {
7
87
  name: 'ManageLti1p1ConsumersDriver',
88
+ components: { Dialog, ManageConsumer },
8
89
  data() {
9
- return {}
90
+ return {
91
+ consumers: [],
92
+ headers: [
93
+ {
94
+ text: this.$t(
95
+ 'windward.integrations.components.external_integration.driver.lti1p1.target_url'
96
+ ),
97
+ value: 'target',
98
+ },
99
+ {
100
+ text: this.$t(
101
+ 'windward.integrations.components.external_integration.driver.lti1p1.name'
102
+ ),
103
+ value: 'name',
104
+ },
105
+ {
106
+ text: this.$t(
107
+ 'windward.integrations.components.external_integration.driver.lti1p1.description'
108
+ ),
109
+ value: 'description',
110
+ },
111
+ {
112
+ text: this.$t(
113
+ 'windward.integrations.components.external_integration.driver.lti1p1.enabled'
114
+ ),
115
+ value: 'enabled',
116
+ },
117
+ { text: this.$t('shared.forms.created'), value: 'created_at' },
118
+ {
119
+ text: this.$t('shared.forms.actions'),
120
+ value: 'actions',
121
+ sortable: false,
122
+ },
123
+ ],
124
+ }
125
+ },
126
+
127
+ async fetch() {
128
+ if (
129
+ !this.$PermissionService.userHasAccessTo(
130
+ 'plugin.windward.integrations.course.externalIntegration',
131
+ 'readable'
132
+ ) ||
133
+ _.isEmpty(this.organization.id) ||
134
+ _.isEmpty(this.course.id)
135
+ ) {
136
+ // Display an angry error that they can't view this driver
137
+ this.$dialog.error(this.$t('shared.error.description_401'), {
138
+ duration: null,
139
+ action: {
140
+ text: this.$t('shared.forms.close'),
141
+ onClick: (_e, toastObject) => {
142
+ toastObject.goAway(0)
143
+ },
144
+ },
145
+ })
146
+ if (_.isEmpty(this.organization.id) || _.isEmpty(this.course.id)) {
147
+ // eslint-disable-next-line no-console
148
+ console.error(
149
+ 'Cannot load external integrations because organization or course is not set!'
150
+ )
151
+ } else {
152
+ // eslint-disable-next-line no-console
153
+ console.error(
154
+ 'You do not have access to this external integration!'
155
+ )
156
+ }
157
+
158
+ // Return so we don't even attempt loading
159
+ return false
160
+ }
161
+
162
+ await this.loadConsumers()
163
+ },
164
+ computed: {
165
+ ...mapGetters({
166
+ organization: 'organization/get',
167
+ course: 'course/get',
168
+ }),
169
+ },
170
+ methods: {
171
+ onSaved() {
172
+ this.loadConsumers()
173
+ },
174
+ async loadConsumers() {
175
+ this.consumers = await new Lti1p1Consumer()
176
+ .for(
177
+ new Organization({ id: this.organization.id }),
178
+ new Course({ id: this.course.id })
179
+ )
180
+ .get()
181
+ },
182
+ onConfirmDelete(consumer) {
183
+ this.$dialog.show(this.$t('shared.forms.confirm_delete_text'), {
184
+ icon: 'mdi-help',
185
+ duration: null,
186
+ action: [
187
+ {
188
+ text: this.$t('shared.forms.cancel'),
189
+ onClick: (_e, toastObject) => {
190
+ toastObject.goAway(0)
191
+ },
192
+ },
193
+ {
194
+ text: this.$t('shared.forms.confirm'),
195
+ // router navigation
196
+ onClick: (_e, toastObject) => {
197
+ this.deleteConsumer(consumer)
198
+ toastObject.goAway(0)
199
+ },
200
+ },
201
+ ],
202
+ })
203
+ },
204
+ deleteConsumer(consumer) {
205
+ consumer.delete()
206
+ },
10
207
  },
11
- methods: {},
12
208
  }
13
209
  </script>
@@ -4,11 +4,18 @@
4
4
  <v-progress-circular size="128" indeterminate />
5
5
  </div>
6
6
  <div v-if="render">
7
- <v-form v-model="formValid" @submit.prevent>
8
- <v-row justify="center" align="center" class="mt-5">
7
+ <v-form ref="form" v-model="formValid" @submit.prevent>
8
+ <v-row justify="center" align="center">
9
9
  <v-col cols="12">
10
+ <ProviderTargetPicker
11
+ :target.sync="provider.target"
12
+ :metadata.sync="provider.metadata"
13
+ ></ProviderTargetPicker>
14
+
10
15
  <v-text-field
16
+ id="target-url"
11
17
  v-model="provider.target"
18
+ disabled
12
19
  :label="
13
20
  $t(
14
21
  'windward.integrations.components.external_integration.driver.lti1p1.target_url'
@@ -19,6 +26,7 @@
19
26
  'windward.integrations.components.external_integration.driver.lti1p1.target_url'
20
27
  )
21
28
  "
29
+ :rules="validation.existsRules"
22
30
  ></v-text-field>
23
31
 
24
32
  <v-switch
@@ -31,6 +39,7 @@
31
39
  />
32
40
 
33
41
  <v-text-field
42
+ id="lti-key"
34
43
  v-model="provider.metadata.key"
35
44
  :placeholder="
36
45
  $t(
@@ -50,6 +59,7 @@
50
59
  ></v-text-field>
51
60
 
52
61
  <v-text-field
62
+ id="lti-secret"
53
63
  v-model="provider.metadata.secret"
54
64
  :placeholder="
55
65
  $t(
@@ -240,15 +250,16 @@
240
250
  <script>
241
251
  import _ from 'lodash'
242
252
  import { mapGetters } from 'vuex'
243
- import Lti1p1Provider from '../../../../models/ExternalIntegration/Lti1p1Provider'
244
- import FormVue from '~/components/Form'
253
+ import FormVue from '~/components/Form.vue'
245
254
  import Role from '~/models/Role'
246
255
  import Organization from '~/models/Organization'
247
256
  import Course from '~/models/Course'
257
+ import ProviderTargetPicker from '../../ProviderTargetPicker.vue'
258
+ import Lti1p1Provider from '../../../../models/ExternalIntegration/Lti1p1Provider'
248
259
 
249
260
  export default {
250
261
  name: 'ManageLti1p1ProviderDriver',
251
- components: {},
262
+ components: { ProviderTargetPicker },
252
263
  extends: FormVue,
253
264
  props: {
254
265
  value: {
@@ -293,6 +304,7 @@ export default {
293
304
  ...mapGetters({
294
305
  organization: 'organization/get',
295
306
  course: 'course/get',
307
+ contentTree: 'content/getTree',
296
308
  }),
297
309
  },
298
310
  created() {
@@ -317,11 +329,12 @@ export default {
317
329
  duration: null,
318
330
  action: {
319
331
  text: this.$t('shared.forms.close'),
320
- onClick: (e, toastObject) => {
332
+ onClick: (_e, toastObject) => {
321
333
  toastObject.goAway(0)
322
334
  },
323
335
  },
324
336
  })
337
+ // eslint-disable-next-line no-console
325
338
  console.error('You do not have access to this provider!')
326
339
 
327
340
  // Return so we don't even attempt loading
@@ -358,13 +371,30 @@ export default {
358
371
 
359
372
  this.$dialog.success(this.$t('shared.forms.saved'))
360
373
  this.$emit('update:provider', providerEvent)
374
+ this.$emit('input', provider)
361
375
  } catch (e) {
362
376
  this.$dialog.error(
363
377
  this.$t('windward.integrations.shared.error.save_failed')
364
378
  )
365
379
  }
380
+
381
+ // Create a new provider to clear the form if the original value is empty aka new
382
+ if (_.isEmpty(this.value)) {
383
+ this.provider = new Lti1p1Provider({
384
+ role_metadata: {},
385
+ metadata: {},
386
+ })
387
+ }
366
388
  },
367
389
  async onSave() {
390
+ this.$refs.form.validate()
391
+ if (this.formValid) {
392
+ await this.save()
393
+ }
394
+ },
395
+
396
+ async onSaveAndNew() {
397
+ this.$refs.form.validate()
368
398
  if (this.formValid) {
369
399
  await this.save()
370
400
  }
@@ -23,17 +23,23 @@
23
23
  :items-per-page="10"
24
24
  class="elevation-1"
25
25
  >
26
- <template #item.metadata.key="{ item }">
26
+ <template #[`item.target`]="{ item }">
27
+ <ProviderTargetViewer
28
+ class="field--target text-truncate"
29
+ :target="item.target"
30
+ ></ProviderTargetViewer>
31
+ </template>
32
+ <template #[`item.metadata.key`]="{ item }">
27
33
  <SecretField v-model="item.metadata.key"></SecretField>
28
34
  </template>
29
- <template #item.metadata.secret="{ item }">
35
+ <template #[`item.metadata.secret`]="{ item }">
30
36
  <SecretField v-model="item.metadata.secret"></SecretField>
31
37
  </template>
32
- <template #item.url="{ item }">
38
+ <template #[`item.url`]="{ item }">
33
39
  <SecretField v-model="item.url" :hidden="false"></SecretField>
34
40
  </template>
35
41
 
36
- <template #item.enabled="{ item }">
42
+ <template #[`item.enabled`]="{ item }">
37
43
  <v-icon :color="item.enabled ? 'success' : 'error'"
38
44
  >{{ item.enabled ? 'mdi-check' : 'mdi-close' }}
39
45
  </v-icon>
@@ -44,8 +50,8 @@
44
50
  }}</span>
45
51
  </template>
46
52
 
47
- <template #item.created_at="{ item }">
48
- {{ $d(new Date(item.created_at), 'long') }}
53
+ <template #[`item.created_at`]="{ item }">
54
+ {{ $d(new Date(item.created_at), 'shorttime') }}
49
55
  </template>
50
56
  <template #[`item.actions`]="{ index, item }">
51
57
  <Dialog color="primary" action-save @click:save="onSaved">
@@ -83,23 +89,24 @@
83
89
  <script>
84
90
  import _ from 'lodash'
85
91
  import { mapGetters } from 'vuex'
86
- import Lti1p1Provider from '../../../../models/ExternalIntegration/Lti1p1Provider'
87
- import SecretField from '../../../SecretField.vue'
88
- import ManageProvider from './ManageProvider.vue'
89
92
  import Course from '~/models/Course'
90
93
  import Organization from '~/models/Organization'
91
94
  import Dialog from '~/components/Dialog.vue'
95
+ import Lti1p1Provider from '../../../../models/ExternalIntegration/Lti1p1Provider'
96
+ import SecretField from '../../../SecretField.vue'
97
+ import ProviderTargetViewer from '../../ProviderTargetViewer.vue'
98
+ import ManageProvider from './ManageProvider.vue'
92
99
 
93
100
  export default {
94
101
  name: 'ManageLti1p1ProvidersDriver',
95
- components: { SecretField, Dialog, ManageProvider },
102
+ components: { SecretField, Dialog, ManageProvider, ProviderTargetViewer },
96
103
  data() {
97
104
  return {
98
105
  providers: [],
99
106
  headers: [
100
107
  {
101
108
  text: this.$t(
102
- 'windward.integrations.components.external_integration.driver.lti1p1.target_url'
109
+ 'windward.integrations.components.external_integration.driver.lti1p1.target'
103
110
  ),
104
111
  value: 'target',
105
112
  },
@@ -151,16 +158,18 @@ export default {
151
158
  duration: null,
152
159
  action: {
153
160
  text: this.$t('shared.forms.close'),
154
- onClick: (e, toastObject) => {
161
+ onClick: (_e, toastObject) => {
155
162
  toastObject.goAway(0)
156
163
  },
157
164
  },
158
165
  })
159
166
  if (_.isEmpty(this.organization.id) || _.isEmpty(this.course.id)) {
167
+ // eslint-disable-next-line no-console
160
168
  console.error(
161
169
  'Cannot load external integrations because organization or course is not set!'
162
170
  )
163
171
  } else {
172
+ // eslint-disable-next-line no-console
164
173
  console.error(
165
174
  'You do not have access to this external integration!'
166
175
  )
@@ -179,7 +188,7 @@ export default {
179
188
  }),
180
189
  },
181
190
  methods: {
182
- onSaved(e) {
191
+ onSaved() {
183
192
  this.loadProviders()
184
193
  },
185
194
  async loadProviders() {
@@ -197,14 +206,14 @@ export default {
197
206
  action: [
198
207
  {
199
208
  text: this.$t('shared.forms.cancel'),
200
- onClick: (e, toastObject) => {
209
+ onClick: (_e, toastObject) => {
201
210
  toastObject.goAway(0)
202
211
  },
203
212
  },
204
213
  {
205
214
  text: this.$t('shared.forms.confirm'),
206
215
  // router navigation
207
- onClick: (e, toastObject) => {
216
+ onClick: (_e, toastObject) => {
208
217
  this.deleteProvider(provider)
209
218
  toastObject.goAway(0)
210
219
  },
@@ -212,9 +221,19 @@ export default {
212
221
  ],
213
222
  })
214
223
  },
215
- deleteProvider(provider) {
216
- provider.delete()
224
+ async deleteProvider(provider) {
225
+ await provider.delete()
226
+ this.$dialog.success(this.$t('shared.response.deleted'))
227
+ // Reload providers now that we deleted one
228
+ this.loadProviders()
217
229
  },
218
230
  },
219
231
  }
220
232
  </script>
233
+
234
+ <style scoped>
235
+ .field--target {
236
+ max-width: 15em;
237
+ display: inline-block;
238
+ }
239
+ </style>
@@ -10,7 +10,7 @@
10
10
  }}
11
11
  </v-expansion-panel-header>
12
12
  <v-expansion-panel-content>
13
- <ListProviders></ListProviders>
13
+ <ManageProviders></ManageProviders>
14
14
  </v-expansion-panel-content>
15
15
  </v-expansion-panel>
16
16
  <v-expansion-panel>
@@ -22,7 +22,7 @@
22
22
  }}
23
23
  </v-expansion-panel-header>
24
24
  <v-expansion-panel-content>
25
- <ListConsumers></ListConsumers>
25
+ <ManageConsumers></ManageConsumers>
26
26
  </v-expansion-panel-content>
27
27
  </v-expansion-panel>
28
28
  </v-expansion-panels>
@@ -30,12 +30,12 @@
30
30
  </template>
31
31
 
32
32
  <script>
33
- import ListProviders from './Lti1p1/ManageProviders.vue'
34
- import ListConsumers from './Lti1p1/ManageConsumers.vue'
33
+ import ManageProviders from './Lti1p1/ManageProviders.vue'
34
+ import ManageConsumers from './Lti1p1/ManageConsumers.vue'
35
35
 
36
36
  export default {
37
37
  name: 'ManageLti1p1Driver',
38
- components: { ListProviders, ListConsumers },
38
+ components: { ManageProviders, ManageConsumers },
39
39
  data() {
40
40
  return {
41
41
  panel: 0,