Feature: Slack - receive messages, create threads, send replies (#974)
Co-authored-by: Pranav Raj S <pranav@thoughtwoot.com>
This commit is contained in:
@@ -10,6 +10,10 @@ class ApiClient {
|
||||
}
|
||||
|
||||
get url() {
|
||||
return `${this.baseUrl()}/${this.resource}`;
|
||||
}
|
||||
|
||||
baseUrl() {
|
||||
let url = this.apiVersion;
|
||||
if (this.options.accountScoped) {
|
||||
const isInsideAccountScopedURLs = window.location.pathname.includes(
|
||||
@@ -21,7 +25,8 @@ class ApiClient {
|
||||
url = `${url}/accounts/${accountId}`;
|
||||
}
|
||||
}
|
||||
return `${url}/${this.resource}`;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
get() {
|
||||
|
||||
21
app/javascript/dashboard/api/integrations.js
Normal file
21
app/javascript/dashboard/api/integrations.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* global axios */
|
||||
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class IntegrationsAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('integrations/apps', { accountScoped: true });
|
||||
}
|
||||
|
||||
connectSlack(code) {
|
||||
return axios.post(`${this.baseUrl()}/integrations/slack`, {
|
||||
code: code,
|
||||
});
|
||||
}
|
||||
|
||||
delete(integrationId) {
|
||||
return axios.delete(`${this.baseUrl()}/integrations/${integrationId}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new IntegrationsAPI();
|
||||
@@ -1,64 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path style="fill:#4D4D4D;" d="M188.287,512c-41.473,0-75.213-33.74-75.213-75.213V246.75c0-4.142,3.358-7.5,7.5-7.5
|
||||
s7.5,3.358,7.5,7.5v190.037c0,33.202,27.011,60.213,60.213,60.213c16.082,0,31.204-6.266,42.582-17.644
|
||||
c11.37-11.37,17.631-26.488,17.631-42.569V75.213C248.5,33.74,282.24,0,323.713,0c20.088,0,38.978,7.826,53.189,22.037
|
||||
c14.203,14.202,22.024,33.087,22.024,53.176V256c0,4.142-3.358,7.5-7.5,7.5s-7.5-3.358-7.5-7.5V75.213
|
||||
c0-16.082-6.261-31.2-17.63-42.569C354.918,21.266,339.794,15,323.713,15C290.511,15,263.5,42.011,263.5,75.213v361.574
|
||||
c0,20.088-7.822,38.973-22.024,53.176C227.265,504.174,208.376,512,188.287,512z"/>
|
||||
<g>
|
||||
<rect x="113.07" y="246.75" style="fill:#3B3B3B;" width="15" height="26.875"/>
|
||||
<rect x="383.93" y="235.31" style="fill:#3B3B3B;" width="15" height="26.875"/>
|
||||
</g>
|
||||
<rect x="361.9" y="385" style="fill:#CCCCCC;" width="57.983" height="39.944"/>
|
||||
<rect x="361.9" y="385" style="fill:#ADADAD;" width="57.983" height="22.19"/>
|
||||
<path style="fill:#A6E2E3;" d="M432.802,298.678v86.977c0,3.616-2.932,6.548-6.548,6.548h-70.721c-3.617,0-6.548-2.932-6.548-6.548
|
||||
v-87.746c0-23.439,19.239-42.39,42.803-41.899C414.709,256.486,432.802,275.751,432.802,298.678z"/>
|
||||
<rect x="92.11" y="87.06" style="fill:#CCCCCC;" width="57.983" height="36.28"/>
|
||||
<rect x="92.11" y="105.43" style="fill:#ADADAD;" width="57.983" height="17.907"/>
|
||||
<path style="fill:#FFA638;" d="M163.015,126.345v86.977c0,22.927-18.093,42.191-41.015,42.668
|
||||
c-23.564,0.49-42.803-18.461-42.803-41.899v-87.746c0-3.616,2.932-6.548,6.548-6.548l0,0h70.721l0,0
|
||||
C160.083,119.797,163.015,122.729,163.015,126.345z"/>
|
||||
<path style="fill:#7CCBCC;" d="M391.787,256.009c-5.066-0.105-9.93,0.693-14.447,2.236c0.396-0.081,0.781-0.166,1.142-0.257
|
||||
c2.982-0.755,5.201-0.896,7.513-0.85c18.954,0.395,34.375,16.494,34.375,35.888v86.981c0,3.614-2.93,6.544-6.544,6.544H355.53
|
||||
c-3.614,0-6.544-2.93-6.544-6.544l0,0v5.648c0,3.616,2.932,6.548,6.548,6.548h70.721c3.617,0,6.548-2.932,6.548-6.548v-86.977
|
||||
C432.802,275.751,414.709,256.486,391.787,256.009z"/>
|
||||
<path style="fill:#EB7100;" d="M79.527,217.153l-0.23-0.322c0.081,1.261,0.209,2.509,0.399,3.737
|
||||
C79.586,219.444,79.527,218.305,79.527,217.153z"/>
|
||||
<path style="fill:#ED8300;" d="M156.467,119.797h-11.188c2.613,0.858,4.502,3.314,4.502,6.215v90.372
|
||||
c0,19.395-15.42,35.494-34.375,35.888c-0.251,0.005-0.503,0.008-0.753,0.008c-9.651,0-18.405-3.914-24.76-10.236
|
||||
c7.856,8.766,19.342,14.212,32.106,13.947c22.922-0.477,41.015-19.741,41.015-42.668v-86.977
|
||||
C163.015,122.728,160.083,119.797,156.467,119.797z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -3,16 +3,17 @@
|
||||
background: $color-white;
|
||||
border: 1px solid $color-border;
|
||||
border-radius: $space-smaller;
|
||||
margin-bottom: $space-normal;
|
||||
padding: $space-normal;
|
||||
|
||||
.integration--image {
|
||||
display: flex;
|
||||
margin-right: $space-normal;
|
||||
width: 8rem;
|
||||
width: 10rem;
|
||||
|
||||
img {
|
||||
max-width: 8rem;
|
||||
padding: $space-small;
|
||||
max-width: 100%;
|
||||
padding: $space-medium;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,6 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
daysLeft: 'getTrialLeft',
|
||||
globalConfig: 'globalConfig/get',
|
||||
inboxes: 'inboxes/getInboxes',
|
||||
accountId: 'getCurrentAccountId',
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
<template>
|
||||
<span class="back-button ion-ios-arrow-left" @click.capture="goBack">Back</span>
|
||||
<span class="back-button ion-ios-arrow-left" @click.capture="goBack">
|
||||
{{ $t('GENERAL_SETTINGS.BACK') }}
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
import router from '../../routes/index';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
backUrl: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
router.go(-1);
|
||||
if (this.backUrl !== '') {
|
||||
router.push(this.backUrl);
|
||||
} else {
|
||||
router.go(-1);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -52,6 +52,7 @@ export const getSidebarItems = accountId => ({
|
||||
'settings_inbox_finish',
|
||||
'settings_integrations',
|
||||
'settings_integrations_webhook',
|
||||
'settings_integrations_integration',
|
||||
'general_settings',
|
||||
'general_settings_index',
|
||||
],
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"GENERAL_SETTINGS": {
|
||||
"TITLE": "Account settings",
|
||||
"SUBMIT": "Update settings",
|
||||
"BACK": "Back",
|
||||
"UPDATE": {
|
||||
"ERROR": "Could not update settings, try again!",
|
||||
"SUCCESS": "Successfully updated account settings"
|
||||
|
||||
@@ -49,6 +49,15 @@
|
||||
"NO": "No, Keep it"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Delete",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Integration deleted successfully"
|
||||
}
|
||||
},
|
||||
"CONNECT": {
|
||||
"BUTTON_TEXT": "Connect"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="settings-header">
|
||||
<h1 class="page-title">
|
||||
<woot-sidemenu-icon></woot-sidemenu-icon>
|
||||
<back-button v-if="showBackButton"></back-button>
|
||||
<back-button v-if="showBackButton" :back-url="backUrl"></back-button>
|
||||
<i :class="iconClass"></i>
|
||||
<span>{{ headerTitle }}</span>
|
||||
</h1>
|
||||
@@ -45,6 +45,10 @@ export default {
|
||||
},
|
||||
showBackButton: { type: Boolean, default: false },
|
||||
showNewButton: { type: Boolean, default: false },
|
||||
backUrl: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:header-title="$t(headerTitle)"
|
||||
:button-text="$t(headerButtonText)"
|
||||
:show-back-button="showBackButton"
|
||||
:back-url="backUrl"
|
||||
:show-new-button="showNewButton"
|
||||
/>
|
||||
<keep-alive>
|
||||
@@ -34,6 +35,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
backUrl: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
|
||||
@@ -3,38 +3,19 @@
|
||||
<div class="row">
|
||||
<div class="small-8 columns integrations-wrap">
|
||||
<div class="row integrations">
|
||||
<div class="small-12 columns integration">
|
||||
<div class="row">
|
||||
<div class="integration--image">
|
||||
<img src="~dashboard/assets/images/integrations/cable.svg" />
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3 class="integration--title">
|
||||
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.TITLE') }}
|
||||
</h3>
|
||||
<p class="integration--description">
|
||||
{{
|
||||
useInstallationName(
|
||||
$t('INTEGRATION_SETTINGS.WEBHOOK.INTEGRATION_TXT'),
|
||||
globalConfig.installationName
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="small-2 column button-wrap">
|
||||
<router-link
|
||||
:to="
|
||||
frontendURL(
|
||||
`accounts/${accountId}/settings/integrations/webhook`
|
||||
)
|
||||
"
|
||||
>
|
||||
<button class="button success nice">
|
||||
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.CONFIGURE') }}
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in integrationsList"
|
||||
:key="item.id"
|
||||
class="small-12 columns integration"
|
||||
>
|
||||
<integration
|
||||
:integration-id="item.id"
|
||||
:integration-logo="item.logo"
|
||||
:integration-name="item.name"
|
||||
:integration-description="item.description"
|
||||
:integration-enabled="item.enabled"
|
||||
:integration-action="item.action"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,20 +24,19 @@
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||
import Integration from './Integration';
|
||||
|
||||
export default {
|
||||
mixins: [globalConfigMixin],
|
||||
components: {
|
||||
Integration,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
globalConfig: 'globalConfig/get',
|
||||
accountId: 'getCurrentAccountId',
|
||||
integrationsList: 'integrations/getIntegrations',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
frontendURL,
|
||||
mounted() {
|
||||
this.$store.dispatch('integrations/get');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="integration--image">
|
||||
<img :src="'/assets/dashboard/integrations/' + integrationLogo" />
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3 class="integration--title">
|
||||
{{ integrationName }}
|
||||
</h3>
|
||||
<p class="integration--description">
|
||||
{{ integrationDescription }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="small-2 column button-wrap">
|
||||
<router-link
|
||||
:to="
|
||||
frontendURL(
|
||||
`accounts/${accountId}/settings/integrations/` + integrationId
|
||||
)
|
||||
"
|
||||
>
|
||||
<div v-if="integrationEnabled">
|
||||
<div v-if="integrationAction === 'disconnect'">
|
||||
<div @click="openDeletePopup()">
|
||||
<woot-submit-button
|
||||
:button-text="
|
||||
$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.BUTTON_TEXT')
|
||||
"
|
||||
icon-class="ion-close-circled"
|
||||
button-class="nice alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button class="button nice">
|
||||
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.CONFIGURE') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
<div v-if="!integrationEnabled">
|
||||
<a :href="integrationAction" class="button success nice">
|
||||
{{ $t('INTEGRATION_SETTINGS.CONNECT.BUTTON_TEXT') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<woot-delete-modal
|
||||
:show.sync="showDeleteConfirmationPopup"
|
||||
:on-close="closeDeletePopup"
|
||||
:on-confirm="confirmDeletion"
|
||||
:title="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.TITLE')"
|
||||
:message="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.MESSAGE')"
|
||||
:confirm-text="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.YES')"
|
||||
:reject-text="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.NO')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
props: [
|
||||
'integrationId',
|
||||
'integrationLogo',
|
||||
'integrationName',
|
||||
'integrationDescription',
|
||||
'integrationEnabled',
|
||||
'integrationAction',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
showDeleteConfirmationPopup: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
frontendURL,
|
||||
openDeletePopup() {
|
||||
this.showDeleteConfirmationPopup = true;
|
||||
},
|
||||
closeDeletePopup() {
|
||||
this.showDeleteConfirmationPopup = false;
|
||||
},
|
||||
confirmDeletion() {
|
||||
this.closeDeletePopup();
|
||||
this.deleteIntegration(this.deleteIntegration);
|
||||
this.$router.push({ name: 'settings_integrations' });
|
||||
},
|
||||
async deleteIntegration() {
|
||||
try {
|
||||
await this.$store.dispatch(
|
||||
'integrations/deleteIntegration',
|
||||
this.integrationId
|
||||
);
|
||||
this.showAlert(
|
||||
this.$t('INTEGRATION_SETTINGS.DELETE.API.SUCCESS_MESSAGE')
|
||||
);
|
||||
} catch (error) {
|
||||
this.showAlert(
|
||||
this.$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.API.ERROR_MESSAGE')
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div class="column content-box">
|
||||
<div class="row">
|
||||
<div class="small-8 columns integrations-wrap">
|
||||
<div class="row integrations">
|
||||
<div v-if="integrationLoaded" class="small-12 columns integration">
|
||||
<integration
|
||||
:integration-id="integration.id"
|
||||
:integration-logo="integration.logo"
|
||||
:integration-name="integration.name"
|
||||
:integration-description="integration.description"
|
||||
:integration-enabled="integration.enabled"
|
||||
:integration-action="integrationAction()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||
import Integration from './Integration';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Integration,
|
||||
},
|
||||
mixins: [globalConfigMixin],
|
||||
props: ['integrationId', 'code'],
|
||||
data() {
|
||||
return {
|
||||
integrationLoaded: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
integration() {
|
||||
return this.$store.getters['integrations/getIntegration'](
|
||||
this.integrationId
|
||||
);
|
||||
},
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
globalConfig: 'globalConfig/get',
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.intializeSlackIntegration();
|
||||
},
|
||||
methods: {
|
||||
integrationAction() {
|
||||
if (this.integration.enabled) {
|
||||
return 'disconnect';
|
||||
}
|
||||
return this.integration.action;
|
||||
},
|
||||
async intializeSlackIntegration() {
|
||||
await this.$store.dispatch('integrations/get', this.integrationId);
|
||||
if (this.code) {
|
||||
await this.$store.dispatch('integrations/connectSlack', this.code);
|
||||
// we are clearing code from the path as subsequent request would throw error
|
||||
this.$router.replace(this.$route.path);
|
||||
}
|
||||
this.integrationLoaded = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -82,16 +82,16 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/* global bus */
|
||||
import { mapGetters } from 'vuex';
|
||||
import NewWebhook from './New';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NewWebhook,
|
||||
},
|
||||
mixins: [globalConfigMixin],
|
||||
mixins: [alertMixin, globalConfigMixin],
|
||||
data() {
|
||||
return {
|
||||
loading: {},
|
||||
@@ -111,9 +111,6 @@ export default {
|
||||
this.$store.dispatch('webhooks/get');
|
||||
},
|
||||
methods: {
|
||||
showAlert(message) {
|
||||
bus.$emit('newToastMessage', message);
|
||||
},
|
||||
openAddPopup() {
|
||||
this.showAddPopup = true;
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Index from './Index';
|
||||
import SettingsContent from '../Wrapper';
|
||||
import Webhook from './Webhook';
|
||||
import ShowIntegration from './ShowIntegration';
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
export default {
|
||||
@@ -10,10 +11,15 @@ export default {
|
||||
component: SettingsContent,
|
||||
props: params => {
|
||||
const showBackButton = params.name !== 'settings_integrations';
|
||||
const backUrl =
|
||||
params.name === 'settings_integrations_integration'
|
||||
? { name: 'settings_integrations' }
|
||||
: '';
|
||||
return {
|
||||
headerTitle: 'INTEGRATION_SETTINGS.HEADER',
|
||||
icon: 'ion-flash',
|
||||
showBackButton,
|
||||
backUrl,
|
||||
};
|
||||
},
|
||||
children: [
|
||||
@@ -29,6 +35,18 @@ export default {
|
||||
name: 'settings_integrations_webhook',
|
||||
roles: ['administrator'],
|
||||
},
|
||||
{
|
||||
path: ':integration_id',
|
||||
name: 'settings_integrations_integration',
|
||||
component: ShowIntegration,
|
||||
roles: ['administrator'],
|
||||
props: route => {
|
||||
return {
|
||||
integrationId: route.params.integration_id,
|
||||
code: route.query.code,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -17,6 +17,7 @@ import conversationTypingStatus from './modules/conversationTypingStatus';
|
||||
import globalConfig from 'shared/store/globalConfig';
|
||||
import inboxes from './modules/inboxes';
|
||||
import inboxMembers from './modules/inboxMembers';
|
||||
import integrations from './modules/integrations';
|
||||
import reports from './modules/reports';
|
||||
import userNotificationSettings from './modules/userNotificationSettings';
|
||||
import webhooks from './modules/webhooks';
|
||||
@@ -40,6 +41,7 @@ export default new Vuex.Store({
|
||||
globalConfig,
|
||||
inboxes,
|
||||
inboxMembers,
|
||||
integrations,
|
||||
reports,
|
||||
userNotificationSettings,
|
||||
webhooks,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint no-param-reassign: 0 */
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import Vue from 'vue';
|
||||
import * as types from '../mutation-types';
|
||||
import authAPI from '../../api/auth';
|
||||
@@ -50,21 +49,6 @@ export const getters = {
|
||||
getCurrentUser(_state) {
|
||||
return _state.currentUser;
|
||||
},
|
||||
|
||||
getSubscription(_state) {
|
||||
return _state.currentUser.subscription === undefined
|
||||
? null
|
||||
: _state.currentUser.subscription;
|
||||
},
|
||||
|
||||
getTrialLeft(_state) {
|
||||
const createdAt =
|
||||
_state.currentUser.subscription === undefined
|
||||
? moment()
|
||||
: _state.currentUser.subscription.expiry * 1000;
|
||||
const daysLeft = moment(createdAt).diff(moment(), 'days');
|
||||
return daysLeft < 0 ? 0 : daysLeft;
|
||||
},
|
||||
};
|
||||
|
||||
// actions
|
||||
|
||||
83
app/javascript/dashboard/store/modules/integrations.js
Normal file
83
app/javascript/dashboard/store/modules/integrations.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/* eslint no-param-reassign: 0 */
|
||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||
import * as types from '../mutation-types';
|
||||
import IntegrationsAPI from '../../api/integrations';
|
||||
|
||||
const state = {
|
||||
records: [],
|
||||
uiFlags: {
|
||||
isFetching: false,
|
||||
isFetchingItem: false,
|
||||
isUpdating: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
getIntegrations($state) {
|
||||
return $state.records;
|
||||
},
|
||||
getIntegration: $state => integrationId => {
|
||||
const [integration] = $state.records.filter(
|
||||
record => record.id === integrationId
|
||||
);
|
||||
return integration || {};
|
||||
},
|
||||
getUIFlags($state) {
|
||||
return $state.uiFlags;
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
get: async ({ commit }) => {
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const response = await IntegrationsAPI.get();
|
||||
commit(types.default.SET_INTEGRATIONS, response.data.payload);
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isFetching: false });
|
||||
} catch (error) {
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
|
||||
connectSlack: async ({ commit }, code) => {
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isUpdating: true });
|
||||
try {
|
||||
const response = await IntegrationsAPI.connectSlack(code);
|
||||
commit(types.default.ADD_INTEGRATION, response.data);
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isUpdating: false });
|
||||
} catch (error) {
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isUpdating: false });
|
||||
}
|
||||
},
|
||||
|
||||
deleteIntegration: async ({ commit }, integrationId) => {
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isDeleting: true });
|
||||
try {
|
||||
await IntegrationsAPI.delete(integrationId);
|
||||
commit(types.default.DELETE_INTEGRATION, {
|
||||
id: integrationId,
|
||||
enabled: false,
|
||||
});
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isDeleting: false });
|
||||
} catch (error) {
|
||||
commit(types.default.SET_INTEGRATIONS_UI_FLAG, { isDeleting: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG]($state, uiFlag) {
|
||||
$state.uiFlags = { ...$state.uiFlags, ...uiFlag };
|
||||
},
|
||||
[types.default.SET_INTEGRATIONS]: MutationHelpers.set,
|
||||
[types.default.ADD_INTEGRATION]: MutationHelpers.updateAttributes,
|
||||
[types.default.DELETE_INTEGRATION]: MutationHelpers.updateAttributes,
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import axios from 'axios';
|
||||
import { actions } from '../../integrations';
|
||||
import * as types from '../../../mutation-types';
|
||||
import integrationsList from './fixtures';
|
||||
|
||||
const commit = jest.fn();
|
||||
global.axios = axios;
|
||||
jest.mock('axios');
|
||||
|
||||
describe('#actions', () => {
|
||||
describe('#get', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
axios.get.mockResolvedValue({ data: integrationsList });
|
||||
await actions.get({ commit });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isFetching: true }],
|
||||
[types.default.SET_INTEGRATIONS, integrationsList.payload],
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isFetching: false }],
|
||||
]);
|
||||
});
|
||||
it('sends correct actions if API is error', async () => {
|
||||
axios.get.mockRejectedValue({ message: 'Incorrect header' });
|
||||
await actions.get({ commit });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isFetching: true }],
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isFetching: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#connectSlack:', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
let data = { id: 'slack', enabled: true };
|
||||
axios.post.mockResolvedValue({ data: data });
|
||||
await actions.connectSlack({ commit });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isUpdating: true }],
|
||||
[types.default.ADD_INTEGRATION, data],
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isUpdating: false }],
|
||||
]);
|
||||
});
|
||||
it('sends correct actions if API is error', async () => {
|
||||
axios.post.mockRejectedValue({ message: 'Incorrect header' });
|
||||
await actions.connectSlack({ commit });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isUpdating: true }],
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isUpdating: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#deleteIntegration:', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
let data = { id: 'slack', enabled: false };
|
||||
axios.delete.mockResolvedValue({ data: data });
|
||||
await actions.deleteIntegration({ commit }, data.id);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isDeleting: true }],
|
||||
[types.default.DELETE_INTEGRATION, data],
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isDeleting: false }],
|
||||
]);
|
||||
});
|
||||
it('sends correct actions if API is error', async () => {
|
||||
axios.delete.mockRejectedValue({ message: 'Incorrect header' });
|
||||
await actions.deleteIntegration({ commit });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isDeleting: true }],
|
||||
[types.default.SET_INTEGRATIONS_UI_FLAG, { isDeleting: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
export default {
|
||||
payload: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'test1',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'test2',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { getters } from '../../integrations';
|
||||
|
||||
describe('#getters', () => {
|
||||
it('getIntegrations', () => {
|
||||
const state = {
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'test1',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'test2',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(getters.getIntegrations(state)).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
name: 'test1',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'test2',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('getUIFlags', () => {
|
||||
const state = {
|
||||
uiFlags: {
|
||||
isFetching: true,
|
||||
isFetchingItem: false,
|
||||
isUpdating: false,
|
||||
},
|
||||
};
|
||||
expect(getters.getUIFlags(state)).toEqual({
|
||||
isFetching: true,
|
||||
isFetchingItem: false,
|
||||
isUpdating: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as types from '../../../mutation-types';
|
||||
import { mutations } from '../../integrations';
|
||||
|
||||
describe('#mutations', () => {
|
||||
describe('#GET_INTEGRATIONS', () => {
|
||||
it('set integrations records', () => {
|
||||
const state = { records: [] };
|
||||
mutations[types.default.SET_INTEGRATIONS](state, [
|
||||
{
|
||||
id: 1,
|
||||
name: 'test1',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
expect(state.records).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
name: 'test1',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -67,6 +67,12 @@ export default {
|
||||
EDIT_CANNED: 'EDIT_CANNED',
|
||||
DELETE_CANNED: 'DELETE_CANNED',
|
||||
|
||||
// Integrations
|
||||
SET_INTEGRATIONS_UI_FLAG: 'SET_INTEGRATIONS_UI_FLAG',
|
||||
SET_INTEGRATIONS: 'SET_INTEGRATIONS',
|
||||
ADD_INTEGRATION: 'ADD_INTEGRATION',
|
||||
DELETE_INTEGRATION: 'DELETE_INTEGRATION',
|
||||
|
||||
// WebHook
|
||||
SET_WEBHOOK_UI_FLAG: 'SET_WEBHOOK_UI_FLAG',
|
||||
SET_WEBHOOK: 'SET_WEBHOOK',
|
||||
|
||||
@@ -25,6 +25,15 @@ export const update = (state, data) => {
|
||||
});
|
||||
};
|
||||
|
||||
/* when you don't want to overwrite the whole object */
|
||||
export const updateAttributes = (state, data) => {
|
||||
state.records.forEach((element, index) => {
|
||||
if (element.id === data.id) {
|
||||
Vue.set(state.records, index, { ...state.records[index], ...data });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const destroy = (state, id) => {
|
||||
state.records = state.records.filter(record => record.id !== id);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user