diff --git a/app/javascript/vue/global_search/container.vue b/app/javascript/vue/global_search/container.vue index 48c1ebd86..f34c457cf 100644 --- a/app/javascript/vue/global_search/container.vue +++ b/app/javascript/vue/global_search/container.vue @@ -24,24 +24,19 @@ {{ i18n.t('search.index.task_results') }} - - - - - {{ i18n.t('search.index.more_search_options') }} - - - - - - - + + + {{ i18n.t('search.index.more_search_options') }} + + {{ activeFilters.length }} + + + - + {{ i18n.t('search.index.clear_filters') }} @@ -55,9 +50,21 @@ :selected="activeGroup === group" :query="localQuery" :searchUrl="searchUrl" + :filters="filters" @selectGroup="setActiveGroup" /> + + + @@ -73,10 +80,11 @@ import RepositoryRowsComponent from './groups/repository_rows.vue'; import ProtocolsComponent from './groups/protocols.vue'; import LabelTemplatesComponent from './groups/label_templates.vue'; import ReportsComponent from './groups/reports.vue'; -import SearchFilters from './filters.vue'; +import FiltersModal from './filters_modal.vue'; import GeneralDropdown from '../shared/general_dropdown.vue'; export default { + emits: ['search', 'selectGroup'], name: 'GlobalSearch', props: { query: { @@ -94,6 +102,10 @@ export default { usersUrl: { type: String, required: true + }, + currentTeam: { + type: Number || String, + required: true } }, components: { @@ -108,12 +120,14 @@ export default { ProtocolsComponent, LabelTemplatesComponent, ReportsComponent, - SearchFilters, + FiltersModal, GeneralDropdown }, data() { return { + filters: {}, localQuery: this.query, + filterModalOpened: false, activeGroup: null, searchGroups: [ 'FoldersComponent', @@ -130,6 +144,48 @@ export default { ] }; }, + computed: { + activeFilters() { + return Object.keys(this.filters).filter((key) => { + if (key === 'created_at' || key === 'updated_at') { + return this.filters[key].on || this.filters[key].from || this.filters[key].to; + } if (key === 'teams' || key === 'users') { + return this.filters[key].length > 0; + } + return this.filters[key]; + }); + } + }, + created() { + const urlParams = new URLSearchParams(window.location.search); + this.filters = { + created_at: { + on: null, + from: null, + to: null + }, + updated_at: { + on: null, + from: null, + to: null + }, + include_archived: urlParams.get('include_archived') === 'true', + teams: urlParams.getAll('teams[]').map((team) => parseInt(team, 10)), + users: urlParams.getAll('users[]').map((user) => parseInt(user, 10)), + group: urlParams.get('group') + }; + ['created_at', 'updated_at'].forEach((key) => { + ['on', 'from', 'to', 'mode'].forEach((subKey) => { + if (urlParams.get(`${key}[${subKey}]`)) { + this.filters[key][subKey] = subKey !== 'mode' ? new Date(urlParams.get(`${key}[${subKey}]`)) : urlParams.get(`${key}[${subKey}]`); + } + }); + }); + + if (this.filters.group) { + this.activeGroup = this.filters.group; + } + }, methods: { setActiveGroup(group) { if (group === this.activeGroup) { @@ -137,9 +193,36 @@ export default { } else { this.activeGroup = group; } + + this.filters.group = this.activeGroup; }, changeQuery(event) { this.localQuery = event.target.value; + }, + applyFilters(filters) { + this.filters = filters; + this.filterModalOpened = false; + + this.activeGroup = this.filters.group; + }, + resetFilters() { + this.filters = { + created_at: { + on: null, + from: null, + to: null + }, + updated_at: { + on: null, + from: null, + to: null + }, + include_archived: false, + teams: [], + users: [], + group: null + }; + this.activeGroup = null; } } }; diff --git a/app/javascript/vue/global_search/filters.vue b/app/javascript/vue/global_search/filters.vue index fa896c1da..04f5e8999 100644 --- a/app/javascript/vue/global_search/filters.vue +++ b/app/javascript/vue/global_search/filters.vue @@ -1,5 +1,5 @@ - + {{ i18n.t('search.filters.by_type') }} @@ -75,7 +75,8 @@ export default { type: String, required: true }, - currentTeam: Number, + filters: Object, + currentTeam: Number || String, searchUrl: String, searchQuery: String }, @@ -84,6 +85,17 @@ export default { if (this.currentTeam) { this.selectedTeams = [this.currentTeam]; } + + if (this.filters) { + this.createdAt = this.filters.created_at; + this.updatedAt = this.filters.updated_at; + this.selectedTeams = this.filters.teams; + this.$nextTick(() => { + this.selectedUsers = this.filters.users; + }); + this.includeArchived = this.filters.include_archived; + this.activeGroup = this.filters.group; + } }, watch: { selectedTeams() { @@ -175,6 +187,15 @@ export default { search() { if (this.searchUrl) { this.openSearchPage(); + } else { + this.$emit('search', { + created_at: this.createdAt, + updated_at: this.updatedAt, + teams: this.selectedTeams, + users: this.selectedUsers, + include_archived: this.includeArchived, + group: this.activeGroup + }); } }, openSearchPage() { @@ -182,9 +203,11 @@ export default { 'created_at[on]': this.createdAt.on || '', 'created_at[from]': this.createdAt.from || '', 'created_at[to]': this.createdAt.to || '', + 'created_at[mode]': this.createdAt.mode || '', 'updated_at[on]': this.updatedAt.on || '', 'updated_at[from]': this.updatedAt.from || '', 'updated_at[to]': this.updatedAt.to || '', + 'updated_at[mode]': this.updatedAt.mode || '', include_archived: this.includeArchived, group: this.activeGroup || '', q: this.searchQuery diff --git a/app/javascript/vue/global_search/filters/date.vue b/app/javascript/vue/global_search/filters/date.vue index 070efcd56..2e385fb95 100644 --- a/app/javascript/vue/global_search/filters/date.vue +++ b/app/javascript/vue/global_search/filters/date.vue @@ -66,28 +66,44 @@ export default { switch (this.selectedOption) { case 'today': - this.newDate = { on: today, from: null, to: null }; + this.newDate = { + on: today, from: null, to: null, mode: 'today' + }; break; case 'yesterday': - this.newDate = { on: yesterday, from: null, to: null }; + this.newDate = { + on: yesterday, from: null, to: null, mode: 'yesterday' + }; break; case 'last_week': - this.newDate = { on: null, from: lastWeekStart, to: lastWeekEnd }; + this.newDate = { + on: null, from: lastWeekStart, to: lastWeekEnd, mode: 'last_week' + }; break; case 'this_month': - this.newDate = { on: null, from: firstMonthDay, to: today }; + this.newDate = { + on: null, from: firstMonthDay, to: today, mode: 'this_month' + }; break; case 'this_year': - this.newDate = { on: null, from: firstYearDay, to: today }; + this.newDate = { + on: null, from: firstYearDay, to: today, mode: 'this_year' + }; break; case 'last_year': - this.newDate = { on: null, from: lastYearStart, to: lastYearEnd }; + this.newDate = { + on: null, from: lastYearStart, to: lastYearEnd, mode: 'last_year' + }; break; case 'on': - this.newDate = { on: null, from: null, to: null }; + this.newDate = { + on: null, from: null, to: null, mode: 'on' + }; break; case 'custom': - this.newDate = { on: null, from: null, to: null }; + this.newDate = { + on: null, from: null, to: null, mode: 'custom' + }; break; default: break; @@ -99,7 +115,7 @@ export default { data() { return { newDate: this.date, - selectedOption: 'on', + selectedOption: (this.date.mode || 'on'), dateOptions: [ ['today', 'Today'], ['yesterday', 'Yesterday'], @@ -114,15 +130,19 @@ export default { }, methods: { setOn(v) { - this.newDate = { on: v, from: null, to: null }; + this.newDate = { + on: v, from: null, to: null, mode: 'on' + }; this.$emit('change', this.newDate); }, setFrom(v) { + this.newDate.mode = 'custom'; this.newDate.on = null; this.newDate.from = v; this.$emit('change', this.newDate); }, setTo(v) { + this.newDate.mode = 'custom'; this.newDate.on = null; this.newDate.to = v; this.$emit('change', this.newDate); diff --git a/app/javascript/vue/global_search/filters_modal.vue b/app/javascript/vue/global_search/filters_modal.vue new file mode 100644 index 000000000..c1a768a8e --- /dev/null +++ b/app/javascript/vue/global_search/filters_modal.vue @@ -0,0 +1,49 @@ + + + + + + + + + + + {{ i18n.t('search.filters.title') }} + + + + + {{ i18n.t('search.filters.sub_title') }} + + { this.$emit('search', newFilters); }" + @close="close" /> + + + + + + + + diff --git a/app/javascript/vue/global_search/groups/search_mixin.js b/app/javascript/vue/global_search/groups/search_mixin.js index 00f87e79f..faa1e51b8 100644 --- a/app/javascript/vue/global_search/groups/search_mixin.js +++ b/app/javascript/vue/global_search/groups/search_mixin.js @@ -6,7 +6,8 @@ export default { props: { searchUrl: String, query: String, - selected: Boolean + selected: Boolean, + filters: Object }, components: { StringWithEllipsis @@ -57,7 +58,7 @@ export default { }, methods: { handleScroll() { - if (this.loading) return; + if (this.loading || !this.selected) return; if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) { if (this.results.length < this.total) { @@ -72,6 +73,7 @@ export default { axios.get(this.searchUrl, { params: { q: this.query, + filters: this.filters, group: this.group, preview: !this.selected, page: this.page diff --git a/app/javascript/vue/navigation/quick_search.vue b/app/javascript/vue/navigation/quick_search.vue index a7c5b90a7..79a622192 100644 --- a/app/javascript/vue/navigation/quick_search.vue +++ b/app/javascript/vue/navigation/quick_search.vue @@ -16,6 +16,7 @@ - diff --git a/app/views/search/index.html.erb b/app/views/search/index.html.erb index 9a08c3522..fc84a611f 100644 --- a/app/views/search/index.html.erb +++ b/app/views/search/index.html.erb @@ -6,6 +6,7 @@ :search-url="'<%= search_path(format: :json) %>'" teams-url="<%= visible_teams_teams_path %>" users-url="<%= visible_users_teams_path %>" + current-team="<%= current_team.id %>" /> diff --git a/config/locales/en.yml b/config/locales/en.yml index de35747bc..6e7d6b6b9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -439,6 +439,8 @@ en: match_case: "Match case sensitive" object_id: "ID:" filters: + title: "More search options" + sub_filters: "Refine your search by applying the filters below." by_type: "Filter by type" by_created_date: "Filter by created date" by_updated_date: "Filter by updated date"
+ {{ i18n.t('search.filters.sub_title') }} +