@windward/integrations 0.0.9 → 0.0.10
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.
- package/components/FileImport/Dropbox.vue +9 -0
- package/components/FileImport/FileImportMenu.vue +111 -0
- package/components/FileImport/GoogleDrive.vue +9 -0
- package/components/FileImport/Resourcespace.vue +202 -0
- package/components/Integration/Driver/ManageAtutor.vue +20 -12
- package/components/Integration/Driver/ManageResourcespace.vue +137 -0
- package/components/Integration/JobLog.vue +208 -25
- package/components/Integration/JobTable.vue +1 -1
- package/components/SecretField.vue +1 -0
- package/config/integration.config.js +2 -0
- package/helpers/Driver/Resourcespace.ts +15 -0
- package/i18n/en-US/components/file_import/index.ts +5 -0
- package/i18n/en-US/components/file_import/resourcespace.ts +4 -0
- package/i18n/en-US/components/index.ts +2 -0
- package/i18n/en-US/components/integration/driver.ts +7 -0
- package/i18n/en-US/components/integration/index.ts +2 -0
- package/i18n/en-US/components/integration/job.ts +0 -2
- package/i18n/en-US/components/integration/job_log.ts +8 -0
- package/i18n/en-US/shared/error.ts +1 -0
- package/i18n/en-US/shared/file.ts +5 -0
- package/i18n/en-US/shared/index.ts +2 -0
- package/models/OrganizationIntegration.ts +5 -0
- package/models/RemoteFile.ts +12 -0
- package/package.json +1 -1
- package/pages/admin/importCourse.vue +7 -1
- package/plugin.js +36 -0
- package/test/Components/FileImport/Dropbox.spec.js +24 -0
- package/test/Components/FileImport/GoogleDrive.spec.js +24 -0
- package/test/Components/FileImport/Resourcespace.spec.js +24 -0
- package/test/Components/Integration/Driver/ManageAtutor.spec.js +22 -0
- package/test/Components/Integration/Driver/ManageResourcespace.spec.js +22 -0
- package/test/__mocks__/componentsMock.js +12 -0
- package/test/__mocks__/modelMock.js +1 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-list-item
|
|
4
|
+
v-for="importVendor in enabledFileImports"
|
|
5
|
+
:key="importVendor.product_code"
|
|
6
|
+
@click="onClickImport(importVendor)"
|
|
7
|
+
>
|
|
8
|
+
<v-list-item-icon>
|
|
9
|
+
<v-icon>{{ importVendor.icon }}</v-icon>
|
|
10
|
+
</v-list-item-icon>
|
|
11
|
+
<v-list-item-title>{{ $t(importVendor.i18n) }}</v-list-item-title>
|
|
12
|
+
</v-list-item>
|
|
13
|
+
|
|
14
|
+
<Dialog
|
|
15
|
+
v-model="showDialog"
|
|
16
|
+
:trigger="false"
|
|
17
|
+
persistent
|
|
18
|
+
@click:close="$emit('click:close')"
|
|
19
|
+
>
|
|
20
|
+
<template #title>{{ $t(activeVendor.i18n) }}</template>
|
|
21
|
+
|
|
22
|
+
<template #form="{ on, attrs }">
|
|
23
|
+
<component
|
|
24
|
+
:is="activeVendor.template"
|
|
25
|
+
v-if="activeVendor"
|
|
26
|
+
:organization-integration="
|
|
27
|
+
activeVendor.organization_integration
|
|
28
|
+
"
|
|
29
|
+
v-bind="attrs"
|
|
30
|
+
v-on="on"
|
|
31
|
+
@uploaded="$emit('uploaded', $event)"
|
|
32
|
+
></component>
|
|
33
|
+
</template>
|
|
34
|
+
</Dialog>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<script>
|
|
39
|
+
import _ from 'lodash'
|
|
40
|
+
import { mapGetters } from 'vuex'
|
|
41
|
+
import Organization from '../../models/Organization'
|
|
42
|
+
import Dialog from '~/components/Dialog.vue'
|
|
43
|
+
|
|
44
|
+
export default {
|
|
45
|
+
name: 'FileImportMenu',
|
|
46
|
+
components: { Dialog },
|
|
47
|
+
props: {
|
|
48
|
+
metadata: {
|
|
49
|
+
type: Object,
|
|
50
|
+
required: false,
|
|
51
|
+
default() {
|
|
52
|
+
return {}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
data() {
|
|
57
|
+
return {
|
|
58
|
+
organizationIntegrations: [],
|
|
59
|
+
activeVendor: null,
|
|
60
|
+
showDialog: false,
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async fetch() {
|
|
64
|
+
const organizationIntegrations = await new Organization({
|
|
65
|
+
id: this.organization.id,
|
|
66
|
+
})
|
|
67
|
+
.integrations()
|
|
68
|
+
.with(['vendor'])
|
|
69
|
+
.where('enabled', true)
|
|
70
|
+
.where('vendor.enabled', true)
|
|
71
|
+
.get()
|
|
72
|
+
if (!_.isEmpty(organizationIntegrations)) {
|
|
73
|
+
this.organizationIntegrations = organizationIntegrations
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
computed: {
|
|
77
|
+
...mapGetters({
|
|
78
|
+
organization: 'organization/get',
|
|
79
|
+
}),
|
|
80
|
+
enabledFileImports() {
|
|
81
|
+
const enabled = []
|
|
82
|
+
// Get only the vendors that are defined to be displayed as a fileImport vendor from plugin.js
|
|
83
|
+
const available = _.get(this.metadata, 'vendors', [])
|
|
84
|
+
|
|
85
|
+
// Loop over all the orgIntegrations that we've confirmed are enabled
|
|
86
|
+
this.organizationIntegrations.forEach((orgInt) => {
|
|
87
|
+
const productCode = _.get(orgInt, 'vendor.product_code')
|
|
88
|
+
const found = available.find((c) => {
|
|
89
|
+
return c.product_code === productCode && !!c.template
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
if (found) {
|
|
93
|
+
// Set the organization integration so we can reference the api later
|
|
94
|
+
found.organization_integration = orgInt
|
|
95
|
+
enabled.push(found)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return enabled
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
mounted() {},
|
|
103
|
+
methods: {
|
|
104
|
+
onClickImport(importVendor) {
|
|
105
|
+
this.$emit('click', importVendor)
|
|
106
|
+
this.activeVendor = importVendor
|
|
107
|
+
this.showDialog = true
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
</script>
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<SearchField
|
|
4
|
+
v-model="search"
|
|
5
|
+
:num-results="numFiles"
|
|
6
|
+
@click:search="onLoadAssets($event)"
|
|
7
|
+
@click:clear="onClickClear"
|
|
8
|
+
></SearchField>
|
|
9
|
+
|
|
10
|
+
<v-btn
|
|
11
|
+
color="primary"
|
|
12
|
+
block
|
|
13
|
+
:disabled="selectedFiles.length === 0"
|
|
14
|
+
:loading="loading"
|
|
15
|
+
@click="onConfirmImportFiles"
|
|
16
|
+
>
|
|
17
|
+
<v-icon>mdi-content-duplicate</v-icon>
|
|
18
|
+
{{
|
|
19
|
+
$t('shared.file.import_selected_count', [selectedFiles.length])
|
|
20
|
+
}}
|
|
21
|
+
</v-btn>
|
|
22
|
+
|
|
23
|
+
<div v-if="loaded && !loading && numFiles === 0" class="text-center">
|
|
24
|
+
<p>{{ $t('shared.file.none_found') }}</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<v-data-table
|
|
28
|
+
v-if="numFiles"
|
|
29
|
+
v-model="selectedFiles"
|
|
30
|
+
class="mt-5"
|
|
31
|
+
show-select
|
|
32
|
+
item-key="id"
|
|
33
|
+
:headers="headers"
|
|
34
|
+
:items="files"
|
|
35
|
+
:items-per-page="25"
|
|
36
|
+
:loading="loading"
|
|
37
|
+
>
|
|
38
|
+
<template #[`item.preview`]="{ item }">
|
|
39
|
+
<v-img :src="item.preview" width="150px" :aspect-ratio="16 / 9">
|
|
40
|
+
<template #placeholder>
|
|
41
|
+
<v-skeleton-loader
|
|
42
|
+
class="mx-auto"
|
|
43
|
+
max-width="300"
|
|
44
|
+
type="image"
|
|
45
|
+
></v-skeleton-loader>
|
|
46
|
+
</template>
|
|
47
|
+
</v-img>
|
|
48
|
+
</template>
|
|
49
|
+
<template #[`item.created_at`]="{ item }">
|
|
50
|
+
{{ $d(new Date(item.created_at), 'shorttime') }}
|
|
51
|
+
</template>
|
|
52
|
+
<template #[`item.updated_at`]="{ item }">
|
|
53
|
+
{{ $d(new Date(item.updated_at), 'shorttime') }}
|
|
54
|
+
</template>
|
|
55
|
+
</v-data-table>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
import { mapGetters } from 'vuex'
|
|
61
|
+
import OrganizationIntegration from '../../models/OrganizationIntegration'
|
|
62
|
+
import RemoteFile from '../../models/RemoteFile'
|
|
63
|
+
import Course from '~/models/Course'
|
|
64
|
+
import SearchField from '~/components/SearchField.vue'
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
name: 'FileImportResourcespace',
|
|
68
|
+
components: { SearchField },
|
|
69
|
+
props: {
|
|
70
|
+
organizationIntegration: { type: Object, required: true },
|
|
71
|
+
},
|
|
72
|
+
data() {
|
|
73
|
+
return {
|
|
74
|
+
loading: false,
|
|
75
|
+
loaded: false,
|
|
76
|
+
search: {},
|
|
77
|
+
files: [],
|
|
78
|
+
selectedFiles: [],
|
|
79
|
+
headers: [
|
|
80
|
+
{
|
|
81
|
+
text: this.$t('shared.file.preview'),
|
|
82
|
+
align: 'start',
|
|
83
|
+
sortable: false,
|
|
84
|
+
value: 'preview',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
text: this.$t('shared.file.name'),
|
|
88
|
+
value: 'filename',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
text: this.$t(
|
|
92
|
+
'windward.integrations.components.file_import.resourcespace.resource_id'
|
|
93
|
+
),
|
|
94
|
+
value: 'id',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
text: this.$t('shared.forms.created'),
|
|
98
|
+
value: 'created_at',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
text: this.$t('shared.forms.updated'),
|
|
102
|
+
value: 'updated_at',
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
async fetch() {},
|
|
108
|
+
computed: {
|
|
109
|
+
...mapGetters({
|
|
110
|
+
organization: 'organization/get',
|
|
111
|
+
course: 'course/get',
|
|
112
|
+
}),
|
|
113
|
+
numFiles() {
|
|
114
|
+
return this.files.length
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
mounted() {},
|
|
118
|
+
methods: {
|
|
119
|
+
async onLoadAssets(search = {}) {
|
|
120
|
+
this.loading = true
|
|
121
|
+
this.loaded = true
|
|
122
|
+
try {
|
|
123
|
+
this.files = await new OrganizationIntegration(
|
|
124
|
+
this.organizationIntegration
|
|
125
|
+
)
|
|
126
|
+
.remoteFiles()
|
|
127
|
+
.where('term', search.term)
|
|
128
|
+
.get()
|
|
129
|
+
} catch (e) {
|
|
130
|
+
this.$dialog.error(
|
|
131
|
+
this.$t(
|
|
132
|
+
'windward.integrations.components.file_import.resourcespace.load_error'
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
console.error(e)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.loading = false
|
|
139
|
+
},
|
|
140
|
+
onClickClear() {
|
|
141
|
+
this.selectedFiles = []
|
|
142
|
+
},
|
|
143
|
+
onConfirmImportFiles() {
|
|
144
|
+
const self = this
|
|
145
|
+
this.$dialog.show(
|
|
146
|
+
this.$t('shared.file.confirm_import', [
|
|
147
|
+
this.selectedFiles.length,
|
|
148
|
+
]),
|
|
149
|
+
{
|
|
150
|
+
icon: 'mdi-question',
|
|
151
|
+
duration: null,
|
|
152
|
+
action: [
|
|
153
|
+
{
|
|
154
|
+
text: this.$t('shared.forms.cancel'),
|
|
155
|
+
onClick: (_e, toastObject) => {
|
|
156
|
+
toastObject.goAway(0)
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
text: this.$t('shared.forms.confirm'),
|
|
161
|
+
// router navigation
|
|
162
|
+
onClick: (_e, toastObject) => {
|
|
163
|
+
self.importFiles()
|
|
164
|
+
toastObject.goAway(0)
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
},
|
|
171
|
+
async importFiles() {
|
|
172
|
+
this.loading = true
|
|
173
|
+
try {
|
|
174
|
+
const uploaded = await new RemoteFile(this.selectedFiles)
|
|
175
|
+
.for(
|
|
176
|
+
new OrganizationIntegration(
|
|
177
|
+
this.organizationIntegration
|
|
178
|
+
),
|
|
179
|
+
new Course({ id: this.course.id })
|
|
180
|
+
)
|
|
181
|
+
.save()
|
|
182
|
+
|
|
183
|
+
this.$dialog.success(
|
|
184
|
+
this.$t('shared.file.import_success', [
|
|
185
|
+
this.selectedFiles.length,
|
|
186
|
+
])
|
|
187
|
+
)
|
|
188
|
+
this.$emit('uploaded', uploaded)
|
|
189
|
+
} catch (e) {
|
|
190
|
+
console.log('Failed to import!', this.selectedFiles)
|
|
191
|
+
console.error(e)
|
|
192
|
+
this.$dialog.error(
|
|
193
|
+
this.$t('shared.file.import_failed', [
|
|
194
|
+
this.selectedFiles.length,
|
|
195
|
+
])
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
this.loading = false
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
</script>
|
|
@@ -130,21 +130,29 @@ export default {
|
|
|
130
130
|
},
|
|
131
131
|
async onTestConnection() {
|
|
132
132
|
this.testConnectionLoading = true
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
let response = { result: false }
|
|
134
|
+
try {
|
|
135
|
+
response = await this.testConnection(this.integration.metadata)
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
if (response.result) {
|
|
138
|
+
this.errorMessage = ''
|
|
139
|
+
this.$dialog.success(
|
|
140
|
+
this.$t(
|
|
141
|
+
'windward.integrations.shared.error.connect_success'
|
|
142
|
+
)
|
|
142
143
|
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
} else {
|
|
145
|
+
this.errorMessage = response.message
|
|
146
|
+
this.$dialog.error(
|
|
147
|
+
this.$t(
|
|
148
|
+
'windward.integrations.shared.error.connect_fail'
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
console.error(e)
|
|
146
154
|
this.$dialog.error(
|
|
147
|
-
this.$t('windward.integrations.shared.error.
|
|
155
|
+
this.$t('windward.integrations.shared.error.unknown')
|
|
148
156
|
)
|
|
149
157
|
}
|
|
150
158
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div v-if="!render" class="integration-loading">
|
|
4
|
+
<v-progress-circular size="128" indeterminate />
|
|
5
|
+
</div>
|
|
6
|
+
<div v-if="render">
|
|
7
|
+
<v-row justify="center" align="center" class="mt-5">
|
|
8
|
+
<v-col cols="12">
|
|
9
|
+
<v-switch
|
|
10
|
+
v-model="integration.enabled"
|
|
11
|
+
:label="
|
|
12
|
+
$t(
|
|
13
|
+
'windward.integrations.components.integration.driver.enabled'
|
|
14
|
+
)
|
|
15
|
+
"
|
|
16
|
+
/>
|
|
17
|
+
|
|
18
|
+
<v-text-field
|
|
19
|
+
v-model="integration.metadata.config.url"
|
|
20
|
+
:label="
|
|
21
|
+
$t(
|
|
22
|
+
'windward.integrations.components.integration.driver.resourcespace.url'
|
|
23
|
+
)
|
|
24
|
+
"
|
|
25
|
+
:hint="
|
|
26
|
+
$t(
|
|
27
|
+
'windward.integrations.components.integration.driver.resourcespace.url_hint'
|
|
28
|
+
)
|
|
29
|
+
"
|
|
30
|
+
></v-text-field>
|
|
31
|
+
<v-text-field
|
|
32
|
+
v-model="integration.metadata.config.username"
|
|
33
|
+
:label="
|
|
34
|
+
$t(
|
|
35
|
+
'windward.integrations.components.integration.driver.resourcespace.username'
|
|
36
|
+
)
|
|
37
|
+
"
|
|
38
|
+
></v-text-field>
|
|
39
|
+
<v-text-field
|
|
40
|
+
v-model="integration.metadata.config.key"
|
|
41
|
+
:label="
|
|
42
|
+
$t(
|
|
43
|
+
'windward.integrations.components.integration.driver.resourcespace.key'
|
|
44
|
+
)
|
|
45
|
+
"
|
|
46
|
+
type="password"
|
|
47
|
+
></v-text-field>
|
|
48
|
+
|
|
49
|
+
<v-switch
|
|
50
|
+
v-model="integration.metadata.config.ssl"
|
|
51
|
+
:label="
|
|
52
|
+
$t(
|
|
53
|
+
'windward.integrations.components.integration.driver.ssl_enabled'
|
|
54
|
+
)
|
|
55
|
+
"
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
<TestConnection
|
|
59
|
+
:disabled="
|
|
60
|
+
!integration.metadata.config.url ||
|
|
61
|
+
!integration.metadata.config.username ||
|
|
62
|
+
!integration.metadata.config.key
|
|
63
|
+
"
|
|
64
|
+
:loading="testConnectionLoading"
|
|
65
|
+
:errors="errorMessage"
|
|
66
|
+
@click="onTestConnection"
|
|
67
|
+
></TestConnection>
|
|
68
|
+
</v-col>
|
|
69
|
+
</v-row>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
import TestConnection from '../TestConnection.vue'
|
|
76
|
+
import ManageBaseVue from './ManageBase.vue'
|
|
77
|
+
|
|
78
|
+
export default {
|
|
79
|
+
name: 'ManageResourcespaceDriver',
|
|
80
|
+
components: { TestConnection },
|
|
81
|
+
extends: ManageBaseVue,
|
|
82
|
+
data() {
|
|
83
|
+
return {
|
|
84
|
+
// formValid: true|false If this form is "complete" and passed validation on THIS component. Defined and watched in ManageBase.vue
|
|
85
|
+
// render: true|false If we should show the form aka when validation has passed. Defined and managed in ManageBase.vue
|
|
86
|
+
// integration: { metadata: {...} } The integration object to write to. Defined and loaded in ManageBase.vue
|
|
87
|
+
errorMessage: '',
|
|
88
|
+
testConnectionLoading: false,
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
methods: {
|
|
92
|
+
/**
|
|
93
|
+
* Lifecycle event called from ManageBase.vue when async fetch() completes.
|
|
94
|
+
* Once called this.integration will be available containing the integration model (or a new one)
|
|
95
|
+
*/
|
|
96
|
+
onIntegrationLoaded() {
|
|
97
|
+
// Set SSL to enabled by default
|
|
98
|
+
if (_.get(this.integration.metadata.config, 'ssl', null) === null) {
|
|
99
|
+
this.integration.metadata.config.ssl = true
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
async onTestConnection() {
|
|
103
|
+
this.testConnectionLoading = true
|
|
104
|
+
let response = { result: false }
|
|
105
|
+
try {
|
|
106
|
+
response = await this.testConnection(this.integration.metadata)
|
|
107
|
+
|
|
108
|
+
if (response.result) {
|
|
109
|
+
this.errorMessage = ''
|
|
110
|
+
this.$dialog.success(
|
|
111
|
+
this.$t(
|
|
112
|
+
'windward.integrations.shared.error.connect_success'
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
} else {
|
|
116
|
+
this.errorMessage = response.message
|
|
117
|
+
this.$dialog.error(
|
|
118
|
+
this.$t(
|
|
119
|
+
'windward.integrations.shared.error.connect_fail'
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error(e)
|
|
125
|
+
this.$dialog.error(
|
|
126
|
+
this.$t('windward.integrations.shared.error.unknown')
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// We will indirectly validate the form via connection tests
|
|
131
|
+
// That way we can 100% confirm that the integration is valid
|
|
132
|
+
this.formValid = response.result
|
|
133
|
+
this.testConnectionLoading = false
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
@@ -5,28 +5,127 @@
|
|
|
5
5
|
</v-btn>
|
|
6
6
|
<Dialog v-model="logDialog" :trigger="false">
|
|
7
7
|
<template #title>{{
|
|
8
|
-
$t(
|
|
8
|
+
$t(
|
|
9
|
+
'windward.integrations.components.integration.job_log.view_log'
|
|
10
|
+
)
|
|
9
11
|
}}</template>
|
|
10
|
-
<template #form>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
<template #form="{ on, attrs }">
|
|
13
|
+
<div v-bind="attrs" v-on="on">
|
|
14
|
+
<v-progress-circular
|
|
15
|
+
v-if="loading"
|
|
16
|
+
size="128"
|
|
17
|
+
indeterminate
|
|
18
|
+
/>
|
|
19
|
+
<div v-if="!loading">
|
|
20
|
+
<SearchField
|
|
21
|
+
v-model="search"
|
|
22
|
+
hide-filters
|
|
23
|
+
></SearchField>
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
<v-alert
|
|
26
|
+
v-for="logItem in filteredLog"
|
|
27
|
+
:key="logItem.id"
|
|
28
|
+
:type="logItem.level"
|
|
29
|
+
:icon="logItemIcon(logItem)"
|
|
30
|
+
class="log-item"
|
|
31
|
+
>
|
|
32
|
+
<p>{{ logItem.message }}</p>
|
|
33
|
+
<div v-if="showDetails(logItem)">
|
|
34
|
+
<v-divider class="mt-1 mb-1" />
|
|
35
|
+
<p
|
|
36
|
+
v-if="
|
|
37
|
+
logItem.context.remote_content &&
|
|
38
|
+
remoteCourse
|
|
39
|
+
"
|
|
40
|
+
>
|
|
41
|
+
{{
|
|
42
|
+
$t(
|
|
43
|
+
'windward.integrations.components.integration.job_log.remote_course_id'
|
|
44
|
+
)
|
|
45
|
+
}}
|
|
46
|
+
{{ remoteCourse.id }}
|
|
47
|
+
</p>
|
|
48
|
+
<p v-if="logItem.context.remote_content">
|
|
49
|
+
{{
|
|
50
|
+
$t(
|
|
51
|
+
'windward.integrations.components.integration.job_log.remote_content_id'
|
|
52
|
+
)
|
|
53
|
+
}}
|
|
54
|
+
{{ logItem.context.remote_content.id }}
|
|
55
|
+
</p>
|
|
56
|
+
<p v-if="logItem.context.remote_url">
|
|
57
|
+
{{
|
|
58
|
+
$t(
|
|
59
|
+
'windward.integrations.components.integration.job_log.remote_url'
|
|
60
|
+
)
|
|
61
|
+
}}
|
|
62
|
+
<v-chip
|
|
63
|
+
:href="
|
|
64
|
+
isUrl(logItem.context.remote_url)
|
|
65
|
+
? logItem.context.remote_url
|
|
66
|
+
: undefined
|
|
67
|
+
"
|
|
68
|
+
label
|
|
69
|
+
target="_blank"
|
|
70
|
+
>
|
|
71
|
+
<span
|
|
72
|
+
class="d-inline-block text-truncate chip-truncate"
|
|
73
|
+
>
|
|
74
|
+
{{ logItem.context.remote_url }}
|
|
75
|
+
</span>
|
|
22
76
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
77
|
+
<v-icon
|
|
78
|
+
v-if="
|
|
79
|
+
isUrl(
|
|
80
|
+
logItem.context.remote_url
|
|
81
|
+
)
|
|
82
|
+
"
|
|
83
|
+
class="ml-2"
|
|
84
|
+
>
|
|
85
|
+
mdi-launch
|
|
86
|
+
</v-icon>
|
|
87
|
+
</v-chip>
|
|
88
|
+
</p>
|
|
89
|
+
<div v-if="logItem.context.remote_code">
|
|
90
|
+
<p>
|
|
91
|
+
{{
|
|
92
|
+
$t(
|
|
93
|
+
'windward.integrations.components.integration.job_log.remote_code'
|
|
94
|
+
)
|
|
95
|
+
}}
|
|
96
|
+
<v-btn
|
|
97
|
+
icon
|
|
98
|
+
@click="
|
|
99
|
+
onClickCopy(
|
|
100
|
+
logItem.context.remote_code
|
|
101
|
+
)
|
|
102
|
+
"
|
|
103
|
+
><v-icon class="ml-2">
|
|
104
|
+
mdi-content-copy
|
|
105
|
+
</v-icon>
|
|
106
|
+
<span class="sr-only">{{
|
|
107
|
+
$t('shared.forms.copy')
|
|
108
|
+
}}</span>
|
|
109
|
+
</v-btn>
|
|
110
|
+
</p>
|
|
111
|
+
<div class="remote-code grey darken-3">
|
|
112
|
+
<code
|
|
113
|
+
class="grey darken-3"
|
|
114
|
+
v-text="logItem.context.remote_code"
|
|
115
|
+
></code>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</v-alert>
|
|
120
|
+
|
|
121
|
+
<p v-if="filteredLog.length === 0">
|
|
122
|
+
{{
|
|
123
|
+
$t(
|
|
124
|
+
'windward.integrations.components.integration.job_log.no_results'
|
|
125
|
+
)
|
|
126
|
+
}}
|
|
127
|
+
</p>
|
|
128
|
+
</div>
|
|
30
129
|
</div>
|
|
31
130
|
</template>
|
|
32
131
|
</Dialog>
|
|
@@ -34,13 +133,14 @@
|
|
|
34
133
|
</template>
|
|
35
134
|
|
|
36
135
|
<script>
|
|
136
|
+
import _ from 'lodash'
|
|
37
137
|
import { mapGetters } from 'vuex'
|
|
38
138
|
import SearchField from '~/components/SearchField.vue'
|
|
39
139
|
import Organization from '../../models/Organization'
|
|
40
140
|
|
|
41
141
|
export default {
|
|
42
|
-
components: { SearchField },
|
|
43
142
|
name: 'IntegrationJobLog',
|
|
143
|
+
components: { SearchField },
|
|
44
144
|
props: {
|
|
45
145
|
id: { type: String, required: true },
|
|
46
146
|
},
|
|
@@ -48,6 +148,7 @@ export default {
|
|
|
48
148
|
return {
|
|
49
149
|
search: {},
|
|
50
150
|
loading: true,
|
|
151
|
+
job: {},
|
|
51
152
|
log: [],
|
|
52
153
|
logDialog: false,
|
|
53
154
|
}
|
|
@@ -58,21 +159,87 @@ export default {
|
|
|
58
159
|
}),
|
|
59
160
|
filteredLog() {
|
|
60
161
|
if (!this.search.term || this.search.term.length < 3) {
|
|
61
|
-
return this.log
|
|
162
|
+
return this.job.log || []
|
|
62
163
|
}
|
|
63
|
-
const filtered = this.log.filter((item) => {
|
|
64
|
-
|
|
164
|
+
const filtered = this.job.log.filter((item) => {
|
|
165
|
+
let match = item.message
|
|
65
166
|
.toLowerCase()
|
|
66
167
|
.includes(this.search.term.toLowerCase())
|
|
168
|
+
|
|
169
|
+
// Search the remote course id
|
|
170
|
+
if (!match && _.get(item, 'context.remote_course.id', null)) {
|
|
171
|
+
match = _.get(item, 'context.remote_course.id', '')
|
|
172
|
+
.toString()
|
|
173
|
+
.toLowerCase()
|
|
174
|
+
.includes(this.search.term.toLowerCase())
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Search the remote assessment id
|
|
178
|
+
if (
|
|
179
|
+
!match &&
|
|
180
|
+
_.get(item, 'context.remote_assessment.id', null)
|
|
181
|
+
) {
|
|
182
|
+
match = _.get(item, 'context.remote_assessment.id', '')
|
|
183
|
+
.toString()
|
|
184
|
+
.toLowerCase()
|
|
185
|
+
.includes(this.search.term.toLowerCase())
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Search the remote url
|
|
189
|
+
if (!match && item.context.remote_url) {
|
|
190
|
+
match = item.context.remote_url
|
|
191
|
+
.toLowerCase()
|
|
192
|
+
.includes(this.search.term.toLowerCase())
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Search the remote code
|
|
196
|
+
if (!match && item.context.remote_code) {
|
|
197
|
+
match = item.context.remote_code
|
|
198
|
+
.toLowerCase()
|
|
199
|
+
.includes(this.search.term.toLowerCase())
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return match
|
|
67
203
|
})
|
|
68
204
|
|
|
69
205
|
return filtered
|
|
70
206
|
},
|
|
207
|
+
remoteCourse() {
|
|
208
|
+
return _.get(this.job, 'metadata.import.remote_course', {})
|
|
209
|
+
},
|
|
210
|
+
localCourse() {
|
|
211
|
+
return _.get(this.job, 'metadata.import.local_course', {})
|
|
212
|
+
},
|
|
213
|
+
showDetails() {
|
|
214
|
+
return (item) => {
|
|
215
|
+
return (
|
|
216
|
+
(Array.isArray(item.context) && item.context.length > 0) ||
|
|
217
|
+
!_.isEmpty(item.context)
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
logItemIcon() {
|
|
222
|
+
return (logItem) => {
|
|
223
|
+
const type = _.get(logItem, 'context.type', null)
|
|
224
|
+
if (type === 'missing_file') {
|
|
225
|
+
return 'mdi-file-alert'
|
|
226
|
+
} else if (type === 'converted_file') {
|
|
227
|
+
return 'mdi-file-arrow-left-right'
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return undefined
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
isUrl() {
|
|
234
|
+
return (url) => {
|
|
235
|
+
return /https?:\/\//.test(url)
|
|
236
|
+
}
|
|
237
|
+
},
|
|
71
238
|
},
|
|
72
239
|
methods: {
|
|
73
240
|
async onViewLog() {
|
|
74
241
|
this.logDialog = true
|
|
75
|
-
this.
|
|
242
|
+
this.job = {}
|
|
76
243
|
|
|
77
244
|
const job = await new Organization({
|
|
78
245
|
id: this.organization.id,
|
|
@@ -81,9 +248,13 @@ export default {
|
|
|
81
248
|
.with('log')
|
|
82
249
|
.find(this.id)
|
|
83
250
|
|
|
84
|
-
this.
|
|
251
|
+
this.job = job
|
|
85
252
|
this.loading = false
|
|
86
253
|
},
|
|
254
|
+
onClickCopy(data) {
|
|
255
|
+
navigator.clipboard.writeText(data)
|
|
256
|
+
this.$dialog.show(this.$t('shared.file.copied'))
|
|
257
|
+
},
|
|
87
258
|
},
|
|
88
259
|
}
|
|
89
260
|
</script>
|
|
@@ -95,4 +266,16 @@ export default {
|
|
|
95
266
|
.col-details {
|
|
96
267
|
max-width: 100px;
|
|
97
268
|
}
|
|
269
|
+
.chip-truncate {
|
|
270
|
+
max-width: 40vw;
|
|
271
|
+
direction: rtl;
|
|
272
|
+
}
|
|
273
|
+
.log-item p {
|
|
274
|
+
margin-bottom: 0px;
|
|
275
|
+
}
|
|
276
|
+
.remote-code {
|
|
277
|
+
background: inherit;
|
|
278
|
+
max-width: 40vw;
|
|
279
|
+
padding: 4px;
|
|
280
|
+
}
|
|
98
281
|
</style>
|
|
@@ -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
|
+
}
|
|
@@ -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)',
|
|
@@ -2,6 +2,7 @@ import contentBlocks from './content_blocks'
|
|
|
2
2
|
import settings from './settings'
|
|
3
3
|
import menu from './menu'
|
|
4
4
|
import permission from './permission'
|
|
5
|
+
import file from './file'
|
|
5
6
|
import error from './error'
|
|
6
7
|
|
|
7
8
|
export default {
|
|
@@ -9,5 +10,6 @@ export default {
|
|
|
9
10
|
settings,
|
|
10
11
|
menu,
|
|
11
12
|
permission,
|
|
13
|
+
file,
|
|
12
14
|
error,
|
|
13
15
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Model from '~/models/Model'
|
|
2
2
|
import RemoteOrganization from './RemoteOrganization'
|
|
3
|
+
import RemoteFile from './RemoteFile'
|
|
3
4
|
|
|
4
5
|
export default class OrganizationIntegration extends Model {
|
|
5
6
|
get required(): string[] {
|
|
@@ -14,4 +15,8 @@ export default class OrganizationIntegration extends Model {
|
|
|
14
15
|
remoteOrganizations() {
|
|
15
16
|
return this.hasMany(RemoteOrganization)
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
remoteFiles() {
|
|
20
|
+
return this.hasMany(RemoteFile)
|
|
21
|
+
}
|
|
17
22
|
}
|
package/package.json
CHANGED
|
@@ -210,13 +210,19 @@ export default {
|
|
|
210
210
|
},
|
|
211
211
|
async fetch() {
|
|
212
212
|
this.loading.integration = true
|
|
213
|
-
|
|
213
|
+
const organizationIntegrations = await new Organization({
|
|
214
214
|
id: this.organization.id,
|
|
215
215
|
})
|
|
216
216
|
.integrations()
|
|
217
217
|
.with(['vendor'])
|
|
218
218
|
.where('enabled', true)
|
|
219
219
|
.get()
|
|
220
|
+
|
|
221
|
+
// Filter out any vendors that don't support course imports
|
|
222
|
+
this.organizationIntegrations = organizationIntegrations.filter((o) => {
|
|
223
|
+
return _.get(o, 'vendor.driver.course_import', false)
|
|
224
|
+
})
|
|
225
|
+
|
|
220
226
|
this.loading.integration = false
|
|
221
227
|
},
|
|
222
228
|
computed: {
|
package/plugin.js
CHANGED
|
@@ -9,6 +9,11 @@ import IntegrationHelper from './helpers/IntegrationHelper'
|
|
|
9
9
|
import LtiConsumerBlock from './components/Content/Blocks/ExternalIntegration/LtiConsumer'
|
|
10
10
|
import LtiConsumerBlockSettings from './components/Settings/ExternalIntegration/LtiConsumerSettings'
|
|
11
11
|
|
|
12
|
+
import FileImportMenu from './components/FileImport/FileImportMenu.vue'
|
|
13
|
+
import FileImportResourcespace from './components/FileImport/Resourcespace.vue'
|
|
14
|
+
import FileImportGoogleDrive from './components/FileImport/GoogleDrive.vue'
|
|
15
|
+
import FileImportDropbox from './components/FileImport/Dropbox.vue'
|
|
16
|
+
|
|
12
17
|
export default {
|
|
13
18
|
name: 'windward.integrations.name',
|
|
14
19
|
hooks: {
|
|
@@ -204,6 +209,37 @@ export default {
|
|
|
204
209
|
},
|
|
205
210
|
},
|
|
206
211
|
],
|
|
212
|
+
fileImport: [
|
|
213
|
+
{
|
|
214
|
+
tag: 'windward-integrations-file-import-menu',
|
|
215
|
+
template: FileImportMenu,
|
|
216
|
+
metadata: {
|
|
217
|
+
vendors: [
|
|
218
|
+
{
|
|
219
|
+
template: FileImportResourcespace,
|
|
220
|
+
i18n: 'windward.integrations.shared.file.import_resourcespace',
|
|
221
|
+
icon: 'mdi-cube',
|
|
222
|
+
product_code: 'resourcespace',
|
|
223
|
+
disabled: false,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
template: FileImportGoogleDrive,
|
|
227
|
+
i18n: 'windward.integrations.shared.file.import_google_drive',
|
|
228
|
+
icon: 'mdi-google-drive',
|
|
229
|
+
product_code: 'google_drive',
|
|
230
|
+
disabled: true,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
template: FileImportDropbox,
|
|
234
|
+
i18n: 'windward.integrations.shared.file.import_dropbox',
|
|
235
|
+
icon: 'mdi-dropbox',
|
|
236
|
+
product_code: 'dropbox',
|
|
237
|
+
disabled: true,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
],
|
|
207
243
|
},
|
|
208
244
|
services: {
|
|
209
245
|
Integration: IntegrationHelper,
|
|
@@ -0,0 +1,24 @@
|
|
|
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 Dropbox from '@/components/FileImport/Dropbox.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('Dropbox', () => {
|
|
12
|
+
test('Dropbox is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(Dropbox, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
propsData: {
|
|
16
|
+
organizationIntegration: {
|
|
17
|
+
id: '00000000-0000-0000-0000-000000000000',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
mocks: defaultMocks,
|
|
21
|
+
})
|
|
22
|
+
expect(wrapper.vm).toBeTruthy()
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
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 GoogleDrive from '@/components/FileImport/GoogleDrive.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('GoogleDrive', () => {
|
|
12
|
+
test('GoogleDrive is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(GoogleDrive, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
propsData: {
|
|
16
|
+
organizationIntegration: {
|
|
17
|
+
id: '00000000-0000-0000-0000-000000000000',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
mocks: defaultMocks,
|
|
21
|
+
})
|
|
22
|
+
expect(wrapper.vm).toBeTruthy()
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
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 Resourcespace from '@/components/FileImport/Resourcespace.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('Resourcespace', () => {
|
|
12
|
+
test('Resourcespace is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(Resourcespace, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
propsData: {
|
|
16
|
+
organizationIntegration: {
|
|
17
|
+
id: '00000000-0000-0000-0000-000000000000',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
mocks: defaultMocks,
|
|
21
|
+
})
|
|
22
|
+
expect(wrapper.vm).toBeTruthy()
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -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 ManageAtutor from '@/components/Integration/Driver/ManageAtutor.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('ManageAtutor', () => {
|
|
12
|
+
test('ManageAtutor is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(ManageAtutor, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
propsData: {
|
|
17
|
+
vendor: {},
|
|
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 ManageResourcespace from '@/components/Integration/Driver/ManageResourcespace.vue'
|
|
8
|
+
|
|
9
|
+
Vue.use(Vuetify)
|
|
10
|
+
|
|
11
|
+
describe('ManageResourcespace', () => {
|
|
12
|
+
test('ManageResourcespace is a Vue instance', () => {
|
|
13
|
+
const wrapper = shallowMount(ManageResourcespace, {
|
|
14
|
+
vuetify: new Vuetify(),
|
|
15
|
+
mocks: defaultMocks,
|
|
16
|
+
propsData: {
|
|
17
|
+
vendor: {},
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
expect(wrapper.vm).toBeTruthy()
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -11,6 +11,18 @@ jest.mock(
|
|
|
11
11
|
{ virtual: true }
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
+
jest.mock(
|
|
15
|
+
'~/components/Breadcrumbs.vue',
|
|
16
|
+
() => {
|
|
17
|
+
return {
|
|
18
|
+
data() {
|
|
19
|
+
return { validation: {} }
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{ virtual: true }
|
|
24
|
+
)
|
|
25
|
+
|
|
14
26
|
jest.mock(
|
|
15
27
|
'~/components/SearchField.vue',
|
|
16
28
|
() => {
|