@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
@@ -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>
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <v-container>
3
+ <v-select
4
+ v-model="block.metadata.config.launch_type"
5
+ :label="
6
+ $t(
7
+ 'windward.integrations.components.settings.external_integration.lti_consumer.launch_type'
8
+ )
9
+ "
10
+ :items="launchTypes"
11
+ item-text="name"
12
+ item-value="value"
13
+ ></v-select>
14
+ {{
15
+ $t(
16
+ 'windward.integrations.components.settings.external_integration.lti_consumer.link_select'
17
+ )
18
+ }}
19
+ <v-list>
20
+ <v-list-item
21
+ v-for="consumer in consumers"
22
+ :key="consumer.id"
23
+ two-line
24
+ :disabled="!consumer.enabled"
25
+ @click="onSelectLink(consumer)"
26
+ >
27
+ <v-list-item-avatar :color="!consumer.enabled ? 'error' : ''">
28
+ <v-icon> mdi-shield-link-variant-outline </v-icon>
29
+ </v-list-item-avatar>
30
+ <v-list-item-content>
31
+ <v-list-item-title>{{ consumer.name }}</v-list-item-title>
32
+ <v-list-item-subtitle v-if="!consumer.enabled">
33
+ <v-chip color="error">{{
34
+ $t(
35
+ 'windward.integrations.components.settings.external_integration.lti_consumer.link_disabled'
36
+ )
37
+ }}</v-chip>
38
+ </v-list-item-subtitle>
39
+ <v-list-item-subtitle>{{
40
+ consumer.description.replace(/(<([^>]+)>)/gi, '').trim()
41
+ }}</v-list-item-subtitle>
42
+ </v-list-item-content>
43
+ </v-list-item>
44
+ </v-list>
45
+ </v-container>
46
+ </template>
47
+
48
+ <script>
49
+ import { mapGetters } from 'vuex'
50
+ import BaseContentSettings from '~/components/Content/Tool/BaseContentSettings.js'
51
+ import Course from '~/models/Course'
52
+ import Organization from '~/models/Organization'
53
+ import Lti1p1Consumer from '../../../models/ExternalIntegration/Lti1p1Consumer'
54
+
55
+ export default {
56
+ name: 'ContentBlockExternalIntegrationLti1p1ConsumerSettings',
57
+ extends: BaseContentSettings,
58
+ props: {
59
+ settings: { type: Object, required: false, default: null },
60
+ context: { type: String, required: false, default: 'block' },
61
+ },
62
+ data() {
63
+ return {
64
+ consumers: [],
65
+ launchTypes: [
66
+ {
67
+ name: this.$t(
68
+ 'windward.integrations.components.settings.external_integration.lti_consumer.launch_type_new_window'
69
+ ),
70
+ value: 'new_window',
71
+ },
72
+ {
73
+ name: this.$t(
74
+ 'windward.integrations.components.settings.external_integration.lti_consumer.launch_type_inline_frame'
75
+ ),
76
+ value: 'inline',
77
+ },
78
+ ],
79
+ }
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
+ },
96
+ watch: {},
97
+ beforeMount() {},
98
+ mounted() {},
99
+ methods: {
100
+ onSelectLink(consumer) {
101
+ this.block.metadata.config.tool_id = consumer.id
102
+ },
103
+ },
104
+ }
105
+ </script>
@@ -0,0 +1,5 @@
1
+ import ltiConsumer from './lti_consumer'
2
+
3
+ export default {
4
+ lti_consumer: ltiConsumer,
5
+ }
@@ -0,0 +1,8 @@
1
+ export default {
2
+ launch: 'Launch',
3
+ configure_warning:
4
+ 'This block needs to be configured. Please select a LTI tool in the settings panel.',
5
+ missing_tool: 'The tool id {0} is missing',
6
+ unknown_error: 'Something went wrong! Could not launch this link!',
7
+ link_disabled: 'Link disabled',
8
+ }
@@ -0,0 +1,5 @@
1
+ import externalIntegration from './external_integration'
2
+
3
+ export default {
4
+ external_integration: externalIntegration,
5
+ }
@@ -0,0 +1,5 @@
1
+ import blocks from './blocks'
2
+
3
+ export default {
4
+ blocks,
5
+ }
@@ -1,8 +1,17 @@
1
1
  export default {
2
+ name: 'Name',
3
+ description: 'Description',
2
4
  key: 'Key',
3
5
  secret: 'Secret',
4
- auto_key: 'Autogenerated Key',
5
- auto_secret: 'Autogenerated Secret',
6
+ parameter_name: 'Custom Parameter Name',
7
+ value: 'Value',
8
+ auto_key: 'Leave empty for autogenerated key',
9
+ auto_secret: 'Leave empty for autogenerated secret',
10
+ change_key: 'Change Key',
11
+ change_secret: 'Change Secret',
12
+ new_key: 'New Key',
13
+ new_secret: 'New Secret',
14
+ target: 'Target',
6
15
  target_url: 'Target Url',
7
16
  launch_url: 'Launch Url',
8
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
+ }
@@ -1,8 +1,12 @@
1
+ import content from './content'
2
+ import settings from './settings'
1
3
  import navigation from './navigation'
2
4
  import integration from './integration'
3
5
  import externalIntegration from './external_integration'
4
6
 
5
7
  export default {
8
+ content,
9
+ settings,
6
10
  navigation,
7
11
  integration,
8
12
  external_integration: externalIntegration,
@@ -0,0 +1,5 @@
1
+ import ltiConsumer from './lti_consumer'
2
+
3
+ export default {
4
+ lti_consumer: ltiConsumer,
5
+ }
@@ -0,0 +1,7 @@
1
+ export default {
2
+ launch_type: 'Launch Type',
3
+ launch_type_new_window: 'New Window',
4
+ launch_type_inline_frame: 'Inline Frame',
5
+ link_select: 'Select a Link:',
6
+ link_disabled: 'Link Disabled',
7
+ }
@@ -0,0 +1,5 @@
1
+ import externalIntegration from './external_integration'
2
+
3
+ export default {
4
+ external_integration: externalIntegration,
5
+ }
@@ -0,0 +1,8 @@
1
+ export default {
2
+ title: {
3
+ lti_consumer: 'LTI Link',
4
+ },
5
+ grouping: {
6
+ integrations: 'Integrations',
7
+ },
8
+ }
@@ -1,9 +1,11 @@
1
+ import contentBlocks from './content_blocks'
1
2
  import settings from './settings'
2
3
  import menu from './menu'
3
4
  import permission from './permission'
4
5
  import error from './error'
5
6
 
6
7
  export default {
8
+ content_blocks: contentBlocks,
7
9
  settings,
8
10
  menu,
9
11
  permission,
@@ -1 +1,5 @@
1
- export default {}
1
+ export default {
2
+ title: {
3
+ lti_consumer: 'LTI Consumer',
4
+ },
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/integrations",
3
- "version": "0.0.5",
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
@@ -6,6 +6,9 @@ import ExternalIntegrationIndexPage from './pages/course/externalIntegration/ind
6
6
  import LoginLtiErrorPage from './pages/login/lti/error.vue'
7
7
  import IntegrationHelper from './helpers/IntegrationHelper'
8
8
 
9
+ import LtiConsumerBlock from './components/Content/Blocks/ExternalIntegration/LtiConsumer'
10
+ import LtiConsumerBlockSettings from './components/Settings/ExternalIntegration/LtiConsumerSettings'
11
+
9
12
  export default {
10
13
  name: 'windward.integrations.name',
11
14
  hooks: {
@@ -15,7 +18,7 @@ export default {
15
18
  beforeDestroy: () => {},
16
19
  beforeNavigate: () => {},
17
20
  onNavigate: () => {},
18
- onLoad: (page) => {},
21
+ onLoad: (_page) => {},
19
22
  onContent: () => {},
20
23
  },
21
24
  i18n: {
@@ -131,6 +134,29 @@ export default {
131
134
  ],
132
135
  },
133
136
  ],
137
+ contentBlock: [
138
+ {
139
+ tag: 'windward-integrations-lti-consumer',
140
+ template: LtiConsumerBlock,
141
+ metadata: {
142
+ icon: 'mdi-shield-link-variant-outline',
143
+ name: 'windward.integrations.shared.content_blocks.title.lti_consumer',
144
+ grouping:
145
+ 'windward.integrations.shared.content_blocks.grouping.integrations',
146
+ },
147
+ },
148
+ ],
149
+ settings: [
150
+ {
151
+ tag: 'windward-integrations-lti-consumer-settings',
152
+ template: LtiConsumerBlockSettings,
153
+ context: ['block.windward-integrations-lti-consumer'],
154
+ metadata: {
155
+ icon: 'mdi-cog',
156
+ name: 'windward.integrations.shared.settings.title.lti_consumer',
157
+ },
158
+ },
159
+ ],
134
160
  },
135
161
  services: {
136
162
  Integration: IntegrationHelper,