diff options
Diffstat (limited to 'app/assets/javascripts/error_tracking/components')
-rw-r--r-- | app/assets/javascripts/error_tracking/components/error_tracking_list.vue | 210 |
1 files changed, 137 insertions, 73 deletions
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue index 9d8e5396dea..5cd68687329 100644 --- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -3,11 +3,17 @@ import { mapActions, mapState } from 'vuex'; import { GlEmptyState, GlButton, + GlIcon, GlLink, GlLoadingIcon, GlTable, - GlSearchBoxByClick, + GlFormInput, + GlDropdown, + GlDropdownItem, + GlDropdownDivider, + GlTooltipDirective, } from '@gitlab/ui'; +import AccessorUtils from '~/lib/utils/accessor'; import Icon from '~/vue_shared/components/icon.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { __ } from '~/locale'; @@ -24,14 +30,19 @@ export default { components: { GlEmptyState, GlButton, + GlDropdown, + GlDropdownItem, + GlDropdownDivider, + GlIcon, GlLink, GlLoadingIcon, GlTable, - GlSearchBoxByClick, + GlFormInput, Icon, TimeAgo, }, directives: { + GlTooltip: GlTooltipDirective, TrackEvent: TrackEventDirective, }, props: { @@ -56,13 +67,14 @@ export default { required: true, }, }, + hasLocalStorage: AccessorUtils.isLocalStorageAccessSafe(), data() { return { errorSearchQuery: '', }; }, computed: { - ...mapState('list', ['errors', 'externalUrl', 'loading']), + ...mapState('list', ['errors', 'externalUrl', 'loading', 'recentSearches']), }, created() { if (this.errorTrackingEnabled) { @@ -70,9 +82,23 @@ export default { } }, methods: { - ...mapActions('list', ['startPolling', 'restartPolling']), + ...mapActions('list', [ + 'startPolling', + 'restartPolling', + 'addRecentSearch', + 'clearRecentSearches', + 'loadRecentSearches', + 'setIndexPath', + ]), filterErrors() { - this.startPolling(`${this.indexPath}?search_term=${this.errorSearchQuery}`); + const searchTerm = this.errorSearchQuery.trim(); + this.addRecentSearch(searchTerm); + + this.startPolling(`${this.indexPath}?search_term=${searchTerm}`); + }, + setSearchText(text) { + this.errorSearchQuery = text; + this.filterErrors(); }, trackViewInSentryOptions, getDetailsLink(errorId) { @@ -85,81 +111,119 @@ export default { <template> <div> <div v-if="errorTrackingEnabled"> - <div> - <div class="d-flex flex-row justify-content-around bg-secondary border"> - <gl-search-box-by-click - v-model="errorSearchQuery" - class="col-lg-10 m-3 p-0" - :placeholder="__('Search or filter results...')" - type="search" - autofocus - @submit="filterErrors" - /> - <gl-button - v-track-event="trackViewInSentryOptions(externalUrl)" - class="m-3" - variant="primary" - :href="externalUrl" - target="_blank" + <div class="d-flex flex-row justify-content-around bg-secondary border p-3"> + <div class="filtered-search-box"> + <gl-dropdown + :text="__('Recent searches')" + class="filtered-search-history-dropdown-wrapper d-none d-md-block" + toggle-class="filtered-search-history-dropdown-toggle-button" + :disabled="loading" > - {{ __('View in Sentry') }} - <icon name="external-link" class="flex-shrink-0" /> - </gl-button> - </div> - - <div v-if="loading" class="py-3"> - <gl-loading-icon size="md" /> + <div v-if="!$options.hasLocalStorage" class="px-3"> + {{ __('This feature requires local storage to be enabled') }} + </div> + <template v-else-if="recentSearches.length > 0"> + <gl-dropdown-item + v-for="searchQuery in recentSearches" + :key="searchQuery" + @click="setSearchText(searchQuery)" + >{{ searchQuery }}</gl-dropdown-item + > + <gl-dropdown-divider /> + <gl-dropdown-item ref="clearRecentSearches" @click="clearRecentSearches">{{ + __('Clear recent searches') + }}</gl-dropdown-item> + </template> + <div v-else class="px-3">{{ __("You don't have any recent searches") }}</div> + </gl-dropdown> + <div class="filtered-search-input-container flex-fill"> + <gl-form-input + v-model="errorSearchQuery" + class="pl-2 filtered-search" + :disabled="loading" + :placeholder="__('Search or filter results…')" + autofocus + @keyup.enter.native="filterErrors" + /> + </div> + <div class="gl-search-box-by-type-right-icons"> + <gl-button + v-if="errorSearchQuery.length > 0" + v-gl-tooltip.hover + :title="__('Clear')" + class="clear-search text-secondary" + name="clear" + @click="errorSearchQuery = ''" + > + <gl-icon name="close" :size="12" /> + </gl-button> + </div> </div> - <gl-table - v-else - class="mt-3" - :items="errors" - :fields="$options.fields" - :show-empty="true" - fixed - stacked="sm" + <gl-button + v-track-event="trackViewInSentryOptions(externalUrl)" + class="ml-3" + variant="primary" + :href="externalUrl" + target="_blank" > - <template slot="HEAD_events" slot-scope="data"> - <div class="text-md-right">{{ data.label }}</div> - </template> - <template slot="HEAD_users" slot-scope="data"> - <div class="text-md-right">{{ data.label }}</div> - </template> - <template slot="error" slot-scope="errors"> - <div class="d-flex flex-column"> - <gl-link class="d-flex text-dark" :href="getDetailsLink(errors.item.id)"> - <strong class="text-truncate">{{ errors.item.title.trim() }}</strong> - </gl-link> - <span class="text-secondary text-truncate"> - {{ errors.item.culprit }} - </span> - </div> - </template> + {{ __('View in Sentry') }} + <icon name="external-link" class="flex-shrink-0" /> + </gl-button> + </div> - <template slot="events" slot-scope="errors"> - <div class="text-md-right">{{ errors.item.count }}</div> - </template> + <div v-if="loading" class="py-3"> + <gl-loading-icon size="md" /> + </div> - <template slot="users" slot-scope="errors"> - <div class="text-md-right">{{ errors.item.userCount }}</div> - </template> + <gl-table + v-else + class="mt-3" + :items="errors" + :fields="$options.fields" + :show-empty="true" + fixed + stacked="sm" + > + <template slot="HEAD_events" slot-scope="data"> + <div class="text-md-right">{{ data.label }}</div> + </template> + <template slot="HEAD_users" slot-scope="data"> + <div class="text-md-right">{{ data.label }}</div> + </template> + <template slot="error" slot-scope="errors"> + <div class="d-flex flex-column"> + <gl-link class="d-flex text-dark" :href="getDetailsLink(errors.item.id)"> + <strong class="text-truncate">{{ errors.item.title.trim() }}</strong> + </gl-link> + <span class="text-secondary text-truncate"> + {{ errors.item.culprit }} + </span> + </div> + </template> - <template slot="lastSeen" slot-scope="errors"> - <div class="d-flex align-items-center"> - <time-ago :time="errors.item.lastSeen" class="text-secondary" /> - </div> - </template> - <template slot="empty"> - <div ref="empty"> - {{ __('No errors to display.') }} - <gl-link class="js-try-again" @click="restartPolling"> - {{ __('Check again') }} - </gl-link> - </div> - </template> - </gl-table> - </div> + <template slot="events" slot-scope="errors"> + <div class="text-md-right">{{ errors.item.count }}</div> + </template> + + <template slot="users" slot-scope="errors"> + <div class="text-md-right">{{ errors.item.userCount }}</div> + </template> + + <template slot="lastSeen" slot-scope="errors"> + <div class="d-flex align-items-center"> + <time-ago :time="errors.item.lastSeen" class="text-secondary" /> + </div> + </template> + <template slot="empty"> + <div ref="empty"> + {{ __('No errors to display.') }} + <gl-link class="js-try-again" @click="restartPolling"> + {{ __('Check again') }} + </gl-link> + </div> + </template> + </gl-table> </div> <div v-else-if="userCanEnableErrorTracking"> <gl-empty-state |