Unable to use multi-select and its giving error
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 25/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Cần làm rõ
- Mức độ hoạt động
- Đình trệ
- Công nghệ
- elasticsearch, javascript
Hướng nghiên cứu
Bắt đầu với header registrants được cung cấp và component cha, tập trung vào các control multi-list và data-search của trường email. Kiểm tra stack trace của query.js và tái hiện yêu cầu lọc để xác định vì sao query được tạo thực hiện một thao tác không được hỗ trợ trên email. Hoàn thành khi tính năng lọc multi-select không còn trả về lỗi Elasticsearch 400 như đã hiển thị.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Affected Projects
Vue.JS
Library Version: x.y.z
"@appbaseio/reactivesearch-vue": "3.1.0-alpha.1",
Describe the bug
I am migrating our app from vue 2 to vue 3, so also need to migrate appbase. there is is migration docs which we following. everything seems work but when we filter it gives this error
bugsnag.js:2688 {"settings":{"took":6,"searchTook":2},"SearchResults":{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [email] in order to load field data by uninverting the inverted index. Note that this can use significant memory."}],"type":"search_phase_execution_exception","reason":"all shards failed","phase":"query","grouped":true,"failed_shards":[{"shard":0,"index":"4760d3c8-ab04-450d-b83c-83ff9bdc1d0d","node":"Fa2quZrSSlCuIwgA2TZRVg","reason":{"type":"illegal_argument_exception","reason":"Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [email] in order to load field data by uninverting the inverted index. Note that this can use significant memory."}}],"caused_by":{"type":"illegal_argument_exception","reason":"Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [email] in order to load field data by uninverting the inverted index. Note that this can use significant memory.","caused_by":{"type":"illegal_argument_exception","reason":"Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [email] in order to load field data by uninverting the inverted index. Note that this can use significant memory."}}},"status":400}}
console.<computed> @ bugsnag.js:2688
handleError @ utils.js:1
eval @ query.js:1
Desktop (please complete the following information):
- Browser [e.g. chrome]
here is my component code
<template>
<!-- use v-show here because the popup can still stay open while the page is loading -->
<tr>
<th
v-for="header of headers"
:key="header.text"
class="tw-border-r tw-border-r-slate-200 !tw-border-b-slate-200 !tw-bg-slate-100 !tw-min-w-[100px]"
>
<div class="tw-flex tw-items-center tw-justify-between">
<div class="tw-mr-1 tw-text-sm tw-font-semibold tw-text-slate-600" :class="['text', { hidden: !header.text }]">
{{ header.text }}
</div>
<div v-if="!header.text" class="tw-flex tw-flex-1 tw-items-center tw-justify-center">
<input
type="checkbox"
class="tw-h-4 tw-w-4 tw-rounded tw-border-solid tw-border-slate-500 tw-text-indigo-600 focus:tw-ring-indigo-500 dark:tw-bg-black"
:checked="allSelected"
@change="toggleColumnSelected"
/>
</div>
<div v-else-if="header.value == 'user__magic_link' || header.value == 'date_registered'" />
<v-menu v-else :modelValue="headerPopups[header.value]" :closeOnContentClick="false">
<template #activator="{ props }">
<button class="tw-h-full tw-mt-1" v-bind="props">
<MagnifyingGlass v-if="header.value == 'full_name' || header.value == 'email'" class="!tw-h-5 !tw-w-5" />
<Funnel v-else class="!tw-h-5 !tw-w-5" />
</button>
</template>
<div class="tw-bg-white tw-rounded tw-flex tw-items-center !tw-p-2">
<multi-list
v-if="header.value === 'role'"
componentId="role"
dataField="role"
:innerClass="{
list: '!tw-p-0'
}"
>
<template #renderItem="{ label }">
<div class="tw-flex tw-text-sm tw-space-2">
{{ $store.state.registration.roleNames[label] }}
<img
v-if="$store.state.registration.roleIcons[label]"
:src="$store.state.registration.roleIcons[label]"
style="width: 13px"
/>
</div>
</template>
</multi-list>
<data-search
v-else-if="header.value === 'full_name'"
queryFormat="and"
componentId="full_name"
className="inlineSearch"
:dataField="['full_name']"
/>
<data-search
v-else-if="header.value === 'email'"
queryFormat="and"
componentId="email"
className="inlineSearch"
:dataField="['email']"
/>
<multi-list
v-else
:componentId="header.value"
:dataField="`${header.value}.keyword`"
title=""
:showCount="false"
:size="500"
:innerClass="{
list: '!tw-p-0'
}"
>
<template #renderItem="{ label }">
<div class="tw-text-sm">
{{ label }}
</div>
</template>
</multi-list>
</div>
</v-menu>
</div>
</th>
</tr>
</template>
<script>
import MagnifyingGlass from '@/studioComponents/Icons/MagnifyingGlass.vue';
import Funnel from '@/studioComponents/Icons/Funnel.vue';
import { mapGetters, mapMutations, mapState } from 'vuex';
export default {
name: 'StudioRegistrantsTableHeader',
components: { MagnifyingGlass, Funnel },
props: {
headers: {
type: Array,
default: () => []
}
},
data() {
const headerPopups = this.headers
.map(it => it.value)
.reduce((acuum, item) => {
acuum[item] = false;
return acuum;
}, {});
return {
headerPopups
};
},
computed: {
...mapState('registration', ['allSelected', 'lastSearchedQueryResultSize']),
...mapGetters('registration', ['numSelected'])
},
methods: {
...mapMutations('registration', ['toggleSelectAll']),
toggleColumnSelected() {
this.toggleSelectAll(!this.allSelected);
}
}
};
</script>
<style lang="scss">
.inlineSearch {
#full_name-downshift {
height: 160px;
}
#email-downshift {
height: 160px;
}
}
.v-menu__content {
background: white;
}
</style>
Parent component
<template>
<div class="tw-flex tw-flex-col tw-px-4">
<StudioPageIntro title="Registrants">
Register attendees for your event and control access for each user. Registrants will automatically be sent email
notifications once added, so confirm your email schedule before uploading registrants.
</StudioPageIntro>
<div class="tw-flex tw-flex-1 tw-flex-col tw-overflow-scroll tw-w-full">
<!--
This is a very frustrating component because of reactive-base. If you make any chnages,
you can start having problems with the components rendered by appbase, such as filters
not updating or the pagination changing back to page 1 randomly.
It seems reactive-base observes all its direct children and on any change that will
re-render the virtual dom of its direct descendants it will refresh. It will also
stop refreshing when it is supposed to refresh if its not a direct descendant.
A rule of thumb, if you want it to make the data update, make it a direct child. If you
want it to not make the reactive-base store refresh, put it as a descendant of a descendant
and then provide all the data it needs through the store instead of through props.
-->
<StudioSpinner v-if="loadingColumns" overlay />
<reactive-base v-else :app="eventId" :credentials="appbaseCredentials" :url="appBaseUrl">
<StudioSpinner v-show="loadingRegistration" overlay />
<div>
<div class="tw-flex tw-flex-row tw-py-3 tw-align-top">
<div class="tw-flex tw-flex-row tw-flex-1 tw-justify-start tw-items-start tw-mt-1">
<button class="hover:tw-opacity-80 tw-mr-2 tw-mt-[2px]" @click="refreshList">
<Refresh class="tw-w-5 tw-h-5 tw-text-indigo-600 tw-transition-transform" />
</button>
<div v-if="summary" class="tw-mr-2">
{{ summary }}
</div>
</div>
<div class="tw-flex tw-flex-row tw-justify-end tw-items-start tw-px-2">
<search-box
placeholder="Search Registrants..."
className="appbase-search"
:innerClass="{
list: 'search-bar-list'
}"
queryFormat="and"
filterLabel="Search"
componentId="SearchUsers"
:dataField="['full_name']"
/>
<StudioAddRegistrant @onRegistrantAdd="searchRegistrants" />
</div>
</div>
<selected-filters :showClearAll="true">
<template #default="{ selectedValues, setValue, clearValues }">
<div class="tw-flex tw-flex-row tw-flex-wrap">
<Chip
v-for="componentId in Object.keys(getFilteredValues(selectedValues))"
:key="componentId"
class="tw-mr-2"
:borderRadius="16"
:label="`${filteredText(componentId)}: ${selectedFilterValue(componentId, selectedValues)}`"
allowClose
@closeChip="() => setValue(componentId, null)"
/>
<StudioButton
v-if="Object.keys(getFilteredValues(selectedValues)).length"
variant="text"
trackingId="registrants-clear-all-filters-button"
@click="clearValues"
>
Clear All Filters
</StudioButton>
</div>
</template>
</selected-filters>
<div class="content tw-w-full tw-mb-8 tw-relative">
<div class="tw-absolute tw-right-[168px] tw-top-[12px]">
<StudioRegistrantsColumnSelectionPopup v-if="$store.state.registration.lastSearchedQueryResultSize > 0" />
</div>
<reactive-list
ref="datalist"
dataField="email"
componentId="SearchResults"
prevLabel="Previous"
nextLabel="Next"
:react="{ and: oredFilters }"
:pagination="true"
:defaultQuery="defaultQuery"
:sortOptions="sortOptions"
:size="25"
:showEndPage="true"
:showResultStats="false"
:innerClass="{
sortOptions: 'appbase-sort-options',
pagination: 'appbase-paginator'
}"
:renderNoResults="() => null"
@data="onResult"
@queryChange="onQueryChange"
>
<template #render="{ data }">
<div>
<div class="tw-flex tw-flex-row tw-flex-1 tw-justify-between tw-py-3">
<StudioRegistrantsActions
:react="{ and: oredFilters }"
:defaultQuery="defaultQuery"
@banfinished="handleBanFinished"
/>
</div>
<v-data-table
:headers="visibleColumns"
:items="data"
showSelect
fixedHeader
class="tw-border tw-border-slate-200 tw-overflow-hidden"
>
<!--
Do not bind props to this component. Route them through the store.
If you bind props to this element other than items then everytime
this component changes the reactive-list component above switches
back to page 1. If you route the changes through the store it cannot
tell.
-->
<template #headers="{ columns }">
<!-- Do not use v-if here. It will break filters. -->
<StudioRegistrantsTableHeader v-show="data.length > 0" :headers="columns" />
</template>
<template #bottom></template>
<template #body></template>
<template #tbody="{ items }">
<div v-if="items.length === 0" class="tw-py-12 tw-text-center">
<h5
class="tw-font-medium tw-leading-tight tw-text-md lg:tw-text-lg lg:tw-leading-tight tw-text-slate-400 dark:tw-text-slate-500"
>
No registrants yet!
</h5>
</div>
<StudioRegistrantsTableRow
:rows="items"
@singleItemSelectionHandler="singleItemSelectionHandler"
/>
</template>
</v-data-table>
</div>
</template>
</reactive-list>
</div>
</div>
</reactive-base>
</div>
</div>
</template>
<script>
import { mapState, mapActions, mapGetters, mapMutations } from 'vuex';
import StudioRegistrantsTableRow from './StudioRegistrantsTableRow.vue';
import StudioRegistrantsTableHeader from './StudioRegistrantsTableHeader.vue';
import StudioRegistrantsActions from './StudioRegistrantsActions.vue';
import StudioRegistrantsColumnSelectionPopup from './StudioRegistrantsColumnSelectionPopup.vue';
import StudioAddRegistrant from './StudioAddRegistrant.vue';
import { snakeCaseToCapitalized } from '@/store/modules/registration';
import LogRocket from 'logrocket';
import StudioButton from '@/studioComponents/StudioButton.vue';
import StudioPageIntro from '@/studioComponents/StudioPageIntro.vue';
import StudioSpinner from '@/studioComponents/StudioSpinner.vue';
import Chip from '@/commonComponents/Chip.vue';
import throttle from 'lodash.throttle';
import Refresh from '@/studioComponents/Icons/Refresh.vue';
import { VDataTable } from 'vuetify/labs/VDataTable';
export default {
name: 'StudioRegistrantsWrapper',
components: {
StudioRegistrantsTableHeader,
StudioRegistrantsTableRow,
StudioRegistrantsActions,
StudioRegistrantsColumnSelectionPopup,
StudioAddRegistrant,
Chip,
StudioButton,
StudioSpinner,
StudioPageIntro,
Refresh,
VDataTable
},
computed: {
...mapState('registration', {
appBaseUrl: 'appBaseUrl',
loadingRegistration: 'loadingRegistration',
appbaseError: 'appbaseError',
columns: 'columns',
roleNames: 'roleNames',
lastSearchedQueryResultSize: 'lastSearchedQueryResultSize'
}),
...mapGetters('registration', ['visibleColumns']),
...mapGetters('studioEventList', {
event: 'getCurrentEvent'
}),
appbaseCredentials() {
return this.event?.appbase_credentials;
},
selectedFilterValue() {
return (componentId, selectedValues) => {
if (componentId === 'role') {
return selectedValues[componentId].value
.map(role => {
return this.roleNames[role];
})
.join(',');
}
const value = selectedValues[componentId].value;
if (Array.isArray(value)) {
return value.join(',');
}
return value;
};
},
oredFilters() {
const ored = this.columns
.map(it => it.value)
.filter(it => {
return !['user__magic_link', 'id', 'event_id', 'profile_picture_url'].includes(it);
});
return ored.concat('SearchUsers').concat('role');
},
eventId() {
return this.$route.params.eventId;
},
summary() {
if (this.loadingRegistration) {
return '';
}
if (this.lastSearchedQueryResultSize <= 0) {
return '';
}
if (this.lastSearchedQueryResultSize === 1) {
return '1 Registrant';
}
return `${this.lastSearchedQueryResultSize} Registrants`;
}
},
data() {
return {
loadingColumns: true,
currentPage: 1,
sortOptions: [
{
label: 'Full Name ↑',
dataField: 'full_name.keyword',
sortBy: 'asc'
},
{
label: 'Full Name ↓',
dataField: 'full_name.keyword',
sortBy: 'desc'
},
{
label: 'Email ↑',
dataField: 'email.keyword',
sortBy: 'asc'
},
{
label: 'Email ↓',
dataField: 'email.keyword',
sortBy: 'desc'
},
{
label: 'Date Registered ↑',
dataField: 'date_registered.keyword',
sortBy: 'asc'
},
{
label: 'Date Registered ↓',
dataField: 'date_registered.keyword',
sortBy: 'desc'
}
]
};
},
mounted() {
this.fetchColumns().then(() => {
this.loadingColumns = false;
});
this.fetchEventMembers({ eventId: this.eventId });
},
beforeRouteLeave(to, from, next) {
this.toggleSelectAll(false);
this.toggleColumnPopup(false);
this.setSelectedQuery(null);
next();
},
methods: {
...mapActions('registration', ['fetchColumns']),
...mapActions('eventList', ['getEventMembers', 'blockEventBulkUser']),
...mapActions('studioEventList', ['fetchEventMembers']),
...mapMutations('registration', [
'toggleSelectAll',
'toggleColumnPopup',
'setSelectedQuery',
'setLoadingRegistration',
'storeAppbaseQueryDSL',
'toggleSelection'
]),
// This cannot be a computed property because it is inside the render
// function of appbase and hence cannot know when to recompute unless
// its passed as a method
handleBanFinished({ timeout }) {
this.toggleSelectAll(false);
this.searchRegistrants();
// The banned data doesn't reflect in appbase immediately so wait to refresh
setTimeout(() => {
this.refreshList();
}, timeout);
},
refreshList: throttle(function () {
const datalist = this.$refs.datalist;
if (datalist) {
this.setLoadingRegistration(true);
if (!this.columns?.length) {
this.fetchColumns();
}
const currentPage = datalist.$el.__vue__.currentPageState;
datalist.$el.__vue__.setPage(currentPage + 1);
setTimeout(() => {
datalist.$el.__vue__.setPage(currentPage);
this.setLoadingRegistration(false);
}, 100);
} else {
LogRocket.info('Could not refresh by page change because reactivebase was not found!');
}
}, 2000),
filteredText(componentId) {
return this.snakeCaseToCapitalized(componentId === 'blocked' ? 'banned' : componentId);
},
onQueryChange(prev, next) {
LogRocket.info('Query change: ', prev, next);
this.storeAppbaseQueryDSL({
next,
numResults: -1
});
},
onResult(payload) {
const numResults = payload.resultStats.numberOfResults;
LogRocket.info('Number of results in appbase query: ' + numResults);
this.storeAppbaseQueryDSL({
numResults
});
},
searchRegistrants() {
if (this.currentPage == 1) {
this.fetchEventMembers({ eventId: this.event.id });
} else {
this.currentPage = 1;
}
},
snakeCaseToCapitalized,
getFilteredValues(values = {}) {
const filteredValues = {};
Object.keys(values).forEach(componentId => {
if (
values[componentId].showFilter &&
(Array.isArray(values[componentId].value) ? values[componentId].value.length : !!values[componentId].value)
) {
filteredValues[componentId] = values[componentId];
}
});
return filteredValues;
},
defaultQuery() {
return {
track_total_hits: true,
query: { match_all: {} }
};
},
singleItemSelectionHandler(row) {
this.toggleSelection(row);
}
}
};
</script>
<style lang="scss">
// This block is for modifying appbase css
// Do not scope it
.appbase-search {
& #SearchUsers-input {
font-size: 14px;
min-height: 0;
border-radius: 999px;
border-color: rgb(209 213 219);
background-color: white;
&:focus {
border-color: rgb(135 128 253);
}
}
& svg.search-icon[alt='Search'] {
fill: #222;
height: 12px;
width: 12px;
}
}
.search-bar-list {
border: 1px solid rgb(209 213 219) !important;
max-height: 160px !important;
margin-top: 2px !important;
border-radius: 0.25rem !important;
}
select.appbase-sort-options {
position: absolute;
right: 2px;
top: 8px;
width: 160px;
border-radius: 0.25rem;
cursor: pointer;
&:hover {
border-color: rgb(135 128 253);
outline-color: rgb(135 128 253);
}
&:focus {
border-color: rgb(135 128 253);
outline: 2px solid rgb(135 128 253);
outline-offset: 0px;
}
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.content {
& .appbase-paginator {
text-align: center;
& > button {
font-family: 'Inter var', sans-serif;
&.active {
background-color: rgb(135 128 253);
color: white;
}
}
& > a {
color: rgba(28, 30, 31, 0.6);
background-color: transparent;
&.active {
color: white;
background: rgba(28, 30, 31, 0.6);
}
&:focus {
outline: none;
}
}
}
& .appbase-noresults {
display: none;
}
}
</style>
- Ngôn ngữ chính
- JavaScript
- Star
- 4.9k
- Fork
- 479
- Merge trung bình
- 4 ngày 10 giờ
- Pull request đã merge (30 ngày)
- 4
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của appbaseio/reactivesearch
-
ci: add Node.js version matrix, caching, and lint step to test workflowCó thể đã có người làm @Talha12Shiekh đã nhận 71 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
appbaseio/reactivesearch#2322 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
chore: add Dependabot config for automated dependency updatesCó thể đã có người làm @Talha12Shiekh đã nhận 71 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
appbaseio/reactivesearch#2321 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
security: user-controlled URL params injected directly into Redux store via JSON.parseCó thể đã có người làm @Talha12Shiekh đã nhận 71 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
appbaseio/reactivesearch#2319 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
refactor: consolidate 12 near-identical SVG icon components into one patternCó thể đã có người làm @Talha12Shiekh đã nhận 73 ngày trước. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
appbaseio/reactivesearch#2324 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
refactor: SearchBox.js is 2692 lines — extract subcomponents for maintainabilityCó thể đã có người làm @Talha12Shiekh đã nhận 71 ngày trước. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 55/100
appbaseio/reactivesearch#2323 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của appbaseio/reactivesearch
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
documentation good first issue help wanted
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 85/100
zmo2s/agent-toolbox#23 ·
-
[Bug]: [MCP/CLI] Bare loopback IP addresses (127.0.0.1:port) and hosts with ports fail to navigate due to erroneous scheme inferenceCó thể đã có người làm @alok-108 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
microsoft/playwright#43263 ·
Maintainer thường phản hồi trong vòng 1 ngày