@windward/integrations 0.0.9 → 0.0.11

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 (34) hide show
  1. package/components/FileImport/Dropbox.vue +9 -0
  2. package/components/FileImport/FileImportMenu.vue +111 -0
  3. package/components/FileImport/GoogleDrive.vue +9 -0
  4. package/components/FileImport/Resourcespace.vue +202 -0
  5. package/components/Integration/Driver/ManageAtutor.vue +20 -12
  6. package/components/Integration/Driver/ManageResourcespace.vue +137 -0
  7. package/components/Integration/JobLog.vue +370 -45
  8. package/components/Integration/JobTable.vue +18 -2
  9. package/components/Navigation/Items/CourseJobLog.vue +55 -0
  10. package/components/SecretField.vue +1 -0
  11. package/config/integration.config.js +2 -0
  12. package/helpers/Driver/Resourcespace.ts +15 -0
  13. package/i18n/en-US/components/file_import/index.ts +5 -0
  14. package/i18n/en-US/components/file_import/resourcespace.ts +4 -0
  15. package/i18n/en-US/components/index.ts +2 -0
  16. package/i18n/en-US/components/integration/driver.ts +7 -0
  17. package/i18n/en-US/components/integration/index.ts +2 -0
  18. package/i18n/en-US/components/integration/job.ts +0 -2
  19. package/i18n/en-US/components/integration/job_log.ts +24 -0
  20. package/i18n/en-US/shared/error.ts +1 -0
  21. package/i18n/en-US/shared/file.ts +5 -0
  22. package/i18n/en-US/shared/index.ts +2 -0
  23. package/models/OrganizationIntegration.ts +5 -0
  24. package/models/RemoteFile.ts +12 -0
  25. package/package.json +1 -1
  26. package/pages/admin/importCourse.vue +7 -1
  27. package/plugin.js +50 -0
  28. package/test/Components/FileImport/Dropbox.spec.js +24 -0
  29. package/test/Components/FileImport/GoogleDrive.spec.js +24 -0
  30. package/test/Components/FileImport/Resourcespace.spec.js +24 -0
  31. package/test/Components/Integration/Driver/ManageAtutor.spec.js +22 -0
  32. package/test/Components/Integration/Driver/ManageResourcespace.spec.js +22 -0
  33. package/test/__mocks__/componentsMock.js +12 -0
  34. package/test/__mocks__/modelMock.js +1 -0
@@ -1,46 +1,198 @@
1
1
  <template>
2
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>
3
+ <v-progress-circular v-if="loading" size="128" indeterminate />
4
+ <div v-if="!loading">
5
+ <SearchField
6
+ v-model="search"
7
+ :filters="filters"
8
+ :tags="levelTags"
9
+ :tag-label="
10
+ $t(
11
+ 'windward.integrations.components.integration.job_log.log_level'
12
+ )
13
+ "
14
+ :tag-chip-label-prefix="
15
+ $t(
16
+ 'windward.integrations.components.integration.job_log.level_prefix'
17
+ )
18
+ "
19
+ :tag-icon="'mdi-scale'"
20
+ ></SearchField>
21
+
22
+ <v-alert
23
+ v-for="logItem in filteredLog"
24
+ :key="logItem.id"
25
+ :type="logItem.level"
26
+ :icon="logItemIcon(logItem)"
27
+ class="log-item"
28
+ >
29
+ <p>{{ logItem.message }}</p>
30
+
31
+ <div v-if="showDetails(logItem)">
32
+ <v-divider class="mt-1 mb-1" />
33
+ <table class="log-table">
34
+ <tbody>
35
+ <tr
36
+ v-if="
37
+ logItem.context.remote_content &&
38
+ remoteCourse
39
+ "
40
+ >
41
+ <th class="text-left">
42
+ {{
43
+ $t(
44
+ 'windward.integrations.components.integration.job_log.remote_course_id'
45
+ )
46
+ }}:
47
+ </th>
48
+ <td>
49
+ {{ remoteCourse.id }}
50
+ </td>
51
+ </tr>
52
+ <tr v-if="logItem.context.remote_content">
53
+ <th class="text-left">
54
+ {{
55
+ $t(
56
+ 'windward.integrations.components.integration.job_log.remote_content_id'
57
+ )
58
+ }}:
59
+ </th>
60
+ <td>
61
+ {{ logItem.context.remote_content.id }}
62
+ </td>
63
+ </tr>
64
+ <tr v-if="logItem.context.remote_content">
65
+ <th class="text-left">
66
+ {{
67
+ $t(
68
+ 'windward.integrations.components.integration.job_log.remote_content_name'
69
+ )
70
+ }}:
71
+ </th>
72
+ <td>
73
+ {{ logItem.context.remote_content.name }}
74
+ </td>
75
+ </tr>
76
+ <tr v-if="logItem.context.remote_url">
77
+ <th class="text-left">
78
+ {{
79
+ $t(
80
+ 'windward.integrations.components.integration.job_log.remote_url'
81
+ )
82
+ }}:
83
+ </th>
84
+ <td>
85
+ <v-chip
86
+ :href="
87
+ isUrl(logItem.context.remote_url)
88
+ ? logItem.context.remote_url
89
+ : undefined
90
+ "
91
+ outlined
92
+ label
93
+ target="_blank"
94
+ >
95
+ <span
96
+ class="d-inline-block text-truncate chip--truncate chip--truncate-left"
97
+ >
98
+ {{ logItem.context.remote_url }}
99
+ </span>
100
+
101
+ <v-icon
102
+ v-if="
103
+ isUrl(
104
+ logItem.context.remote_url
105
+ )
106
+ "
107
+ class="ml-2"
108
+ >
109
+ mdi-launch
110
+ </v-icon>
111
+ </v-chip>
112
+ </td>
113
+ </tr>
114
+ <tr v-if="logItem.context.remote_code">
115
+ <th class="text-left">
116
+ {{
117
+ $t(
118
+ 'windward.integrations.components.integration.job_log.remote_code'
119
+ )
120
+ }}:
121
+ </th>
122
+ <td>
123
+ <div class="remote-code grey darken-3">
124
+ <v-btn
125
+ icon
126
+ @click="
127
+ onClickCopy(
128
+ logItem.context.remote_code
129
+ )
130
+ "
131
+ ><v-icon class="ml-2">
132
+ mdi-content-copy
133
+ </v-icon>
134
+ <span class="sr-only">{{
135
+ $t('shared.forms.copy')
136
+ }}</span>
137
+ </v-btn>
138
+ <code
139
+ class="grey darken-3"
140
+ v-text="logItem.context.remote_code"
141
+ ></code>
142
+ </div>
143
+ </td>
144
+ </tr>
145
+ <tr v-if="logItem.context.remote_sso">
146
+ <th class="text-left">
147
+ {{
148
+ $t(
149
+ 'windward.integrations.components.integration.job_log.remote_sso'
150
+ )
151
+ }}
152
+ </th>
153
+ <td>
154
+ <v-chip
155
+ :href="logItem.context.remote_sso"
156
+ outlined
157
+ label
158
+ target="_blank"
159
+ >
160
+ <span
161
+ class="d-inline-block text-truncate chip--truncate"
162
+ >
163
+ {{ logItem.context.remote_sso }}
164
+ </span>
165
+
166
+ <v-icon class="ml-2">
167
+ mdi-launch
168
+ </v-icon>
169
+ </v-chip>
170
+ </td>
171
+ </tr>
172
+ </tbody>
173
+ </table>
30
174
  </div>
31
- </template>
32
- </Dialog>
175
+ </v-alert>
176
+ <p v-if="filteredLog.length === 0">
177
+ {{
178
+ $t(
179
+ 'windward.integrations.components.integration.job_log.no_results'
180
+ )
181
+ }}
182
+ </p>
183
+ </div>
33
184
  </div>
34
185
  </template>
35
186
 
36
187
  <script>
188
+ import _ from 'lodash'
37
189
  import { mapGetters } from 'vuex'
38
190
  import SearchField from '~/components/SearchField.vue'
39
191
  import Organization from '../../models/Organization'
40
192
 
41
193
  export default {
42
- components: { SearchField },
43
194
  name: 'IntegrationJobLog',
195
+ components: { SearchField },
44
196
  props: {
45
197
  id: { type: String, required: true },
46
198
  },
@@ -48,8 +200,72 @@ export default {
48
200
  return {
49
201
  search: {},
50
202
  loading: true,
203
+ job: {},
51
204
  log: [],
52
- logDialog: false,
205
+ filters: [
206
+ {
207
+ value: 'tag',
208
+ text: this.$t(
209
+ 'windward.integrations.components.integration.job_log.log_level'
210
+ ),
211
+ },
212
+ {
213
+ value: 'file_missing_error',
214
+ text: this.$t(
215
+ 'windward.integrations.components.integration.job_log.search.file_missing_error'
216
+ ),
217
+ },
218
+ {
219
+ value: 'data_error',
220
+ text: this.$t(
221
+ 'windward.integrations.components.integration.job_log.search.data_error'
222
+ ),
223
+ },
224
+ {
225
+ value: 'converted_file',
226
+ text: this.$t(
227
+ 'windward.integrations.components.integration.job_log.search.converted_file'
228
+ ),
229
+ },
230
+ {
231
+ value: 'converted_file_error',
232
+ text: this.$t(
233
+ 'windward.integrations.components.integration.job_log.search.converted_file_error'
234
+ ),
235
+ },
236
+ {
237
+ value: 'parse_error',
238
+ text: this.$t(
239
+ 'windward.integrations.components.integration.job_log.search.parse_error'
240
+ ),
241
+ },
242
+ {
243
+ value: 'linking_error',
244
+ text: this.$t(
245
+ 'windward.integrations.components.integration.job_log.search.linking_error'
246
+ ),
247
+ },
248
+ ],
249
+ levelTags: [
250
+ {
251
+ value: 'info',
252
+ text: this.$t(
253
+ 'windward.integrations.components.integration.job_log.search.level_info'
254
+ ),
255
+ },
256
+ {
257
+ value: 'warning',
258
+ text: this.$t(
259
+ 'windward.integrations.components.integration.job_log.search.level_warning'
260
+ ),
261
+ },
262
+ {
263
+ value: 'error',
264
+ text: this.$t(
265
+ 'windward.integrations.components.integration.job_log.search.level_error'
266
+ ),
267
+ },
268
+ ],
53
269
  }
54
270
  },
55
271
  computed: {
@@ -57,22 +273,115 @@ export default {
57
273
  organization: 'organization/get',
58
274
  }),
59
275
  filteredLog() {
60
- if (!this.search.term || this.search.term.length < 3) {
61
- return this.log
276
+ // Get the term and if it doesn't exist or is null convert to empty string
277
+ const term = (_.get(this.search, 'term', '') || '').toLowerCase()
278
+
279
+ // If there's no fiters and no terms then just return the whole log
280
+ if (
281
+ _.get(this.search, 'filter', []).length === 0 &&
282
+ term.length < 3
283
+ ) {
284
+ return this.job.log || []
62
285
  }
63
- const filtered = this.log.filter((item) => {
64
- return item.message
65
- .toLowerCase()
66
- .includes(this.search.term.toLowerCase())
286
+ const filtered = this.job.log.filter((item) => {
287
+ let match = false
288
+
289
+ // Apply any search filters
290
+ if (this.search.filter && this.search.filter.length > 0) {
291
+ match = this.search.filter.includes(
292
+ _.get(item, 'context.type', '')
293
+ )
294
+ }
295
+
296
+ // Apply any log level tag filters
297
+ if (this.search.tags && this.search.tags.length > 0) {
298
+ match = this.search.tags.includes(item.level)
299
+ }
300
+
301
+ // Search by term on various fields
302
+ if (term.length >= 3) {
303
+ match = item.message.toLowerCase().includes(term)
304
+
305
+ // Search the remote course id
306
+ if (
307
+ !match &&
308
+ _.get(item, 'context.remote_course.id', null)
309
+ ) {
310
+ match = _.get(item, 'context.remote_course.id', '')
311
+ .toString()
312
+ .toLowerCase()
313
+ .includes(term)
314
+ }
315
+
316
+ // Search the remote assessment id
317
+ if (
318
+ !match &&
319
+ _.get(item, 'context.remote_assessment.id', null)
320
+ ) {
321
+ match = _.get(item, 'context.remote_assessment.id', '')
322
+ .toString()
323
+ .toLowerCase()
324
+ .includes(term)
325
+ }
326
+
327
+ // Search the remote url
328
+ if (!match && item.context.remote_url) {
329
+ match = item.context.remote_url
330
+ .toLowerCase()
331
+ .includes(term)
332
+ }
333
+
334
+ // Search the remote code
335
+ if (!match && item.context.remote_code) {
336
+ match = item.context.remote_code
337
+ .toLowerCase()
338
+ .includes(term)
339
+ }
340
+ }
341
+
342
+ return match
67
343
  })
68
344
 
69
345
  return filtered
70
346
  },
347
+ remoteCourse() {
348
+ return _.get(this.job, 'metadata.import.remote_course', {})
349
+ },
350
+ localCourse() {
351
+ return _.get(this.job, 'metadata.import.local_course', {})
352
+ },
353
+ showDetails() {
354
+ return (item) => {
355
+ return (
356
+ (Array.isArray(item.context) && item.context.length > 0) ||
357
+ !_.isEmpty(item.context)
358
+ )
359
+ }
360
+ },
361
+ logItemIcon() {
362
+ return (logItem) => {
363
+ const type = _.get(logItem, 'context.type', null)
364
+ if (type === 'missing_file') {
365
+ return 'mdi-file-alert'
366
+ } else if (type === 'converted_file') {
367
+ return 'mdi-file-arrow-left-right'
368
+ }
369
+
370
+ return undefined
371
+ }
372
+ },
373
+ isUrl() {
374
+ return (url) => {
375
+ return /https?:\/\//.test(url)
376
+ }
377
+ },
378
+ },
379
+ mounted() {
380
+ this.loadLog()
71
381
  },
72
382
  methods: {
73
- async onViewLog() {
74
- this.logDialog = true
75
- this.log = []
383
+ async loadLog() {
384
+ this.job = {}
76
385
 
77
386
  const job = await new Organization({
78
387
  id: this.organization.id,
@@ -81,18 +390,34 @@ export default {
81
390
  .with('log')
82
391
  .find(this.id)
83
392
 
84
- this.log = job.log
393
+ this.job = job
85
394
  this.loading = false
86
395
  },
396
+ onClickCopy(data) {
397
+ navigator.clipboard.writeText(data)
398
+ this.$dialog.show(this.$t('shared.file.copied'))
399
+ },
87
400
  },
88
401
  }
89
402
  </script>
90
403
 
91
404
  <style scoped>
92
- .col-progress {
93
- min-width: 200px;
405
+ .chip--truncate {
406
+ max-width: 40vw;
407
+ }
408
+ .chip--truncate-left {
409
+ direction: rtl;
410
+ }
411
+ .log-table th {
412
+ padding-right: 8px;
413
+ min-width: 12em;
414
+ }
415
+ .log-table td {
416
+ max-width: 30vw;
94
417
  }
95
- .col-details {
96
- max-width: 100px;
418
+ .remote-code {
419
+ background: inherit;
420
+ max-width: 30vw;
421
+ padding: 4px;
97
422
  }
98
423
  </style>
@@ -49,7 +49,7 @@
49
49
  <th class="text-left">
50
50
  {{
51
51
  $t(
52
- 'windward.integrations.components.integration.job.view_log'
52
+ 'windward.integrations.components.integration.job_log.view_log'
53
53
  )
54
54
  }}
55
55
  </th>
@@ -127,7 +127,23 @@
127
127
  </td>
128
128
  <td>{{ $d(new Date(item.created_at), 'long') }}</td>
129
129
  <td>
130
- <JobLog :id="item.id"></JobLog>
130
+ <Dialog color="primary" outlined>
131
+ <template #trigger>
132
+ <v-icon>mdi-note-search</v-icon>
133
+ </template>
134
+ <template #title>{{
135
+ $t(
136
+ 'windward.integrations.components.integration.job_log.view_log'
137
+ )
138
+ }}</template>
139
+ <template #form="{ on, attrs }">
140
+ <JobLog
141
+ :id="item.id"
142
+ v-bind="attrs"
143
+ v-on="on"
144
+ ></JobLog>
145
+ </template>
146
+ </Dialog>
131
147
  </td>
132
148
  </tr>
133
149
  </tbody>
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <v-list-item v-if="showLog" @click="dialog = true">
3
+ <v-list-item-action>
4
+ <v-icon>{{ config.icon }}</v-icon>
5
+ </v-list-item-action>
6
+ <v-list-item-content>
7
+ <v-list-item-title>
8
+ {{ $t(config.i18n) }}
9
+ </v-list-item-title>
10
+ <Dialog v-model="dialog" color="primary" text :trigger="false">
11
+ <template #title>{{ $t(config.i18n) }}</template>
12
+ <template #form="{ on, attrs }">
13
+ <JobLog :id="jobId" v-bind="attrs" v-on="on"></JobLog>
14
+ </template>
15
+ </Dialog>
16
+ </v-list-item-content>
17
+ </v-list-item>
18
+ </template>
19
+
20
+ <script>
21
+ import _ from 'lodash'
22
+ import { mapGetters } from 'vuex'
23
+ import JobLog from '../../Integration/JobLog.vue'
24
+
25
+ export default {
26
+ name: 'NavigationItemCourseJobLog',
27
+ components: { JobLog },
28
+ props: {
29
+ config: { type: Object, required: true },
30
+ },
31
+ data() {
32
+ return {
33
+ dialog: false,
34
+ jobId: false,
35
+ }
36
+ },
37
+ computed: {
38
+ ...mapGetters({
39
+ course: 'course/get',
40
+ }),
41
+ showLog() {
42
+ return !_.isEmpty(this.jobId)
43
+ },
44
+ },
45
+ mounted() {
46
+ // Check to see if this course was imported
47
+ this.jobId = _.get(
48
+ this.course,
49
+ 'metadata.integration.integration_job_id',
50
+ null
51
+ )
52
+ },
53
+ methods: {},
54
+ }
55
+ </script>
@@ -6,6 +6,7 @@
6
6
  </v-btn>
7
7
  <v-btn v-if="copy" icon @click="copyText(value)">
8
8
  <v-icon>mdi-content-copy</v-icon>
9
+ <span class="sr-only">{{ $t('shared.forms.copy') }}</span>
9
10
  </v-btn>
10
11
  </template>
11
12
  </v-text-field>
@@ -4,6 +4,7 @@ import Canvas from '../helpers/Driver/Canvas'
4
4
  import Desire2Learn from '../helpers/Driver/Desire2Learn'
5
5
  import Moodle from '../helpers/Driver/Moodle'
6
6
  import GoogleClassroom from '../helpers/Driver/GoogleClassroom'
7
+ import Resourcespace from '../helpers/Driver/Resourcespace'
7
8
 
8
9
  export default {
9
10
  /**
@@ -20,5 +21,6 @@ export default {
20
21
  desire2learn: { driver: Desire2Learn },
21
22
  moodle: { driver: Moodle },
22
23
  google_classroom: { driver: GoogleClassroom },
24
+ resourcespace: { driver: Resourcespace },
23
25
  },
24
26
  }
@@ -0,0 +1,15 @@
1
+ // @ts-ignore
2
+ import Manage from '../../components/Integration/Driver/ManageResourcespace.vue'
3
+ import DriverInterface, { IntegrationComponents } from './DriverInterface'
4
+ import BaseDriver from './BaseDriver'
5
+
6
+ export default class Resourcespace
7
+ extends BaseDriver
8
+ implements DriverInterface
9
+ {
10
+ public components(): IntegrationComponents {
11
+ return {
12
+ Manage,
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,5 @@
1
+ import resourcespace from './resourcespace'
2
+
3
+ export default {
4
+ resourcespace,
5
+ }
@@ -0,0 +1,4 @@
1
+ export default {
2
+ resource_id: 'Resource Id',
3
+ load_error: 'Failed to connect to Resourcespace',
4
+ }
@@ -3,6 +3,7 @@ import settings from './settings'
3
3
  import navigation from './navigation'
4
4
  import integration from './integration'
5
5
  import externalIntegration from './external_integration'
6
+ import fileImport from './file_import'
6
7
 
7
8
  export default {
8
9
  content,
@@ -10,4 +11,5 @@ export default {
10
11
  navigation,
11
12
  integration,
12
13
  external_integration: externalIntegration,
14
+ file_import: fileImport,
13
15
  }
@@ -27,6 +27,13 @@ export default {
27
27
  google_classroom: {
28
28
  manage_dialog_title: 'Manage Google Classroom Integration',
29
29
  },
30
+ resourcespace: {
31
+ manage_dialog_title: 'Manage Resourcespace Integration',
32
+ url: 'Resourcespace Url',
33
+ url_hint: 'Eg: https://resourcespace.mindedgeuniversity.com',
34
+ username: 'Username',
35
+ key: 'API Key',
36
+ },
30
37
  enabled: 'Integration Enabled',
31
38
  disabled: 'Integration Disabled',
32
39
  ssl_enabled: 'SSL Enabled (Should be enabled for production)',
@@ -1,7 +1,9 @@
1
1
  import driver from './driver'
2
2
  import job from './job'
3
+ import jobLog from './job_log'
3
4
 
4
5
  export default {
5
6
  driver,
6
7
  job,
8
+ job_log: jobLog,
7
9
  }
@@ -9,8 +9,6 @@ export default {
9
9
  created: 'Created',
10
10
  details: 'Details',
11
11
  started: 'Date started',
12
- view_log: 'View Log',
13
- log_no_results: 'No results found',
14
12
  job_details: {
15
13
  none: 'No details available',
16
14
  import_course: "Importing Course Id {0} '{1}'",