Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Unable to use multi-select and its giving error

Đang mở
#2,247 10 bình luận 0 reaction 0 người được giao Xem trên GitHub

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
Lĩnh vực
frontend, search

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ả

bug

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
Screenshot 2023-09-13 at 3 02 53 PM

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

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của appbaseio/reactivesearch

Tất cả issue của appbaseio/reactivesearch

Issue tương tự

Thêm issue về JavaScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.