feat: Articles store integration (#5133)
This commit is contained in:
parent
82207c0d3e
commit
5735a8e377
22 changed files with 272 additions and 82 deletions
|
@ -5,6 +5,7 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||||
before_action :set_current_page, only: [:index]
|
before_action :set_current_page, only: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@articles_count = @portal.articles.count
|
||||||
@articles = @portal.articles
|
@articles = @portal.articles
|
||||||
@articles = @articles.search(list_params) if list_params.present?
|
@articles = @articles.search(list_params) if list_params.present?
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
|
/* global axios */
|
||||||
import ApiClient from '../ApiClient';
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
class PortalsAPI extends ApiClient {
|
class PortalsAPI extends ApiClient {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('portals', { accountScoped: true });
|
super('portals', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getArticles({ pageNumber, portalSlug, locale }) {
|
||||||
|
return axios.get(
|
||||||
|
`${this.url}/${portalSlug}/articles?page=${pageNumber}&locale=${locale}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new PortalsAPI();
|
export default new PortalsAPI();
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="row--article-block">
|
<div class="row--article-block">
|
||||||
<div class="article-block">
|
<div class="article-block">
|
||||||
<h6 class="sub-block-title text-truncate">
|
<h6 class="sub-block-title text-truncate">
|
||||||
<router-link class="article-name" :to="articlePath">
|
<router-link class="article-name" :to="articleUrl(id)">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</h6>
|
</h6>
|
||||||
|
@ -24,16 +24,20 @@
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
|
||||||
import Label from 'dashboard/components/ui/Label';
|
import Label from 'dashboard/components/ui/Label';
|
||||||
import timeMixin from 'dashboard/mixins/time';
|
import timeMixin from 'dashboard/mixins/time';
|
||||||
|
import portalMixin from '../mixins/portalMixin';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Label,
|
Label,
|
||||||
},
|
},
|
||||||
mixins: [timeMixin],
|
mixins: [timeMixin, portalMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -79,9 +83,6 @@ export default {
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
articlePath() {
|
|
||||||
return frontendURL(`accounts/${this.accountId}/hc/articles/${this.id}`);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -16,20 +16,22 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<ArticleItem
|
<ArticleItem
|
||||||
v-for="article in articles"
|
v-for="article in articles"
|
||||||
|
:id="article.id"
|
||||||
:key="article.id"
|
:key="article.id"
|
||||||
:title="article.title"
|
:title="article.title"
|
||||||
:author="article.author"
|
:author="article.author"
|
||||||
:category="article.category"
|
:category="article.category"
|
||||||
:read-count="article.readCount"
|
:read-count="article.readCount"
|
||||||
:status="article.status"
|
:status="article.status"
|
||||||
:updated-at="article.updatedAt"
|
:updated-at="article.updated_at"
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<table-footer
|
<table-footer
|
||||||
:on-page-change="onPageChange"
|
:on-page-change="onPageChange"
|
||||||
:current-page="Number(currentPage)"
|
:current-page="Number(currentPage)"
|
||||||
:total-count="articleCount"
|
:total-count="totalCount"
|
||||||
|
:page-size="pageSize"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -47,7 +49,7 @@ export default {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
articleCount: {
|
totalCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
@ -55,10 +57,14 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 15,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onPageChange() {
|
onPageChange(page) {
|
||||||
this.$emit('onPageChange');
|
this.$emit('on-page-change', page);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,6 +66,12 @@ export default {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
accountId: 'getCurrentAccountId',
|
accountId: 'getCurrentAccountId',
|
||||||
}),
|
}),
|
||||||
|
portalSlug() {
|
||||||
|
return this.$route.params.portalSlug;
|
||||||
|
},
|
||||||
|
locale() {
|
||||||
|
return this.$route.params.locale;
|
||||||
|
},
|
||||||
accessibleMenuItems() {
|
accessibleMenuItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -74,7 +80,7 @@ export default {
|
||||||
key: 'list_all_locale_articles',
|
key: 'list_all_locale_articles',
|
||||||
count: 199,
|
count: 199,
|
||||||
toState: frontendURL(
|
toState: frontendURL(
|
||||||
`accounts/${this.accountId}/portals/:portalSlug/:locale/articles`
|
`accounts/${this.accountId}/portals/${this.portalSlug}/${this.locale}/articles`
|
||||||
),
|
),
|
||||||
toolTip: 'All Articles',
|
toolTip: 'All Articles',
|
||||||
toStateName: 'list_all_locale_articles',
|
toStateName: 'list_all_locale_articles',
|
||||||
|
@ -85,7 +91,7 @@ export default {
|
||||||
key: 'mine_articles',
|
key: 'mine_articles',
|
||||||
count: 112,
|
count: 112,
|
||||||
toState: frontendURL(
|
toState: frontendURL(
|
||||||
`accounts/${this.accountId}/portals/:portalSlug/:locale/articles/mine`
|
`accounts/${this.accountId}/portals/${this.portalSlug}/${this.locale}/articles/mine`
|
||||||
),
|
),
|
||||||
toolTip: 'My articles',
|
toolTip: 'My articles',
|
||||||
toStateName: 'mine_articles',
|
toStateName: 'mine_articles',
|
||||||
|
@ -96,7 +102,7 @@ export default {
|
||||||
key: 'list_draft_articles',
|
key: 'list_draft_articles',
|
||||||
count: 32,
|
count: 32,
|
||||||
toState: frontendURL(
|
toState: frontendURL(
|
||||||
`accounts/${this.accountId}/portals/:portalSlug/:locale/articles/draft`
|
`accounts/${this.accountId}/portals/${this.portalSlug}/${this.locale}/articles/draft`
|
||||||
),
|
),
|
||||||
toolTip: 'Draft',
|
toolTip: 'Draft',
|
||||||
toStateName: 'list_draft_articles',
|
toStateName: 'list_draft_articles',
|
||||||
|
@ -107,7 +113,7 @@ export default {
|
||||||
key: 'list_archived_articles',
|
key: 'list_archived_articles',
|
||||||
count: 10,
|
count: 10,
|
||||||
toState: frontendURL(
|
toState: frontendURL(
|
||||||
`accounts/${this.accountId}/portals/:portalSlug/:locale/articles/archived`
|
`accounts/${this.accountId}/portals/${this.portalSlug}/${this.locale}/articles/archived`
|
||||||
),
|
),
|
||||||
toolTip: 'Archived',
|
toolTip: 'Archived',
|
||||||
toStateName: 'list_archived_articles',
|
toStateName: 'list_archived_articles',
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapGetters({ accountId: 'getCurrentAccountId' }),
|
||||||
|
portalSlug() {
|
||||||
|
return this.$route.params.portalSlug;
|
||||||
|
},
|
||||||
|
locale() {
|
||||||
|
return this.$route.params.locale;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
articleUrl(id) {
|
||||||
|
return frontendURL(
|
||||||
|
`accounts/${this.accountId}/portals/${this.portalSlug}/${this.locale}/articles/${id}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||||
|
import portalMixin from '../portalMixin';
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
import VueRouter from 'vue-router';
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
localVue.use(Vuex);
|
||||||
|
localVue.use(VueRouter);
|
||||||
|
import ListAllArticles from '../../pages/portals/ListAllPortals.vue';
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: ':portalSlug/:locale/articles',
|
||||||
|
name: 'list_all_locale_articles',
|
||||||
|
component: ListAllArticles,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('portalMixin', () => {
|
||||||
|
let getters;
|
||||||
|
let store;
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getters = {
|
||||||
|
getCurrentAccountId: () => 1,
|
||||||
|
};
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
title: 'TestComponent',
|
||||||
|
mixins: [portalMixin],
|
||||||
|
router,
|
||||||
|
};
|
||||||
|
store = new Vuex.Store({ getters });
|
||||||
|
wrapper = shallowMount(Component, { store, localVue });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return account id', () => {
|
||||||
|
expect(wrapper.vm.accountId).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns portal url', () => {
|
||||||
|
router.push({
|
||||||
|
name: 'list_all_locale_articles',
|
||||||
|
params: { portalSlug: 'fur-rent', locale: 'en' },
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.articleUrl(1)).toBe(
|
||||||
|
'/app/accounts/1/portals/fur-rent/en/articles/1'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns portal locale', () => {
|
||||||
|
router.push({
|
||||||
|
name: 'list_all_locale_articles',
|
||||||
|
params: { portalSlug: 'fur-rent', locale: 'es' },
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.portalSlug).toBe('fur-rent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns portal slug', () => {
|
||||||
|
router.push({
|
||||||
|
name: 'list_all_locale_articles',
|
||||||
|
params: { portalSlug: 'campaign', locale: 'es' },
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.portalSlug).toBe('campaign');
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,26 +2,30 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<article-header
|
<article-header
|
||||||
:header-title="headerTitle"
|
:header-title="headerTitle"
|
||||||
:count="articleCount"
|
:count="meta.count"
|
||||||
selected-value="Published"
|
selected-value="Published"
|
||||||
@newArticlePage="newArticlePage"
|
@newArticlePage="newArticlePage"
|
||||||
/>
|
/>
|
||||||
<article-table :articles="articles" :article-count="articles.length" />
|
<article-table
|
||||||
<empty-state
|
:articles="articles"
|
||||||
v-if="showSearchEmptyState"
|
:article-count="articles.length"
|
||||||
:title="$t('HELP_CENTER.TABLE.404')"
|
:current-page="Number(meta.currentPage)"
|
||||||
|
:total-count="meta.count"
|
||||||
|
@on-page-change="onPageChange"
|
||||||
/>
|
/>
|
||||||
<empty-state
|
<div v-if="isFetching" class="articles--loader">
|
||||||
v-else-if="!isLoading && !articles.length"
|
|
||||||
:title="$t('CONTACTS_PAGE.LIST.NO_CONTACTS')"
|
|
||||||
/>
|
|
||||||
<div v-if="isLoading" class="articles--loader">
|
|
||||||
<spinner />
|
<spinner />
|
||||||
<span>{{ $t('HELP_CENTER.TABLE.LOADING_MESSAGE') }}</span>
|
<span>{{ $t('HELP_CENTER.TABLE.LOADING_MESSAGE') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<empty-state
|
||||||
|
v-else-if="!isFetching && !articles.length"
|
||||||
|
:title="$t('HELP_CENTER.TABLE.NO_ARTICLES')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
import ArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/ArticleHeader';
|
import ArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/ArticleHeader';
|
||||||
import EmptyState from 'dashboard/components/widgets/EmptyState';
|
import EmptyState from 'dashboard/components/widgets/EmptyState';
|
||||||
|
@ -35,45 +39,18 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// Dummy data will remove once the state is implemented.
|
pageNumber: 1,
|
||||||
articles: [
|
|
||||||
{
|
|
||||||
title: 'Setup your account',
|
|
||||||
author: {
|
|
||||||
name: 'John Doe',
|
|
||||||
},
|
|
||||||
readCount: 13,
|
|
||||||
category: 'Getting started',
|
|
||||||
status: 'published',
|
|
||||||
updatedAt: 1657255863,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Docker Configuration',
|
|
||||||
author: {
|
|
||||||
name: 'Sam Manuel',
|
|
||||||
},
|
|
||||||
readCount: 13,
|
|
||||||
category: 'Engineering',
|
|
||||||
status: 'draft',
|
|
||||||
updatedAt: 1656658046,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Campaigns',
|
|
||||||
author: {
|
|
||||||
name: 'Sam Manuel',
|
|
||||||
},
|
|
||||||
readCount: 28,
|
|
||||||
category: 'Engineering',
|
|
||||||
status: 'archived',
|
|
||||||
updatedAt: 1657590446,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
articleCount: 12,
|
|
||||||
isLoading: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showSearchEmptyState() {
|
...mapGetters({
|
||||||
|
articles: 'articles/allArticles',
|
||||||
|
uiFlags: 'articles/uiFlags',
|
||||||
|
meta: 'articles/getMeta',
|
||||||
|
isFetching: 'articles/isFetching',
|
||||||
|
}),
|
||||||
|
|
||||||
|
showEmptyState() {
|
||||||
return this.articles.length === 0;
|
return this.articles.length === 0;
|
||||||
},
|
},
|
||||||
articleType() {
|
articleType() {
|
||||||
|
@ -92,10 +69,23 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchArticles({ pageNumber: this.pageNumber });
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
newArticlePage() {
|
newArticlePage() {
|
||||||
this.$router.push({ name: 'new_article' });
|
this.$router.push({ name: 'new_article' });
|
||||||
},
|
},
|
||||||
|
fetchArticles({ pageNumber }) {
|
||||||
|
this.$store.dispatch('articles/index', {
|
||||||
|
pageNumber,
|
||||||
|
portalSlug: this.$route.params.portalSlug,
|
||||||
|
locale: this.$route.params.locale,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onPageChange(page) {
|
||||||
|
this.fetchArticles({ pageNumber: page });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -35,6 +35,7 @@ import teamMembers from './modules/teamMembers';
|
||||||
import teams from './modules/teams';
|
import teams from './modules/teams';
|
||||||
import userNotificationSettings from './modules/userNotificationSettings';
|
import userNotificationSettings from './modules/userNotificationSettings';
|
||||||
import webhooks from './modules/webhooks';
|
import webhooks from './modules/webhooks';
|
||||||
|
import articles from './modules/helpCenterArticles';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
|
@ -73,5 +74,6 @@ export default new Vuex.Store({
|
||||||
teams,
|
teams,
|
||||||
userNotificationSettings,
|
userNotificationSettings,
|
||||||
webhooks,
|
webhooks,
|
||||||
|
articles,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
import articlesAPI from 'dashboard/api/helpCenter/articles.js';
|
import portalAPI from 'dashboard/api/helpCenter/portals';
|
||||||
|
import articlesAPI from 'dashboard/api/helpCenter/articles';
|
||||||
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||||
import types from '../../mutation-types';
|
import types from '../../mutation-types';
|
||||||
export const actions = {
|
export const actions = {
|
||||||
index: async ({ commit }) => {
|
index: async ({ commit }, { pageNumber, portalSlug, locale }) => {
|
||||||
try {
|
try {
|
||||||
commit(types.SET_UI_FLAG, { isFetching: true });
|
commit(types.SET_UI_FLAG, { isFetching: true });
|
||||||
const { data } = await articlesAPI.get();
|
const {
|
||||||
const articleIds = data.map(article => article.id);
|
data: { payload, meta },
|
||||||
commit(types.ADD_MANY_ARTICLES, data);
|
} = await portalAPI.getArticles({
|
||||||
|
pageNumber,
|
||||||
|
portalSlug,
|
||||||
|
locale,
|
||||||
|
});
|
||||||
|
const articleIds = payload.map(article => article.id);
|
||||||
|
commit(types.CLEAR_ARTICLES);
|
||||||
|
commit(types.ADD_MANY_ARTICLES, payload);
|
||||||
|
commit(types.SET_ARTICLES_META, meta);
|
||||||
commit(types.ADD_MANY_ARTICLES_ID, articleIds);
|
commit(types.ADD_MANY_ARTICLES_ID, articleIds);
|
||||||
return articleIds;
|
return articleIds;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -31,6 +40,22 @@ export const actions = {
|
||||||
commit(types.SET_UI_FLAG, { isCreating: false });
|
commit(types.SET_UI_FLAG, { isCreating: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
show: async ({ commit }, { id, portalSlug }) => {
|
||||||
|
commit(types.SET_UI_FLAG, { isFetching: true });
|
||||||
|
try {
|
||||||
|
const response = await portalAPI.getArticle({ id, portalSlug });
|
||||||
|
const {
|
||||||
|
data: { payload },
|
||||||
|
} = response;
|
||||||
|
const { id: articleId } = payload;
|
||||||
|
commit(types.ADD_ARTICLE, payload);
|
||||||
|
commit(types.ADD_ARTICLE_ID, articleId);
|
||||||
|
commit(types.SET_UI_FLAG, { isFetching: false });
|
||||||
|
} catch (error) {
|
||||||
|
commit(types.SET_UI_FLAG, { isFetching: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
update: async ({ commit }, params) => {
|
update: async ({ commit }, params) => {
|
||||||
const articleId = params.id;
|
const articleId = params.id;
|
||||||
commit(types.ADD_ARTICLE_FLAG, {
|
commit(types.ADD_ARTICLE_FLAG, {
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
export const getters = {
|
export const getters = {
|
||||||
uiFlagsIn: state => helpCenterId => {
|
uiFlags: state => helpCenterId => {
|
||||||
const uiFlags = state.articles.uiFlags.byId[helpCenterId];
|
const uiFlags = state.articles.uiFlags.byId[helpCenterId];
|
||||||
if (uiFlags) return uiFlags;
|
if (uiFlags) return uiFlags;
|
||||||
return { isFetching: false, isUpdating: false, isDeleting: false };
|
return { isFetching: false, isUpdating: false, isDeleting: false };
|
||||||
},
|
},
|
||||||
isFetchingHelpCenterArticles: state => state.uiFlags.isFetching,
|
isFetching: state => state.uiFlags.isFetching,
|
||||||
articleById: (...getterArguments) => articleId => {
|
articleById: (...getterArguments) => articleId => {
|
||||||
const [state] = getterArguments;
|
const [state] = getterArguments;
|
||||||
const article = state.articles.byId[articleId];
|
const article = state.articles.byId[articleId];
|
||||||
|
|
||||||
if (!article) return undefined;
|
if (!article) return undefined;
|
||||||
|
|
||||||
return article;
|
return article;
|
||||||
},
|
},
|
||||||
allArticles: (...getterArguments) => {
|
allArticles: (...getterArguments) => {
|
||||||
|
@ -20,4 +18,7 @@ export const getters = {
|
||||||
});
|
});
|
||||||
return articles;
|
return articles;
|
||||||
},
|
},
|
||||||
|
getMeta: state => {
|
||||||
|
return state.meta;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,10 @@ export const defaultHelpCenterFlags = {
|
||||||
isDeleting: false,
|
isDeleting: false,
|
||||||
};
|
};
|
||||||
const state = {
|
const state = {
|
||||||
|
meta: {
|
||||||
|
count: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
},
|
||||||
articles: {
|
articles: {
|
||||||
byId: {},
|
byId: {},
|
||||||
allIds: [],
|
allIds: [],
|
||||||
|
|
|
@ -16,19 +16,28 @@ export const mutations = {
|
||||||
...article,
|
...article,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
[types.CLEAR_ARTICLES]: $state => {
|
||||||
|
Vue.set($state.articles, 'byId', {});
|
||||||
|
Vue.set($state.articles, 'allIds', []);
|
||||||
|
Vue.set($state.articles, 'uiFlags', {});
|
||||||
|
},
|
||||||
[types.ADD_MANY_ARTICLES]($state, articles) {
|
[types.ADD_MANY_ARTICLES]($state, articles) {
|
||||||
const allArticles = { ...$state.articles.byId };
|
const allArticles = { ...$state.articles.byId };
|
||||||
articles.forEach(article => {
|
articles.forEach(article => {
|
||||||
allArticles[article.id] = article;
|
allArticles[article.id] = article;
|
||||||
});
|
});
|
||||||
Vue.set($state.articles, 'byId', {
|
Vue.set($state.articles, 'byId', allArticles);
|
||||||
allArticles,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[types.ADD_MANY_ARTICLES_ID]($state, articleIds) {
|
[types.ADD_MANY_ARTICLES_ID]($state, articleIds) {
|
||||||
$state.articles.allIds.push(...articleIds);
|
$state.articles.allIds.push(...articleIds);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[types.SET_ARTICLES_META]: ($state, data) => {
|
||||||
|
const { articles_count: count, current_page: currentPage } = data;
|
||||||
|
Vue.set($state.meta, 'count', count);
|
||||||
|
Vue.set($state.meta, 'currentPage', currentPage);
|
||||||
|
},
|
||||||
|
|
||||||
[types.ADD_ARTICLE_ID]: ($state, articleId) => {
|
[types.ADD_ARTICLE_ID]: ($state, articleId) => {
|
||||||
$state.articles.allIds.push(articleId);
|
$state.articles.allIds.push(articleId);
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,10 +15,22 @@ jest.mock('axios');
|
||||||
describe('#actions', () => {
|
describe('#actions', () => {
|
||||||
describe('#index', () => {
|
describe('#index', () => {
|
||||||
it('sends correct actions if API is success', async () => {
|
it('sends correct actions if API is success', async () => {
|
||||||
axios.get.mockResolvedValue({ data: articleList });
|
axios.get.mockResolvedValue({
|
||||||
await actions.index({ commit });
|
data: {
|
||||||
|
payload: articleList,
|
||||||
|
meta: {
|
||||||
|
current_page: '1',
|
||||||
|
articles_count: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await actions.index(
|
||||||
|
{ commit },
|
||||||
|
{ pageNumber: 1, portalSlug: 'test', locale: 'en' }
|
||||||
|
);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[types.default.SET_UI_FLAG, { isFetching: true }],
|
[types.default.SET_UI_FLAG, { isFetching: true }],
|
||||||
|
[types.default.CLEAR_ARTICLES],
|
||||||
[
|
[
|
||||||
types.default.ADD_MANY_ARTICLES,
|
types.default.ADD_MANY_ARTICLES,
|
||||||
[
|
[
|
||||||
|
@ -29,13 +41,22 @@ describe('#actions', () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
types.default.SET_ARTICLES_META,
|
||||||
|
{ current_page: '1', articles_count: 5 },
|
||||||
|
],
|
||||||
[types.default.ADD_MANY_ARTICLES_ID, [1]],
|
[types.default.ADD_MANY_ARTICLES_ID, [1]],
|
||||||
[types.default.SET_UI_FLAG, { isFetching: false }],
|
[types.default.SET_UI_FLAG, { isFetching: false }],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('sends correct actions if API is error', async () => {
|
it('sends correct actions if API is error', async () => {
|
||||||
axios.get.mockRejectedValue({ message: 'Incorrect header' });
|
axios.get.mockRejectedValue({ message: 'Incorrect header' });
|
||||||
await expect(actions.index({ commit })).rejects.toThrow(Error);
|
await expect(
|
||||||
|
actions.index(
|
||||||
|
{ commit },
|
||||||
|
{ pageNumber: 1, portalSlug: 'test', locale: 'en' }
|
||||||
|
)
|
||||||
|
).rejects.toThrow(Error);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[types.default.SET_UI_FLAG, { isFetching: true }],
|
[types.default.SET_UI_FLAG, { isFetching: true }],
|
||||||
[types.default.SET_UI_FLAG, { isFetching: false }],
|
[types.default.SET_UI_FLAG, { isFetching: false }],
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
export default {
|
export default {
|
||||||
|
meta: {
|
||||||
|
count: 123,
|
||||||
|
currentPage: 2,
|
||||||
|
},
|
||||||
articles: {
|
articles: {
|
||||||
byId: {
|
byId: {
|
||||||
1: {
|
1: {
|
||||||
|
|
|
@ -5,8 +5,8 @@ describe('#getters', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state = articles;
|
state = articles;
|
||||||
});
|
});
|
||||||
it('uiFlagsIn', () => {
|
it('uiFlags', () => {
|
||||||
expect(getters.uiFlagsIn(state)(1)).toEqual({
|
expect(getters.uiFlags(state)(1)).toEqual({
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
isUpdating: true,
|
isUpdating: true,
|
||||||
isDeleting: false,
|
isDeleting: false,
|
||||||
|
@ -34,7 +34,7 @@ describe('#getters', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isFetchingHelpCenters', () => {
|
it('isFetchingArticles', () => {
|
||||||
expect(getters.isFetchingHelpCenterArticles(state)).toEqual(true);
|
expect(getters.isFetching(state)).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,6 +46,19 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#ARTICLES_META', () => {
|
||||||
|
it('add meta to state', () => {
|
||||||
|
mutations[types.SET_ARTICLES_META](state, {
|
||||||
|
articles_count: 3,
|
||||||
|
current_page: 1,
|
||||||
|
});
|
||||||
|
expect(state.meta).toEqual({
|
||||||
|
count: 3,
|
||||||
|
currentPage: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#ADD_ARTICLE_ID', () => {
|
describe('#ADD_ARTICLE_ID', () => {
|
||||||
it('add valid article id to state', () => {
|
it('add valid article id to state', () => {
|
||||||
mutations[types.ADD_ARTICLE_ID](state, 3);
|
mutations[types.ADD_ARTICLE_ID](state, 3);
|
||||||
|
@ -87,4 +100,13 @@ describe('#mutations', () => {
|
||||||
expect(state.articles.byId[2]).toEqual(undefined);
|
expect(state.articles.byId[2]).toEqual(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#CLEAR_ARTICLES', () => {
|
||||||
|
it('clears articles', () => {
|
||||||
|
mutations[types.CLEAR_ARTICLES](state);
|
||||||
|
expect(state.articles.allIds).toEqual([]);
|
||||||
|
expect(state.articles.byId).toEqual({});
|
||||||
|
expect(state.articles.uiFlags).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -226,8 +226,10 @@ export default {
|
||||||
ADD_ARTICLE_ID: 'ADD_ARTICLE_ID',
|
ADD_ARTICLE_ID: 'ADD_ARTICLE_ID',
|
||||||
ADD_MANY_ARTICLES: 'ADD_MANY_ARTICLES',
|
ADD_MANY_ARTICLES: 'ADD_MANY_ARTICLES',
|
||||||
ADD_MANY_ARTICLES_ID: 'ADD_MANY_ARTICLES_ID',
|
ADD_MANY_ARTICLES_ID: 'ADD_MANY_ARTICLES_ID',
|
||||||
|
SET_ARTICLES_META: 'SET_ARTICLES_META',
|
||||||
ADD_ARTICLE_FLAG: 'ADD_ARTICLE_FLAG',
|
ADD_ARTICLE_FLAG: 'ADD_ARTICLE_FLAG',
|
||||||
UPDATE_ARTICLE: 'UPDATE_ARTICLE',
|
UPDATE_ARTICLE: 'UPDATE_ARTICLE',
|
||||||
|
CLEAR_ARTICLES: 'CLEAR_ARTICLES',
|
||||||
REMOVE_ARTICLE: 'REMOVE_ARTICLE',
|
REMOVE_ARTICLE: 'REMOVE_ARTICLE',
|
||||||
REMOVE_ARTICLE_ID: 'REMOVE_ARTICLE_ID',
|
REMOVE_ARTICLE_ID: 'REMOVE_ARTICLE_ID',
|
||||||
SET_UI_FLAG: 'SET_UI_FLAG',
|
SET_UI_FLAG: 'SET_UI_FLAG',
|
||||||
|
|
|
@ -5,6 +5,7 @@ json.content article.content
|
||||||
json.description article.description
|
json.description article.description
|
||||||
json.status article.status
|
json.status article.status
|
||||||
json.account_id article.account_id
|
json.account_id article.account_id
|
||||||
|
json.updated_at article.updated_at.to_i
|
||||||
|
|
||||||
if article.portal.present?
|
if article.portal.present?
|
||||||
json.portal do
|
json.portal do
|
||||||
|
|
|
@ -4,5 +4,5 @@ end
|
||||||
|
|
||||||
json.meta do
|
json.meta do
|
||||||
json.current_page @current_page
|
json.current_page @current_page
|
||||||
json.articles_count @articles.size
|
json.articles_count @articles_count
|
||||||
end
|
end
|
||||||
|
|
|
@ -190,7 +190,7 @@ RSpec.describe 'Api::V1::Accounts::Articles', type: :request do
|
||||||
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['payload'].count).to be 1
|
expect(json_response['payload'].count).to be 1
|
||||||
expect(json_response['meta']['articles_count']).to be json_response['payload'].size
|
expect(json_response['meta']['articles_count']).to be 2
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue