diff --git a/app/controllers/api/v1/accounts/kbase/categories_controller.rb b/app/controllers/api/v1/accounts/categories_controller.rb similarity index 72% rename from app/controllers/api/v1/accounts/kbase/categories_controller.rb rename to app/controllers/api/v1/accounts/categories_controller.rb index a40053dd2..246eeb2a2 100644 --- a/app/controllers/api/v1/accounts/kbase/categories_controller.rb +++ b/app/controllers/api/v1/accounts/categories_controller.rb @@ -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] def index @@ -24,6 +25,10 @@ class Api::V1::Accounts::Kbase::CategoriesController < Api::V1::Accounts::Kbase: @category = @portal.categories.find(params[:id]) end + def portal + @portal ||= Current.account.portals.find_by(slug: params[:portal_id]) + end + def category_params params.require(:category).permit( :name, :description, :position diff --git a/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb index 347f028f7..7b5c51d6e 100644 --- a/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb +++ b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb @@ -5,7 +5,7 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base RESULTS_PER_PAGE = 25 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_surveys, only: [:index] 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 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 def set_total_sent_messages_count diff --git a/app/controllers/api/v1/accounts/kbase/base_controller.rb b/app/controllers/api/v1/accounts/kbase/base_controller.rb deleted file mode 100644 index 4f62cd858..000000000 --- a/app/controllers/api/v1/accounts/kbase/base_controller.rb +++ /dev/null @@ -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 diff --git a/app/controllers/api/v1/accounts/kbase/portals_controller.rb b/app/controllers/api/v1/accounts/portals_controller.rb similarity index 58% rename from app/controllers/api/v1/accounts/kbase/portals_controller.rb rename to app/controllers/api/v1/accounts/portals_controller.rb index 5ec1b4a83..75ffc35e9 100644 --- a/app/controllers/api/v1/accounts/kbase/portals_controller.rb +++ b/app/controllers/api/v1/accounts/portals_controller.rb @@ -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] def index - @portals = Current.account.kbase_portals + @portals = Current.account.portals end def show; end def create - @portal = Current.account.kbase_portals.create!(portal_params) + @portal = Current.account.portals.create!(portal_params) end def update @@ -23,7 +23,7 @@ class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::BaseContr private 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 def permitted_params @@ -32,7 +32,7 @@ class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::BaseContr def portal_params 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 diff --git a/app/javascript/dashboard/api/csatReports.js b/app/javascript/dashboard/api/csatReports.js index cc0271d7d..4decc8de3 100644 --- a/app/javascript/dashboard/api/csatReports.js +++ b/app/javascript/dashboard/api/csatReports.js @@ -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 } = {}) { return axios.get(`${this.url}/metrics`, { params: { since: from, until: to, user_ids }, diff --git a/app/javascript/dashboard/api/specs/csatReports.spec.js b/app/javascript/dashboard/api/specs/csatReports.spec.js index 0022a91ad..a1d6e50f2 100644 --- a/app/javascript/dashboard/api/specs/csatReports.spec.js +++ b/app/javascript/dashboard/api/specs/csatReports.spec.js @@ -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', + }, + } + ); + }); }); }); diff --git a/app/javascript/dashboard/helper/downloadCsvFile.js b/app/javascript/dashboard/helper/downloadHelper.js similarity index 51% rename from app/javascript/dashboard/helper/downloadCsvFile.js rename to app/javascript/dashboard/helper/downloadHelper.js index f0a13a1fd..150ac1fd8 100644 --- a/app/javascript/dashboard/helper/downloadCsvFile.js +++ b/app/javascript/dashboard/helper/downloadHelper.js @@ -1,6 +1,12 @@ +import fromUnixTime from 'date-fns/fromUnixTime'; +import format from 'date-fns/format'; + export const downloadCsvFile = (fileName, fileContent) => { const link = document.createElement('a'); link.download = fileName; link.href = `data:text/csv;charset=utf-8,` + encodeURI(fileContent); link.click(); }; + +export const generateFileName = ({ type, to }) => + `${type}-report-${format(fromUnixTime(to), 'dd-MM-yyyy')}.csv`; diff --git a/app/javascript/dashboard/helper/specs/downloadCsvFile.spec.js b/app/javascript/dashboard/helper/specs/downloadHelper.spec.js similarity index 68% rename from app/javascript/dashboard/helper/specs/downloadCsvFile.spec.js rename to app/javascript/dashboard/helper/specs/downloadHelper.spec.js index d05b0a841..b294dfe16 100644 --- a/app/javascript/dashboard/helper/specs/downloadCsvFile.spec.js +++ b/app/javascript/dashboard/helper/specs/downloadHelper.spec.js @@ -1,4 +1,4 @@ -import { downloadCsvFile } from '../downloadCsvFile'; +import { downloadCsvFile, generateFileName } from '../downloadHelper'; const fileName = 'test.csv'; 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); }); }); + +describe('#generateFileName', () => { + it('should generate the correct file name', () => { + expect(generateFileName({ type: 'csat', to: 1652812199 })).toEqual( + 'csat-report-17-05-2022.csv' + ); + }); +}); diff --git a/app/javascript/dashboard/i18n/locale/ar/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ar/inboxMgmt.json index da5e213e9..2b590b411 100644 --- a/app/javascript/dashboard/i18n/locale/ar/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ar/inboxMgmt.json @@ -398,8 +398,8 @@ "MESSENGER_SUB_HEAD": "ضع هذا الكود داخل وسم الـ body في موقعك", "INBOX_AGENTS": "موظف الدعم", "INBOX_AGENTS_SUB_TEXT": "إضافة أو إزالة موظفين من قناة التواصل هذه", - "AGENT_ASSIGNMENT": "Conversation Assignment", - "AGENT_ASSIGNMENT_SUB_TEXT": "Update conversation assignment settings", + "AGENT_ASSIGNMENT": "إسناد المحادثات", + "AGENT_ASSIGNMENT_SUB_TEXT": "تحديث إعدادات إسناد المحادثات", "UPDATE": "تحديث", "ENABLE_EMAIL_COLLECT_BOX": "تفعيل صندوق جمع البريد الإلكتروني", "ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "تمكين أو تعطيل مربع جمع البريد الإلكتروني في محادثة جديدة", diff --git a/app/javascript/dashboard/i18n/locale/ar/settings.json b/app/javascript/dashboard/i18n/locale/ar/settings.json index 45ea1db14..9b779f25c 100644 --- a/app/javascript/dashboard/i18n/locale/ar/settings.json +++ b/app/javascript/dashboard/i18n/locale/ar/settings.json @@ -151,7 +151,7 @@ }, "SIDEBAR": { "CURRENTLY_VIEWING_ACCOUNT": "مشاهدة حاليا:", - "SWITCH": "Switch", + "SWITCH": "تبديل", "CONVERSATIONS": "المحادثات", "ALL_CONVERSATIONS": "كل المحادثات", "MENTIONED_CONVERSATIONS": "الإشارات", diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json index 35a349b56..5f2c6ae48 100644 --- a/app/javascript/dashboard/i18n/locale/en/report.json +++ b/app/javascript/dashboard/i18n/locale/en/report.json @@ -354,6 +354,7 @@ "CSAT_REPORTS": { "HEADER": "CSAT Reports", "NO_RECORDS": "There are no CSAT survey responses available.", + "DOWNLOAD": "Download CSAT Reports", "FILTERS": { "AGENTS": { "PLACEHOLDER": "Choose Agents" diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue index 985179d83..3a3076e9c 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue +++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue @@ -8,7 +8,6 @@ diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue index 24aeeff88..db20d7043 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue @@ -65,7 +65,6 @@
+ + {{ $t('CSAT_REPORTS.DOWNLOAD') }} +
@@ -15,6 +24,7 @@ import CsatMetrics from './components/CsatMetrics'; import CsatTable from './components/CsatTable'; import ReportFilterSelector from './components/FilterSelector'; import { mapGetters } from 'vuex'; +import { generateFileName } from '../../../../helper/downloadHelper'; export default { name: 'CsatResponses', @@ -24,7 +34,7 @@ export default { ReportFilterSelector, }, data() { - return { pageIndex: 1, from: 0, to: 0, user_ids: [] }; + return { pageIndex: 1, from: 0, to: 0, userIds: [] }; }, computed: { ...mapGetters({ @@ -39,7 +49,7 @@ export default { this.$store.dispatch('csat/getMetrics', { from: this.from, to: this.to, - user_ids: this.user_ids, + user_ids: this.userIds, }); this.getResponses(); }, @@ -48,7 +58,7 @@ export default { page: this.pageIndex, from: this.from, to: this.to, - user_ids: this.user_ids, + user_ids: this.userIds, }); }, onPageNumberChange(pageIndex) { @@ -61,9 +71,18 @@ export default { this.getAllData(); }, onAgentsFilterChange(agents) { - this.user_ids = agents.map(el => el.id); + this.userIds = agents.map(el => el.id); 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 }), + }); + }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue index cadf4078e..848555a4d 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue @@ -61,7 +61,10 @@ @input="handleAgentsFilterSelection" /> -
+
{{ $t('REPORT.BUSINESS_HOURS') }} @@ -105,6 +108,10 @@ export default { type: Boolean, default: false, }, + showBusinessHoursSwitch: { + type: Boolean, + default: true, + }, }, data() { return { diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue index 6d8b49c62..34188a12a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue @@ -61,6 +61,7 @@ import format from 'date-fns/format'; import { GROUP_BY_FILTER, METRIC_CHART } from '../constants'; import reportMixin from '../../../../../mixins/reportMixin'; import { formatTime } from '@chatwoot/utils'; +import { generateFileName } from '../../../../../helper/downloadHelper'; const REPORTS_KEYS = { CONVERSATIONS: 'conversations_count', @@ -250,26 +251,17 @@ export default { }); }, downloadReports() { - const { from, to } = this; - const fileName = `${this.type}-report-${format( - fromUnixTime(to), - 'dd-MM-yyyy' - )}.csv`; - switch (this.type) { - case 'agent': - this.$store.dispatch('downloadAgentReports', { from, to, fileName }); - break; - case 'label': - this.$store.dispatch('downloadLabelReports', { from, to, fileName }); - break; - case 'inbox': - this.$store.dispatch('downloadInboxReports', { from, to, fileName }); - break; - case 'team': - this.$store.dispatch('downloadTeamReports', { from, to, fileName }); - break; - default: - break; + const { from, to, type } = this; + const dispatchMethods = { + agent: 'downloadAgentReports', + label: 'downloadLabelReports', + inbox: 'downloadInboxReports', + team: 'downloadTeamReports', + }; + if (dispatchMethods[type]) { + const fileName = generateFileName({ type, to }); + const params = { from, to, fileName }; + this.$store.dispatch(dispatchMethods[type], params); } }, changeSelection(index) { diff --git a/app/javascript/dashboard/store/modules/csat.js b/app/javascript/dashboard/store/modules/csat.js index 7bc1dad6d..2557e2698 100644 --- a/app/javascript/dashboard/store/modules/csat.js +++ b/app/javascript/dashboard/store/modules/csat.js @@ -1,6 +1,7 @@ import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; import types from '../mutation-types'; import CSATReports from '../../api/csatReports'; +import { downloadCsvFile } from '../../helper/downloadHelper'; const computeDistribution = (value, total) => ((value * 100) / total).toFixed(2); @@ -107,6 +108,11 @@ export const actions = { 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 = { diff --git a/app/javascript/dashboard/store/modules/reports.js b/app/javascript/dashboard/store/modules/reports.js index dc9a6c33b..9ae571803 100644 --- a/app/javascript/dashboard/store/modules/reports.js +++ b/app/javascript/dashboard/store/modules/reports.js @@ -5,7 +5,7 @@ import * as types from '../mutation-types'; import Report from '../../api/reports'; import Vue from 'vue'; -import { downloadCsvFile } from '../../helper/downloadCsvFile'; +import { downloadCsvFile } from '../../helper/downloadHelper'; const state = { fetchingStatus: false, diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index b0e081f98..184c7c6df 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -39,6 +39,7 @@ import { } from '../dashboard/helper/scriptHelpers'; import FluentIcon from 'shared/components/FluentIcon/DashboardIcon'; import VueDOMPurifyHTML from 'vue-dompurify-html'; +import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer'; Vue.config.env = process.env; @@ -55,7 +56,8 @@ if (window.analyticsConfig) { api_host: window.analyticsConfig.host, }); } -Vue.use(VueDOMPurifyHTML); + +Vue.use(VueDOMPurifyHTML, domPurifyConfig); Vue.use(VueRouter); Vue.use(VueI18n); Vue.use(WootUiKit); diff --git a/app/javascript/packs/widget.js b/app/javascript/packs/widget.js index 3b9661bc7..6eab2db8b 100644 --- a/app/javascript/packs/widget.js +++ b/app/javascript/packs/widget.js @@ -9,9 +9,10 @@ import ActionCableConnector from '../widget/helpers/actionCable'; import i18n from '../widget/i18n'; import { isPhoneE164OrEmpty } from 'shared/helpers/Validators'; import router from '../widget/router'; +import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer'; Vue.use(VueI18n); Vue.use(Vuelidate); -Vue.use(VueDOMPurifyHTML); +Vue.use(VueDOMPurifyHTML, domPurifyConfig); const i18nConfig = new VueI18n({ locale: 'en', diff --git a/app/javascript/shared/helpers/HTMLSanitizer.js b/app/javascript/shared/helpers/HTMLSanitizer.js index b119b3681..889948c0b 100644 --- a/app/javascript/shared/helpers/HTMLSanitizer.js +++ b/app/javascript/shared/helpers/HTMLSanitizer.js @@ -6,3 +6,15 @@ export const escapeHtml = (unsafe = '') => { .replace(/"/g, '"') .replace(/'/g, '''); }; + +export const afterSanitizeAttributes = currentNode => { + if ('target' in currentNode) { + currentNode.setAttribute('target', '_blank'); + } +}; + +export const domPurifyConfig = { + hooks: { + afterSanitizeAttributes, + }, +}; diff --git a/app/javascript/shared/helpers/MessageFormatter.js b/app/javascript/shared/helpers/MessageFormatter.js index 260436da4..c4d0bb62e 100644 --- a/app/javascript/shared/helpers/MessageFormatter.js +++ b/app/javascript/shared/helpers/MessageFormatter.js @@ -1,6 +1,6 @@ import { marked } from 'marked'; 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_REPLACEMENT = @@ -48,9 +48,7 @@ class MessageFormatter { const markedDownOutput = marked(withHash); return markedDownOutput; } - DOMPurify.addHook('afterSanitizeAttributes', node => { - if ('target' in node) node.setAttribute('target', '_blank'); - }); + DOMPurify.addHook('afterSanitizeAttributes', afterSanitizeAttributes); return DOMPurify.sanitize( marked(this.message, { breaks: true, gfm: true }) ); diff --git a/app/models/account.rb b/app/models/account.rb index 22aeefcaf..b14887957 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -51,9 +51,9 @@ class Account < ApplicationRecord has_many :facebook_pages, dependent: :destroy_async, class_name: '::Channel::FacebookPage' has_many :hooks, dependent: :destroy_async, class_name: 'Integrations::Hook' has_many :inboxes, dependent: :destroy_async - has_many :kbase_articles, dependent: :destroy_async, class_name: '::Kbase::Article' - has_many :kbase_categories, dependent: :destroy_async, class_name: '::Kbase::Category' - has_many :kbase_portals, dependent: :destroy_async, class_name: '::Kbase::Portal' + has_many :articles, dependent: :destroy_async, class_name: '::Article' + has_many :categories, dependent: :destroy_async, class_name: '::Category' + has_many :portals, dependent: :destroy_async, class_name: '::Portal' has_many :labels, dependent: :destroy_async has_many :line_channels, dependent: :destroy_async, class_name: '::Channel::Line' has_many :mentions, dependent: :destroy_async diff --git a/app/models/kbase/article.rb b/app/models/article.rb similarity index 92% rename from app/models/kbase/article.rb rename to app/models/article.rb index 0f14f6709..750e3a4fd 100644 --- a/app/models/kbase/article.rb +++ b/app/models/article.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: kbase_articles +# Table name: articles # # id :bigint not null, primary key # content :text @@ -16,7 +16,7 @@ # folder_id :integer # portal_id :integer not null # -class Kbase::Article < ApplicationRecord +class Article < ApplicationRecord belongs_to :account belongs_to :category belongs_to :portal diff --git a/app/models/kbase/category.rb b/app/models/category.rb similarity index 79% rename from app/models/kbase/category.rb rename to app/models/category.rb index d2b270306..2fb5815f2 100644 --- a/app/models/kbase/category.rb +++ b/app/models/category.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: kbase_categories +# Table name: categories # # id :bigint not null, primary key # description :text @@ -14,9 +14,10 @@ # # 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 :portal has_many :folders, dependent: :destroy_async diff --git a/app/models/kbase/folder.rb b/app/models/folder.rb similarity index 87% rename from app/models/kbase/folder.rb rename to app/models/folder.rb index 887522b5d..315d1f340 100644 --- a/app/models/kbase/folder.rb +++ b/app/models/folder.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: kbase_folders +# Table name: folders # # id :bigint not null, primary key # name :string @@ -9,7 +9,7 @@ # account_id :integer not null # category_id :integer not null # -class Kbase::Folder < ApplicationRecord +class Folder < ApplicationRecord belongs_to :account belongs_to :category has_many :articles, dependent: :nullify diff --git a/app/models/kbase/portal.rb b/app/models/portal.rb similarity index 80% rename from app/models/kbase/portal.rb rename to app/models/portal.rb index c381e5c46..8774820be 100644 --- a/app/models/kbase/portal.rb +++ b/app/models/portal.rb @@ -1,8 +1,9 @@ # == Schema Information # -# Table name: kbase_portals +# Table name: portals # # id :bigint not null, primary key +# archived :boolean default(FALSE) # color :string # config :jsonb # custom_domain :string @@ -17,13 +18,14 @@ # # Indexes # -# index_kbase_portals_on_slug (slug) UNIQUE +# index_portals_on_slug (slug) UNIQUE # -class Kbase::Portal < ApplicationRecord +class Portal < ApplicationRecord belongs_to :account has_many :categories, dependent: :destroy_async has_many :folders, through: :categories has_many :articles, dependent: :destroy_async + has_many :users, through: :portals_members validates :account_id, presence: true validates :name, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index 3b7a74672..6c3349473 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -90,6 +90,7 @@ class User < ApplicationRecord has_many :notifications, dependent: :destroy_async has_many :team_members, dependent: :destroy_async has_many :teams, through: :team_members + has_many :portals, through: :portals_members before_validation :set_password_and_uid, on: :create diff --git a/app/policies/csat_survey_response_policy.rb b/app/policies/csat_survey_response_policy.rb index c0ce8821b..afcce00e9 100644 --- a/app/policies/csat_survey_response_policy.rb +++ b/app/policies/csat_survey_response_policy.rb @@ -6,4 +6,8 @@ class CsatSurveyResponsePolicy < ApplicationPolicy def metrics? @account_user.administrator? end + + def download? + @account_user.administrator? + end end diff --git a/app/views/api/v1/accounts/kbase/categories/_category.json.jbuilder b/app/views/api/v1/accounts/categories/_category.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/categories/_category.json.jbuilder rename to app/views/api/v1/accounts/categories/_category.json.jbuilder diff --git a/app/views/api/v1/accounts/kbase/categories/create.json.jbuilder b/app/views/api/v1/accounts/categories/create.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/categories/create.json.jbuilder rename to app/views/api/v1/accounts/categories/create.json.jbuilder diff --git a/app/views/api/v1/accounts/kbase/categories/index.json.jbuilder b/app/views/api/v1/accounts/categories/index.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/categories/index.json.jbuilder rename to app/views/api/v1/accounts/categories/index.json.jbuilder diff --git a/app/views/api/v1/accounts/kbase/categories/update.json.jbuilder b/app/views/api/v1/accounts/categories/update.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/categories/update.json.jbuilder rename to app/views/api/v1/accounts/categories/update.json.jbuilder diff --git a/app/views/api/v1/accounts/csat_survey_responses/download.csv.erb b/app/views/api/v1/accounts/csat_survey_responses/download.csv.erb new file mode 100644 index 000000000..8b694ed35 --- /dev/null +++ b/app/views/api/v1/accounts/csat_survey_responses/download.csv.erb @@ -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') + ) + ]) +-%> diff --git a/app/views/public/api/v1/models/kbase/_portal.json.jbuilder b/app/views/api/v1/accounts/portals/_portal.json.jbuilder similarity index 89% rename from app/views/public/api/v1/models/kbase/_portal.json.jbuilder rename to app/views/api/v1/accounts/portals/_portal.json.jbuilder index d612f5265..1f828d4ad 100644 --- a/app/views/public/api/v1/models/kbase/_portal.json.jbuilder +++ b/app/views/api/v1/accounts/portals/_portal.json.jbuilder @@ -6,4 +6,5 @@ json.homepage_link portal.homepage_link json.name portal.name json.page_title portal.page_title json.slug portal.slug +json.archived portal.archived json.config portal.config diff --git a/app/views/api/v1/accounts/kbase/portals/create.json.jbuilder b/app/views/api/v1/accounts/portals/create.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/portals/create.json.jbuilder rename to app/views/api/v1/accounts/portals/create.json.jbuilder diff --git a/app/views/api/v1/accounts/kbase/portals/index.json.jbuilder b/app/views/api/v1/accounts/portals/index.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/portals/index.json.jbuilder rename to app/views/api/v1/accounts/portals/index.json.jbuilder diff --git a/app/views/api/v1/accounts/kbase/portals/show.json.jbuilder b/app/views/api/v1/accounts/portals/show.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/portals/show.json.jbuilder rename to app/views/api/v1/accounts/portals/show.json.jbuilder diff --git a/app/views/api/v1/accounts/kbase/portals/update.json.jbuilder b/app/views/api/v1/accounts/portals/update.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/portals/update.json.jbuilder rename to app/views/api/v1/accounts/portals/update.json.jbuilder diff --git a/app/views/public/api/v1/models/kbase/_category.json.jbuilder b/app/views/public/api/v1/models/_category.json.jbuilder similarity index 100% rename from app/views/public/api/v1/models/kbase/_category.json.jbuilder rename to app/views/public/api/v1/models/_category.json.jbuilder diff --git a/app/views/api/v1/accounts/kbase/portals/_portal.json.jbuilder b/app/views/public/api/v1/models/_portal.json.jbuilder similarity index 100% rename from app/views/api/v1/accounts/kbase/portals/_portal.json.jbuilder rename to app/views/public/api/v1/models/_portal.json.jbuilder diff --git a/config/app.yml b/config/app.yml index d370e1d74..1ea45235d 100644 --- a/config/app.yml +++ b/config/app.yml @@ -1,5 +1,5 @@ shared: &shared - version: '2.4.1' + version: '2.5.0' development: <<: *shared diff --git a/config/locales/en.yml b/config/locales/en.yml index 0c92c32eb..bd7829b53 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -60,6 +60,16 @@ en: avg_first_response_time: Avg first response time (Minutes) avg_resolution_time: Avg resolution time (Minutes) 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: notification_title: diff --git a/config/routes.rb b/config/routes.rb index 7ed667160..a80f6ca39 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -106,6 +106,7 @@ Rails.application.routes.draw do resources :csat_survey_responses, only: [:index] do collection do get :metrics + get :download end end resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy] @@ -154,14 +155,15 @@ Rails.application.routes.draw do end resources :working_hours, only: [:update] - namespace :kbase do - resources :portals do - resources :categories do - resources :folders - end - resources :articles + resources :portals do + member do + post :archive + end + resources :categories do + resources :folders end end + resources :articles end end # end of account scoped api routes diff --git a/db/migrate/20220506061540_change_kbase_portals_to_portals.rb b/db/migrate/20220506061540_change_kbase_portals_to_portals.rb new file mode 100644 index 000000000..c9765abbb --- /dev/null +++ b/db/migrate/20220506061540_change_kbase_portals_to_portals.rb @@ -0,0 +1,5 @@ +class ChangeKbasePortalsToPortals < ActiveRecord::Migration[6.1] + def change + rename_table :kbase_portals, :portals + end +end diff --git a/db/migrate/20220506064938_create_portals_members_join_table.rb b/db/migrate/20220506064938_create_portals_members_join_table.rb new file mode 100644 index 000000000..a24bb7356 --- /dev/null +++ b/db/migrate/20220506064938_create_portals_members_join_table.rb @@ -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 diff --git a/db/migrate/20220506072007_change_kbase_categories_to_categories.rb b/db/migrate/20220506072007_change_kbase_categories_to_categories.rb new file mode 100644 index 000000000..b527e3e80 --- /dev/null +++ b/db/migrate/20220506072007_change_kbase_categories_to_categories.rb @@ -0,0 +1,5 @@ +class ChangeKbaseCategoriesToCategories < ActiveRecord::Migration[6.1] + def change + rename_table :kbase_categories, :categories + end +end diff --git a/db/migrate/20220506080338_change_kbase_folders_to_folders.rb b/db/migrate/20220506080338_change_kbase_folders_to_folders.rb new file mode 100644 index 000000000..8e0ba6642 --- /dev/null +++ b/db/migrate/20220506080338_change_kbase_folders_to_folders.rb @@ -0,0 +1,5 @@ +class ChangeKbaseFoldersToFolders < ActiveRecord::Migration[6.1] + def change + rename_table :kbase_folders, :folders + end +end diff --git a/db/migrate/20220506080429_change_kbase_articles_to_articles.rb b/db/migrate/20220506080429_change_kbase_articles_to_articles.rb new file mode 100644 index 000000000..ba041ca84 --- /dev/null +++ b/db/migrate/20220506080429_change_kbase_articles_to_articles.rb @@ -0,0 +1,5 @@ +class ChangeKbaseArticlesToArticles < ActiveRecord::Migration[6.1] + def change + rename_table :kbase_articles, :articles + end +end diff --git a/db/migrate/20220511072655_add_archive_column_to_portal.rb b/db/migrate/20220511072655_add_archive_column_to_portal.rb new file mode 100644 index 000000000..b3a504e97 --- /dev/null +++ b/db/migrate/20220511072655_add_archive_column_to_portal.rb @@ -0,0 +1,5 @@ +class AddArchiveColumnToPortal < ActiveRecord::Migration[6.1] + def change + add_column :portals, :archived, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 157e2ac2f..32f7dbefb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # 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 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" 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| t.integer "file_type", default: 0 t.string "external_url" @@ -169,6 +184,19 @@ ActiveRecord::Schema.define(version: 2022_05_06_163839) do t.datetime "updated_at", null: false 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| t.integer "account_id", null: false 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 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| t.integer "user_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: {} 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| t.string "title" t.text "description" @@ -653,6 +639,30 @@ ActiveRecord::Schema.define(version: 2022_05_06_163839) do t.datetime "updated_at", precision: 6, null: false 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| t.string "name" t.float "value" diff --git a/package.json b/package.json index 8bfdaf74a..f862e0fbe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chatwoot/chatwoot", - "version": "2.4.1", + "version": "2.5.0", "license": "MIT", "scripts": { "eslint": "eslint app/**/*.{js,vue} --fix", @@ -63,7 +63,7 @@ "vue-chartjs": "3.5.1", "vue-clickaway": "~2.1.0", "vue-color": "2.8.1", - "vue-dompurify-html": "^2.5.1", + "vue-dompurify-html": "^2.5.2", "vue-easytable": "2.5.5", "vue-i18n": "8.24.3", "vue-loader": "15.9.6", diff --git a/spec/controllers/api/v1/accounts/kbase/categories_controller_spec.rb b/spec/controllers/api/v1/accounts/categories_controller_spec.rb similarity index 61% rename from spec/controllers/api/v1/accounts/kbase/categories_controller_spec.rb rename to spec/controllers/api/v1/accounts/categories_controller_spec.rb index 4d98cd898..a1b767166 100644 --- a/spec/controllers/api/v1/accounts/kbase/categories_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/categories_controller_spec.rb @@ -1,15 +1,15 @@ 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(:agent) { create(:user, account: account, role: :agent) } - let!(:portal) { create(:kbase_portal, name: 'test_portal', account_id: account.id) } - let!(:category) { create(:kbase_category, name: 'category', portal: portal, account_id: account.id) } + let!(:portal) { create(:portal, name: 'test_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 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) end end @@ -23,7 +23,7 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Categories', type: :request do 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, headers: agent.create_new_auth_token expect(response).to have_http_status(:success) @@ -33,10 +33,10 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Categories', type: :request do 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 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) 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]) - 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, headers: agent.create_new_auth_token expect(response).to have_http_status(:success) @@ -63,39 +63,39 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Categories', type: :request do 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 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) end end context 'when it is an authenticated user' 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 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 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 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) end end context 'when it is an authenticated user' 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 - 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 expect(response).to have_http_status(:success) json_response = JSON.parse(response.body) diff --git a/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb b/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb index abb4231d9..f431f0ed5 100644 --- a/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb @@ -148,4 +148,38 @@ RSpec.describe 'CSAT Survey Responses API', type: :request do 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 diff --git a/spec/controllers/api/v1/accounts/kbase/portals_controller_spec.rb b/spec/controllers/api/v1/accounts/portals_controller_spec.rb similarity index 60% rename from spec/controllers/api/v1/accounts/kbase/portals_controller_spec.rb rename to spec/controllers/api/v1/accounts/portals_controller_spec.rb index eae356930..d4c923d58 100644 --- a/spec/controllers/api/v1/accounts/kbase/portals_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/portals_controller_spec.rb @@ -1,23 +1,23 @@ 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(: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 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) end end context 'when it is an authenticated user' 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 - get "/api/v1/accounts/#{account.id}/kbase/portals", + get "/api/v1/accounts/#{account.id}/portals", headers: agent.create_new_auth_token expect(response).to have_http_status(:success) json_response = JSON.parse(response.body) @@ -26,17 +26,17 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do 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 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) end end context 'when it is an authenticated user' 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 expect(response).to have_http_status(:success) json_response = JSON.parse(response.body) @@ -45,10 +45,10 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do 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 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) end end @@ -61,7 +61,7 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do slug: 'test_kbase' } } - post "/api/v1/accounts/#{account.id}/kbase/portals", + post "/api/v1/accounts/#{account.id}/portals", params: portal_params, headers: agent.create_new_auth_token expect(response).to have_http_status(:success) @@ -71,10 +71,10 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do 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 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) end end @@ -89,30 +89,50 @@ RSpec.describe 'Api::V1::Accounts::Kbase::Portals', type: :request do 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, headers: agent.create_new_auth_token expect(response).to have_http_status(:success) json_response = JSON.parse(response.body) expect(json_response['name']).to eql(portal_params[:portal][:name]) 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 - 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 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) end end context 'when it is an authenticated user' 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 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 end end diff --git a/spec/factories/kbase/articles.rb b/spec/factories/articles.rb similarity index 81% rename from spec/factories/kbase/articles.rb rename to spec/factories/articles.rb index 9c19d7c9e..3f0c94c47 100644 --- a/spec/factories/kbase/articles.rb +++ b/spec/factories/articles.rb @@ -1,5 +1,5 @@ FactoryBot.define do - factory :kbase_article, class: 'Kbase::Article' do + factory :article, class: 'Article' do account_id { 1 } category_id { 1 } folder_id { 1 } diff --git a/spec/factories/kbase/categories.rb b/spec/factories/categories.rb similarity index 70% rename from spec/factories/kbase/categories.rb rename to spec/factories/categories.rb index 6b4c47ce4..f7becf8fc 100644 --- a/spec/factories/kbase/categories.rb +++ b/spec/factories/categories.rb @@ -1,6 +1,6 @@ FactoryBot.define do - factory :kbase_category, class: 'Kbase::Category' do - portal { kbase_portal } + factory :category, class: 'Category' do + portal { portal } name { 'MyString' } description { 'MyText' } position { 1 } diff --git a/spec/factories/kbase/folders.rb b/spec/factories/folders.rb similarity index 71% rename from spec/factories/kbase/folders.rb rename to spec/factories/folders.rb index 00cbabedd..62be3d263 100644 --- a/spec/factories/kbase/folders.rb +++ b/spec/factories/folders.rb @@ -1,5 +1,5 @@ FactoryBot.define do - factory :kbase_folder, class: 'Kbase::Folder' do + factory :folder, class: 'Folder' do account_id { 1 } name { 'MyString' } description { 'MyText' } diff --git a/spec/factories/kbase/portals.rb b/spec/factories/portals.rb similarity index 66% rename from spec/factories/kbase/portals.rb rename to spec/factories/portals.rb index fb37b8b56..d49de096a 100644 --- a/spec/factories/kbase/portals.rb +++ b/spec/factories/portals.rb @@ -1,5 +1,5 @@ FactoryBot.define do - factory :kbase_portal, class: 'Kbase::Portal' do + factory :portal, class: 'Portal' do account name { Faker::Book.name } slug { SecureRandom.hex } diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index ff5f9cca5..e9906f131 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -19,8 +19,8 @@ RSpec.describe Account do 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(:reporting_events) } - it { is_expected.to have_many(:kbase_portals).dependent(:destroy_async) } - it { is_expected.to have_many(:kbase_categories).dependent(:destroy_async) } + it { is_expected.to have_many(:portals).dependent(:destroy_async) } + it { is_expected.to have_many(:categories).dependent(:destroy_async) } it { is_expected.to have_many(:teams).dependent(:destroy_async) } describe 'usage_limits' do diff --git a/spec/models/kbase/article_spec.rb b/spec/models/article_spec.rb similarity index 91% rename from spec/models/kbase/article_spec.rb rename to spec/models/article_spec.rb index 14729b1ed..ee1e7f618 100644 --- a/spec/models/kbase/article_spec.rb +++ b/spec/models/article_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Kbase::Article, type: :model do +RSpec.describe Article, type: :model do context 'with validations' do it { is_expected.to validate_presence_of(:account_id) } it { is_expected.to validate_presence_of(:category_id) } diff --git a/spec/models/kbase/category_spec.rb b/spec/models/category_spec.rb similarity index 89% rename from spec/models/kbase/category_spec.rb rename to spec/models/category_spec.rb index 4b84cb352..55bad9a14 100644 --- a/spec/models/kbase/category_spec.rb +++ b/spec/models/category_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Kbase::Category, type: :model do +RSpec.describe Category, type: :model do context 'with validations' do it { is_expected.to validate_presence_of(:account_id) } it { is_expected.to validate_presence_of(:name) } diff --git a/spec/models/kbase/folder_spec.rb b/spec/models/folder_spec.rb similarity index 90% rename from spec/models/kbase/folder_spec.rb rename to spec/models/folder_spec.rb index 4af97a644..47c9adf06 100644 --- a/spec/models/kbase/folder_spec.rb +++ b/spec/models/folder_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Kbase::Folder, type: :model do +RSpec.describe Folder, type: :model do context 'with validations' do it { is_expected.to validate_presence_of(:account_id) } it { is_expected.to validate_presence_of(:category_id) } diff --git a/spec/models/kbase/portal_spec.rb b/spec/models/portal_spec.rb similarity index 90% rename from spec/models/kbase/portal_spec.rb rename to spec/models/portal_spec.rb index 67f79bf81..e7fe633bc 100644 --- a/spec/models/kbase/portal_spec.rb +++ b/spec/models/portal_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Kbase::Portal, type: :model do +RSpec.describe Portal, type: :model do context 'with validations' do it { is_expected.to validate_presence_of(:account_id) } it { is_expected.to validate_presence_of(:slug) } diff --git a/yarn.lock b/yarn.lock index 82d266618..fa46ddb37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15284,10 +15284,10 @@ vue-docgen-loader@^1.5.0: loader-utils "^1.2.3" querystring "^0.2.0" -vue-dompurify-html@^2.5.1: - version "2.5.1" - resolved "https://registry.npmjs.org/vue-dompurify-html/-/vue-dompurify-html-2.5.1.tgz#a754f4ac7b18eb8fe41f461cb2bb1c4956a9bd2d" - integrity sha512-B8rQj2jAPJJhtKHHa6jg5B3/RoKBmmUl/awP/GxWXGu75j4Y7+MHqv0DG52v0Uz0taEpHyZun34KEYMAfrPWnA== +vue-dompurify-html@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/vue-dompurify-html/-/vue-dompurify-html-2.5.2.tgz#f547d4eacae4640f95eb0e9308e7ef8e223887c6" + integrity sha512-G6I135+BhlACJ9xftqK7fvhXyjNrgHCI594qHnUW5e2Bmp8BOTV1kz7cxwI37b4BJnHkj9IY10RwMPOtJqw+pw== dependencies: dompurify "^2.3.4"