Merge branch 'feat/custom-attrs-automations' of github.com:chatwoot/chatwoot into feat/custom-attrs-automations
This commit is contained in:
commit
8cb54af92a
66 changed files with 420 additions and 182 deletions
|
@ -1,4 +1,5 @@
|
||||||
class Api::V1::Accounts::Kbase::CategoriesController < Api::V1::Accounts::Kbase::BaseController
|
class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :portal
|
||||||
before_action :fetch_category, except: [:index, :create]
|
before_action :fetch_category, except: [:index, :create]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -24,6 +25,10 @@ class Api::V1::Accounts::Kbase::CategoriesController < Api::V1::Accounts::Kbase:
|
||||||
@category = @portal.categories.find(params[:id])
|
@category = @portal.categories.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def portal
|
||||||
|
@portal ||= Current.account.portals.find_by(slug: params[:portal_id])
|
||||||
|
end
|
||||||
|
|
||||||
def category_params
|
def category_params
|
||||||
params.require(:category).permit(
|
params.require(:category).permit(
|
||||||
:name, :description, :position
|
:name, :description, :position
|
|
@ -5,7 +5,7 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
|
||||||
RESULTS_PER_PAGE = 25
|
RESULTS_PER_PAGE = 25
|
||||||
|
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :set_csat_survey_responses, only: [:index, :metrics]
|
before_action :set_csat_survey_responses, only: [:index, :metrics, :download]
|
||||||
before_action :set_current_page, only: [:index]
|
before_action :set_current_page, only: [:index]
|
||||||
before_action :set_current_page_surveys, only: [:index]
|
before_action :set_current_page_surveys, only: [:index]
|
||||||
before_action :set_total_sent_messages_count, only: [:metrics]
|
before_action :set_total_sent_messages_count, only: [:metrics]
|
||||||
|
@ -19,6 +19,12 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
|
||||||
@ratings_count = @csat_survey_responses.group(:rating).count
|
@ratings_count = @csat_survey_responses.group(:rating).count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def download
|
||||||
|
response.headers['Content-Type'] = 'text/csv'
|
||||||
|
response.headers['Content-Disposition'] = 'attachment; filename=csat_report.csv'
|
||||||
|
render layout: false, template: 'api/v1/accounts/csat_survey_responses/download.csv.erb', format: 'csv'
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_total_sent_messages_count
|
def set_total_sent_messages_count
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
class Api::V1::Accounts::Kbase::BaseController < Api::V1::Accounts::BaseController
|
|
||||||
before_action :portal
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def portal
|
|
||||||
@portal ||= Current.account.kbase_portals.find_by(slug: params[:portal_id])
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,14 +1,14 @@
|
||||||
class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::BaseController
|
class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
||||||
before_action :fetch_portal, except: [:index, :create]
|
before_action :fetch_portal, except: [:index, :create]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@portals = Current.account.kbase_portals
|
@portals = Current.account.portals
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@portal = Current.account.kbase_portals.create!(portal_params)
|
@portal = Current.account.portals.create!(portal_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@ -23,7 +23,7 @@ class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::BaseContr
|
||||||
private
|
private
|
||||||
|
|
||||||
def fetch_portal
|
def fetch_portal
|
||||||
@portal = Current.account.kbase_portals.find_by(slug: permitted_params[:id])
|
@portal = Current.account.portals.find_by(slug: permitted_params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
|
@ -32,7 +32,7 @@ class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::BaseContr
|
||||||
|
|
||||||
def portal_params
|
def portal_params
|
||||||
params.require(:portal).permit(
|
params.require(:portal).permit(
|
||||||
:account_id, :color, :custom_domain, :header_text, :homepage_link, :name, :page_title, :slug
|
:account_id, :color, :custom_domain, :header_text, :homepage_link, :name, :page_title, :slug, :archived
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -18,6 +18,17 @@ class CSATReportsAPI extends ApiClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
download({ from, to, user_ids } = {}) {
|
||||||
|
return axios.get(`${this.url}/download`, {
|
||||||
|
params: {
|
||||||
|
since: from,
|
||||||
|
until: to,
|
||||||
|
sort: '-created_at',
|
||||||
|
user_ids,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getMetrics({ from, to, user_ids } = {}) {
|
getMetrics({ from, to, user_ids } = {}) {
|
||||||
return axios.get(`${this.url}/metrics`, {
|
return axios.get(`${this.url}/metrics`, {
|
||||||
params: { since: from, until: to, user_ids },
|
params: { since: from, until: to, user_ids },
|
||||||
|
|
|
@ -33,5 +33,23 @@ describe('#Reports API', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('#download', () => {
|
||||||
|
csatReportsAPI.download({
|
||||||
|
from: 1622485800,
|
||||||
|
to: 1623695400,
|
||||||
|
user_ids: 1,
|
||||||
|
});
|
||||||
|
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||||
|
'/api/v1/csat_survey_responses/download',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
since: 1622485800,
|
||||||
|
until: 1623695400,
|
||||||
|
user_ids: 1,
|
||||||
|
sort: '-created_at',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||||
|
import format from 'date-fns/format';
|
||||||
|
|
||||||
export const downloadCsvFile = (fileName, fileContent) => {
|
export const downloadCsvFile = (fileName, fileContent) => {
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = fileName;
|
link.download = fileName;
|
||||||
link.href = `data:text/csv;charset=utf-8,` + encodeURI(fileContent);
|
link.href = `data:text/csv;charset=utf-8,` + encodeURI(fileContent);
|
||||||
link.click();
|
link.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateFileName = ({ type, to }) =>
|
||||||
|
`${type}-report-${format(fromUnixTime(to), 'dd-MM-yyyy')}.csv`;
|
|
@ -1,4 +1,4 @@
|
||||||
import { downloadCsvFile } from '../downloadCsvFile';
|
import { downloadCsvFile, generateFileName } from '../downloadHelper';
|
||||||
|
|
||||||
const fileName = 'test.csv';
|
const fileName = 'test.csv';
|
||||||
const fileData = `Agent name,Conversations count,Avg first response time (Minutes),Avg resolution time (Minutes)
|
const fileData = `Agent name,Conversations count,Avg first response time (Minutes),Avg resolution time (Minutes)
|
||||||
|
@ -19,3 +19,11 @@ describe('#downloadCsvFile', () => {
|
||||||
expect(link.click).toHaveBeenCalledTimes(1);
|
expect(link.click).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#generateFileName', () => {
|
||||||
|
it('should generate the correct file name', () => {
|
||||||
|
expect(generateFileName({ type: 'csat', to: 1652812199 })).toEqual(
|
||||||
|
'csat-report-17-05-2022.csv'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -398,8 +398,8 @@
|
||||||
"MESSENGER_SUB_HEAD": "ضع هذا الكود داخل وسم الـ body في موقعك",
|
"MESSENGER_SUB_HEAD": "ضع هذا الكود داخل وسم الـ body في موقعك",
|
||||||
"INBOX_AGENTS": "موظف الدعم",
|
"INBOX_AGENTS": "موظف الدعم",
|
||||||
"INBOX_AGENTS_SUB_TEXT": "إضافة أو إزالة موظفين من قناة التواصل هذه",
|
"INBOX_AGENTS_SUB_TEXT": "إضافة أو إزالة موظفين من قناة التواصل هذه",
|
||||||
"AGENT_ASSIGNMENT": "Conversation Assignment",
|
"AGENT_ASSIGNMENT": "إسناد المحادثات",
|
||||||
"AGENT_ASSIGNMENT_SUB_TEXT": "Update conversation assignment settings",
|
"AGENT_ASSIGNMENT_SUB_TEXT": "تحديث إعدادات إسناد المحادثات",
|
||||||
"UPDATE": "تحديث",
|
"UPDATE": "تحديث",
|
||||||
"ENABLE_EMAIL_COLLECT_BOX": "تفعيل صندوق جمع البريد الإلكتروني",
|
"ENABLE_EMAIL_COLLECT_BOX": "تفعيل صندوق جمع البريد الإلكتروني",
|
||||||
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "تمكين أو تعطيل مربع جمع البريد الإلكتروني في محادثة جديدة",
|
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "تمكين أو تعطيل مربع جمع البريد الإلكتروني في محادثة جديدة",
|
||||||
|
|
|
@ -151,7 +151,7 @@
|
||||||
},
|
},
|
||||||
"SIDEBAR": {
|
"SIDEBAR": {
|
||||||
"CURRENTLY_VIEWING_ACCOUNT": "مشاهدة حاليا:",
|
"CURRENTLY_VIEWING_ACCOUNT": "مشاهدة حاليا:",
|
||||||
"SWITCH": "Switch",
|
"SWITCH": "تبديل",
|
||||||
"CONVERSATIONS": "المحادثات",
|
"CONVERSATIONS": "المحادثات",
|
||||||
"ALL_CONVERSATIONS": "كل المحادثات",
|
"ALL_CONVERSATIONS": "كل المحادثات",
|
||||||
"MENTIONED_CONVERSATIONS": "الإشارات",
|
"MENTIONED_CONVERSATIONS": "الإشارات",
|
||||||
|
|
|
@ -354,6 +354,7 @@
|
||||||
"CSAT_REPORTS": {
|
"CSAT_REPORTS": {
|
||||||
"HEADER": "CSAT Reports",
|
"HEADER": "CSAT Reports",
|
||||||
"NO_RECORDS": "There are no CSAT survey responses available.",
|
"NO_RECORDS": "There are no CSAT survey responses available.",
|
||||||
|
"DOWNLOAD": "Download CSAT Reports",
|
||||||
"FILTERS": {
|
"FILTERS": {
|
||||||
"AGENTS": {
|
"AGENTS": {
|
||||||
"PLACEHOLDER": "Choose Agents"
|
"PLACEHOLDER": "Choose Agents"
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
</span>
|
</span>
|
||||||
<contact-info
|
<contact-info
|
||||||
:show-avatar="showAvatar"
|
:show-avatar="showAvatar"
|
||||||
show-new-message
|
|
||||||
:contact="contact"
|
:contact="contact"
|
||||||
@panel-close="onClose"
|
@panel-close="onClose"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -65,7 +65,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="contact-actions">
|
<div class="contact-actions">
|
||||||
<woot-button
|
<woot-button
|
||||||
v-if="showNewMessage"
|
|
||||||
v-tooltip="$t('CONTACT_PANEL.NEW_MESSAGE')"
|
v-tooltip="$t('CONTACT_PANEL.NEW_MESSAGE')"
|
||||||
title="$t('CONTACT_PANEL.NEW_MESSAGE')"
|
title="$t('CONTACT_PANEL.NEW_MESSAGE')"
|
||||||
class="new-message"
|
class="new-message"
|
||||||
|
@ -172,10 +171,6 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
showNewMessage: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
showAvatar: {
|
showAvatar: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
|
|
@ -52,6 +52,10 @@ export default {
|
||||||
},
|
},
|
||||||
async onSubmit(contactItem) {
|
async onSubmit(contactItem) {
|
||||||
await this.$store.dispatch('contacts/update', contactItem);
|
await this.$store.dispatch('contacts/update', contactItem);
|
||||||
|
await this.$store.dispatch(
|
||||||
|
'contacts/fetchContactableInbox',
|
||||||
|
this.contact.id
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,9 +3,18 @@
|
||||||
<report-filter-selector
|
<report-filter-selector
|
||||||
agents-filter
|
agents-filter
|
||||||
:agents-filter-items-list="agentList"
|
:agents-filter-items-list="agentList"
|
||||||
|
:show-business-hours-switch="false"
|
||||||
@date-range-change="onDateRangeChange"
|
@date-range-change="onDateRangeChange"
|
||||||
@agents-filter-change="onAgentsFilterChange"
|
@agents-filter-change="onAgentsFilterChange"
|
||||||
/>
|
/>
|
||||||
|
<woot-button
|
||||||
|
color-scheme="success"
|
||||||
|
class-names="button--fixed-right-top"
|
||||||
|
icon="arrow-download"
|
||||||
|
@click="downloadReports"
|
||||||
|
>
|
||||||
|
{{ $t('CSAT_REPORTS.DOWNLOAD') }}
|
||||||
|
</woot-button>
|
||||||
<csat-metrics />
|
<csat-metrics />
|
||||||
<csat-table :page-index="pageIndex" @page-change="onPageNumberChange" />
|
<csat-table :page-index="pageIndex" @page-change="onPageNumberChange" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,6 +24,7 @@ import CsatMetrics from './components/CsatMetrics';
|
||||||
import CsatTable from './components/CsatTable';
|
import CsatTable from './components/CsatTable';
|
||||||
import ReportFilterSelector from './components/FilterSelector';
|
import ReportFilterSelector from './components/FilterSelector';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import { generateFileName } from '../../../../helper/downloadHelper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CsatResponses',
|
name: 'CsatResponses',
|
||||||
|
@ -24,7 +34,7 @@ export default {
|
||||||
ReportFilterSelector,
|
ReportFilterSelector,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { pageIndex: 1, from: 0, to: 0, user_ids: [] };
|
return { pageIndex: 1, from: 0, to: 0, userIds: [] };
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
@ -39,7 +49,7 @@ export default {
|
||||||
this.$store.dispatch('csat/getMetrics', {
|
this.$store.dispatch('csat/getMetrics', {
|
||||||
from: this.from,
|
from: this.from,
|
||||||
to: this.to,
|
to: this.to,
|
||||||
user_ids: this.user_ids,
|
user_ids: this.userIds,
|
||||||
});
|
});
|
||||||
this.getResponses();
|
this.getResponses();
|
||||||
},
|
},
|
||||||
|
@ -48,7 +58,7 @@ export default {
|
||||||
page: this.pageIndex,
|
page: this.pageIndex,
|
||||||
from: this.from,
|
from: this.from,
|
||||||
to: this.to,
|
to: this.to,
|
||||||
user_ids: this.user_ids,
|
user_ids: this.userIds,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPageNumberChange(pageIndex) {
|
onPageNumberChange(pageIndex) {
|
||||||
|
@ -61,9 +71,18 @@ export default {
|
||||||
this.getAllData();
|
this.getAllData();
|
||||||
},
|
},
|
||||||
onAgentsFilterChange(agents) {
|
onAgentsFilterChange(agents) {
|
||||||
this.user_ids = agents.map(el => el.id);
|
this.userIds = agents.map(el => el.id);
|
||||||
this.getAllData();
|
this.getAllData();
|
||||||
},
|
},
|
||||||
|
downloadReports() {
|
||||||
|
const type = 'csat';
|
||||||
|
this.$store.dispatch('csat/downloadCSATReports', {
|
||||||
|
from: this.from,
|
||||||
|
to: this.to,
|
||||||
|
user_ids: this.userIds,
|
||||||
|
fileName: generateFileName({ type, to: this.to }),
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -61,7 +61,10 @@
|
||||||
@input="handleAgentsFilterSelection"
|
@input="handleAgentsFilterSelection"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-12 medium-3 business-hours">
|
<div
|
||||||
|
v-if="showBusinessHoursSwitch"
|
||||||
|
class="small-12 medium-3 business-hours"
|
||||||
|
>
|
||||||
<span class="business-hours-text margin-right-small">
|
<span class="business-hours-text margin-right-small">
|
||||||
{{ $t('REPORT.BUSINESS_HOURS') }}
|
{{ $t('REPORT.BUSINESS_HOURS') }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -105,6 +108,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
showBusinessHoursSwitch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -61,6 +61,7 @@ import format from 'date-fns/format';
|
||||||
import { GROUP_BY_FILTER, METRIC_CHART } from '../constants';
|
import { GROUP_BY_FILTER, METRIC_CHART } from '../constants';
|
||||||
import reportMixin from '../../../../../mixins/reportMixin';
|
import reportMixin from '../../../../../mixins/reportMixin';
|
||||||
import { formatTime } from '@chatwoot/utils';
|
import { formatTime } from '@chatwoot/utils';
|
||||||
|
import { generateFileName } from '../../../../../helper/downloadHelper';
|
||||||
|
|
||||||
const REPORTS_KEYS = {
|
const REPORTS_KEYS = {
|
||||||
CONVERSATIONS: 'conversations_count',
|
CONVERSATIONS: 'conversations_count',
|
||||||
|
@ -250,26 +251,17 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
downloadReports() {
|
downloadReports() {
|
||||||
const { from, to } = this;
|
const { from, to, type } = this;
|
||||||
const fileName = `${this.type}-report-${format(
|
const dispatchMethods = {
|
||||||
fromUnixTime(to),
|
agent: 'downloadAgentReports',
|
||||||
'dd-MM-yyyy'
|
label: 'downloadLabelReports',
|
||||||
)}.csv`;
|
inbox: 'downloadInboxReports',
|
||||||
switch (this.type) {
|
team: 'downloadTeamReports',
|
||||||
case 'agent':
|
};
|
||||||
this.$store.dispatch('downloadAgentReports', { from, to, fileName });
|
if (dispatchMethods[type]) {
|
||||||
break;
|
const fileName = generateFileName({ type, to });
|
||||||
case 'label':
|
const params = { from, to, fileName };
|
||||||
this.$store.dispatch('downloadLabelReports', { from, to, fileName });
|
this.$store.dispatch(dispatchMethods[type], params);
|
||||||
break;
|
|
||||||
case 'inbox':
|
|
||||||
this.$store.dispatch('downloadInboxReports', { from, to, fileName });
|
|
||||||
break;
|
|
||||||
case 'team':
|
|
||||||
this.$store.dispatch('downloadTeamReports', { from, to, fileName });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
changeSelection(index) {
|
changeSelection(index) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||||
import types from '../mutation-types';
|
import types from '../mutation-types';
|
||||||
import CSATReports from '../../api/csatReports';
|
import CSATReports from '../../api/csatReports';
|
||||||
|
import { downloadCsvFile } from '../../helper/downloadHelper';
|
||||||
|
|
||||||
const computeDistribution = (value, total) =>
|
const computeDistribution = (value, total) =>
|
||||||
((value * 100) / total).toFixed(2);
|
((value * 100) / total).toFixed(2);
|
||||||
|
@ -107,6 +108,11 @@ export const actions = {
|
||||||
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetchingMetrics: false });
|
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetchingMetrics: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
downloadCSATReports(_, params) {
|
||||||
|
return CSATReports.download(params).then(response => {
|
||||||
|
downloadCsvFile(params.fileName, response.data);
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import * as types from '../mutation-types';
|
||||||
import Report from '../../api/reports';
|
import Report from '../../api/reports';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
import { downloadCsvFile } from '../../helper/downloadCsvFile';
|
import { downloadCsvFile } from '../../helper/downloadHelper';
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
fetchingStatus: false,
|
fetchingStatus: false,
|
||||||
|
|
|
@ -39,6 +39,7 @@ import {
|
||||||
} from '../dashboard/helper/scriptHelpers';
|
} from '../dashboard/helper/scriptHelpers';
|
||||||
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon';
|
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon';
|
||||||
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
||||||
|
import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer';
|
||||||
|
|
||||||
Vue.config.env = process.env;
|
Vue.config.env = process.env;
|
||||||
|
|
||||||
|
@ -55,7 +56,8 @@ if (window.analyticsConfig) {
|
||||||
api_host: window.analyticsConfig.host,
|
api_host: window.analyticsConfig.host,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Vue.use(VueDOMPurifyHTML);
|
|
||||||
|
Vue.use(VueDOMPurifyHTML, domPurifyConfig);
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
Vue.use(VueI18n);
|
Vue.use(VueI18n);
|
||||||
Vue.use(WootUiKit);
|
Vue.use(WootUiKit);
|
||||||
|
|
|
@ -9,9 +9,10 @@ import ActionCableConnector from '../widget/helpers/actionCable';
|
||||||
import i18n from '../widget/i18n';
|
import i18n from '../widget/i18n';
|
||||||
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
|
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
|
||||||
import router from '../widget/router';
|
import router from '../widget/router';
|
||||||
|
import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer';
|
||||||
Vue.use(VueI18n);
|
Vue.use(VueI18n);
|
||||||
Vue.use(Vuelidate);
|
Vue.use(Vuelidate);
|
||||||
Vue.use(VueDOMPurifyHTML);
|
Vue.use(VueDOMPurifyHTML, domPurifyConfig);
|
||||||
|
|
||||||
const i18nConfig = new VueI18n({
|
const i18nConfig = new VueI18n({
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
|
|
|
@ -6,3 +6,15 @@ export const escapeHtml = (unsafe = '') => {
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const afterSanitizeAttributes = currentNode => {
|
||||||
|
if ('target' in currentNode) {
|
||||||
|
currentNode.setAttribute('target', '_blank');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const domPurifyConfig = {
|
||||||
|
hooks: {
|
||||||
|
afterSanitizeAttributes,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import { escapeHtml } from './HTMLSanitizer';
|
import { escapeHtml, afterSanitizeAttributes } from './HTMLSanitizer';
|
||||||
|
|
||||||
const TWITTER_USERNAME_REGEX = /(^|[^@\w])@(\w{1,15})\b/g;
|
const TWITTER_USERNAME_REGEX = /(^|[^@\w])@(\w{1,15})\b/g;
|
||||||
const TWITTER_USERNAME_REPLACEMENT =
|
const TWITTER_USERNAME_REPLACEMENT =
|
||||||
|
@ -48,9 +48,7 @@ class MessageFormatter {
|
||||||
const markedDownOutput = marked(withHash);
|
const markedDownOutput = marked(withHash);
|
||||||
return markedDownOutput;
|
return markedDownOutput;
|
||||||
}
|
}
|
||||||
DOMPurify.addHook('afterSanitizeAttributes', node => {
|
DOMPurify.addHook('afterSanitizeAttributes', afterSanitizeAttributes);
|
||||||
if ('target' in node) node.setAttribute('target', '_blank');
|
|
||||||
});
|
|
||||||
return DOMPurify.sanitize(
|
return DOMPurify.sanitize(
|
||||||
marked(this.message, { breaks: true, gfm: true })
|
marked(this.message, { breaks: true, gfm: true })
|
||||||
);
|
);
|
||||||
|
|
|
@ -51,9 +51,9 @@ class Account < ApplicationRecord
|
||||||
has_many :facebook_pages, dependent: :destroy_async, class_name: '::Channel::FacebookPage'
|
has_many :facebook_pages, dependent: :destroy_async, class_name: '::Channel::FacebookPage'
|
||||||
has_many :hooks, dependent: :destroy_async, class_name: 'Integrations::Hook'
|
has_many :hooks, dependent: :destroy_async, class_name: 'Integrations::Hook'
|
||||||
has_many :inboxes, dependent: :destroy_async
|
has_many :inboxes, dependent: :destroy_async
|
||||||
has_many :kbase_articles, dependent: :destroy_async, class_name: '::Kbase::Article'
|
has_many :articles, dependent: :destroy_async, class_name: '::Article'
|
||||||
has_many :kbase_categories, dependent: :destroy_async, class_name: '::Kbase::Category'
|
has_many :categories, dependent: :destroy_async, class_name: '::Category'
|
||||||
has_many :kbase_portals, dependent: :destroy_async, class_name: '::Kbase::Portal'
|
has_many :portals, dependent: :destroy_async, class_name: '::Portal'
|
||||||
has_many :labels, dependent: :destroy_async
|
has_many :labels, dependent: :destroy_async
|
||||||
has_many :line_channels, dependent: :destroy_async, class_name: '::Channel::Line'
|
has_many :line_channels, dependent: :destroy_async, class_name: '::Channel::Line'
|
||||||
has_many :mentions, dependent: :destroy_async
|
has_many :mentions, dependent: :destroy_async
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
# Table name: kbase_articles
|
# Table name: articles
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# content :text
|
# content :text
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
# folder_id :integer
|
# folder_id :integer
|
||||||
# portal_id :integer not null
|
# portal_id :integer not null
|
||||||
#
|
#
|
||||||
class Kbase::Article < ApplicationRecord
|
class Article < ApplicationRecord
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :category
|
belongs_to :category
|
||||||
belongs_to :portal
|
belongs_to :portal
|
|
@ -1,6 +1,6 @@
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
# Table name: kbase_categories
|
# Table name: categories
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# description :text
|
# description :text
|
||||||
|
@ -14,9 +14,10 @@
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_kbase_categories_on_locale_and_account_id (locale,account_id)
|
# index_categories_on_locale (locale)
|
||||||
|
# index_categories_on_locale_and_account_id (locale,account_id)
|
||||||
#
|
#
|
||||||
class Kbase::Category < ApplicationRecord
|
class Category < ApplicationRecord
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :portal
|
belongs_to :portal
|
||||||
has_many :folders, dependent: :destroy_async
|
has_many :folders, dependent: :destroy_async
|
|
@ -1,6 +1,6 @@
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
# Table name: kbase_folders
|
# Table name: folders
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# name :string
|
# name :string
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
# account_id :integer not null
|
# account_id :integer not null
|
||||||
# category_id :integer not null
|
# category_id :integer not null
|
||||||
#
|
#
|
||||||
class Kbase::Folder < ApplicationRecord
|
class Folder < ApplicationRecord
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :category
|
belongs_to :category
|
||||||
has_many :articles, dependent: :nullify
|
has_many :articles, dependent: :nullify
|
|
@ -1,8 +1,9 @@
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
# Table name: kbase_portals
|
# Table name: portals
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
|
# archived :boolean default(FALSE)
|
||||||
# color :string
|
# color :string
|
||||||
# config :jsonb
|
# config :jsonb
|
||||||
# custom_domain :string
|
# custom_domain :string
|
||||||
|
@ -17,13 +18,14 @@
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_kbase_portals_on_slug (slug) UNIQUE
|
# index_portals_on_slug (slug) UNIQUE
|
||||||
#
|
#
|
||||||
class Kbase::Portal < ApplicationRecord
|
class Portal < ApplicationRecord
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
has_many :categories, dependent: :destroy_async
|
has_many :categories, dependent: :destroy_async
|
||||||
has_many :folders, through: :categories
|
has_many :folders, through: :categories
|
||||||
has_many :articles, dependent: :destroy_async
|
has_many :articles, dependent: :destroy_async
|
||||||
|
has_many :users, through: :portals_members
|
||||||
|
|
||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
|
@ -90,6 +90,7 @@ class User < ApplicationRecord
|
||||||
has_many :notifications, dependent: :destroy_async
|
has_many :notifications, dependent: :destroy_async
|
||||||
has_many :team_members, dependent: :destroy_async
|
has_many :team_members, dependent: :destroy_async
|
||||||
has_many :teams, through: :team_members
|
has_many :teams, through: :team_members
|
||||||
|
has_many :portals, through: :portals_members
|
||||||
|
|
||||||
before_validation :set_password_and_uid, on: :create
|
before_validation :set_password_and_uid, on: :create
|
||||||
|
|
||||||
|
|
|
@ -6,4 +6,8 @@ class CsatSurveyResponsePolicy < ApplicationPolicy
|
||||||
def metrics?
|
def metrics?
|
||||||
@account_user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def download?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<%=
|
||||||
|
CSV.generate_line([
|
||||||
|
I18n.t('reports.csat.headers.agent_name'),
|
||||||
|
I18n.t('reports.csat.headers.rating'),
|
||||||
|
I18n.t('reports.csat.headers.feedback'),
|
||||||
|
I18n.t('reports.csat.headers.contact_name'),
|
||||||
|
I18n.t('reports.csat.headers.contact_email_address'),
|
||||||
|
I18n.t('reports.csat.headers.contact_phone_number'),
|
||||||
|
I18n.t('reports.csat.headers.link_to_the_conversation'),
|
||||||
|
I18n.t('reports.csat.headers.recorded_at')
|
||||||
|
])
|
||||||
|
-%>
|
||||||
|
<% @csat_survey_responses.each do |csat_response| %>
|
||||||
|
<% assigned_agent = csat_response.assigned_agent %>
|
||||||
|
<% contact = csat_response.contact %>
|
||||||
|
<% conversation = csat_response.conversation %>
|
||||||
|
<%=
|
||||||
|
CSV.generate_line([
|
||||||
|
assigned_agent ? "#{assigned_agent.name} (#{assigned_agent.email})" : nil,
|
||||||
|
csat_response.rating,
|
||||||
|
csat_response.feedback_message.present? ? csat_response.feedback_message : nil,
|
||||||
|
contact&.name.present? ? contact&.name: nil,
|
||||||
|
contact&.email.present? ? contact&.email: nil,
|
||||||
|
contact&.phone_number.present? ? contact&.phone_number: nil,
|
||||||
|
conversation ? app_account_conversation_url(account_id: Current.account.id, id: conversation.display_id): nil,
|
||||||
|
csat_response.created_at,
|
||||||
|
])
|
||||||
|
-%>
|
||||||
|
<% end %>
|
||||||
|
<%=
|
||||||
|
CSV.generate_line([
|
||||||
|
I18n.t(
|
||||||
|
'reports.period',
|
||||||
|
since: Date.strptime(params[:since], '%s'),
|
||||||
|
until: Date.strptime(params[:until], '%s')
|
||||||
|
)
|
||||||
|
])
|
||||||
|
-%>
|
|
@ -6,4 +6,5 @@ json.homepage_link portal.homepage_link
|
||||||
json.name portal.name
|
json.name portal.name
|
||||||
json.page_title portal.page_title
|
json.page_title portal.page_title
|
||||||
json.slug portal.slug
|
json.slug portal.slug
|
||||||
|
json.archived portal.archived
|
||||||
json.config portal.config
|
json.config portal.config
|
|
@ -1,5 +1,5 @@
|
||||||
shared: &shared
|
shared: &shared
|
||||||
version: '2.4.1'
|
version: '2.5.0'
|
||||||
|
|
||||||
development:
|
development:
|
||||||
<<: *shared
|
<<: *shared
|
||||||
|
|
|
@ -60,6 +60,16 @@ en:
|
||||||
avg_first_response_time: Avg first response time (Minutes)
|
avg_first_response_time: Avg first response time (Minutes)
|
||||||
avg_resolution_time: Avg resolution time (Minutes)
|
avg_resolution_time: Avg resolution time (Minutes)
|
||||||
default_group_by: day
|
default_group_by: day
|
||||||
|
csat:
|
||||||
|
headers:
|
||||||
|
contact_name: Contact Name
|
||||||
|
contact_email_address: Contact Email Address
|
||||||
|
contact_phone_number: Contact Phone Number
|
||||||
|
link_to_the_conversation: Link to the conversation
|
||||||
|
agent_name: Agent Name
|
||||||
|
rating: Rating
|
||||||
|
feedback: Feedback Comment
|
||||||
|
recorded_at: Recorded date
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
notification_title:
|
notification_title:
|
||||||
|
|
|
@ -106,6 +106,7 @@ Rails.application.routes.draw do
|
||||||
resources :csat_survey_responses, only: [:index] do
|
resources :csat_survey_responses, only: [:index] do
|
||||||
collection do
|
collection do
|
||||||
get :metrics
|
get :metrics
|
||||||
|
get :download
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy]
|
resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy]
|
||||||
|
@ -154,16 +155,17 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
resources :working_hours, only: [:update]
|
resources :working_hours, only: [:update]
|
||||||
|
|
||||||
namespace :kbase do
|
|
||||||
resources :portals do
|
resources :portals do
|
||||||
|
member do
|
||||||
|
post :archive
|
||||||
|
end
|
||||||
resources :categories do
|
resources :categories do
|
||||||
resources :folders
|
resources :folders
|
||||||
end
|
end
|
||||||
|
end
|
||||||
resources :articles
|
resources :articles
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
# end of account scoped api routes
|
# end of account scoped api routes
|
||||||
# ----------------------------------
|
# ----------------------------------
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class ChangeKbasePortalsToPortals < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
rename_table :kbase_portals, :portals
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
class CreatePortalsMembersJoinTable < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
create_join_table :portals, :users, table_name: :portals_members do |t|
|
||||||
|
t.index :portal_id
|
||||||
|
t.index :user_id
|
||||||
|
t.index [:portal_id, :user_id], unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class ChangeKbaseCategoriesToCategories < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
rename_table :kbase_categories, :categories
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class ChangeKbaseFoldersToFolders < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
rename_table :kbase_folders, :folders
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class ChangeKbaseArticlesToArticles < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
rename_table :kbase_articles, :articles
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddArchiveColumnToPortal < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :portals, :archived, :boolean, default: false
|
||||||
|
end
|
||||||
|
end
|
112
db/schema.rb
112
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2022_05_06_163839) do
|
ActiveRecord::Schema.define(version: 2022_05_11_072655) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
|
@ -111,6 +111,21 @@ ActiveRecord::Schema.define(version: 2022_05_06_163839) do
|
||||||
t.index ["account_id"], name: "index_agent_bots_on_account_id"
|
t.index ["account_id"], name: "index_agent_bots_on_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "articles", force: :cascade do |t|
|
||||||
|
t.integer "account_id", null: false
|
||||||
|
t.integer "portal_id", null: false
|
||||||
|
t.integer "category_id"
|
||||||
|
t.integer "folder_id"
|
||||||
|
t.integer "author_id"
|
||||||
|
t.string "title"
|
||||||
|
t.text "description"
|
||||||
|
t.text "content"
|
||||||
|
t.integer "status"
|
||||||
|
t.integer "views"
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
end
|
||||||
|
|
||||||
create_table "attachments", id: :serial, force: :cascade do |t|
|
create_table "attachments", id: :serial, force: :cascade do |t|
|
||||||
t.integer "file_type", default: 0
|
t.integer "file_type", default: 0
|
||||||
t.string "external_url"
|
t.string "external_url"
|
||||||
|
@ -169,6 +184,19 @@ ActiveRecord::Schema.define(version: 2022_05_06_163839) do
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "categories", force: :cascade do |t|
|
||||||
|
t.integer "account_id", null: false
|
||||||
|
t.integer "portal_id", null: false
|
||||||
|
t.string "name"
|
||||||
|
t.text "description"
|
||||||
|
t.integer "position"
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.string "locale", default: "en"
|
||||||
|
t.index ["locale", "account_id"], name: "index_categories_on_locale_and_account_id"
|
||||||
|
t.index ["locale"], name: "index_categories_on_locale"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "channel_api", force: :cascade do |t|
|
create_table "channel_api", force: :cascade do |t|
|
||||||
t.integer "account_id", null: false
|
t.integer "account_id", null: false
|
||||||
t.string "webhook_url"
|
t.string "webhook_url"
|
||||||
|
@ -434,6 +462,14 @@ ActiveRecord::Schema.define(version: 2022_05_06_163839) do
|
||||||
t.index ["name", "account_id"], name: "index_email_templates_on_name_and_account_id", unique: true
|
t.index ["name", "account_id"], name: "index_email_templates_on_name_and_account_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "folders", force: :cascade do |t|
|
||||||
|
t.integer "account_id", null: false
|
||||||
|
t.integer "category_id", null: false
|
||||||
|
t.string "name"
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
end
|
||||||
|
|
||||||
create_table "inbox_members", id: :serial, force: :cascade do |t|
|
create_table "inbox_members", id: :serial, force: :cascade do |t|
|
||||||
t.integer "user_id", null: false
|
t.integer "user_id", null: false
|
||||||
t.integer "inbox_id", null: false
|
t.integer "inbox_id", null: false
|
||||||
|
@ -486,56 +522,6 @@ ActiveRecord::Schema.define(version: 2022_05_06_163839) do
|
||||||
t.jsonb "settings", default: {}
|
t.jsonb "settings", default: {}
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "kbase_articles", force: :cascade do |t|
|
|
||||||
t.integer "account_id", null: false
|
|
||||||
t.integer "portal_id", null: false
|
|
||||||
t.integer "category_id"
|
|
||||||
t.integer "folder_id"
|
|
||||||
t.integer "author_id"
|
|
||||||
t.string "title"
|
|
||||||
t.text "description"
|
|
||||||
t.text "content"
|
|
||||||
t.integer "status"
|
|
||||||
t.integer "views"
|
|
||||||
t.datetime "created_at", precision: 6, null: false
|
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "kbase_categories", force: :cascade do |t|
|
|
||||||
t.integer "account_id", null: false
|
|
||||||
t.integer "portal_id", null: false
|
|
||||||
t.string "name"
|
|
||||||
t.text "description"
|
|
||||||
t.integer "position"
|
|
||||||
t.datetime "created_at", precision: 6, null: false
|
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
|
||||||
t.string "locale", default: "en"
|
|
||||||
t.index ["locale", "account_id"], name: "index_kbase_categories_on_locale_and_account_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "kbase_folders", force: :cascade do |t|
|
|
||||||
t.integer "account_id", null: false
|
|
||||||
t.integer "category_id", null: false
|
|
||||||
t.string "name"
|
|
||||||
t.datetime "created_at", precision: 6, null: false
|
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "kbase_portals", force: :cascade do |t|
|
|
||||||
t.integer "account_id", null: false
|
|
||||||
t.string "name", null: false
|
|
||||||
t.string "slug", null: false
|
|
||||||
t.string "custom_domain"
|
|
||||||
t.string "color"
|
|
||||||
t.string "homepage_link"
|
|
||||||
t.string "page_title"
|
|
||||||
t.text "header_text"
|
|
||||||
t.datetime "created_at", precision: 6, null: false
|
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
|
||||||
t.jsonb "config", default: {"allowed_locales"=>["en"]}
|
|
||||||
t.index ["slug"], name: "index_kbase_portals_on_slug", unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "labels", force: :cascade do |t|
|
create_table "labels", force: :cascade do |t|
|
||||||
t.string "title"
|
t.string "title"
|
||||||
t.text "description"
|
t.text "description"
|
||||||
|
@ -653,6 +639,30 @@ ActiveRecord::Schema.define(version: 2022_05_06_163839) do
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "portals", force: :cascade do |t|
|
||||||
|
t.integer "account_id", null: false
|
||||||
|
t.string "name", null: false
|
||||||
|
t.string "slug", null: false
|
||||||
|
t.string "custom_domain"
|
||||||
|
t.string "color"
|
||||||
|
t.string "homepage_link"
|
||||||
|
t.string "page_title"
|
||||||
|
t.text "header_text"
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.jsonb "config", default: {"allowed_locales"=>["en"]}
|
||||||
|
t.boolean "archived", default: false
|
||||||
|
t.index ["slug"], name: "index_portals_on_slug", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "portals_members", id: false, force: :cascade do |t|
|
||||||
|
t.bigint "portal_id", null: false
|
||||||
|
t.bigint "user_id", null: false
|
||||||
|
t.index ["portal_id", "user_id"], name: "index_portals_members_on_portal_id_and_user_id", unique: true
|
||||||
|
t.index ["portal_id"], name: "index_portals_members_on_portal_id"
|
||||||
|
t.index ["user_id"], name: "index_portals_members_on_user_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "reporting_events", force: :cascade do |t|
|
create_table "reporting_events", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
t.float "value"
|
t.float "value"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@chatwoot/chatwoot",
|
"name": "@chatwoot/chatwoot",
|
||||||
"version": "2.4.1",
|
"version": "2.5.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"eslint": "eslint app/**/*.{js,vue} --fix",
|
"eslint": "eslint app/**/*.{js,vue} --fix",
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
"vue-chartjs": "3.5.1",
|
"vue-chartjs": "3.5.1",
|
||||||
"vue-clickaway": "~2.1.0",
|
"vue-clickaway": "~2.1.0",
|
||||||
"vue-color": "2.8.1",
|
"vue-color": "2.8.1",
|
||||||
"vue-dompurify-html": "^2.5.1",
|
"vue-dompurify-html": "^2.5.2",
|
||||||
"vue-easytable": "2.5.5",
|
"vue-easytable": "2.5.5",
|
||||||
"vue-i18n": "8.24.3",
|
"vue-i18n": "8.24.3",
|
||||||
"vue-loader": "15.9.6",
|
"vue-loader": "15.9.6",
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe 'Api::V1::Accounts::Kbase::Categories', type: :request do
|
RSpec.describe 'Api::V1::Accounts::Categories', type: :request do
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:agent) { create(:user, account: account, role: :agent) }
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
let!(:portal) { create(:kbase_portal, name: 'test_portal', account_id: account.id) }
|
let!(:portal) { create(:portal, name: 'test_portal', account_id: account.id) }
|
||||||
let!(:category) { create(:kbase_category, name: 'category', portal: portal, account_id: account.id) }
|
let!(:category) { create(:category, name: 'category', portal: portal, account_id: account.id) }
|
||||||
|
|
||||||
describe 'POST /api/v1/accounts/{account.id}/kbase/portals/{portal.slug}/categories' do
|
describe 'POST /api/v1/accounts/{account.id}/portals/{portal.slug}/categories' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
post "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}/categories", params: {}
|
post "/api/v1/accounts/#{account.id}/portals/#{portal.slug}/categories", params: {}
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -23,7 +23,7 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Categories', type: :request do
|
||||||
position: 1
|
position: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}/categories",
|
post "/api/v1/accounts/#{account.id}/portals/#{portal.slug}/categories",
|
||||||
params: category_params,
|
params: category_params,
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
|
@ -33,10 +33,10 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Categories', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'PUT /api/v1/accounts/{account.id}/kbase/portals/{portal.slug}/categories/{category.id}' do
|
describe 'PUT /api/v1/accounts/{account.id}/portals/{portal.slug}/categories/{category.id}' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
put "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}/categories/#{category.id}", params: {}
|
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}/categories/#{category.id}", params: {}
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -53,7 +53,7 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Categories', type: :request do
|
||||||
|
|
||||||
expect(category.name).not_to eql(category_params[:category][:name])
|
expect(category.name).not_to eql(category_params[:category][:name])
|
||||||
|
|
||||||
put "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}/categories/#{category.id}",
|
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}/categories/#{category.id}",
|
||||||
params: category_params,
|
params: category_params,
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
|
@ -63,39 +63,39 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Categories', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'DELETE /api/v1/accounts/{account.id}/kbase/portals/{portal.slug}/categories/{category.id}' do
|
describe 'DELETE /api/v1/accounts/{account.id}/portals/{portal.slug}/categories/{category.id}' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
delete "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}/categories/#{category.id}", params: {}
|
delete "/api/v1/accounts/#{account.id}/portals/#{portal.slug}/categories/#{category.id}", params: {}
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated user' do
|
context 'when it is an authenticated user' do
|
||||||
it 'deletes category' do
|
it 'deletes category' do
|
||||||
delete "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}/categories/#{category.id}",
|
delete "/api/v1/accounts/#{account.id}/portals/#{portal.slug}/categories/#{category.id}",
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
deleted_category = Kbase::Category.find_by(id: category.id)
|
deleted_category = Category.find_by(id: category.id)
|
||||||
expect(deleted_category).to be nil
|
expect(deleted_category).to be nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /api/v1/accounts/{account.id}/kbase/portals/{portal.slug}/categories' do
|
describe 'GET /api/v1/accounts/{account.id}/portals/{portal.slug}/categories' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
get "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}/categories"
|
get "/api/v1/accounts/#{account.id}/portals/#{portal.slug}/categories"
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated user' do
|
context 'when it is an authenticated user' do
|
||||||
it 'get all portals' do
|
it 'get all portals' do
|
||||||
category2 = create(:kbase_category, name: 'test_category_2', portal: portal)
|
category2 = create(:category, name: 'test_category_2', portal: portal)
|
||||||
expect(category2.id).not_to be nil
|
expect(category2.id).not_to be nil
|
||||||
|
|
||||||
get "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}/categories",
|
get "/api/v1/accounts/#{account.id}/portals/#{portal.slug}/categories",
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
|
@ -148,4 +148,38 @@ RSpec.describe 'CSAT Survey Responses API', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/csat_survey_responses/download' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/csat_survey_responses/download"
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:params) { { since: 5.days.ago.to_time.to_i.to_s, until: Time.zone.tomorrow.to_time.to_i.to_s } }
|
||||||
|
|
||||||
|
it 'returns unauthorized for agents' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/csat_survey_responses/download",
|
||||||
|
params: params,
|
||||||
|
headers: agent.create_new_auth_token
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns summary' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/csat_survey_responses/download",
|
||||||
|
params: params,
|
||||||
|
headers: administrator.create_new_auth_token
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
|
||||||
|
content = CSV.parse(response.body)
|
||||||
|
# Check rating from CSAT Row
|
||||||
|
expect(content[1][1]).to eq '1'
|
||||||
|
expect(content.length).to eq 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do
|
RSpec.describe 'Api::V1::Accounts::Portals', type: :request do
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:agent) { create(:user, account: account, role: :agent) }
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
let!(:portal) { create(:kbase_portal, slug: 'portal-1', name: 'test_portal', account_id: account.id) }
|
let!(:portal) { create(:portal, slug: 'portal-1', name: 'test_portal', account_id: account.id) }
|
||||||
|
|
||||||
describe 'GET /api/v1/accounts/{account.id}/kbase/portals' do
|
describe 'GET /api/v1/accounts/{account.id}/portals' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
get "/api/v1/accounts/#{account.id}/kbase/portals"
|
get "/api/v1/accounts/#{account.id}/portals"
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated user' do
|
context 'when it is an authenticated user' do
|
||||||
it 'get all portals' do
|
it 'get all portals' do
|
||||||
portal2 = create(:kbase_portal, name: 'test_portal_2', account_id: account.id, slug: 'portal-2')
|
portal2 = create(:portal, name: 'test_portal_2', account_id: account.id, slug: 'portal-2')
|
||||||
expect(portal2.id).not_to be nil
|
expect(portal2.id).not_to be nil
|
||||||
get "/api/v1/accounts/#{account.id}/kbase/portals",
|
get "/api/v1/accounts/#{account.id}/portals",
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
||||||
|
@ -26,17 +26,17 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /api/v1/accounts/{account.id}/kbase/portals/{portal.slug}' do
|
describe 'GET /api/v1/accounts/{account.id}/portals/{portal.slug}' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
get "/api/v1/accounts/#{account.id}/kbase/portals"
|
get "/api/v1/accounts/#{account.id}/portals"
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated user' do
|
context 'when it is an authenticated user' do
|
||||||
it 'get one portals' do
|
it 'get one portals' do
|
||||||
get "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}",
|
get "/api/v1/accounts/#{account.id}/portals/#{portal.slug}",
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
||||||
|
@ -45,10 +45,10 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST /api/v1/accounts/{account.id}/kbase/portals' do
|
describe 'POST /api/v1/accounts/{account.id}/portals' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
post "/api/v1/accounts/#{account.id}/kbase/portals", params: {}
|
post "/api/v1/accounts/#{account.id}/portals", params: {}
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -61,7 +61,7 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do
|
||||||
slug: 'test_kbase'
|
slug: 'test_kbase'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post "/api/v1/accounts/#{account.id}/kbase/portals",
|
post "/api/v1/accounts/#{account.id}/portals",
|
||||||
params: portal_params,
|
params: portal_params,
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
|
@ -71,10 +71,10 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'PUT /api/v1/accounts/{account.id}/kbase/portals/{portal.slug}' do
|
describe 'PUT /api/v1/accounts/{account.id}/portals/{portal.slug}' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
put "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}", params: {}
|
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}", params: {}
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -89,30 +89,50 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do
|
||||||
|
|
||||||
expect(portal.name).to eql('test_portal')
|
expect(portal.name).to eql('test_portal')
|
||||||
|
|
||||||
put "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}",
|
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}",
|
||||||
params: portal_params,
|
params: portal_params,
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
||||||
expect(json_response['name']).to eql(portal_params[:portal][:name])
|
expect(json_response['name']).to eql(portal_params[:portal][:name])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'archive portal' do
|
||||||
|
portal_params = {
|
||||||
|
portal: {
|
||||||
|
archived: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(portal.archived).to be_falsy
|
||||||
|
|
||||||
|
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}",
|
||||||
|
params: portal_params,
|
||||||
|
headers: agent.create_new_auth_token
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
json_response = JSON.parse(response.body)
|
||||||
|
expect(json_response['archived']).to eql(portal_params[:portal][:archived])
|
||||||
|
|
||||||
|
portal.reload
|
||||||
|
expect(portal.archived).to be_truthy
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'DELETE /api/v1/accounts/{account.id}/kbase/portals/{portal.slug}' do
|
describe 'DELETE /api/v1/accounts/{account.id}/portals/{portal.slug}' do
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
delete "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}", params: {}
|
delete "/api/v1/accounts/#{account.id}/portals/#{portal.slug}", params: {}
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated user' do
|
context 'when it is an authenticated user' do
|
||||||
it 'deletes portal' do
|
it 'deletes portal' do
|
||||||
delete "/api/v1/accounts/#{account.id}/kbase/portals/#{portal.slug}",
|
delete "/api/v1/accounts/#{account.id}/portals/#{portal.slug}",
|
||||||
headers: agent.create_new_auth_token
|
headers: agent.create_new_auth_token
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
deleted_portal = Kbase::Portal.find_by(id: portal.slug)
|
deleted_portal = Portal.find_by(id: portal.slug)
|
||||||
expect(deleted_portal).to be nil
|
expect(deleted_portal).to be nil
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -1,5 +1,5 @@
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :kbase_article, class: 'Kbase::Article' do
|
factory :article, class: 'Article' do
|
||||||
account_id { 1 }
|
account_id { 1 }
|
||||||
category_id { 1 }
|
category_id { 1 }
|
||||||
folder_id { 1 }
|
folder_id { 1 }
|
|
@ -1,6 +1,6 @@
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :kbase_category, class: 'Kbase::Category' do
|
factory :category, class: 'Category' do
|
||||||
portal { kbase_portal }
|
portal { portal }
|
||||||
name { 'MyString' }
|
name { 'MyString' }
|
||||||
description { 'MyText' }
|
description { 'MyText' }
|
||||||
position { 1 }
|
position { 1 }
|
|
@ -1,5 +1,5 @@
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :kbase_folder, class: 'Kbase::Folder' do
|
factory :folder, class: 'Folder' do
|
||||||
account_id { 1 }
|
account_id { 1 }
|
||||||
name { 'MyString' }
|
name { 'MyString' }
|
||||||
description { 'MyText' }
|
description { 'MyText' }
|
|
@ -1,5 +1,5 @@
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :kbase_portal, class: 'Kbase::Portal' do
|
factory :portal, class: 'Portal' do
|
||||||
account
|
account
|
||||||
name { Faker::Book.name }
|
name { Faker::Book.name }
|
||||||
slug { SecureRandom.hex }
|
slug { SecureRandom.hex }
|
|
@ -19,8 +19,8 @@ RSpec.describe Account do
|
||||||
it { is_expected.to have_many(:webhooks).dependent(:destroy_async) }
|
it { is_expected.to have_many(:webhooks).dependent(:destroy_async) }
|
||||||
it { is_expected.to have_many(:notification_settings).dependent(:destroy_async) }
|
it { is_expected.to have_many(:notification_settings).dependent(:destroy_async) }
|
||||||
it { is_expected.to have_many(:reporting_events) }
|
it { is_expected.to have_many(:reporting_events) }
|
||||||
it { is_expected.to have_many(:kbase_portals).dependent(:destroy_async) }
|
it { is_expected.to have_many(:portals).dependent(:destroy_async) }
|
||||||
it { is_expected.to have_many(:kbase_categories).dependent(:destroy_async) }
|
it { is_expected.to have_many(:categories).dependent(:destroy_async) }
|
||||||
it { is_expected.to have_many(:teams).dependent(:destroy_async) }
|
it { is_expected.to have_many(:teams).dependent(:destroy_async) }
|
||||||
|
|
||||||
describe 'usage_limits' do
|
describe 'usage_limits' do
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Kbase::Article, type: :model do
|
RSpec.describe Article, type: :model do
|
||||||
context 'with validations' do
|
context 'with validations' do
|
||||||
it { is_expected.to validate_presence_of(:account_id) }
|
it { is_expected.to validate_presence_of(:account_id) }
|
||||||
it { is_expected.to validate_presence_of(:category_id) }
|
it { is_expected.to validate_presence_of(:category_id) }
|
|
@ -1,6 +1,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Kbase::Category, type: :model do
|
RSpec.describe Category, type: :model do
|
||||||
context 'with validations' do
|
context 'with validations' do
|
||||||
it { is_expected.to validate_presence_of(:account_id) }
|
it { is_expected.to validate_presence_of(:account_id) }
|
||||||
it { is_expected.to validate_presence_of(:name) }
|
it { is_expected.to validate_presence_of(:name) }
|
|
@ -1,6 +1,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Kbase::Folder, type: :model do
|
RSpec.describe Folder, type: :model do
|
||||||
context 'with validations' do
|
context 'with validations' do
|
||||||
it { is_expected.to validate_presence_of(:account_id) }
|
it { is_expected.to validate_presence_of(:account_id) }
|
||||||
it { is_expected.to validate_presence_of(:category_id) }
|
it { is_expected.to validate_presence_of(:category_id) }
|
|
@ -1,6 +1,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Kbase::Portal, type: :model do
|
RSpec.describe Portal, type: :model do
|
||||||
context 'with validations' do
|
context 'with validations' do
|
||||||
it { is_expected.to validate_presence_of(:account_id) }
|
it { is_expected.to validate_presence_of(:account_id) }
|
||||||
it { is_expected.to validate_presence_of(:slug) }
|
it { is_expected.to validate_presence_of(:slug) }
|
|
@ -15284,10 +15284,10 @@ vue-docgen-loader@^1.5.0:
|
||||||
loader-utils "^1.2.3"
|
loader-utils "^1.2.3"
|
||||||
querystring "^0.2.0"
|
querystring "^0.2.0"
|
||||||
|
|
||||||
vue-dompurify-html@^2.5.1:
|
vue-dompurify-html@^2.5.2:
|
||||||
version "2.5.1"
|
version "2.5.2"
|
||||||
resolved "https://registry.npmjs.org/vue-dompurify-html/-/vue-dompurify-html-2.5.1.tgz#a754f4ac7b18eb8fe41f461cb2bb1c4956a9bd2d"
|
resolved "https://registry.yarnpkg.com/vue-dompurify-html/-/vue-dompurify-html-2.5.2.tgz#f547d4eacae4640f95eb0e9308e7ef8e223887c6"
|
||||||
integrity sha512-B8rQj2jAPJJhtKHHa6jg5B3/RoKBmmUl/awP/GxWXGu75j4Y7+MHqv0DG52v0Uz0taEpHyZun34KEYMAfrPWnA==
|
integrity sha512-G6I135+BhlACJ9xftqK7fvhXyjNrgHCI594qHnUW5e2Bmp8BOTV1kz7cxwI37b4BJnHkj9IY10RwMPOtJqw+pw==
|
||||||
dependencies:
|
dependencies:
|
||||||
dompurify "^2.3.4"
|
dompurify "^2.3.4"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue