@windward/integrations 0.0.6 → 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.
@@ -28,16 +28,16 @@
28
28
  </v-alert>
29
29
 
30
30
  <v-form
31
- name="ltiForm"
32
31
  ref="ltiForm"
32
+ name="ltiForm"
33
33
  :target="target"
34
34
  :action="launchData.target || ''"
35
35
  :method="launchData.method || 'POST'"
36
36
  >
37
37
  <input
38
- type="hidden"
39
38
  v-for="(value, key) of launchData.payload"
40
39
  :key="key"
40
+ type="hidden"
41
41
  :name="key"
42
42
  :value="value"
43
43
  />
@@ -83,17 +83,8 @@ import Lti1p1Consumer from '../../../../models/ExternalIntegration/Lti1p1Consume
83
83
 
84
84
  export default {
85
85
  name: 'ContentBlockExternalIntegrationLti1p1Consumer',
86
- extends: BaseContentBlock,
87
86
  components: { TextViewer },
88
87
  extends: BaseContentBlock,
89
- beforeMount() {
90
- if (_.isEmpty(this.block.metadata.config)) {
91
- this.block.metadata.config = {}
92
- }
93
- if (_.isEmpty(this.block.metadata.config.tool_id)) {
94
- this.block.metadata.config.tool_id = null
95
- }
96
- },
97
88
  data() {
98
89
  return {
99
90
  consumer: null,
@@ -105,6 +96,17 @@ export default {
105
96
  launched: false,
106
97
  }
107
98
  },
99
+ async fetch() {
100
+ this.target = this.frameId
101
+ await this.loadConsumer()
102
+ },
103
+ computed: {
104
+ ...mapGetters({
105
+ organization: 'organization/get',
106
+ course: 'course/get',
107
+ enrollment: 'enrollment/get',
108
+ }),
109
+ },
108
110
  watch: {
109
111
  'block.metadata.config.tool_id': {
110
112
  deep: true,
@@ -116,16 +118,13 @@ export default {
116
118
  },
117
119
  },
118
120
  },
119
- async fetch() {
120
- this.target = this.frameId
121
- this.loadConsumer()
122
- },
123
- computed: {
124
- ...mapGetters({
125
- organization: 'organization/get',
126
- course: 'course/get',
127
- enrollment: 'enrollment/get',
128
- }),
121
+ beforeMount() {
122
+ if (_.isEmpty(this.block.metadata.config)) {
123
+ this.block.metadata.config = {}
124
+ }
125
+ if (_.isEmpty(this.block.metadata.config.tool_id)) {
126
+ this.block.metadata.config.tool_id = null
127
+ }
129
128
  },
130
129
  mounted() {},
131
130
  methods: {
@@ -186,8 +185,8 @@ export default {
186
185
  )
187
186
  }
188
187
  } catch (e) {
189
- console.error('LTI Link Launch Fail')
190
- console.error(e)
188
+ // eslint-disable-next-line no-console
189
+ console.error('LTI Link Launch Fail', e)
191
190
  }
192
191
  },
193
192
  onBeforeSave() {
@@ -31,8 +31,8 @@
31
31
  $t('shared.forms.description')
32
32
  }}</label>
33
33
  <TextEditor
34
- v-model="consumer.description"
35
34
  id="description"
35
+ v-model="consumer.description"
36
36
  :height="200"
37
37
  ></TextEditor>
38
38
 
@@ -86,7 +86,7 @@
86
86
  hide-default-footer
87
87
  class="elevation-1"
88
88
  >
89
- <template #item.key="{ index }">
89
+ <template #[`item.key`]="{ index }">
90
90
  <v-text-field
91
91
  v-model="
92
92
  consumer.metadata.custom[index].key
@@ -98,7 +98,7 @@
98
98
  "
99
99
  />
100
100
  </template>
101
- <template #item.value="{ index }">
101
+ <template #[`item.value`]="{ index }">
102
102
  <v-text-field
103
103
  v-model="
104
104
  consumer.metadata.custom[index].value
@@ -110,7 +110,7 @@
110
110
  "
111
111
  />
112
112
  </template>
113
- <template #item.actions="{ index }">
113
+ <template #[`item.actions`]="{ index }">
114
114
  <v-btn
115
115
  text
116
116
  color="primary"
@@ -152,11 +152,11 @@
152
152
  <script>
153
153
  import _ from 'lodash'
154
154
  import { mapGetters } from 'vuex'
155
- import Lti1p1Consumer from '../../../../models/ExternalIntegration/Lti1p1Consumer'
156
- import FormVue from '~/components/Form'
157
155
  import Organization from '~/models/Organization'
158
156
  import Course from '~/models/Course'
159
157
  import TextEditor from '~/components/Text/TextEditor.vue'
158
+ import Lti1p1Consumer from '../../../../models/ExternalIntegration/Lti1p1Consumer'
159
+ import FormVue from '~/components/Form'
160
160
 
161
161
  export default {
162
162
  name: 'ManageLti1p1ConsumerDriver',
@@ -240,11 +240,13 @@ export default {
240
240
  duration: null,
241
241
  action: {
242
242
  text: this.$t('shared.forms.close'),
243
- onClick: (e, toastObject) => {
243
+ onClick: (_e, toastObject) => {
244
244
  toastObject.goAway(0)
245
245
  },
246
246
  },
247
247
  })
248
+
249
+ // eslint-disable-next-line no-console
248
250
  console.error('You do not have access to this consumer!')
249
251
 
250
252
  // Return so we don't even attempt loading
@@ -23,11 +23,11 @@
23
23
  :items-per-page="10"
24
24
  class="elevation-1"
25
25
  >
26
- <template #item.description="{ item }">
26
+ <template #[`item.description`]="{ item }">
27
27
  {{ item.description.replace(/(<([^>]+)>)/gi, '').trim() }}
28
28
  </template>
29
29
 
30
- <template #item.enabled="{ item }">
30
+ <template #[`item.enabled`]="{ item }">
31
31
  <v-icon :color="item.enabled ? 'success' : 'error'"
32
32
  >{{ item.enabled ? 'mdi-check' : 'mdi-close' }}
33
33
  </v-icon>
@@ -38,7 +38,7 @@
38
38
  }}</span>
39
39
  </template>
40
40
 
41
- <template #item.created_at="{ item }">
41
+ <template #[`item.created_at`]="{ item }">
42
42
  {{ $d(new Date(item.created_at), 'long') }}
43
43
  </template>
44
44
  <template #[`item.actions`]="{ index, item }">
@@ -77,16 +77,15 @@
77
77
  <script>
78
78
  import _ from 'lodash'
79
79
  import { mapGetters } from 'vuex'
80
- import Lti1p1Consumer from '../../../../models/ExternalIntegration/Lti1p1Consumer'
81
- import SecretField from '../../../SecretField.vue'
82
- import ManageConsumer from './ManageConsumer.vue'
83
80
  import Course from '~/models/Course'
84
81
  import Organization from '~/models/Organization'
85
82
  import Dialog from '~/components/Dialog.vue'
83
+ import Lti1p1Consumer from '../../../../models/ExternalIntegration/Lti1p1Consumer'
84
+ import ManageConsumer from './ManageConsumer.vue'
86
85
 
87
86
  export default {
88
87
  name: 'ManageLti1p1ConsumersDriver',
89
- components: { SecretField, Dialog, ManageConsumer },
88
+ components: { Dialog, ManageConsumer },
90
89
  data() {
91
90
  return {
92
91
  consumers: [],
@@ -139,16 +138,18 @@ export default {
139
138
  duration: null,
140
139
  action: {
141
140
  text: this.$t('shared.forms.close'),
142
- onClick: (e, toastObject) => {
141
+ onClick: (_e, toastObject) => {
143
142
  toastObject.goAway(0)
144
143
  },
145
144
  },
146
145
  })
147
146
  if (_.isEmpty(this.organization.id) || _.isEmpty(this.course.id)) {
147
+ // eslint-disable-next-line no-console
148
148
  console.error(
149
149
  'Cannot load external integrations because organization or course is not set!'
150
150
  )
151
151
  } else {
152
+ // eslint-disable-next-line no-console
152
153
  console.error(
153
154
  'You do not have access to this external integration!'
154
155
  )
@@ -167,7 +168,7 @@ export default {
167
168
  }),
168
169
  },
169
170
  methods: {
170
- onSaved(e) {
171
+ onSaved() {
171
172
  this.loadConsumers()
172
173
  },
173
174
  async loadConsumers() {
@@ -185,14 +186,14 @@ export default {
185
186
  action: [
186
187
  {
187
188
  text: this.$t('shared.forms.cancel'),
188
- onClick: (e, toastObject) => {
189
+ onClick: (_e, toastObject) => {
189
190
  toastObject.goAway(0)
190
191
  },
191
192
  },
192
193
  {
193
194
  text: this.$t('shared.forms.confirm'),
194
195
  // router navigation
195
- onClick: (e, toastObject) => {
196
+ onClick: (_e, toastObject) => {
196
197
  this.deleteConsumer(consumer)
197
198
  toastObject.goAway(0)
198
199
  },
@@ -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>
@@ -0,0 +1,234 @@
1
+ <template>
2
+ <div>
3
+ <v-radio-group
4
+ v-model="localMetadata.target_type"
5
+ row
6
+ @change="onTargetTypeChange"
7
+ >
8
+ <template #label>
9
+ <div>
10
+ {{
11
+ $t(
12
+ 'windward.integrations.components.external_integration.provider_target.link_to'
13
+ )
14
+ }}:
15
+ </div>
16
+ </template>
17
+ <v-radio
18
+ :label="
19
+ $t(
20
+ 'windward.integrations.components.external_integration.provider_target.whole_course'
21
+ )
22
+ "
23
+ value="course"
24
+ ></v-radio>
25
+ <v-radio
26
+ :label="
27
+ $t(
28
+ 'windward.integrations.components.external_integration.provider_target.content_with_children'
29
+ )
30
+ "
31
+ value="locked_course_content_children"
32
+ ></v-radio>
33
+ <v-radio
34
+ :label="
35
+ $t(
36
+ 'windward.integrations.components.external_integration.provider_target.content_no_children'
37
+ )
38
+ "
39
+ value="locked_course_content"
40
+ ></v-radio>
41
+ <v-radio
42
+ :label="
43
+ $t(
44
+ 'windward.integrations.components.external_integration.provider_target.whole_course_page'
45
+ )
46
+ "
47
+ value="course_content"
48
+ ></v-radio>
49
+ </v-radio-group>
50
+
51
+ <div
52
+ v-if="
53
+ localMetadata.target_type === 'course_content' ||
54
+ localMetadata.target_type ===
55
+ 'locked_course_content_children' ||
56
+ localMetadata.target_type === 'locked_course_content'
57
+ "
58
+ class="content-tree secondary"
59
+ >
60
+ <v-treeview
61
+ v-model="selectedTreeviewContent"
62
+ selectable
63
+ selected-color="primary"
64
+ selection-type="independent"
65
+ return-object
66
+ open-all
67
+ :items="contentTree"
68
+ @input="onTreeChange"
69
+ >
70
+ <template #prepend="{ item }">
71
+ <v-chip v-if="isIncludedChild(item)" color="primary">
72
+ {{
73
+ $t(
74
+ 'windward.integrations.components.external_integration.provider_target.included'
75
+ )
76
+ }}
77
+ </v-chip>
78
+ </template>
79
+ <template #label="{ item }">
80
+ {{ item.content.name_prefix }}
81
+ {{ item.content.name }}
82
+ </template>
83
+ </v-treeview>
84
+ </div>
85
+ </div>
86
+ </template>
87
+
88
+ <script>
89
+ import _ from 'lodash'
90
+ import { mapGetters } from 'vuex'
91
+ export default {
92
+ name: 'ExternalIntegrationProviderTargetPicker',
93
+ props: {
94
+ target: { type: String, required: true, default: '' },
95
+ metadata: { type: Object, required: true, default: () => {} },
96
+ },
97
+ data() {
98
+ return {
99
+ selectedContent: null,
100
+ selectedTreeviewContent: [],
101
+ localTarget: '',
102
+ localMetadata: {},
103
+ }
104
+ },
105
+ computed: {
106
+ ...mapGetters({
107
+ contentTree: 'content/getTree',
108
+ course: 'course/get',
109
+ }),
110
+ },
111
+ watch: {
112
+ // Apply any changes coming up
113
+ target: {
114
+ handler(value) {
115
+ this.localTarget = _.cloneDeep(value)
116
+ },
117
+ },
118
+ metadata: {
119
+ deep: true,
120
+ handler(value) {
121
+ this.localMetadata = _.cloneDeep(value)
122
+ },
123
+ },
124
+ },
125
+ mounted() {
126
+ // Init our local vars
127
+ this.localTarget = _.cloneDeep(this.target)
128
+ this.localMetadata = _.cloneDeep(this.metadata)
129
+
130
+ // Set the target_type key if it doesn't exist
131
+ if (!this.localMetadata.target_type) {
132
+ this.localMetadata.target_type = ''
133
+ }
134
+
135
+ // Preset the selected content
136
+ if (this.localTarget) {
137
+ const match = this.localTarget.match(
138
+ /\/content\/(?<content_id>[a-zA-Z0-9-]{36})/i
139
+ )
140
+
141
+ if (!_.isEmpty(match) && _.get(match, 'groups.content_id', null)) {
142
+ this.setSelectedContentById(
143
+ _.get(match, 'groups.content_id', null)
144
+ )
145
+ }
146
+ }
147
+ this.sync(this.localMetadata.target_type)
148
+ },
149
+ methods: {
150
+ setSelectedContentById(id) {
151
+ this.selectedContent = { id }
152
+ this.selectedTreeviewContent = [{ id }]
153
+ },
154
+ sync(targetType) {
155
+ let url = ''
156
+
157
+ if (
158
+ this.selectedContent &&
159
+ (targetType === 'course_content' ||
160
+ targetType === 'locked_course_content' ||
161
+ targetType === 'locked_course_content_children')
162
+ ) {
163
+ url = `/course/${this.course.id}/content/${this.selectedContent.id}`
164
+ } else if (targetType === 'course') {
165
+ url = `/course/${this.course.id}/`
166
+ }
167
+ this.localTarget = url
168
+ // Sync up the changes to the parent component
169
+ this.$emit('update:target', this.localTarget)
170
+ this.$emit('update:metadata', this.localMetadata)
171
+ },
172
+ onTargetTypeChange(e) {
173
+ // Reset the selected content if you pick by course
174
+ if (e === 'course') {
175
+ this.selectedContent = null
176
+ this.selectedTreeviewContent = []
177
+ }
178
+
179
+ this.sync(e)
180
+ },
181
+ isIncludedChild(page, children = null) {
182
+ let isIncluded = false
183
+ // If we aren't including children then return hard false
184
+ if (
185
+ this.localMetadata.target_type !==
186
+ 'locked_course_content_children'
187
+ ) {
188
+ return false
189
+ }
190
+
191
+ // First run, assign the children from the selected page
192
+ if (this.selectedContent && children === null) {
193
+ children = _.get(this.selectedContent, 'children', [])
194
+ }
195
+
196
+ if (this.selectedContent && page.parent_id) {
197
+ if (this.selectedContent.id === page.parent_id) {
198
+ return true
199
+ }
200
+
201
+ for (let i = 0; i < children.length; i++) {
202
+ const child = children[i]
203
+
204
+ if (child.id === page.parent_id) {
205
+ return true
206
+ } else if (child.children && child.children.length) {
207
+ isIncluded = this.isIncludedChild(page, child.children)
208
+ }
209
+ }
210
+ }
211
+ return isIncluded
212
+ },
213
+ onTreeChange() {
214
+ // Enforce that only 1 item is selected at a time in the treeview
215
+ if (this.selectedTreeviewContent.length > 0) {
216
+ const last = this.selectedTreeviewContent.pop()
217
+ this.selectedTreeviewContent = [last]
218
+ this.selectedContent = last
219
+ } else {
220
+ this.selectedContent = null
221
+ }
222
+
223
+ this.sync(this.localMetadata.target_type)
224
+ },
225
+ },
226
+ }
227
+ </script>
228
+
229
+ <style scoped>
230
+ .content-tree {
231
+ height: 230px;
232
+ overflow-y: auto;
233
+ }
234
+ </style>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <span>{{ targetDisplayName }}</span>
3
+ </template>
4
+
5
+ <script>
6
+ import _ from 'lodash'
7
+ import { mapGetters } from 'vuex'
8
+ export default {
9
+ name: 'ExternalIntegrationProviderTargetViewer',
10
+ props: {
11
+ target: { type: String, required: true, default: '' },
12
+ },
13
+ computed: {
14
+ ...mapGetters({
15
+ organization: 'organization/get',
16
+ course: 'course/get',
17
+ contentTree: 'content/getTree',
18
+ }),
19
+ targetDisplayName() {
20
+ if (_.isEmpty(this.target)) {
21
+ return ''
22
+ }
23
+ const match = this.target.match(
24
+ /\/content\/(?<content_id>[a-zA-Z0-9-]{36})/i
25
+ )
26
+ if (!_.isEmpty(match) && _.get(match, 'groups.content_id', null)) {
27
+ const flatTree = this.$ContentService.flattenTree(
28
+ this.contentTree
29
+ )
30
+ const item = flatTree.find(
31
+ (c) => c.id === _.get(match, 'groups.content_id', null)
32
+ )
33
+
34
+ if (!_.isEmpty(item)) {
35
+ return (
36
+ _.get(item, 'content.name_prefix', '') +
37
+ ' ' +
38
+ _.get(item, 'content.name', '')
39
+ )
40
+ }
41
+ } else {
42
+ return this.$t(
43
+ 'windward.integrations.components.external_integration.provider_target.whole_course'
44
+ )
45
+ }
46
+ return ''
47
+ },
48
+ },
49
+ }
50
+ </script>
@@ -38,11 +38,12 @@ export default {
38
38
  duration: null,
39
39
  action: {
40
40
  text: this.$t('shared.forms.close'),
41
- onClick: (e, toastObject) => {
41
+ onClick: (_e, toastObject) => {
42
42
  toastObject.goAway(0)
43
43
  },
44
44
  },
45
45
  })
46
+ // eslint-disable-next-line no-console
46
47
  console.error('You do not have access to this integration!')
47
48
 
48
49
  // Return so we don't even attempt loading
@@ -129,6 +130,7 @@ export default {
129
130
  },
130
131
 
131
132
  onIntegrationLoaded() {
133
+ // eslint-disable-next-line no-console
132
134
  console.warn(
133
135
  'Integration/Driver/ManageBase.vue onIntegrationLoaded called. Not extended!'
134
136
  )
@@ -247,7 +247,7 @@ export default {
247
247
  connect(channel, event) {
248
248
  this.socket.connector.pusher.connection.bind(
249
249
  'connected',
250
- function (socket) {
250
+ function (_socket) {
251
251
  // Connected to socket id: socket.socket_id
252
252
  }
253
253
  )
@@ -266,6 +266,7 @@ export default {
266
266
 
267
267
  onSocketJob(data) {
268
268
  if (_.isEmpty(data.integrationJob)) {
269
+ // eslint-disable-next-line no-console
269
270
  console.error('onSocketJob Missing data', data)
270
271
  return false
271
272
  }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-text-field :value="value" readonly :type="fieldType">
2
+ <v-text-field id="secret-field" :value="value" readonly :type="fieldType">
3
3
  <template #append>
4
4
  <v-btn icon @click="toggleClear">
5
5
  <v-icon>{{ showClear ? 'mdi-eye-off' : 'mdi-eye' }}</v-icon>
@@ -46,10 +46,8 @@
46
46
  </template>
47
47
 
48
48
  <script>
49
- import _ from 'lodash'
50
49
  import { mapGetters } from 'vuex'
51
50
  import BaseContentSettings from '~/components/Content/Tool/BaseContentSettings.js'
52
- import ManageConsumer from '../../ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue'
53
51
  import Course from '~/models/Course'
54
52
  import Organization from '~/models/Organization'
55
53
  import Lti1p1Consumer from '../../../models/ExternalIntegration/Lti1p1Consumer'
@@ -57,27 +55,10 @@ import Lti1p1Consumer from '../../../models/ExternalIntegration/Lti1p1Consumer'
57
55
  export default {
58
56
  name: 'ContentBlockExternalIntegrationLti1p1ConsumerSettings',
59
57
  extends: BaseContentSettings,
60
- components: { ManageConsumer },
61
58
  props: {
62
59
  settings: { type: Object, required: false, default: null },
63
60
  context: { type: String, required: false, default: 'block' },
64
61
  },
65
- async fetch() {
66
- this.consumers = await new Lti1p1Consumer()
67
- .for(
68
- new Organization({ id: this.organization.id }),
69
- new Course({ id: this.course.id })
70
- )
71
- .orderBy('name')
72
- .get()
73
- },
74
- computed: {
75
- ...mapGetters({
76
- organization: 'organization/get',
77
- course: 'course/get',
78
- }),
79
- },
80
- beforeMount() {},
81
62
  data() {
82
63
  return {
83
64
  consumers: [],
@@ -97,7 +78,23 @@ export default {
97
78
  ],
98
79
  }
99
80
  },
81
+ async fetch() {
82
+ this.consumers = await new Lti1p1Consumer()
83
+ .for(
84
+ new Organization({ id: this.organization.id }),
85
+ new Course({ id: this.course.id })
86
+ )
87
+ .orderBy('name')
88
+ .get()
89
+ },
90
+ computed: {
91
+ ...mapGetters({
92
+ organization: 'organization/get',
93
+ course: 'course/get',
94
+ }),
95
+ },
100
96
  watch: {},
97
+ beforeMount() {},
101
98
  mounted() {},
102
99
  methods: {
103
100
  onSelectLink(consumer) {
@@ -11,6 +11,7 @@ export default {
11
11
  change_secret: 'Change Secret',
12
12
  new_key: 'New Key',
13
13
  new_secret: 'New Secret',
14
+ target: 'Target',
14
15
  target_url: 'Target Url',
15
16
  launch_url: 'Launch Url',
16
17
  new: 'New LTI Link',
@@ -1,5 +1,6 @@
1
1
  import lti1p1 from './driver/lti1p1'
2
-
2
+ import providerTarget from './provider_target'
3
3
  export default {
4
4
  driver: { lti1p1 },
5
+ provider_target: providerTarget,
5
6
  }
@@ -0,0 +1,9 @@
1
+ export default {
2
+ name: 'Target Name',
3
+ link_to: 'Link To',
4
+ included: 'Included',
5
+ whole_course: 'Whole Course',
6
+ whole_course_page: 'Whole Course Choose Landing Page',
7
+ content_with_children: 'Content with Child Pages',
8
+ content_no_children: 'Single Content Page',
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/integrations",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Windward UI Plugin Integrations for 3rd Party Systems",
5
5
  "main": "plugin.js",
6
6
  "scripts": {
@@ -287,6 +287,7 @@ export default {
287
287
  .get()
288
288
  } catch (e) {
289
289
  if (e.response.status === 404) {
290
+ // eslint-disable-next-line no-console
290
291
  console.error(e)
291
292
  this.$dialog.error(
292
293
  this.$t(
@@ -329,6 +330,7 @@ export default {
329
330
  .get()
330
331
  } catch (e) {
331
332
  if (e.response.status === 404) {
333
+ // eslint-disable-next-line no-console
332
334
  console.error(e)
333
335
  this.$dialog.error(
334
336
  this.$t(
@@ -374,6 +376,7 @@ export default {
374
376
  }
375
377
  } catch (e) {
376
378
  if (e.response.status === 404) {
379
+ // eslint-disable-next-line no-console
377
380
  console.error(e)
378
381
  this.$dialog.error(
379
382
  this.$t(
@@ -151,9 +151,9 @@
151
151
  import _ from 'lodash'
152
152
  import { mapGetters } from 'vuex'
153
153
 
154
+ import Dialog from '~/components/Dialog.vue'
154
155
  import Organization from '../../models/Organization'
155
156
  import IntegrationJobTable from '../../components/Integration/JobTable.vue'
156
- import Dialog from '~/components/Dialog.vue'
157
157
 
158
158
  export default {
159
159
  name: 'PluginIntegrationsAdminVendorsPage',
@@ -216,6 +216,7 @@ export default {
216
216
  .integrations()
217
217
  .get()
218
218
  } catch (e) {
219
+ // eslint-disable-next-line no-console
219
220
  console.error('Could not load organization integrations', e)
220
221
  }
221
222
  },
package/plugin.js CHANGED
@@ -18,7 +18,7 @@ export default {
18
18
  beforeDestroy: () => {},
19
19
  beforeNavigate: () => {},
20
20
  onNavigate: () => {},
21
- onLoad: (page) => {},
21
+ onLoad: (_page) => {},
22
22
  onContent: () => {},
23
23
  },
24
24
  i18n: {
@@ -0,0 +1,22 @@
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 ProviderTargetPicker from '@/components/ExternalIntegration/ProviderTargetPicker.vue'
8
+
9
+ Vue.use(Vuetify)
10
+
11
+ describe('ProviderTargetPicker', () => {
12
+ test('ProviderTargetPicker is a Vue instance', () => {
13
+ const wrapper = shallowMount(ProviderTargetPicker, {
14
+ vuetify: new Vuetify(),
15
+ mocks: defaultMocks,
16
+ propsData: {
17
+ metadata: {},
18
+ },
19
+ })
20
+ expect(wrapper.vm).toBeTruthy()
21
+ })
22
+ })
@@ -0,0 +1,22 @@
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 ProviderTargetViewer from '@/components/ExternalIntegration/ProviderTargetViewer.vue'
8
+
9
+ Vue.use(Vuetify)
10
+
11
+ describe('ProviderTargetViewer', () => {
12
+ test('ProviderTargetViewer is a Vue instance', () => {
13
+ const wrapper = shallowMount(ProviderTargetViewer, {
14
+ vuetify: new Vuetify(),
15
+ mocks: defaultMocks,
16
+ propsData: {
17
+ target: '',
18
+ },
19
+ })
20
+ expect(wrapper.vm).toBeTruthy()
21
+ })
22
+ })
@@ -0,0 +1,12 @@
1
+ // TODO find better way for jest to recognize utils imports
2
+ jest.mock(
3
+ '~/components/Form.vue',
4
+ () => {
5
+ return {
6
+ data() {
7
+ return { validation: {} }
8
+ },
9
+ }
10
+ },
11
+ { virtual: true }
12
+ )
@@ -44,7 +44,7 @@ class mockBaseModel extends mockModel {
44
44
  return 'http://windwardapi.local'
45
45
  }
46
46
 
47
- request(config) {
47
+ request(_config) {
48
48
  return new Promise((resolve) => {
49
49
  resolve({ data: {} })
50
50
  })
package/test/mocks.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { defaultMocks as mocks } from '@windward/core/test/mocks'
2
2
 
3
3
  require('./__mocks__/modelMock')
4
+ require('./__mocks__/componentsMock')
4
5
 
5
6
  mocks.$Integration = {
6
7
  async load() {