@windward/integrations 0.0.8 → 0.0.9

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 (25) hide show
  1. package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +9 -6
  2. package/components/ExternalIntegration/Driver/Lti1p1/ManageProviders.vue +6 -5
  3. package/components/ExternalIntegration/Driver/Lti1p3/ManageProvider.vue +445 -0
  4. package/components/ExternalIntegration/Driver/Lti1p3/ManageProviders.vue +259 -0
  5. package/components/ExternalIntegration/Driver/ManageLti1p3.vue +45 -0
  6. package/components/Integration/Driver/ManageAtutor.vue +15 -0
  7. package/components/Integration/JobLog.vue +98 -0
  8. package/components/Integration/JobTable.vue +15 -1
  9. package/i18n/en-US/components/external_integration/driver/lti1p3.ts +13 -0
  10. package/i18n/en-US/components/external_integration/index.ts +2 -1
  11. package/i18n/en-US/components/integration/driver.ts +1 -0
  12. package/i18n/en-US/components/integration/job.ts +3 -0
  13. package/models/ExternalIntegration/{Lti1p1Provider.ts → LtiProvider.ts} +2 -2
  14. package/package.json +1 -1
  15. package/pages/admin/importCourse.vue +1 -1
  16. package/pages/admin/vendors.vue +3 -2
  17. package/pages/course/externalIntegration/index.vue +4 -3
  18. package/plugin.js +48 -1
  19. package/test/Components/ExternalIntegration/Lti1p3/ManageProvider.spec.js +19 -0
  20. package/test/Components/ExternalIntegration/Lti1p3/ManageProviders.spec.js +19 -0
  21. package/test/Components/ExternalIntegration/ManageLti1p3.spec.js +19 -0
  22. package/test/Components/Integration/JobLog.spec.js +22 -0
  23. package/test/Components/Integration/JobTable.spec.js +23 -0
  24. package/test/__mocks__/componentsMock.js +12 -0
  25. package/test/mocks.js +12 -0
@@ -0,0 +1,259 @@
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.target`]="{ item }">
27
+ <ProviderTargetViewer
28
+ class="field--target text-truncate"
29
+ :target="item.target"
30
+ ></ProviderTargetViewer>
31
+ </template>
32
+ <template #[`item.metadata.tool_public_keyset_url`]="{ item }">
33
+ <SecretField
34
+ v-model="item.metadata.tool_public_keyset_url"
35
+ :hidden="false"
36
+ ></SecretField>
37
+ </template>
38
+ <template #[`item.metadata.tool_oidc_auth_endpoint`]="{ item }">
39
+ <SecretField
40
+ v-model="item.metadata.tool_oidc_auth_endpoint"
41
+ :hidden="false"
42
+ ></SecretField>
43
+ </template>
44
+ <template #[`item.url`]="{ item }">
45
+ <SecretField v-model="item.url" :hidden="false"></SecretField>
46
+ </template>
47
+
48
+ <template #[`item.enabled`]="{ item }">
49
+ <v-icon :color="item.enabled ? 'success' : 'error'"
50
+ >{{ item.enabled ? 'mdi-check' : 'mdi-close' }}
51
+ </v-icon>
52
+ <span v-if="!item.enabled" class="sr-only">{{
53
+ $t('shared.forms.enabled')
54
+ }}</span>
55
+ </template>
56
+
57
+ <template #[`item.grade_sync`]="{ item }">
58
+ <v-icon :color="item.grade_sync ? 'success' : 'error'"
59
+ >{{ item.grade_sync ? 'mdi-check' : 'mdi-close' }}
60
+ </v-icon>
61
+ <span v-if="!item.grade_sync" class="sr-only">{{
62
+ $t(
63
+ 'windward.integrations.components.external_integration.grade_sync'
64
+ )
65
+ }}</span>
66
+ </template>
67
+
68
+ <template #[`item.created_at`]="{ item }">
69
+ {{ $d(new Date(item.created_at), 'short') }}
70
+ </template>
71
+ <template #[`item.actions`]="{ index, item }">
72
+ <Dialog color="primary" action-save @click:save="onSaved">
73
+ <template #title>{{
74
+ $t(
75
+ 'windward.integrations.components.external_integration.driver.lti1p3.edit'
76
+ )
77
+ }}</template>
78
+ <template #trigger>
79
+ <v-icon small>mdi-pencil</v-icon>
80
+ <span class="sr-only">{{
81
+ $t(
82
+ 'windward.integrations.components.external_integration.driver.lti1p3.edit'
83
+ )
84
+ }}</span>
85
+ </template>
86
+ <template #form="{ on, attrs }"
87
+ ><ManageProvider
88
+ v-model="providers[index]"
89
+ v-bind="attrs"
90
+ v-on="on"
91
+ ></ManageProvider
92
+ ></template>
93
+ </Dialog>
94
+
95
+ <v-btn icon>
96
+ <v-icon @click="onConfirmDelete(item)"> mdi-delete </v-icon>
97
+ <span class="sr-only">{{ $t('shared.forms.delete') }}</span>
98
+ </v-btn>
99
+ </template>
100
+ </v-data-table>
101
+ </div>
102
+ </template>
103
+
104
+ <script>
105
+ import _ from 'lodash'
106
+ import { mapGetters } from 'vuex'
107
+ import LtiProvider from '../../../../models/ExternalIntegration/LtiProvider'
108
+ import SecretField from '../../../SecretField.vue'
109
+ import ProviderTargetViewer from '../../ProviderTargetViewer.vue'
110
+ import ManageProvider from './ManageProvider.vue'
111
+ import Dialog from '~/components/Dialog.vue'
112
+ import Organization from '~/models/Organization'
113
+ import Course from '~/models/Course'
114
+
115
+ export default {
116
+ name: 'ManageLti1p1ProvidersDriver',
117
+ components: { SecretField, Dialog, ManageProvider, ProviderTargetViewer },
118
+ data() {
119
+ return {
120
+ providers: [],
121
+ headers: [
122
+ {
123
+ text: this.$t(
124
+ 'windward.integrations.components.external_integration.target'
125
+ ),
126
+ value: 'target',
127
+ },
128
+ {
129
+ text: this.$t(
130
+ 'windward.integrations.components.external_integration.driver.lti1p3.tool_public_keyset_url'
131
+ ),
132
+ value: 'metadata.tool_public_keyset_url',
133
+ },
134
+ {
135
+ text: this.$t(
136
+ 'windward.integrations.components.external_integration.driver.lti1p3.tool_oidc_auth_endpoint'
137
+ ),
138
+ value: 'metadata.tool_oidc_auth_endpoint',
139
+ },
140
+ {
141
+ text: this.$t(
142
+ 'windward.integrations.components.external_integration.launch_url'
143
+ ),
144
+ value: 'url',
145
+ },
146
+ {
147
+ text: this.$t('shared.forms.enabled'),
148
+ value: 'enabled',
149
+ },
150
+ {
151
+ text: this.$t(
152
+ 'windward.integrations.components.external_integration.grade_sync'
153
+ ),
154
+ value: 'grade_sync',
155
+ },
156
+ { text: this.$t('shared.forms.created'), value: 'created_at' },
157
+ {
158
+ text: this.$t('shared.forms.actions'),
159
+ value: 'actions',
160
+ sortable: false,
161
+ },
162
+ ],
163
+ }
164
+ },
165
+
166
+ async fetch() {
167
+ if (
168
+ !this.$PermissionService.userHasAccessTo(
169
+ 'plugin.windward.integrations.course.externalIntegration',
170
+ 'readable'
171
+ ) ||
172
+ _.isEmpty(this.organization.id) ||
173
+ _.isEmpty(this.course.id)
174
+ ) {
175
+ // Display an angry error that they can't view this driver
176
+ this.$dialog.error(this.$t('shared.error.description_401'), {
177
+ duration: null,
178
+ action: {
179
+ text: this.$t('shared.forms.close'),
180
+ onClick: (_e, toastObject) => {
181
+ toastObject.goAway(0)
182
+ },
183
+ },
184
+ })
185
+ if (_.isEmpty(this.organization.id) || _.isEmpty(this.course.id)) {
186
+ // eslint-disable-next-line no-console
187
+ console.error(
188
+ 'Cannot load external integrations because organization or course is not set!'
189
+ )
190
+ } else {
191
+ // eslint-disable-next-line no-console
192
+ console.error(
193
+ 'You do not have access to this external integration!'
194
+ )
195
+ }
196
+
197
+ // Return so we don't even attempt loading
198
+ return false
199
+ }
200
+
201
+ await this.loadProviders()
202
+ },
203
+ computed: {
204
+ ...mapGetters({
205
+ organization: 'organization/get',
206
+ course: 'course/get',
207
+ }),
208
+ },
209
+ methods: {
210
+ onSaved() {
211
+ this.loadProviders()
212
+ },
213
+ async loadProviders() {
214
+ this.providers = await new LtiProvider()
215
+ .for(
216
+ new Organization({ id: this.organization.id }),
217
+ new Course({ id: this.course.id })
218
+ )
219
+ .where('version', '1.3')
220
+ .get()
221
+ },
222
+ onConfirmDelete(provider) {
223
+ this.$dialog.show(this.$t('shared.forms.confirm_delete_text'), {
224
+ icon: 'mdi-help',
225
+ duration: null,
226
+ action: [
227
+ {
228
+ text: this.$t('shared.forms.cancel'),
229
+ onClick: (_e, toastObject) => {
230
+ toastObject.goAway(0)
231
+ },
232
+ },
233
+ {
234
+ text: this.$t('shared.forms.confirm'),
235
+ // router navigation
236
+ onClick: (_e, toastObject) => {
237
+ this.deleteProvider(provider)
238
+ toastObject.goAway(0)
239
+ },
240
+ },
241
+ ],
242
+ })
243
+ },
244
+ async deleteProvider(provider) {
245
+ await provider.delete()
246
+ this.$dialog.success(this.$t('shared.response.deleted'))
247
+ // Reload providers now that we deleted one
248
+ this.loadProviders()
249
+ },
250
+ },
251
+ }
252
+ </script>
253
+
254
+ <style scoped>
255
+ .field--target {
256
+ max-width: 15em;
257
+ display: inline-block;
258
+ }
259
+ </style>
@@ -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.provider_panel_title'
9
+ )
10
+ }}
11
+ </v-expansion-panel-header>
12
+ <v-expansion-panel-content>
13
+ <ManageProviders></ManageProviders>
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.consumer_panel_title'
21
+ )
22
+ }}
23
+ </v-expansion-panel-header>
24
+ <v-expansion-panel-content>
25
+ <!-- <ManageConsumers></ManageConsumers>-->
26
+ </v-expansion-panel-content>
27
+ </v-expansion-panel>
28
+ </v-expansion-panels>
29
+ </div>
30
+ </template>
31
+
32
+ <script>
33
+ import ManageProviders from './Lti1p3/ManageProviders.vue'
34
+ /* import ManageConsumers from './Lti1p3/ManageConsumers.vue' */
35
+
36
+ export default {
37
+ name: 'ManageLti1p3Driver',
38
+ components: { ManageProviders },
39
+ data() {
40
+ return {
41
+ panel: 0,
42
+ }
43
+ },
44
+ }
45
+ </script>
@@ -16,6 +16,7 @@
16
16
  />
17
17
 
18
18
  <v-text-field
19
+ ref="atutor_url"
19
20
  v-model="integration.metadata.config.url"
20
21
  :label="
21
22
  $t(
@@ -27,6 +28,7 @@
27
28
  'windward.integrations.components.integration.driver.atutor.url_hint'
28
29
  )
29
30
  "
31
+ :rules="urlValidation"
30
32
  ></v-text-field>
31
33
  <v-text-field
32
34
  v-model="integration.metadata.config.username"
@@ -47,6 +49,7 @@
47
49
  ></v-text-field>
48
50
 
49
51
  <v-text-field
52
+ ref="aws-url"
50
53
  v-model="integration.metadata.config.aws_secure_url"
51
54
  :label="
52
55
  $t(
@@ -58,6 +61,7 @@
58
61
  'windward.integrations.components.integration.driver.atutor.aws_secure_url_hint'
59
62
  )
60
63
  "
64
+ :rules="urlValidation"
61
65
  ></v-text-field>
62
66
 
63
67
  <v-switch
@@ -100,6 +104,17 @@ export default {
100
104
  // integration: { metadata: {...} } The integration object to write to. Defined and loaded in ManageBase.vue
101
105
  errorMessage: '',
102
106
  testConnectionLoading: false,
107
+ urlValidation: [
108
+ (value) => {
109
+ if (value && value.slice(-1) === '/') {
110
+ return this.$t(
111
+ 'windward.integrations.components.integration.driver.remove_slash'
112
+ )
113
+ } else {
114
+ return true
115
+ }
116
+ },
117
+ ],
103
118
  }
104
119
  },
105
120
  methods: {
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <div>
3
+ <v-btn color="primary" outlined @click="onViewLog()">
4
+ <v-icon>mdi-note-search</v-icon>
5
+ </v-btn>
6
+ <Dialog v-model="logDialog" :trigger="false">
7
+ <template #title>{{
8
+ $t('windward.integrations.components.integration.job.view_log')
9
+ }}</template>
10
+ <template #form>
11
+ <v-progress-circular v-if="loading" size="128" indeterminate />
12
+ <div v-if="!loading">
13
+ <SearchField v-model="search" hide-filters></SearchField>
14
+
15
+ <v-alert
16
+ v-for="logItem in filteredLog"
17
+ :key="logItem.id"
18
+ :type="logItem.level"
19
+ >
20
+ {{ logItem.message }}
21
+ </v-alert>
22
+
23
+ <p v-if="filteredLog.length === 0">
24
+ {{
25
+ $t(
26
+ 'windward.integrations.components.integration.job.log_no_results'
27
+ )
28
+ }}
29
+ </p>
30
+ </div>
31
+ </template>
32
+ </Dialog>
33
+ </div>
34
+ </template>
35
+
36
+ <script>
37
+ import { mapGetters } from 'vuex'
38
+ import SearchField from '~/components/SearchField.vue'
39
+ import Organization from '../../models/Organization'
40
+
41
+ export default {
42
+ components: { SearchField },
43
+ name: 'IntegrationJobLog',
44
+ props: {
45
+ id: { type: String, required: true },
46
+ },
47
+ data() {
48
+ return {
49
+ search: {},
50
+ loading: true,
51
+ log: [],
52
+ logDialog: false,
53
+ }
54
+ },
55
+ computed: {
56
+ ...mapGetters({
57
+ organization: 'organization/get',
58
+ }),
59
+ filteredLog() {
60
+ if (!this.search.term || this.search.term.length < 3) {
61
+ return this.log
62
+ }
63
+ const filtered = this.log.filter((item) => {
64
+ return item.message
65
+ .toLowerCase()
66
+ .includes(this.search.term.toLowerCase())
67
+ })
68
+
69
+ return filtered
70
+ },
71
+ },
72
+ methods: {
73
+ async onViewLog() {
74
+ this.logDialog = true
75
+ this.log = []
76
+
77
+ const job = await new Organization({
78
+ id: this.organization.id,
79
+ })
80
+ .integrationJobs()
81
+ .with('log')
82
+ .find(this.id)
83
+
84
+ this.log = job.log
85
+ this.loading = false
86
+ },
87
+ },
88
+ }
89
+ </script>
90
+
91
+ <style scoped>
92
+ .col-progress {
93
+ min-width: 200px;
94
+ }
95
+ .col-details {
96
+ max-width: 100px;
97
+ }
98
+ </style>
@@ -42,7 +42,14 @@
42
42
  <th class="text-left">
43
43
  {{
44
44
  $t(
45
- 'windward.integrations.components.integration.job.created'
45
+ 'windward.integrations.components.integration.job.started'
46
+ )
47
+ }}
48
+ </th>
49
+ <th class="text-left">
50
+ {{
51
+ $t(
52
+ 'windward.integrations.components.integration.job.view_log'
46
53
  )
47
54
  }}
48
55
  </th>
@@ -119,6 +126,9 @@
119
126
  {{ jobDetails(item) }}
120
127
  </td>
121
128
  <td>{{ $d(new Date(item.created_at), 'long') }}</td>
129
+ <td>
130
+ <JobLog :id="item.id"></JobLog>
131
+ </td>
122
132
  </tr>
123
133
  </tbody>
124
134
  </template>
@@ -132,9 +142,11 @@ import _ from 'lodash'
132
142
  import { mapGetters } from 'vuex'
133
143
  import Vendor from '../../models/Vendor'
134
144
  import Organization from '../../models/Organization'
145
+ import JobLog from './JobLog.vue'
135
146
 
136
147
  export default {
137
148
  name: 'IntegrationJobs',
149
+ components: { JobLog },
138
150
  props: {
139
151
  channel: { type: String, required: true },
140
152
  event: { type: String, required: true },
@@ -145,6 +157,8 @@ export default {
145
157
  vendors: [],
146
158
  orgIntegrations: [],
147
159
  jobs: [],
160
+ log: [],
161
+ logDialog: false,
148
162
  }
149
163
  },
150
164
  async fetch() {
@@ -0,0 +1,13 @@
1
+ export default {
2
+ key: 'Key',
3
+ tool_public_keyset_url: 'Tool Keyset Url',
4
+ tool_oidc_auth_endpoint: 'Tool OpenID Connect Endpoint',
5
+ platform_public_keyset_url: 'Platform Keyset Url',
6
+ platform_oidc_auth_endpoint: 'Platform OpenID Connect Endpoint',
7
+ platform_client_id: 'Platform Client ID',
8
+ platform_deployment_id: 'Platform Deployment ID',
9
+ parameter_name: 'Custom Parameter Name',
10
+ value: 'Value',
11
+ new: 'New LTI Link',
12
+ edit: 'Edit LTI Link',
13
+ }
@@ -1,7 +1,8 @@
1
1
  import lti1p1 from './driver/lti1p1'
2
+ import lti1p3 from './driver/lti1p3'
2
3
  import providerTarget from './provider_target'
3
4
  export default {
4
- driver: { lti1p1 },
5
+ driver: { lti1p1, lti1p3 },
5
6
  provider_target: providerTarget,
6
7
 
7
8
  provider_panel_title: 'Provider (Students incoming to Windward)',
@@ -8,6 +8,7 @@ export default {
8
8
  aws_secure_url: 'AWS CDN Url',
9
9
  aws_secure_url_hint: 'Eg: https://cdn-d.mindedgecollege.com',
10
10
  },
11
+ remove_slash: 'Remove / at end of url',
11
12
  atutor_wgu: {
12
13
  manage_dialog_title: 'Manage ATutor WGU Integration',
13
14
  },
@@ -8,6 +8,9 @@ export default {
8
8
  progress: 'Progress',
9
9
  created: 'Created',
10
10
  details: 'Details',
11
+ started: 'Date started',
12
+ view_log: 'View Log',
13
+ log_no_results: 'No results found',
11
14
  job_details: {
12
15
  none: 'No details available',
13
16
  import_course: "Importing Course Id {0} '{1}'",
@@ -1,13 +1,13 @@
1
1
  // @ts-ignore
2
2
  import Model from '~/models/Model'
3
3
 
4
- export default class Lti1p1Provider extends Model {
4
+ export default class LtiProvider extends Model {
5
5
  get required(): string[] {
6
6
  return []
7
7
  }
8
8
 
9
9
  // Set the resource route of the model
10
10
  resource() {
11
- return 'external-integrations/lti/1.1/providers'
11
+ return 'external-integrations/lti/providers'
12
12
  }
13
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/integrations",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Windward UI Plugin Integrations for 3rd Party Systems",
5
5
  "main": "plugin.js",
6
6
  "scripts": {
@@ -164,13 +164,13 @@
164
164
  <script>
165
165
  import _ from 'lodash'
166
166
  import { mapGetters } from 'vuex'
167
- import Course from '~/models/Course'
168
167
  import Organization from '../../models/Organization'
169
168
  import OrganizationIntegration from '../../models/OrganizationIntegration'
170
169
  import RemoteOrganization from '../../models/RemoteOrganization'
171
170
  import RemoteCourse from '../../models/RemoteCourse'
172
171
  import RemoteContent from '../../models/RemoteContent'
173
172
  import IntegrationJobTable from '../../components/Integration/JobTable.vue'
173
+ import Course from '~/models/Course'
174
174
 
175
175
  export default {
176
176
  name: 'PluginIntegrationsAdminImportCoursePage',
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <div>
3
+ <Breadcrumbs></Breadcrumbs>
3
4
  <v-row justify="center" align="center">
4
5
  <v-col cols="12">
5
6
  <div class="d-flex mb-5">
@@ -150,14 +151,14 @@
150
151
  <script>
151
152
  import _ from 'lodash'
152
153
  import { mapGetters } from 'vuex'
153
-
154
+ import Breadcrumbs from '~/components/Breadcrumbs.vue'
154
155
  import Dialog from '~/components/Dialog.vue'
155
156
  import Organization from '../../models/Organization'
156
157
  import IntegrationJobTable from '../../components/Integration/JobTable.vue'
157
158
 
158
159
  export default {
159
160
  name: 'PluginIntegrationsAdminVendorsPage',
160
- components: { Dialog, IntegrationJobTable },
161
+ components: { Dialog, IntegrationJobTable, Breadcrumbs },
161
162
  layout: 'authenticated',
162
163
  meta: {
163
164
  privilege: {
@@ -38,7 +38,8 @@
38
38
  <v-tabs-items v-model="tab">
39
39
  <v-tab-item> <ManageLti1p1></ManageLti1p1> </v-tab-item>
40
40
  <v-tab-item>
41
- <p class="ma-5">LTI 1.3 Not yet implemented</p>
41
+ <ManageLti1p3 />
42
+ <!-- <p class="ma-5">LTI 1.3 Not yet implemented</p>-->
42
43
  </v-tab-item>
43
44
  <v-tab-item>
44
45
  <p class="ma-5">SCORM 1.2 Not yet implemented</p>
@@ -49,10 +50,10 @@
49
50
 
50
51
  <script>
51
52
  import ManageLti1p1 from '../../../components/ExternalIntegration/Driver/ManageLti1p1.vue'
52
-
53
+ import ManageLti1p3 from '../../../components/ExternalIntegration/Driver/ManageLti1p3.vue'
53
54
  export default {
54
55
  name: 'PluginIntegrationsExternalIntegrationIndexPage',
55
- components: { ManageLti1p1 },
56
+ components: { ManageLti1p1, ManageLti1p3 },
56
57
  layout: 'authenticated',
57
58
  meta: {
58
59
  privilege: {