@windward/integrations 0.0.1
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/.editorconfig +13 -0
- package/.eslintrc.js +15 -0
- package/.prettierrc +4 -0
- package/README.md +19 -0
- package/babel.config.js +1 -0
- package/components/Integration/Driver/ManageAtutor.vue +143 -0
- package/components/Integration/Driver/ManageBase.vue +145 -0
- package/components/Integration/JobTable.vue +308 -0
- package/components/Integration/TestConnection.vue +45 -0
- package/config/integration.config.js +13 -0
- package/helpers/Driver/Atutor.ts +12 -0
- package/helpers/Driver/BaseDriver.ts +25 -0
- package/helpers/Driver/DriverInterface.ts +7 -0
- package/helpers/IntegrationHelper.ts +150 -0
- package/i18n/en-US/components/index.ts +7 -0
- package/i18n/en-US/components/integration/driver.ts +18 -0
- package/i18n/en-US/components/integration/index.ts +7 -0
- package/i18n/en-US/components/integration/job.ts +22 -0
- package/i18n/en-US/components/navigation/index.ts +5 -0
- package/i18n/en-US/components/navigation/integrations.ts +8 -0
- package/i18n/en-US/index.ts +16 -0
- package/i18n/en-US/modules/index.ts +5 -0
- package/i18n/en-US/pages/importContent.ts +3 -0
- package/i18n/en-US/pages/importCourse.ts +13 -0
- package/i18n/en-US/pages/index.ts +9 -0
- package/i18n/en-US/pages/vendor.ts +11 -0
- package/i18n/en-US/shared/error.ts +8 -0
- package/i18n/en-US/shared/index.ts +11 -0
- package/i18n/en-US/shared/menu.ts +3 -0
- package/i18n/en-US/shared/permission.ts +26 -0
- package/i18n/en-US/shared/settings.ts +1 -0
- package/jest.config.js +17 -0
- package/models/CourseSectionIntegration.ts +12 -0
- package/models/IntegrationJob.ts +12 -0
- package/models/Organization.ts +14 -0
- package/models/OrganizationIntegration.ts +17 -0
- package/models/RemoteContent.ts +12 -0
- package/models/RemoteCourse.ts +17 -0
- package/models/RemoteOrganization.ts +17 -0
- package/models/Vendor.ts +12 -0
- package/package.json +44 -0
- package/pages/admin/importCourse.vue +390 -0
- package/pages/admin/vendors.vue +241 -0
- package/pages/course/importContent.vue +25 -0
- package/plugin.js +110 -0
- package/test/Helpers/IntegrationHelper.spec.js +92 -0
- package/test/Pages/Admin/ImportCourse.spec.js +19 -0
- package/test/Pages/Admin/vendors.spec.js +19 -0
- package/test/Pages/Course/importContent.spec.js +19 -0
- package/test/__mocks__/lodashMock.js +31 -0
- package/test/__mocks__/modelMock.js +101 -0
- package/test/__mocks__/vuexMock.js +31 -0
- package/test/mocks.js +18 -0
- package/tsconfig.json +21 -0
package/.editorconfig
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
env: {
|
|
4
|
+
browser: true,
|
|
5
|
+
node: true,
|
|
6
|
+
},
|
|
7
|
+
extends: [
|
|
8
|
+
'@nuxtjs/eslint-config-typescript',
|
|
9
|
+
'plugin:nuxt/recommended',
|
|
10
|
+
'plugin:prettier/recommended',
|
|
11
|
+
],
|
|
12
|
+
plugins: [],
|
|
13
|
+
// add your custom rules here
|
|
14
|
+
rules: {},
|
|
15
|
+
}
|
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Windward UI Integrations
|
|
2
|
+
|
|
3
|
+
## Getting Started Locally with npm
|
|
4
|
+
|
|
5
|
+
**Software Requirements**:
|
|
6
|
+
|
|
7
|
+
- Node v16+, npm v8+
|
|
8
|
+
|
|
9
|
+
## New .env variables
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
BROADCAST_DRIVER=pusher
|
|
13
|
+
BROADCAST_HOST=
|
|
14
|
+
BROADCAST_PORT=
|
|
15
|
+
# windward-development pusher key found on https://dashboard.pusher.com/
|
|
16
|
+
BROADCAST_KEY=798e489618e5e8bce5a1
|
|
17
|
+
BROADCAST_CLUSTER=mt1
|
|
18
|
+
BROADCAST_FORCE_TLS=true
|
|
19
|
+
```
|
package/babel.config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = { presets: ["@babel/preset-env"] };
|
|
@@ -0,0 +1,143 @@
|
|
|
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.atutor.url'
|
|
23
|
+
)
|
|
24
|
+
"
|
|
25
|
+
:hint="
|
|
26
|
+
$t(
|
|
27
|
+
'windward.integrations.components.integration.driver.atutor.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.atutor.username'
|
|
36
|
+
)
|
|
37
|
+
"
|
|
38
|
+
></v-text-field>
|
|
39
|
+
<v-text-field
|
|
40
|
+
v-model="integration.metadata.config.password"
|
|
41
|
+
:label="
|
|
42
|
+
$t(
|
|
43
|
+
'windward.integrations.components.integration.driver.atutor.password'
|
|
44
|
+
)
|
|
45
|
+
"
|
|
46
|
+
type="password"
|
|
47
|
+
></v-text-field>
|
|
48
|
+
|
|
49
|
+
<v-text-field
|
|
50
|
+
v-model="integration.metadata.config.aws_secure_url"
|
|
51
|
+
:label="
|
|
52
|
+
$t(
|
|
53
|
+
'windward.integrations.components.integration.driver.atutor.aws_secure_url'
|
|
54
|
+
)
|
|
55
|
+
"
|
|
56
|
+
:hint="
|
|
57
|
+
$t(
|
|
58
|
+
'windward.integrations.components.integration.driver.atutor.aws_secure_url_hint'
|
|
59
|
+
)
|
|
60
|
+
"
|
|
61
|
+
></v-text-field>
|
|
62
|
+
|
|
63
|
+
<v-switch
|
|
64
|
+
v-model="integration.metadata.config.ssl"
|
|
65
|
+
:label="
|
|
66
|
+
$t(
|
|
67
|
+
'windward.integrations.components.integration.driver.ssl_enabled'
|
|
68
|
+
)
|
|
69
|
+
"
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
<TestConnection
|
|
73
|
+
:disabled="
|
|
74
|
+
!integration.metadata.config.url ||
|
|
75
|
+
!integration.metadata.config.username ||
|
|
76
|
+
!integration.metadata.config.password
|
|
77
|
+
"
|
|
78
|
+
:loading="testConnectionLoading"
|
|
79
|
+
:errors="errorMessage"
|
|
80
|
+
@click="onTestConnection"
|
|
81
|
+
></TestConnection>
|
|
82
|
+
</v-col>
|
|
83
|
+
</v-row>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
import TestConnection from '../TestConnection.vue'
|
|
90
|
+
import ManageBaseVue from './ManageBase.vue'
|
|
91
|
+
|
|
92
|
+
export default {
|
|
93
|
+
name: 'ManageAtutorDriver',
|
|
94
|
+
components: { TestConnection },
|
|
95
|
+
extends: ManageBaseVue,
|
|
96
|
+
data() {
|
|
97
|
+
return {
|
|
98
|
+
// formValid: true|false If this form is "complete" and passed validation on THIS component. Defined and watched in ManageBase.vue
|
|
99
|
+
// render: true|false If we should show the form aka when validation has passed. Defined and managed in ManageBase.vue
|
|
100
|
+
// integration: { metadata: {...} } The integration object to write to. Defined and loaded in ManageBase.vue
|
|
101
|
+
errorMessage: '',
|
|
102
|
+
testConnectionLoading: false,
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
methods: {
|
|
106
|
+
/**
|
|
107
|
+
* Lifecycle event called from ManageBase.vue when async fetch() completes.
|
|
108
|
+
* Once called this.integration will be available containing the integration model (or a new one)
|
|
109
|
+
*/
|
|
110
|
+
onIntegrationLoaded() {
|
|
111
|
+
// Set SSL to enabled by default
|
|
112
|
+
if (_.get(this.integration.metadata.config, 'ssl', null) === null) {
|
|
113
|
+
this.integration.metadata.config.ssl = true
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
async onTestConnection() {
|
|
117
|
+
this.testConnectionLoading = true
|
|
118
|
+
const response = await this.testConnection(
|
|
119
|
+
this.integration.metadata
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if (response.result) {
|
|
123
|
+
this.errorMessage = ''
|
|
124
|
+
this.$dialog.success(
|
|
125
|
+
this.$t(
|
|
126
|
+
'windward.integrations.shared.error.connect_success'
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
} else {
|
|
130
|
+
this.errorMessage = response.message
|
|
131
|
+
this.$dialog.error(
|
|
132
|
+
this.$t('windward.integrations.shared.error.connect_fail')
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// We will indirectly validate the form via connection tests
|
|
137
|
+
// That way we can 100% confirm that the integration is valid
|
|
138
|
+
this.formValid = response.result
|
|
139
|
+
this.testConnectionLoading = false
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
</script>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { mapGetters } from 'vuex'
|
|
3
|
+
import Organization from '../../../models/Organization'
|
|
4
|
+
import OrganizationIntegration from '../../../models/OrganizationIntegration'
|
|
5
|
+
import Vendor from '../../../models/Vendor'
|
|
6
|
+
import FormVue from '~/components/Form'
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'ManageBase',
|
|
10
|
+
components: {},
|
|
11
|
+
extends: FormVue,
|
|
12
|
+
props: {
|
|
13
|
+
vendor: { type: Object, required: true, default: null },
|
|
14
|
+
},
|
|
15
|
+
emits: ['update:integration'],
|
|
16
|
+
meta: {
|
|
17
|
+
privilege: {
|
|
18
|
+
'': {
|
|
19
|
+
writable: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
data() {
|
|
24
|
+
return {
|
|
25
|
+
render: false,
|
|
26
|
+
integration: { metadata: { config: {} } },
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
async fetch() {
|
|
30
|
+
if (
|
|
31
|
+
!this.$PermissionService.userHasAccessTo(
|
|
32
|
+
'plugin.integration.organization',
|
|
33
|
+
'writable'
|
|
34
|
+
)
|
|
35
|
+
) {
|
|
36
|
+
// Display an angry error that they can't view this driver
|
|
37
|
+
this.$dialog.error(this.$t('shared.error.description_401'), {
|
|
38
|
+
duration: null,
|
|
39
|
+
action: {
|
|
40
|
+
text: this.$t('shared.forms.close'),
|
|
41
|
+
onClick: (e, toastObject) => {
|
|
42
|
+
toastObject.goAway(0)
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
console.error('You do not have access to this integration!')
|
|
47
|
+
|
|
48
|
+
// Return so we don't even attempt loading
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Load the existing org settings
|
|
53
|
+
const integration = await new Organization({
|
|
54
|
+
id: this.organization.id,
|
|
55
|
+
})
|
|
56
|
+
.integrations()
|
|
57
|
+
.where('integration_vendor_id', this.vendor.id)
|
|
58
|
+
.append(['metadata'])
|
|
59
|
+
.first()
|
|
60
|
+
|
|
61
|
+
if (_.isEmpty(integration)) {
|
|
62
|
+
// Configure a new organization integration, otherwise load existing
|
|
63
|
+
this.integration = new OrganizationIntegration({
|
|
64
|
+
integration_vendor_id: this.vendor.id,
|
|
65
|
+
organization_id: this.organization.id,
|
|
66
|
+
enabled: false,
|
|
67
|
+
metadata: { config: {} },
|
|
68
|
+
})
|
|
69
|
+
} else {
|
|
70
|
+
// Make sure the `metadata.config` prop exists
|
|
71
|
+
this.integration = integration
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Make sure metadata.config is an object
|
|
75
|
+
if (
|
|
76
|
+
_.isEmpty(_.get(this.integration.metadata, 'config')) ||
|
|
77
|
+
_.isArray(_.get(this.integration.metadata, 'config'))
|
|
78
|
+
) {
|
|
79
|
+
this.integration.metadata.config = {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.onIntegrationLoaded()
|
|
83
|
+
this.render = true
|
|
84
|
+
},
|
|
85
|
+
computed: {
|
|
86
|
+
...mapGetters({
|
|
87
|
+
organization: 'organization/get',
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
methods: {
|
|
91
|
+
async testConnection(config) {
|
|
92
|
+
const response = await Vendor.config({
|
|
93
|
+
method: 'POST',
|
|
94
|
+
data: config,
|
|
95
|
+
})
|
|
96
|
+
.custom(
|
|
97
|
+
'integration-vendors/' + this.vendor.id + '/test-connection'
|
|
98
|
+
)
|
|
99
|
+
.first()
|
|
100
|
+
|
|
101
|
+
return response
|
|
102
|
+
},
|
|
103
|
+
async save() {
|
|
104
|
+
let integration = new OrganizationIntegration(this.integration).for(
|
|
105
|
+
new Organization({ id: this.organization.id })
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
integration = await integration.save()
|
|
110
|
+
this.integration = integration
|
|
111
|
+
|
|
112
|
+
// Clone and delete the metadata since we don't need to share that around
|
|
113
|
+
// Also include the vendor going back for easier mapping
|
|
114
|
+
const integrationEvent = _.cloneDeep(integration)
|
|
115
|
+
delete integrationEvent.metadata
|
|
116
|
+
|
|
117
|
+
this.$dialog.success(this.$t('shared.forms.saved'))
|
|
118
|
+
this.$emit('update:integration', integrationEvent)
|
|
119
|
+
} catch (e) {
|
|
120
|
+
this.$dialog.error(
|
|
121
|
+
this.$t('windward.integrations.shared.error.save_failed')
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
async onSave() {
|
|
126
|
+
if (this.formValid) {
|
|
127
|
+
await this.save()
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
onIntegrationLoaded() {
|
|
132
|
+
console.warn(
|
|
133
|
+
'Integration/Driver/ManageBase.vue onIntegrationLoaded called. Not extended!'
|
|
134
|
+
)
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<style scoped>
|
|
141
|
+
.integration-loading {
|
|
142
|
+
text-align: center;
|
|
143
|
+
height: 150px;
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-simple-table>
|
|
4
|
+
<template #default>
|
|
5
|
+
<thead>
|
|
6
|
+
<tr>
|
|
7
|
+
<th class="text-left">
|
|
8
|
+
{{
|
|
9
|
+
$t(
|
|
10
|
+
'windward.integrations.components.integration.job.job_id'
|
|
11
|
+
)
|
|
12
|
+
}}
|
|
13
|
+
</th>
|
|
14
|
+
<th class="text-left">
|
|
15
|
+
{{
|
|
16
|
+
$t(
|
|
17
|
+
'windward.integrations.components.integration.job.vendor_name'
|
|
18
|
+
)
|
|
19
|
+
}}
|
|
20
|
+
</th>
|
|
21
|
+
<th class="text-left">
|
|
22
|
+
{{
|
|
23
|
+
$t(
|
|
24
|
+
'windward.integrations.components.integration.job.status'
|
|
25
|
+
)
|
|
26
|
+
}}
|
|
27
|
+
</th>
|
|
28
|
+
<th class="text-left col-progress">
|
|
29
|
+
{{
|
|
30
|
+
$t(
|
|
31
|
+
'windward.integrations.components.integration.job.progress'
|
|
32
|
+
)
|
|
33
|
+
}}
|
|
34
|
+
</th>
|
|
35
|
+
<th class="text-left col-details">
|
|
36
|
+
{{
|
|
37
|
+
$t(
|
|
38
|
+
'windward.integrations.components.integration.job.details'
|
|
39
|
+
)
|
|
40
|
+
}}
|
|
41
|
+
</th>
|
|
42
|
+
<th class="text-left">
|
|
43
|
+
{{
|
|
44
|
+
$t(
|
|
45
|
+
'windward.integrations.components.integration.job.created'
|
|
46
|
+
)
|
|
47
|
+
}}
|
|
48
|
+
</th>
|
|
49
|
+
</tr>
|
|
50
|
+
</thead>
|
|
51
|
+
<tbody>
|
|
52
|
+
<tr v-if="jobs.length === 0">
|
|
53
|
+
<td colspan="100%">
|
|
54
|
+
{{
|
|
55
|
+
$t(
|
|
56
|
+
'windward.integrations.components.integration.job.no_recent_jobs'
|
|
57
|
+
)
|
|
58
|
+
}}
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
<tr v-for="item in jobs" :key="item.id">
|
|
62
|
+
<td>{{ shortUuid(item.id) }}</td>
|
|
63
|
+
<td>
|
|
64
|
+
{{
|
|
65
|
+
vendorFromOrgIntegration(
|
|
66
|
+
item.organization_integration_id
|
|
67
|
+
).name
|
|
68
|
+
}}
|
|
69
|
+
</td>
|
|
70
|
+
<td>
|
|
71
|
+
{{
|
|
72
|
+
$t(
|
|
73
|
+
'windward.integrations.components.integration.job.job_status.' +
|
|
74
|
+
item.status
|
|
75
|
+
)
|
|
76
|
+
}}
|
|
77
|
+
</td>
|
|
78
|
+
<td class="col-progress">
|
|
79
|
+
<v-row
|
|
80
|
+
v-if="
|
|
81
|
+
item.status !== 'failed' &&
|
|
82
|
+
item.progress < 100
|
|
83
|
+
"
|
|
84
|
+
>
|
|
85
|
+
<v-col cols="2">
|
|
86
|
+
{{ Math.ceil(item.progress) }}%
|
|
87
|
+
</v-col>
|
|
88
|
+
<v-col cols="10">
|
|
89
|
+
<v-progress-linear
|
|
90
|
+
v-model="item.progress"
|
|
91
|
+
buffer-value="0"
|
|
92
|
+
height="15"
|
|
93
|
+
color="primary"
|
|
94
|
+
stream
|
|
95
|
+
>
|
|
96
|
+
</v-progress-linear>
|
|
97
|
+
</v-col>
|
|
98
|
+
</v-row>
|
|
99
|
+
<span v-else-if="item.status !== 'failed'">
|
|
100
|
+
{{
|
|
101
|
+
$t(
|
|
102
|
+
'windward.integrations.components.integration.job.job_completed'
|
|
103
|
+
)
|
|
104
|
+
}}
|
|
105
|
+
</span>
|
|
106
|
+
<div v-else>
|
|
107
|
+
<v-icon color="error"> mdi-close </v-icon>
|
|
108
|
+
<span class="sr-only">
|
|
109
|
+
{{
|
|
110
|
+
$t(
|
|
111
|
+
'windward.integrations.components.integration.job.job_status.failed'
|
|
112
|
+
)
|
|
113
|
+
}}
|
|
114
|
+
</span>
|
|
115
|
+
{{ Math.ceil(item.progress) }}%
|
|
116
|
+
</div>
|
|
117
|
+
</td>
|
|
118
|
+
<td class="col-details">
|
|
119
|
+
{{ jobDetails(item) }}
|
|
120
|
+
</td>
|
|
121
|
+
<td>{{ $d(new Date(item.created_at), 'long') }}</td>
|
|
122
|
+
</tr>
|
|
123
|
+
</tbody>
|
|
124
|
+
</template>
|
|
125
|
+
</v-simple-table>
|
|
126
|
+
</div>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<script>
|
|
130
|
+
import Confetti from 'canvas-confetti'
|
|
131
|
+
import _ from 'lodash'
|
|
132
|
+
import { mapGetters } from 'vuex'
|
|
133
|
+
import Vendor from '../../models/Vendor'
|
|
134
|
+
import Organization from '../../models/Organization'
|
|
135
|
+
|
|
136
|
+
export default {
|
|
137
|
+
name: 'IntegrationJobs',
|
|
138
|
+
props: {
|
|
139
|
+
channel: { type: String, required: true },
|
|
140
|
+
event: { type: String, required: true },
|
|
141
|
+
},
|
|
142
|
+
data() {
|
|
143
|
+
return {
|
|
144
|
+
socket: null,
|
|
145
|
+
vendors: [],
|
|
146
|
+
orgIntegrations: [],
|
|
147
|
+
jobs: [],
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
async fetch() {
|
|
151
|
+
await this.loadJobs()
|
|
152
|
+
},
|
|
153
|
+
computed: {
|
|
154
|
+
...mapGetters({
|
|
155
|
+
organization: 'organization/get',
|
|
156
|
+
}),
|
|
157
|
+
// Filters are deprecated. Use computed methods instead
|
|
158
|
+
shortUuid() {
|
|
159
|
+
return (v) => {
|
|
160
|
+
if (typeof v === 'undefined' || v === '') {
|
|
161
|
+
return '-'
|
|
162
|
+
}
|
|
163
|
+
return v.substring(v.length - 6).toUpperCase()
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
vendorFromOrgIntegration() {
|
|
167
|
+
return (id) => {
|
|
168
|
+
const orgIntegration = this.orgIntegrations.find(
|
|
169
|
+
(o) => o.id === id
|
|
170
|
+
)
|
|
171
|
+
let vendor = { name: '???' }
|
|
172
|
+
if (orgIntegration) {
|
|
173
|
+
const foundVendor = this.vendors.find(
|
|
174
|
+
(v) => v.id === orgIntegration.integration_vendor_id
|
|
175
|
+
)
|
|
176
|
+
if (foundVendor) {
|
|
177
|
+
vendor = foundVendor
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// this.orgIntegrations.map(({ foo }) => foo)
|
|
181
|
+
return vendor
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
jobDetails() {
|
|
185
|
+
return (job) => {
|
|
186
|
+
if (
|
|
187
|
+
_.get(job.metadata, 'import.remote_course.id') &&
|
|
188
|
+
_.get(job.metadata, 'import.remote_course.name')
|
|
189
|
+
) {
|
|
190
|
+
return this.$t(
|
|
191
|
+
'windward.integrations.components.integration.job.job_details.import_course',
|
|
192
|
+
[
|
|
193
|
+
_.get(job.metadata, 'import.remote_course.id'),
|
|
194
|
+
_.get(job.metadata, 'import.remote_course.name'),
|
|
195
|
+
]
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return this.$t(
|
|
200
|
+
'windward.integrations.components.integration.job.job_details.none'
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
beforeUnmount() {
|
|
206
|
+
this.socket.leave(this.channel)
|
|
207
|
+
},
|
|
208
|
+
mounted() {
|
|
209
|
+
this.socket = this.$Integration.getSocket()
|
|
210
|
+
this.connect(this.channel, this.event)
|
|
211
|
+
},
|
|
212
|
+
methods: {
|
|
213
|
+
doConfetti() {
|
|
214
|
+
// launch a few confetti from the left edge
|
|
215
|
+
Confetti({
|
|
216
|
+
particleCount: 150,
|
|
217
|
+
angle: 60,
|
|
218
|
+
spread: 55,
|
|
219
|
+
origin: { x: 0 },
|
|
220
|
+
})
|
|
221
|
+
// and launch a few from the right edge
|
|
222
|
+
Confetti({
|
|
223
|
+
particleCount: 150,
|
|
224
|
+
angle: 120,
|
|
225
|
+
spread: 55,
|
|
226
|
+
origin: { x: 1 },
|
|
227
|
+
})
|
|
228
|
+
},
|
|
229
|
+
async loadJobs() {
|
|
230
|
+
this.vendors = await Vendor.get()
|
|
231
|
+
|
|
232
|
+
this.orgIntegrations = await new Organization({
|
|
233
|
+
id: this.organization.id,
|
|
234
|
+
})
|
|
235
|
+
.integrations()
|
|
236
|
+
.include('vendor')
|
|
237
|
+
.get()
|
|
238
|
+
|
|
239
|
+
this.jobs = await new Organization({
|
|
240
|
+
id: this.organization.id,
|
|
241
|
+
})
|
|
242
|
+
.integrationJobs()
|
|
243
|
+
.orderBy('-created_at')
|
|
244
|
+
.limit(10)
|
|
245
|
+
.get()
|
|
246
|
+
},
|
|
247
|
+
connect(channel, event) {
|
|
248
|
+
this.socket.connector.pusher.connection.bind(
|
|
249
|
+
'connected',
|
|
250
|
+
function (socket) {
|
|
251
|
+
// Connected to socket id: socket.socket_id
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
this.socket.connector.pusher.connection.bind(
|
|
255
|
+
'disconnected',
|
|
256
|
+
function () {
|
|
257
|
+
// disconnected
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
// `Connecting to channel '${channel}' for event: '${event}`
|
|
262
|
+
this.socket.private(channel).listen(event, (eventData) => {
|
|
263
|
+
this.onSocketJob(eventData)
|
|
264
|
+
})
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
onSocketJob(data) {
|
|
268
|
+
if (_.isEmpty(data.integrationJob)) {
|
|
269
|
+
console.error('onSocketJob Missing data', data)
|
|
270
|
+
return false
|
|
271
|
+
}
|
|
272
|
+
let found = false
|
|
273
|
+
|
|
274
|
+
for (const job of this.jobs) {
|
|
275
|
+
if (job.id === data.integrationJob.id) {
|
|
276
|
+
// If we found the job and it isn't completed
|
|
277
|
+
// We check our local status state because if a socket message is "late" we don't want to un-complete a job by accident
|
|
278
|
+
if (job.status !== 'completed') {
|
|
279
|
+
job.status = data.integrationJob.status
|
|
280
|
+
job.progress = data.integrationJob.progress
|
|
281
|
+
job.metadata = data.integrationJob.metadata
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (job.status === 'completed') {
|
|
285
|
+
this.doConfetti()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
found = true
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (!found) {
|
|
292
|
+
const newJob = data.integrationJob
|
|
293
|
+
// Push to the top
|
|
294
|
+
this.jobs.unshift(newJob)
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
</script>
|
|
300
|
+
|
|
301
|
+
<style scoped>
|
|
302
|
+
.col-progress {
|
|
303
|
+
min-width: 200px;
|
|
304
|
+
}
|
|
305
|
+
.col-details {
|
|
306
|
+
max-width: 100px;
|
|
307
|
+
}
|
|
308
|
+
</style>
|