From bb7ee9c65e2567ebd09ccb7bf84fcd499888f6bf Mon Sep 17 00:00:00 2001 From: Gasper Grom Date: Mon, 5 Jun 2023 21:16:42 +0200 Subject: [PATCH 1/3] Member filters connect --- .../database/repositories/tagRepository.ts | 7 + frontend/src/modules/auth/auth-socket.js | 6 +- .../components/list/member-list-table.vue | 68 +-- .../components/list/member-list-toolbar.vue | 511 ++++++++++-------- .../member/components/member-dropdown.vue | 24 +- .../activityType/ActivityTypeFilter.vue | 42 +- .../config/filters/activityType/config.ts | 12 +- .../config/filters/avgSentiment/config.ts | 14 +- .../config/filters/engagementLevel/config.ts | 18 +- .../member/config/filters/tags/config.ts | 40 +- .../config/saved-views/views/all-members.ts | 2 +- .../config/saved-views/views/influential.ts | 2 +- .../config/saved-views/views/most-engaged.ts | 2 +- .../saved-views/views/new-and-active.ts | 2 +- .../config/saved-views/views/slipping-away.ts | 2 +- .../config/saved-views/views/team-members.ts | 2 +- frontend/src/modules/member/member-service.js | 24 + .../modules/member/pages/member-form-page.vue | 4 +- .../modules/member/pages/member-list-page.vue | 47 +- .../modules/member/pages/member-view-page.vue | 4 +- .../src/modules/member/store/pinia/actions.ts | 19 + .../src/modules/member/store/pinia/state.ts | 19 +- frontend/src/modules/member/types/Member.ts | 56 ++ .../filterTypes/MultiSelectAsyncFilter.vue | 7 +- .../multiselect.filter.renderer.ts | 16 +- .../multiselectasync.label.renderer.ts | 2 +- .../filters/services/filter-query.service.ts | 31 +- .../shared/pagination/pagination-sorter.vue | 56 +- frontend/src/shared/types/Pagination.ts | 6 + 29 files changed, 681 insertions(+), 364 deletions(-) create mode 100644 frontend/src/modules/member/types/Member.ts create mode 100644 frontend/src/shared/types/Pagination.ts diff --git a/backend/src/database/repositories/tagRepository.ts b/backend/src/database/repositories/tagRepository.ts index ccc2067a87..42fc97e5e4 100644 --- a/backend/src/database/repositories/tagRepository.ts +++ b/backend/src/database/repositories/tagRepository.ts @@ -198,6 +198,13 @@ class TagRepository { id: filter.id, }) } + if (filter.ids) { + advancedFilter.and.push({ + or: filter.ids.map((id) => ({ + id, + })) + }) + } if (filter.name) { advancedFilter.and.push({ diff --git a/frontend/src/modules/auth/auth-socket.js b/frontend/src/modules/auth/auth-socket.js index 65dd98d6cb..470509abab 100644 --- a/frontend/src/modules/auth/auth-socket.js +++ b/frontend/src/modules/auth/auth-socket.js @@ -8,6 +8,7 @@ import { showEnrichmentSuccessMessage, getEnrichmentMax, } from '@/modules/member/member-enrichment'; +import { useMemberStore } from '@/modules/member/store/pinia'; let socketIoClient; @@ -110,9 +111,8 @@ export const connectSocket = (token) => { // Update members list if tenant hasn't changed if (currentTenant.value.id === parsed.tenantId) { // Refresh list page - await store.dispatch('member/doFetch', { - keepPagination: true, - }); + const { fetchMembers } = useMemberStore(); + await fetchMembers(null, true); } } }); diff --git a/frontend/src/modules/member/components/list/member-list-table.vue b/frontend/src/modules/member/components/list/member-list-table.vue index b458b9804e..cdaea40980 100644 --- a/frontend/src/modules/member/components/list/member-list-table.vue +++ b/frontend/src/modules/member/components/list/member-list-table.vue @@ -27,7 +27,7 @@ />
@@ -86,12 +85,13 @@ id="members-table" ref="table" v-loading="loading" - :data="rows" + :data="members" :default-sort="defaultSort" row-key="id" border :row-class-name="rowClass" @sort-change="doChangeSort" + @selection-change="selectedMembers = $event" > @@ -375,11 +375,11 @@ -
+
store.getters['member/activeView']); +const memberStore = useMemberStore(); +const { + members, totalMembers, filters, selectedMembers, +} = storeToRefs(memberStore); -const defaultSort = computed(() => { - if (activeView.value?.sorter) { - return activeView.value.sorter; - } - - return { - field: 'lastActive', - order: 'descending', - }; -}); +const defaultSort = computed(() => ({ + field: 'lastActive', + order: 'descending', +})); const integrations = computed( () => store.getters['integration/activeList'] || {}, @@ -459,17 +458,13 @@ const showReach = computed( || integrations.value.github?.status === 'done', ); -const rows = computed(() => store.getters['member/rows']); -const count = computed(() => store.state.member.count); -const tableWidth = computed(() => store.state.member.list.table?.bodyWidth); const loading = computed( - () => store.state.member.list.loading || props.isPageLoading, + () => props.isPageLoading, ); const tagsColumnWidth = computed(() => { let maxTabWidth = 0; - - rows.value.forEach((row) => { + members.value.forEach((row) => { if (row.tags) { const tabWidth = row.tags .map((tag) => tag.name.length * 20) @@ -487,7 +482,7 @@ const tagsColumnWidth = computed(() => { const emailsColumnWidth = computed(() => { let maxTabWidth = 0; - rows.value.forEach((row) => { + members.value.forEach((row) => { const tabWidth = row.emails .map((email) => (email ? email.length * 12 : 0)) .reduce((a, b) => a + b, 0); @@ -500,8 +495,10 @@ const emailsColumnWidth = computed(() => { return maxTabWidth; }); -const selectedRows = computed(() => store.getters['member/selectedRows']); -const pagination = computed(() => store.getters['member/pagination']); +const selectedRows = computed(() => selectedMembers.value); +const pagination = computed(() => filters.value.pagination); + +const tableWidth = ref(0); document.onmouseup = () => { // As soon as mouse is released, set scrollbar visibility @@ -511,15 +508,18 @@ document.onmouseup = () => { }; function doChangeSort(sorter) { - store.dispatch('member/doChangeSort', sorter); + filters.value.order = { + prop: sorter.prop, + order: sorter.order, + }; } function doChangePaginationCurrentPage(currentPage) { - store.dispatch('member/doChangePaginationCurrentPage', currentPage); + filters.value.pagination.page = currentPage; } function doChangePaginationPageSize(pageSize) { - store.dispatch('member/doChangePaginationPageSize', pageSize); + filters.value.pagination.perPage = pageSize; } function translate(key) { @@ -582,7 +582,7 @@ const trackEmailClick = () => { watch(table, (newValue) => { if (newValue) { - store.dispatch('member/doMountTable', table.value); + tableWidth.value = table.value; } // Add scroll events to table, it's not possible to access it from 'el-table' diff --git a/frontend/src/modules/member/components/list/member-list-toolbar.vue b/frontend/src/modules/member/components/list/member-list-toolbar.vue index f012f0a16e..b71b619d89 100644 --- a/frontend/src/modules/member/components/list/member-list-toolbar.vue +++ b/frontend/src/modules/member/components/list/member-list-toolbar.vue @@ -1,10 +1,10 @@ - + + diff --git a/frontend/src/modules/member/components/member-dropdown.vue b/frontend/src/modules/member/components/member-dropdown.vue index 6685f2ccd7..14d6dd66d5 100644 --- a/frontend/src/modules/member/components/member-dropdown.vue +++ b/frontend/src/modules/member/components/member-dropdown.vue @@ -166,12 +166,14 @@ diff --git a/frontend/src/modules/member/config/filters/activityType/config.ts b/frontend/src/modules/member/config/filters/activityType/config.ts index d0fcbe0d85..737cd3cff6 100644 --- a/frontend/src/modules/member/config/filters/activityType/config.ts +++ b/frontend/src/modules/member/config/filters/activityType/config.ts @@ -1,10 +1,9 @@ import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; import { CustomFilterConfig } from '@/shared/modules/filters/types/filterTypes/CustomFilterConfig'; -import ActivityTypeFilter from '@/modules/activity/config/filters/activityType/ActivityTypeFilter.vue'; +import ActivityTypeFilter from '@/modules/member/config/filters/activityType/ActivityTypeFilter.vue'; import { SelectFilterOptions, SelectFilterValue } from '@/shared/modules/filters/types/filterTypes/SelectFilterConfig'; import { queryUrlParserByType } from '@/shared/modules/filters/config/queryUrlParserByType'; import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; -import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; const activityType: CustomFilterConfig = { id: 'activityType', @@ -15,11 +14,14 @@ const activityType: CustomFilterConfig = { options: { }, queryUrlParser: queryUrlParserByType[FilterConfigType.SELECT], - itemLabelRenderer(value: SelectFilterValue, options: SelectFilterOptions): string { - return itemLabelRendererByType[FilterConfigType.SELECT]('Activity type', value, options); + itemLabelRenderer(value: SelectFilterValue, options: SelectFilterOptions, data: any): string { + console.log(value, options, data); + return itemLabelRendererByType[FilterConfigType.SELECT]('Activity type', value, data); }, apiFilterRenderer(value: SelectFilterValue): any[] { - return apiFilterRendererByType[FilterConfigType.SELECT]('activityTypes', value); + console.log(value); + return []; + // return apiFilterRendererByType[FilterConfigType.SELECT]('activityTypes', value); }, }; diff --git a/frontend/src/modules/member/config/filters/avgSentiment/config.ts b/frontend/src/modules/member/config/filters/avgSentiment/config.ts index 84c0e8f33e..87692bcf6e 100644 --- a/frontend/src/modules/member/config/filters/avgSentiment/config.ts +++ b/frontend/src/modules/member/config/filters/avgSentiment/config.ts @@ -5,7 +5,6 @@ import { MultiSelectFilterValue, } from '@/shared/modules/filters/types/filterTypes/MultiSelectFilterConfig'; import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; -import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; import options from './options'; const avgSentiment: MultiSelectFilterConfig = { @@ -19,8 +18,17 @@ const avgSentiment: MultiSelectFilterConfig = { itemLabelRenderer(value: MultiSelectFilterValue, options: MultiSelectFilterOptions): string { return itemLabelRendererByType[FilterConfigType.MULTISELECT]('Avg. sentiment', value, options); }, - apiFilterRenderer(value: MultiSelectFilterValue): any[] { - return apiFilterRendererByType[FilterConfigType.MULTISELECT]('averageSentiment', value); + apiFilterRenderer({ value, include }: MultiSelectFilterValue): any[] { + const filter = { + or: [ + ...(value.includes('positive') ? [{ averageSentiment: { gte: 67 } }] : []), + ...(value.includes('negative') ? [{ averageSentiment: { lt: 33 } }] : []), + ...(value.includes('neutral') ? [{ averageSentiment: { between: [33, 67] } }] : []), + ], + }; + return [ + (include ? filter : { not: filter }), + ]; }, }; diff --git a/frontend/src/modules/member/config/filters/engagementLevel/config.ts b/frontend/src/modules/member/config/filters/engagementLevel/config.ts index c7122cc08e..d86980d919 100644 --- a/frontend/src/modules/member/config/filters/engagementLevel/config.ts +++ b/frontend/src/modules/member/config/filters/engagementLevel/config.ts @@ -5,7 +5,6 @@ import { MultiSelectFilterValue, } from '@/shared/modules/filters/types/filterTypes/MultiSelectFilterConfig'; import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; -import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; import options from './options'; const engagementLevel: MultiSelectFilterConfig = { @@ -19,8 +18,21 @@ const engagementLevel: MultiSelectFilterConfig = { itemLabelRenderer(value: MultiSelectFilterValue, options: MultiSelectFilterOptions): string { return itemLabelRendererByType[FilterConfigType.MULTISELECT]('Engagement level', value, options); }, - apiFilterRenderer(value: MultiSelectFilterValue): any[] { - return apiFilterRendererByType[FilterConfigType.MULTISELECT]('score', value); + apiFilterRenderer({ value, include }: MultiSelectFilterValue): any[] { + const filter = { + score: { + in: [ + ...(value.includes('silent') ? [0, 1] : []), + ...(value.includes('quiet') ? [2, 3] : []), + ...(value.includes('engaged') ? [4, 5, 6] : []), + ...(value.includes('fan') ? [7, 8] : []), + ...(value.includes('ultra') ? [9, 10] : []), + ], + }, + }; + return [ + (include ? filter : { not: filter }), + ]; }, }; diff --git a/frontend/src/modules/member/config/filters/tags/config.ts b/frontend/src/modules/member/config/filters/tags/config.ts index 2b2190a9b8..4bea046f36 100644 --- a/frontend/src/modules/member/config/filters/tags/config.ts +++ b/frontend/src/modules/member/config/filters/tags/config.ts @@ -1,26 +1,38 @@ import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; -import { - MultiSelectFilterConfig, - MultiSelectFilterOptions, - MultiSelectFilterValue, -} from '@/shared/modules/filters/types/filterTypes/MultiSelectFilterConfig'; import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; -import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; +import { + MultiSelectAsyncFilterConfig, MultiSelectAsyncFilterOptions, + MultiSelectAsyncFilterValue, +} from '@/shared/modules/filters/types/filterTypes/MultiSelectAsyncFilterConfig'; +import { TagService } from '@/modules/tag/tag-service'; -const tags: MultiSelectFilterConfig = { +const tags: MultiSelectAsyncFilterConfig = { id: 'tags', label: 'Tags', iconClass: 'ri-bookmark-line', - type: FilterConfigType.MULTISELECT, + type: FilterConfigType.MULTISELECT_ASYNC, options: { - // TODO: load this options remote - options: [], + remoteMethod: (query) => TagService.listAutocomplete(query, 10) + .then((data: any[]) => data.map((tag) => ({ + label: tag.label, + value: tag.id, + }))), + remotePopulateItems: (ids: string[]) => TagService.list({ + ids, + }, null, ids.length, 0) + .then(({ rows }: any) => rows.map((tag: any) => ({ + label: tag.name, + value: tag.id, + }))), }, - itemLabelRenderer(value: MultiSelectFilterValue, options: MultiSelectFilterOptions): string { - return itemLabelRendererByType[FilterConfigType.MULTISELECT]('Active on', value, options); + itemLabelRenderer(value: MultiSelectAsyncFilterValue, options: MultiSelectAsyncFilterOptions, data: any): string { + return itemLabelRendererByType[FilterConfigType.MULTISELECT_ASYNC]('Tags', value, options, data); }, - apiFilterRenderer(value: MultiSelectFilterValue): any[] { - return apiFilterRendererByType[FilterConfigType.MULTISELECT]('tags', value); + apiFilterRenderer({ value, include }: MultiSelectAsyncFilterValue): any[] { + const filter = { tags: value }; + return [ + (include ? filter : { not: filter }), + ]; }, }; diff --git a/frontend/src/modules/member/config/saved-views/views/all-members.ts b/frontend/src/modules/member/config/saved-views/views/all-members.ts index 9fff32ed44..360981b023 100644 --- a/frontend/src/modules/member/config/saved-views/views/all-members.ts +++ b/frontend/src/modules/member/config/saved-views/views/all-members.ts @@ -7,7 +7,7 @@ const allMembers: SavedView = { search: '', relation: 'and', order: { - prop: 'createdBy', + prop: 'lastActive', order: 'descending', }, settings: { diff --git a/frontend/src/modules/member/config/saved-views/views/influential.ts b/frontend/src/modules/member/config/saved-views/views/influential.ts index b4eb564988..8562d9e033 100644 --- a/frontend/src/modules/member/config/saved-views/views/influential.ts +++ b/frontend/src/modules/member/config/saved-views/views/influential.ts @@ -7,7 +7,7 @@ const influential: SavedView = { search: '', relation: 'and', order: { - prop: 'lastActivity', + prop: 'lastActive', order: 'descending', }, settings: { diff --git a/frontend/src/modules/member/config/saved-views/views/most-engaged.ts b/frontend/src/modules/member/config/saved-views/views/most-engaged.ts index af3fd52cf7..b8e4157b0d 100644 --- a/frontend/src/modules/member/config/saved-views/views/most-engaged.ts +++ b/frontend/src/modules/member/config/saved-views/views/most-engaged.ts @@ -7,7 +7,7 @@ const mostEngaged: SavedView = { search: '', relation: 'and', order: { - prop: 'lastActivity', + prop: 'lastActive', order: 'descending', }, settings: { diff --git a/frontend/src/modules/member/config/saved-views/views/new-and-active.ts b/frontend/src/modules/member/config/saved-views/views/new-and-active.ts index 4745ee5d40..1b56ff6dea 100644 --- a/frontend/src/modules/member/config/saved-views/views/new-and-active.ts +++ b/frontend/src/modules/member/config/saved-views/views/new-and-active.ts @@ -8,7 +8,7 @@ const newAndActive: SavedView = { search: '', relation: 'and', order: { - prop: 'createdBy', + prop: 'lastActive', order: 'descending', }, settings: { diff --git a/frontend/src/modules/member/config/saved-views/views/slipping-away.ts b/frontend/src/modules/member/config/saved-views/views/slipping-away.ts index 098e818f24..99c8eb5515 100644 --- a/frontend/src/modules/member/config/saved-views/views/slipping-away.ts +++ b/frontend/src/modules/member/config/saved-views/views/slipping-away.ts @@ -8,7 +8,7 @@ const slippingAway: SavedView = { search: '', relation: 'and', order: { - prop: 'lastActivity', + prop: 'lastActive', order: 'descending', }, settings: { diff --git a/frontend/src/modules/member/config/saved-views/views/team-members.ts b/frontend/src/modules/member/config/saved-views/views/team-members.ts index d7e8e092e9..a55c633067 100644 --- a/frontend/src/modules/member/config/saved-views/views/team-members.ts +++ b/frontend/src/modules/member/config/saved-views/views/team-members.ts @@ -7,7 +7,7 @@ const teamMembers: SavedView = { search: '', relation: 'and', order: { - prop: 'lastActivity', + prop: 'lastActive', order: 'descending', }, settings: { diff --git a/frontend/src/modules/member/member-service.js b/frontend/src/modules/member/member-service.js index 0e248ed5d9..d488cbb7e0 100644 --- a/frontend/src/modules/member/member-service.js +++ b/frontend/src/modules/member/member-service.js @@ -150,6 +150,30 @@ export class MemberService { return response.data; } + static async listMembers( + body, + countOnly = false, + ) { + const sampleTenant = AuthCurrentTenant.getSampleTenantData(); + const tenantId = sampleTenant?.id || AuthCurrentTenant.get(); + + const response = await authAxios.post( + `/tenant/${tenantId}/member/query`, + { + ...body, + countOnly, + }, + { + headers: { + 'x-crowd-api-version': '1', + Authorization: sampleTenant?.token, + }, + }, + ); + + return response.data; + } + static async listActive({ platform, isTeamMember, diff --git a/frontend/src/modules/member/pages/member-form-page.vue b/frontend/src/modules/member/pages/member-form-page.vue index 9f01f4e0ab..0b721d9aef 100644 --- a/frontend/src/modules/member/pages/member-form-page.vue +++ b/frontend/src/modules/member/pages/member-form-page.vue @@ -121,6 +121,7 @@ import ConfirmDialog from '@/shared/dialog/confirm-dialog'; import getCustomAttributes from '@/shared/fields/get-custom-attributes'; import getAttributesModel from '@/shared/attributes/get-attributes-model'; import getParsedAttributes from '@/shared/attributes/get-parsed-attributes'; +import { useMemberStore } from '@/modules/member/store/pinia'; const LoaderIcon = h( 'i', @@ -137,6 +138,7 @@ const ArrowPrevIcon = h( [], ); +const { getMemberCustomAttributes } = useMemberStore(); const router = useRouter(); const route = useRoute(); const store = useStore(); @@ -249,7 +251,7 @@ onBeforeRouteLeave((to) => { onMounted(async () => { // Fetch custom attributes on mount - await store.dispatch('member/doFetchCustomAttributes'); + await getMemberCustomAttributes(); if (isEditPage.value) { const { id } = route.params; diff --git a/frontend/src/modules/member/pages/member-list-page.vue b/frontend/src/modules/member/pages/member-list-page.vue index 93e0e4cfa8..e3383c9cb3 100644 --- a/frontend/src/modules/member/pages/member-list-page.vue +++ b/frontend/src/modules/member/pages/member-list-page.vue @@ -61,11 +61,11 @@ :custom-config="customAttributes" @fetch="fetch($event)" /> - - - - - +
@@ -81,15 +81,13 @@ import { MemberPermissions } from '@/modules/member/member-permissions'; import { mapGetters } from '@/shared/vuex/vuex.helpers'; import { FilterQuery } from '@/shared/modules/filters/types/FilterQuery'; import CrSavedViews from '@/shared/modules/saved-views/components/SavedViews.vue'; +import AppMemberListTable from '@/modules/member/components/list/member-list-table.vue'; import { memberFilters, memberSearchFilter } from '../config/filters/main'; import { memberSavedViews, memberViews } from '../config/saved-views/main'; -// import MemberListFilter from '@/modules/member/components/list/member-list-filter.vue'; -// import MemberListTable from '@/modules/member/components/list/member-list-table.vue'; -// import MemberListTabs from '@/modules/member/components/list/member-list-tabs.vue'; const memberStore = useMemberStore(); -const { getMemberCustomAttributes } = memberStore; -const { filters, customAttributes } = storeToRefs(memberStore); +const { getMemberCustomAttributes, fetchMembers } = memberStore; +const { filters, customAttributes, savedFilterBody } = storeToRefs(memberStore); const membersCount = ref(0); const membersToMergeCount = ref(0); @@ -116,11 +114,13 @@ const isEditLockedForSampleData = computed(() => new MemberPermissions( const fetchMembersToMergeCount = () => { MemberService.fetchMergeSuggestions(1, 0) - .then(({ count }) => { + .then(({ count }: any) => { membersToMergeCount.value = count; }); }; +const loading = ref(true); + const doGetMembersCount = () => { (MemberService.list( {}, @@ -135,11 +135,32 @@ const doGetMembersCount = () => { }); }; +const showLoading = (filter: any, body: any): boolean => { + const saved: any = { ...savedFilterBody.value }; + delete saved.offset; + delete saved.limit; + delete saved.orderBy; + const compare = { + ...body, + filter, + }; + return JSON.stringify(saved) !== JSON.stringify(compare); +}; + const fetch = ({ filter, offset, limit, orderBy, body, }: FilterQuery) => { - console.log(filter, offset, limit, orderBy, body); - // TODO: fetch members + loading.value = showLoading(filter, body); + fetchMembers({ + ...body, + filter, + offset, + limit, + orderBy, + }) + .finally(() => { + loading.value = false; + }); }; onMounted(() => { diff --git a/frontend/src/modules/member/pages/member-view-page.vue b/frontend/src/modules/member/pages/member-view-page.vue index cec2648eda..f6f2e553f4 100644 --- a/frontend/src/modules/member/pages/member-view-page.vue +++ b/frontend/src/modules/member/pages/member-view-page.vue @@ -76,6 +76,7 @@ import AppMemberViewAside from '@/modules/member/components/view/member-view-asi import AppMemberViewNotes from '@/modules/member/components/view/member-view-notes.vue'; import AppMemberViewContributions from '@/modules/member/components/view/member-view-contributions.vue'; import AppMemberViewTasks from '@/modules/member/components/view/member-view-tasks.vue'; +import { useMemberStore } from '@/modules/member/store/pinia'; const store = useStore(); const props = defineProps({ @@ -86,6 +87,7 @@ const props = defineProps({ }); const { currentTenant, currentUser } = mapGetters('auth'); +const { getMemberCustomAttributes } = useMemberStore(); const member = computed(() => store.getters['member/find'](props.id) || {}); @@ -113,7 +115,7 @@ onMounted(async () => { Object.keys(store.state.member.customAttributes) .length === 0 ) { - await store.dispatch('member/doFetchCustomAttributes'); + await getMemberCustomAttributes(); } loading.value = false; }); diff --git a/frontend/src/modules/member/store/pinia/actions.ts b/frontend/src/modules/member/store/pinia/actions.ts index 69eac2c5b0..f38b80184f 100644 --- a/frontend/src/modules/member/store/pinia/actions.ts +++ b/frontend/src/modules/member/store/pinia/actions.ts @@ -3,10 +3,29 @@ import { customAttributesService } from '@/shared/modules/filters/services/custo import { FilterCustomAttribute } from '@/shared/modules/filters/types/FilterCustomAttribute'; import { FilterConfig } from '@/shared/modules/filters/types/FilterConfig'; import { MemberState } from '@/modules/member/store/pinia/state'; +import { Pagination } from '@/shared/types/Pagination'; +import { Member } from '@/modules/member/types/Member'; const { buildFilterFromAttributes } = customAttributesService(); export default { + fetchMembers(this: MemberState, body: any, reload = false): Promise> { + const mappedBody = reload ? this.savedFilterBody : body; + this.selectedMembers = []; + this.members = []; + return MemberService.listMembers(mappedBody) + .then((data: Pagination) => { + this.members = data.rows; + this.totalMembers = data.count; + this.savedFilterBody = mappedBody; + return Promise.resolve(data); + }) + .catch((err) => { + this.members = []; + this.totalMembers = 0; + return Promise.reject(err); + }); + }, getMemberCustomAttributes(this: MemberState): Promise> { return MemberService.fetchCustomAttributes() .then(({ rows }: {rows: FilterCustomAttribute[]}) => { diff --git a/frontend/src/modules/member/store/pinia/state.ts b/frontend/src/modules/member/store/pinia/state.ts index 49cf94d68b..70ebde4647 100644 --- a/frontend/src/modules/member/store/pinia/state.ts +++ b/frontend/src/modules/member/store/pinia/state.ts @@ -1,11 +1,16 @@ import { Filter, FilterConfig } from '@/shared/modules/filters/types/FilterConfig'; +import { Member } from '@/modules/member/types/Member'; export interface MemberState { filters: Filter, - customAttributes: Record + savedFilterBody: any, + customAttributes: Record, + members: Member[]; + selectedMembers: Member[]; + totalMembers: number; } -export default () => ({ +const state: MemberState = { filters: { search: '', relation: 'and', @@ -14,9 +19,15 @@ export default () => ({ perPage: 20, }, order: { - prop: 'createdBy', + prop: 'lastActive', order: 'descending', }, } as Filter, + savedFilterBody: {}, customAttributes: {}, -} as MemberState); + members: [], + totalMembers: 0, + selectedMembers: [], +}; + +export default () => state; diff --git a/frontend/src/modules/member/types/Member.ts b/frontend/src/modules/member/types/Member.ts new file mode 100644 index 0000000000..93ba3364cf --- /dev/null +++ b/frontend/src/modules/member/types/Member.ts @@ -0,0 +1,56 @@ +export interface MemberAttribute { + default: string; + custom: string; + github?: string; + twitter?: string; +} + +export interface MemberContribution { + firstCommitDate: string; + id: number; + lastCommitDate: string; + numberCommits: number; + summary: string; + topics: string[]; + url: string; +} + +export interface MemberReach { + total: number; + github: number; + twitter: number; +} + +export interface MemberTag { + id: string; + name: string; +} + +export interface Member { + activeDaysCount: string; + activeOn: string[] | null; + activityCount: string; + activityTypes:string[] | null; + attributes: Record + averageSentiment: string | null; + contributions: MemberContribution[] + createdAt: string; + displayName: string; + emails: string[] + id: string; + identities: string[] | null; + importHash: string | null; + joinedAt: string; + lastActive: string | null; + lastEnriched: string | null; + noMergeIds: string[] | null; + numberOfOpenSourceContributions: number | null; + organizations: any[]; + reach:MemberReach; + score: number; + tags: MemberTag[]; + tenantId: string; + toMergeIds: string[] | null; + updatedAt: string; + username: Record +} diff --git a/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue index 2febe7baf3..89baee4091 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue @@ -100,10 +100,9 @@ const include = computed({ }, }); -watch(() => props.modelValue.value, (value: string[]) => { - if (value.length !== data.value.selected?.length) { - console.log(value); - props.remotePopulateItems(value) +watch(() => props.modelValue.value, (value?: string[]) => { + if (value?.length !== data.value.selected?.length) { + props.remotePopulateItems(value || []) .then((options) => { data.value.selected = options; }); diff --git a/frontend/src/shared/modules/filters/config/apiFilterRenderer/multiselect.filter.renderer.ts b/frontend/src/shared/modules/filters/config/apiFilterRenderer/multiselect.filter.renderer.ts index 3369c78809..dbf210251a 100644 --- a/frontend/src/shared/modules/filters/config/apiFilterRenderer/multiselect.filter.renderer.ts +++ b/frontend/src/shared/modules/filters/config/apiFilterRenderer/multiselect.filter.renderer.ts @@ -1,15 +1,15 @@ import { MultiSelectFilterValue } from '@/shared/modules/filters/types/filterTypes/MultiSelectFilterConfig'; export const multiSelectApiFilterRenderer = (property: string, { value, include }: MultiSelectFilterValue): any[] => { - const filter = { contains: value }; + const filter = { + [property]: { + contains: value, + }, + }; return [ - { - [property]: (include ? filter : { - not: { - filter, - }, - }), - }, + (include ? filter : { + not: filter, + }), ]; }; diff --git a/frontend/src/shared/modules/filters/config/itemLabelRenderer/multiselectasync.label.renderer.ts b/frontend/src/shared/modules/filters/config/itemLabelRenderer/multiselectasync.label.renderer.ts index 6734a23906..7774475c6e 100644 --- a/frontend/src/shared/modules/filters/config/itemLabelRenderer/multiselectasync.label.renderer.ts +++ b/frontend/src/shared/modules/filters/config/itemLabelRenderer/multiselectasync.label.renderer.ts @@ -14,7 +14,7 @@ export const multiSelectAsyncItemLabelRenderer = ( const charLimit = 30; - const labelObject = (data.selected as MultiSelectAsyncFilterOption[]) + const labelObject = ((data?.selected || []) as MultiSelectAsyncFilterOption[]) .filter((option) => value.includes(option.value)) .reduce((obj, option) => { // eslint-disable-next-line no-param-reassign diff --git a/frontend/src/shared/modules/filters/services/filter-query.service.ts b/frontend/src/shared/modules/filters/services/filter-query.service.ts index 7adf7c5b88..1a5c4bdc7d 100644 --- a/frontend/src/shared/modules/filters/services/filter-query.service.ts +++ b/frontend/src/shared/modules/filters/services/filter-query.service.ts @@ -1,4 +1,4 @@ -import { Filter, FilterConfig } from '@/shared/modules/filters/types/FilterConfig'; +import { Filter, FilterConfig, FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; import { queryUrlParserByType } from '@/shared/modules/filters/config/queryUrlParserByType'; import { CustomFilterConfig } from '@/shared/modules/filters/types/filterTypes/CustomFilterConfig'; import { SavedViewsConfig } from '@/shared/modules/saved-views/types/SavedViewsConfig'; @@ -32,7 +32,7 @@ export const filterQueryService = () => { }); } else if (key in config) { const { type } = config[key]; - const queryUrlParser = queryUrlParserByType[type] ?? (config[key] as CustomFilterConfig).queryUrlParser; + const queryUrlParser = type === FilterConfigType.CUSTOM ? (config[key] as CustomFilterConfig).queryUrlParser : queryUrlParserByType[type]; if (queryUrlParser) { object[key] = queryUrlParser(object[key]); } @@ -52,21 +52,24 @@ export const filterQueryService = () => { // Prepares query object to be only one level nested for query params to be more readable function setQuery(value: Filter) { const query: Record = {}; - Object.entries(value).forEach(([key, filterValue]) => { - if (typeof filterValue === 'object') { - Object.entries(filterValue).forEach(([subKey, subFilterValue]) => { - const value = setQueryValue(subFilterValue); + if (value) { + Object.entries(value).forEach(([key, filterValue]) => { + if (typeof filterValue === 'object') { + Object.entries(filterValue).forEach(([subKey, subFilterValue]) => { + const value = setQueryValue(subFilterValue); + if (value !== undefined) { + query[`${key}.${subKey}`] = value; + } + }); + } else { + const value = setQueryValue(filterValue); if (value !== undefined) { - query[`${key}.${subKey}`] = value; + query[key] = value; } - }); - } else { - const value = setQueryValue(filterValue); - if (value !== undefined) { - query[key] = value; } - } - }); + }); + } + return query; } diff --git a/frontend/src/shared/pagination/pagination-sorter.vue b/frontend/src/shared/pagination/pagination-sorter.vue index 6a1c25a9c7..f40a7c2cf4 100644 --- a/frontend/src/shared/pagination/pagination-sorter.vue +++ b/frontend/src/shared/pagination/pagination-sorter.vue @@ -40,11 +40,15 @@ diff --git a/frontend/src/shared/types/Pagination.ts b/frontend/src/shared/types/Pagination.ts new file mode 100644 index 0000000000..e4faec35c2 --- /dev/null +++ b/frontend/src/shared/types/Pagination.ts @@ -0,0 +1,6 @@ +export interface Pagination{ + count: number; + limit: number; + offset: number; + rows: T[] +} From f330b467e320d88a05bc64dd12d261b8eb4dc3bf Mon Sep 17 00:00:00 2001 From: Gasper Grom Date: Wed, 7 Jun 2023 09:21:25 +0200 Subject: [PATCH 2/3] Bug fixes & improvements --- .../database/repositories/tagRepository.ts | 2 +- .../organizationEnrichmentService.ts | 2 +- frontend/src/modules/auth/auth-socket.js | 2 +- .../components/list/member-list-table.vue | 12 +++- .../components/list/member-list-toolbar.vue | 6 +- .../member/components/member-dropdown.vue | 8 +-- .../config/filters/activityType/config.ts | 6 +- .../modules/member/pages/member-list-page.vue | 26 ++++--- .../src/modules/member/store/pinia/actions.ts | 2 +- .../components/filterTypes/BooleanFilter.vue | 2 +- .../components/filterTypes/DateFilter.vue | 2 +- .../filterTypes/MultiSelectAsyncFilter.vue | 3 +- .../filterTypes/MultiSelectFilter.vue | 2 +- .../components/filterTypes/NumberFilter.vue | 2 +- .../components/filterTypes/SelectFilter.vue | 2 +- .../components/filterTypes/StringFilter.vue | 2 +- .../multiselect/MultiSelectTagsFilter.vue | 71 +++++++++---------- .../multiselect/FilterMultiSelectOption.vue | 2 +- .../number.filter.renderer.ts | 4 +- .../string.filter.renderer.ts | 4 +- .../shared/pagination/pagination-sorter.vue | 22 +++--- 21 files changed, 90 insertions(+), 94 deletions(-) diff --git a/backend/src/database/repositories/tagRepository.ts b/backend/src/database/repositories/tagRepository.ts index 42fc97e5e4..aa34884276 100644 --- a/backend/src/database/repositories/tagRepository.ts +++ b/backend/src/database/repositories/tagRepository.ts @@ -202,7 +202,7 @@ class TagRepository { advancedFilter.and.push({ or: filter.ids.map((id) => ({ id, - })) + })), }) } diff --git a/backend/src/services/premium/enrichment/organizationEnrichmentService.ts b/backend/src/services/premium/enrichment/organizationEnrichmentService.ts index 813e1c51ce..5c8e7ce945 100644 --- a/backend/src/services/premium/enrichment/organizationEnrichmentService.ts +++ b/backend/src/services/premium/enrichment/organizationEnrichmentService.ts @@ -148,7 +148,7 @@ export default class OrganizationEnrichmentService extends LoggingBase { ) { acc[platform] = { handle, - [platform === PlatformType.TWITTER? "site": "url"]: social, + [platform === PlatformType.TWITTER ? 'site' : 'url']: social, } } return acc diff --git a/frontend/src/modules/auth/auth-socket.js b/frontend/src/modules/auth/auth-socket.js index 470509abab..9676e5dab5 100644 --- a/frontend/src/modules/auth/auth-socket.js +++ b/frontend/src/modules/auth/auth-socket.js @@ -112,7 +112,7 @@ export const connectSocket = (token) => { if (currentTenant.value.id === parsed.tenantId) { // Refresh list page const { fetchMembers } = useMemberStore(); - await fetchMembers(null, true); + await fetchMembers({ reload: true }); } } }); diff --git a/frontend/src/modules/member/components/list/member-list-table.vue b/frontend/src/modules/member/components/list/member-list-table.vue index cdaea40980..38d1ed5982 100644 --- a/frontend/src/modules/member/components/list/member-list-table.vue +++ b/frontend/src/modules/member/components/list/member-list-table.vue @@ -41,6 +41,7 @@ :total="totalMembers" :current-page="pagination.page" :has-page-counter="false" + :export="doExport" module="member" position="top" @change-sorter="doChangePaginationPageSize" @@ -406,6 +407,7 @@ import { formatDateToTimeAgo } from '@/utils/date'; import { formatNumberToCompact, formatNumber } from '@/utils/number'; import { useMemberStore } from '@/modules/member/store/pinia'; import { storeToRefs } from 'pinia'; +import { MemberService } from '@/modules/member/member-service'; import AppMemberBadge from '../member-badge.vue'; import AppMemberDropdown from '../member-dropdown.vue'; import AppMemberIdentities from '../member-identities.vue'; @@ -441,7 +443,7 @@ const props = defineProps({ const memberStore = useMemberStore(); const { - members, totalMembers, filters, selectedMembers, + members, totalMembers, filters, selectedMembers, savedFilterBody, } = storeToRefs(memberStore); const defaultSort = computed(() => ({ @@ -606,6 +608,14 @@ watch(table, (newValue) => { } }); +const doExport = () => MemberService.export( + savedFilterBody.value.filter, + savedFilterBody.value.orderBy, + 0, + null, + false, +); + onMounted(async () => { if (store.state.integration.count === 0) { await store.dispatch('integration/doFetch'); diff --git a/frontend/src/modules/member/components/list/member-list-toolbar.vue b/frontend/src/modules/member/components/list/member-list-toolbar.vue index b71b619d89..c097e9eb53 100644 --- a/frontend/src/modules/member/components/list/member-list-toolbar.vue +++ b/frontend/src/modules/member/components/list/member-list-toolbar.vue @@ -209,7 +209,7 @@ const handleMergeMembers = () => { return MemberService.merge(firstMember, secondMember) .then(() => { Message.success('Members merged successfuly'); - fetchMembers({}, true); + fetchMembers({ reload: true }); }) .catch(() => { Message.error('Error merging members'); @@ -229,7 +229,7 @@ const doDestroyAllWithConfirm = () => ConfirmDialog({ const ids = selectedMembers.value.map((m) => m.id); return MemberService.destroyAll(ids); }) - .then(() => fetchMembers(null, true)); + .then(() => fetchMembers({ reload: true })); const handleDoExport = async () => { const ids = selectedMembers.value.map((i) => i.id); @@ -299,7 +299,7 @@ const doMarkAsTeamMember = (value) => { }, }))) .then(() => { - fetchMembers(null, true); + fetchMembers({ reload: true }); Message.success( `Member${ selectedMembers.value.length > 1 ? 's' : '' diff --git a/frontend/src/modules/member/components/member-dropdown.vue b/frontend/src/modules/member/components/member-dropdown.vue index 14d6dd66d5..aa552d202b 100644 --- a/frontend/src/modules/member/components/member-dropdown.vue +++ b/frontend/src/modules/member/components/member-dropdown.vue @@ -265,10 +265,10 @@ export default { }, }, }); - await this.fetchMembers(null, true); + await this.fetchMembers({ reload: true }); Message.success('Member updated successfully'); if (this.$route.name === 'member') { - await this.fetchMembers(null, true); + await this.fetchMembers({ reload: true }); } else { this.doFind(command.member.id); } @@ -281,10 +281,10 @@ export default { }, }, }); - await this.fetchMembers(null, true); + await this.fetchMembers({ reload: true }); Message.success('Member updated successfully'); if (this.$route.name === 'member') { - await this.fetchMembers(null, true); + await this.fetchMembers({ reload: true }); } else { this.doFind(command.member.id); } diff --git a/frontend/src/modules/member/config/filters/activityType/config.ts b/frontend/src/modules/member/config/filters/activityType/config.ts index 737cd3cff6..3b9bd492ff 100644 --- a/frontend/src/modules/member/config/filters/activityType/config.ts +++ b/frontend/src/modules/member/config/filters/activityType/config.ts @@ -4,6 +4,7 @@ import ActivityTypeFilter from '@/modules/member/config/filters/activityType/Act import { SelectFilterOptions, SelectFilterValue } from '@/shared/modules/filters/types/filterTypes/SelectFilterConfig'; import { queryUrlParserByType } from '@/shared/modules/filters/config/queryUrlParserByType'; import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; const activityType: CustomFilterConfig = { id: 'activityType', @@ -15,13 +16,10 @@ const activityType: CustomFilterConfig = { }, queryUrlParser: queryUrlParserByType[FilterConfigType.SELECT], itemLabelRenderer(value: SelectFilterValue, options: SelectFilterOptions, data: any): string { - console.log(value, options, data); return itemLabelRendererByType[FilterConfigType.SELECT]('Activity type', value, data); }, apiFilterRenderer(value: SelectFilterValue): any[] { - console.log(value); - return []; - // return apiFilterRendererByType[FilterConfigType.SELECT]('activityTypes', value); + return apiFilterRendererByType[FilterConfigType.SELECT]('activityTypes', value); }, }; diff --git a/frontend/src/modules/member/pages/member-list-page.vue b/frontend/src/modules/member/pages/member-list-page.vue index e3383c9cb3..aef705560e 100644 --- a/frontend/src/modules/member/pages/member-list-page.vue +++ b/frontend/src/modules/member/pages/member-list-page.vue @@ -63,7 +63,7 @@ />
@@ -122,14 +122,10 @@ const fetchMembersToMergeCount = () => { const loading = ref(true); const doGetMembersCount = () => { - (MemberService.list( - {}, - '', - 1, - 0, - false, - true, - ) as Promise) + (MemberService.listMembers({ + limit: 1, + offset: 0, + }, true) as Promise) .then(({ count }) => { membersCount.value = count; }); @@ -152,11 +148,13 @@ const fetch = ({ }: FilterQuery) => { loading.value = showLoading(filter, body); fetchMembers({ - ...body, - filter, - offset, - limit, - orderBy, + body: { + ...body, + filter, + offset, + limit, + orderBy, + }, }) .finally(() => { loading.value = false; diff --git a/frontend/src/modules/member/store/pinia/actions.ts b/frontend/src/modules/member/store/pinia/actions.ts index f38b80184f..fabd5ffbb7 100644 --- a/frontend/src/modules/member/store/pinia/actions.ts +++ b/frontend/src/modules/member/store/pinia/actions.ts @@ -9,7 +9,7 @@ import { Member } from '@/modules/member/types/Member'; const { buildFilterFromAttributes } = customAttributesService(); export default { - fetchMembers(this: MemberState, body: any, reload = false): Promise> { + fetchMembers(this: MemberState, { body = {}, reload = false } :{ body?: any, reload?: boolean }): Promise> { const mappedBody = reload ? this.savedFilterBody : body; this.selectedMembers = []; this.members = []; diff --git a/frontend/src/shared/modules/filters/components/filterTypes/BooleanFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/BooleanFilter.vue index b6681c76d9..39e0396dcb 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/BooleanFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/BooleanFilter.vue @@ -54,7 +54,7 @@ const rules: any = { useVuelidate(rules, form); onMounted(() => { - if (!form.value || Object.keys(form.value).length === 0) { + if (!form.value || Object.keys(form.value).length < 2) { form.value = defaultForm; } }); diff --git a/frontend/src/shared/modules/filters/components/filterTypes/DateFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/DateFilter.vue index 2b1f24af21..0a372861ef 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/DateFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/DateFilter.vue @@ -83,7 +83,7 @@ watch(() => form.value.operator, (operator, previousOperator) => { }); onMounted(() => { - if (!form.value || Object.keys(form.value).length === 0) { + if (!form.value || Object.keys(form.value).length < 3) { form.value = defaultForm; } }); diff --git a/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue index 89baee4091..f416dd6582 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue @@ -17,6 +17,7 @@ class="filter-multiselect" popper-class="filter-multiselect-popper" :loading="loading" + no-data-text="No results" > { onMounted(() => { searchOptions(''); - if (!props.modelValue.value || Object.keys(props.modelValue.value).length === 0) { + if (!props.modelValue.value || Object.keys(props.modelValue.value).length < 2) { emit('update:modelValue', defaultForm); } if (!data.value.selected) { diff --git a/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectFilter.vue index a802440089..2f3728864b 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectFilter.vue @@ -61,7 +61,7 @@ const rules: any = { useVuelidate(rules, form); onMounted(() => { - if (!form.value || Object.keys(form.value).length === 0) { + if (!form.value || Object.keys(form.value).length < 2) { form.value = defaultForm; } }); diff --git a/frontend/src/shared/modules/filters/components/filterTypes/NumberFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/NumberFilter.vue index 735cb9f32e..5414fb5a0f 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/NumberFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/NumberFilter.vue @@ -128,7 +128,7 @@ watch(() => form.value.operator, (operator) => { }); onMounted(() => { - if (!form.value || Object.keys(form.value).length === 0) { + if (!form.value || Object.keys(form.value).length < 3>) { form.value = defaultForm; } }); diff --git a/frontend/src/shared/modules/filters/components/filterTypes/SelectFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/SelectFilter.vue index 27096853a0..1cc955d80b 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/SelectFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/SelectFilter.vue @@ -79,7 +79,7 @@ const rules: any = { useVuelidate(rules, form); onMounted(() => { - if (!form.value || Object.keys(form.value).length === 0) { + if (!form.value || Object.keys(form.value).length < 2) { form.value = defaultForm; } }); diff --git a/frontend/src/shared/modules/filters/components/filterTypes/StringFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/StringFilter.vue index 22c7d95d76..a5c4127bb8 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/StringFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/StringFilter.vue @@ -62,7 +62,7 @@ const rules: any = { useVuelidate(rules, form); onMounted(() => { - if (!form.value || Object.keys(form.value).length === 0) { + if (!form.value || Object.keys(form.value).length < 3) { form.value = defaultForm; } }); diff --git a/frontend/src/shared/modules/filters/components/filterTypes/multiselect/MultiSelectTagsFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/multiselect/MultiSelectTagsFilter.vue index 2c13301613..3128b62f8c 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/multiselect/MultiSelectTagsFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/multiselect/MultiSelectTagsFilter.vue @@ -1,40 +1,39 @@ diff --git a/frontend/src/shared/modules/filters/components/partials/multiselect/FilterMultiSelectOption.vue b/frontend/src/shared/modules/filters/components/partials/multiselect/FilterMultiSelectOption.vue index 358d4d74b6..41f884eb12 100644 --- a/frontend/src/shared/modules/filters/components/partials/multiselect/FilterMultiSelectOption.vue +++ b/frontend/src/shared/modules/filters/components/partials/multiselect/FilterMultiSelectOption.vue @@ -34,7 +34,7 @@ const form = computed({ }, }); -const selected = computed(() => form.value.includes(props.value)); +const selected = computed(() => form.value?.includes(props.value)); const selectOption = () => { if (selected.value) { diff --git a/frontend/src/shared/modules/filters/config/apiFilterRenderer/number.filter.renderer.ts b/frontend/src/shared/modules/filters/config/apiFilterRenderer/number.filter.renderer.ts index 23344437b4..9c1e64296e 100644 --- a/frontend/src/shared/modules/filters/config/apiFilterRenderer/number.filter.renderer.ts +++ b/frontend/src/shared/modules/filters/config/apiFilterRenderer/number.filter.renderer.ts @@ -12,9 +12,7 @@ export const numberApiFilterRenderer = (property: string, { return [ { [property]: (include ? filter : { - not: { - filter, - }, + not: filter, }), }, ]; diff --git a/frontend/src/shared/modules/filters/config/apiFilterRenderer/string.filter.renderer.ts b/frontend/src/shared/modules/filters/config/apiFilterRenderer/string.filter.renderer.ts index 38e331fe4d..7bb7e637cb 100644 --- a/frontend/src/shared/modules/filters/config/apiFilterRenderer/string.filter.renderer.ts +++ b/frontend/src/shared/modules/filters/config/apiFilterRenderer/string.filter.renderer.ts @@ -14,9 +14,7 @@ export const stringApiFilterRenderer = (property: string, { include, value, oper return [ { [property]: (include ? filter : { - not: { - filter, - }, + not: filter, }), }, ]; diff --git a/frontend/src/shared/pagination/pagination-sorter.vue b/frontend/src/shared/pagination/pagination-sorter.vue index f40a7c2cf4..a58bb5891a 100644 --- a/frontend/src/shared/pagination/pagination-sorter.vue +++ b/frontend/src/shared/pagination/pagination-sorter.vue @@ -20,7 +20,7 @@ v-if="module === 'member'" type="button" class="btn btn--transparent btn--md mr-3" - @click="exportMembers" + @click="doExport" > true, }, + export: { + type: Function, + default: () => false, + }, }); -const memberStore = useMemberStore(); -const { savedFilterBody } = storeToRefs(memberStore); const { currentTenant } = mapGetters('auth'); const { doRefreshCurrentUser } = mapActions('auth'); @@ -177,7 +177,7 @@ const onChange = (value) => { emit('changeSorter', value); }; -const exportMembers = async () => { +const doExport = async () => { try { const tenantCsvExportCount = currentTenant.value.csvExportCount; const planExportCountMax = getExportMax( @@ -189,13 +189,7 @@ const exportMembers = async () => { planExportCountMax, }); - await MemberService.export( - savedFilterBody.value.filter, - savedFilterBody.value.orderBy, - 0, - null, - false, - ); + await props.export(); await doRefreshCurrentUser(null); From 5979d1d98258970a34fbe7a32e644bf57867f853 Mon Sep 17 00:00:00 2001 From: Gasper Grom Date: Wed, 7 Jun 2023 10:13:54 +0200 Subject: [PATCH 3/3] Fix multiselect async filter --- .../components/filterTypes/MultiSelectAsyncFilter.vue | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue index f416dd6582..170be79d44 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/MultiSelectAsyncFilter.vue @@ -19,13 +19,6 @@ :loading="loading" no-data-text="No results" > - data.value.selected, (value) => { const loading = ref(false); const filteredOptions = ref([]); +const unselectedOptions = computed(() => filteredOptions.value.filter((o) => !props.modelValue.value.includes(o.value))); + const searchOptions = (query: string) => { loading.value = true; props.remoteMethod(query)