Commit 2f8795eb authored by Michael Lunøe's avatar Michael Lunøe Committed by Andrew Fontaine

Feat(Code Review): use general filter bar

Move to use the general filtered search component
to be more consistent
parent 90c70427
...@@ -44,7 +44,8 @@ export default { ...@@ -44,7 +44,8 @@ export default {
}, },
sortOptions: { sortOptions: {
type: Array, type: Array,
required: true, default: () => [],
required: false,
}, },
initialFilterValue: { initialFilterValue: {
type: Array, type: Array,
...@@ -63,7 +64,7 @@ export default { ...@@ -63,7 +64,7 @@ export default {
}, },
}, },
data() { data() {
let selectedSortOption = this.sortOptions[0].sortDirection.descending; let selectedSortOption = this.sortOptions[0]?.sortDirection?.descending;
let selectedSortDirection = SortDirection.descending; let selectedSortDirection = SortDirection.descending;
// Extract correct sortBy value based on initialSortBy // Extract correct sortBy value based on initialSortBy
...@@ -267,7 +268,7 @@ export default { ...@@ -267,7 +268,7 @@ export default {
</template> </template>
</template> </template>
</gl-filtered-search> </gl-filtered-search>
<gl-button-group class="sort-dropdown-container d-flex"> <gl-button-group v-if="selectedSortOption" class="sort-dropdown-container d-flex">
<gl-dropdown :text="selectedSortOption.title" :right="true" class="w-100"> <gl-dropdown :text="selectedSortOption.title" :right="true" class="w-100">
<gl-dropdown-item <gl-dropdown-item
v-for="sortBy in sortOptions" v-for="sortBy in sortOptions"
......
...@@ -6,12 +6,12 @@ export default () => { ...@@ -6,12 +6,12 @@ export default () => {
const container = document.getElementById('js-code-review-analytics'); const container = document.getElementById('js-code-review-analytics');
const { const {
projectId, projectId,
projectPath,
newMergeRequestUrl, newMergeRequestUrl,
emptyStateSvgPath, emptyStateSvgPath,
milestonePath, milestonePath,
labelsPath, labelsPath,
} = container.dataset; } = container.dataset;
if (!container) return; if (!container) return;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
...@@ -22,6 +22,7 @@ export default () => { ...@@ -22,6 +22,7 @@ export default () => {
return h(CodeAnalyticsApp, { return h(CodeAnalyticsApp, {
props: { props: {
projectId: Number(projectId), projectId: Number(projectId),
projectPath,
newMergeRequestUrl, newMergeRequestUrl,
emptyStateSvgPath, emptyStateSvgPath,
milestonePath, milestonePath,
......
...@@ -21,6 +21,10 @@ export default { ...@@ -21,6 +21,10 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
projectPath: {
type: String,
required: true,
},
newMergeRequestUrl: { newMergeRequestUrl: {
type: String, type: String,
required: true, required: true,
...@@ -79,7 +83,7 @@ export default { ...@@ -79,7 +83,7 @@ export default {
<template> <template>
<div> <div>
<filter-bar v-if="codeReviewAnalyticsHasNewSearch" /> <filter-bar v-if="codeReviewAnalyticsHasNewSearch" :project-path="projectPath" />
<div class="mt-2"> <div class="mt-2">
<gl-loading-icon v-show="isLoading" size="md" class="mt-3" /> <gl-loading-icon v-show="isLoading" size="md" class="mt-3" />
<template v-if="!isLoading"> <template v-if="!isLoading">
......
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { GlFilteredSearch } from '@gitlab/ui'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import MilestoneToken from '../../shared/components/tokens/milestone_token.vue'; import MilestoneToken from '../../shared/components/tokens/milestone_token.vue';
import LabelToken from '../../shared/components/tokens/label_token.vue'; import LabelToken from '../../shared/components/tokens/label_token.vue';
export default { export default {
components: { components: {
GlFilteredSearch, FilteredSearchBar,
},
props: {
projectPath: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
searchTerms: [], initialFilterValue: [],
}; };
}, },
computed: { computed: {
...@@ -78,7 +84,7 @@ export default { ...@@ -78,7 +84,7 @@ export default {
return acc; return acc;
}, {}); }, {});
}, },
filteredSearchSubmit(filters) { handleFilter(filters) {
const { label: labelNames, milestone } = this.processFilters(filters); const { label: labelNames, milestone } = this.processFilters(filters);
const milestoneTitle = milestone ? milestone[0] : null; const milestoneTitle = milestone ? milestone[0] : null;
this.setFilters({ labelNames, milestoneTitle }); this.setFilters({ labelNames, milestoneTitle });
...@@ -88,14 +94,13 @@ export default { ...@@ -88,14 +94,13 @@ export default {
</script> </script>
<template> <template>
<div class="bg-secondary-50 p-3 border-top border-bottom"> <filtered-search-bar
<gl-filtered-search :namespace="projectPath"
:v-model="searchTerms" recent-searches-storage-key="code-review-analytics"
:placeholder="__('Filter results')" :search-input-placeholder="__('Filter results')"
:clear-button-title="__('Clear')" :tokens="tokens"
:close-button-title="__('Close')" :initial-filter-value="initialFilterValue"
:available-tokens="tokens" class="row-content-block"
@submit="filteredSearchSubmit" @onFilter="handleFilter"
/> />
</div>
</template> </template>
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
%span.text-secondary= _('Review time is defined as the time it takes from first comment until merged.') %span.text-secondary= _('Review time is defined as the time it takes from first comment until merged.')
- if Feature.disabled?(:code_review_analytics_has_new_search) - if Feature.disabled?(:code_review_analytics_has_new_search)
= render 'shared/issuable/search_bar', type: :issues_analytics, show_sorting_dropdown: false, placeholder: _('Filter results...') = render 'shared/issuable/search_bar', type: :issues_analytics, show_sorting_dropdown: false, placeholder: _('Filter results...')
#js-code-review-analytics{ data: { project_id: @project.id, new_merge_request_url: namespace_project_new_merge_request_path(@project.namespace), empty_state_svg_path: image_path('illustrations/merge_requests.svg'), milestone_path: project_milestones_path(@project), labels_path: project_labels_path(@project) } } #js-code-review-analytics{ data: { project_id: @project.id, project_path: project_path(@project), new_merge_request_url: namespace_project_new_merge_request_path(@project.namespace), empty_state_svg_path: image_path('illustrations/merge_requests.svg'), milestone_path: project_milestones_path(@project), labels_path: project_labels_path(@project) } }
...@@ -75,6 +75,7 @@ describe('CodeReviewAnalyticsApp component', () => { ...@@ -75,6 +75,7 @@ describe('CodeReviewAnalyticsApp component', () => {
newMergeRequestUrl: 'new_merge_request', newMergeRequestUrl: 'new_merge_request',
emptyStateSvgPath: 'svg', emptyStateSvgPath: 'svg',
milestonePath: `${TEST_HOST}/milestones`, milestonePath: `${TEST_HOST}/milestones`,
projectPath: TEST_HOST,
labelsPath: `${TEST_HOST}/labels`, labelsPath: `${TEST_HOST}/labels`,
}, },
provide: { provide: {
......
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { GlFilteredSearch } from '@gitlab/ui'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import FilterBar from 'ee/analytics/code_review_analytics/components/filter_bar.vue'; import FilterBar from 'ee/analytics/code_review_analytics/components/filter_bar.vue';
import createFiltersState from 'ee/analytics/code_review_analytics/store/modules/filters/state'; import createFiltersState from 'ee/analytics/code_review_analytics/store/modules/filters/state';
import { mockMilestones, mockLabels } from '../mock_data'; import { mockMilestones, mockLabels } from '../mock_data';
...@@ -42,19 +42,22 @@ describe('FilteredSearchBar', () => { ...@@ -42,19 +42,22 @@ describe('FilteredSearchBar', () => {
shallowMount(FilterBar, { shallowMount(FilterBar, {
localVue, localVue,
store, store,
propsData: {
projectPath: 'foo',
},
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
const findFilteredSearch = () => wrapper.find(GlFilteredSearch); const findFilteredSearch = () => wrapper.find(FilteredSearchBar);
const getSearchToken = type => const getSearchToken = type =>
findFilteredSearch() findFilteredSearch()
.props('availableTokens') .props('tokens')
.filter(token => token.type === type)[0]; .find(token => token.type === type);
it('renders GlFilteredSearch component', () => { it('renders FilteredSearchBar component', () => {
vuexStore = createStore(); vuexStore = createStore();
wrapper = createComponent(vuexStore); wrapper = createComponent(vuexStore);
...@@ -71,7 +74,7 @@ describe('FilteredSearchBar', () => { ...@@ -71,7 +74,7 @@ describe('FilteredSearchBar', () => {
}); });
it('displays the milestone and label token', () => { it('displays the milestone and label token', () => {
const tokens = findFilteredSearch().props('availableTokens'); const tokens = findFilteredSearch().props('tokens');
expect(tokens).toHaveLength(2); expect(tokens).toHaveLength(2);
expect(tokens[0].type).toBe(milestoneTokenType); expect(tokens[0].type).toBe(milestoneTokenType);
...@@ -101,7 +104,7 @@ describe('FilteredSearchBar', () => { ...@@ -101,7 +104,7 @@ describe('FilteredSearchBar', () => {
}); });
it('clicks on the search button, setFilters is dispatched', () => { it('clicks on the search button, setFilters is dispatched', () => {
findFilteredSearch().vm.$emit('submit', [ findFilteredSearch().vm.$emit('onFilter', [
{ type: 'milestone', value: { data: 'my-milestone', operator: '=' } }, { type: 'milestone', value: { data: 'my-milestone', operator: '=' } },
{ type: 'label', value: { data: 'my-label', operator: '=' } }, { type: 'label', value: { data: 'my-label', operator: '=' } },
]); ]);
...@@ -117,7 +120,7 @@ describe('FilteredSearchBar', () => { ...@@ -117,7 +120,7 @@ describe('FilteredSearchBar', () => {
}); });
it('removes wrapping double quotes from the data and dispatches setFilters', () => { it('removes wrapping double quotes from the data and dispatches setFilters', () => {
findFilteredSearch().vm.$emit('submit', [ findFilteredSearch().vm.$emit('onFilter', [
{ type: 'milestone', value: { data: '"milestone with spaces"', operator: '=' } }, { type: 'milestone', value: { data: '"milestone with spaces"', operator: '=' } },
]); ]);
...@@ -132,7 +135,7 @@ describe('FilteredSearchBar', () => { ...@@ -132,7 +135,7 @@ describe('FilteredSearchBar', () => {
}); });
it('removes wrapping single quotes from the data and dispatches setFilters', () => { it('removes wrapping single quotes from the data and dispatches setFilters', () => {
findFilteredSearch().vm.$emit('submit', [ findFilteredSearch().vm.$emit('onFilter', [
{ type: 'milestone', value: { data: "'milestone with spaces'", operator: '=' } }, { type: 'milestone', value: { data: "'milestone with spaces'", operator: '=' } },
]); ]);
...@@ -147,7 +150,7 @@ describe('FilteredSearchBar', () => { ...@@ -147,7 +150,7 @@ describe('FilteredSearchBar', () => {
}); });
it('does not remove inner double quotes from the data and dispatches setFilters ', () => { it('does not remove inner double quotes from the data and dispatches setFilters ', () => {
findFilteredSearch().vm.$emit('submit', [ findFilteredSearch().vm.$emit('onFilter', [
{ type: 'milestone', value: { data: 'milestone "with" spaces', operator: '=' } }, { type: 'milestone', value: { data: 'milestone "with" spaces', operator: '=' } },
]); ]);
......
...@@ -20,7 +20,7 @@ const createComponent = ({ ...@@ -20,7 +20,7 @@ const createComponent = ({
namespace = 'gitlab-org/gitlab-test', namespace = 'gitlab-org/gitlab-test',
recentSearchesStorageKey = 'requirements', recentSearchesStorageKey = 'requirements',
tokens = mockAvailableTokens, tokens = mockAvailableTokens,
sortOptions = mockSortOptions, sortOptions,
searchInputPlaceholder = 'Filter requirements', searchInputPlaceholder = 'Filter requirements',
} = {}) => { } = {}) => {
const mountMethod = shallow ? shallowMount : mount; const mountMethod = shallow ? shallowMount : mount;
...@@ -40,7 +40,7 @@ describe('FilteredSearchBarRoot', () => { ...@@ -40,7 +40,7 @@ describe('FilteredSearchBarRoot', () => {
let wrapper; let wrapper;
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent({ sortOptions: mockSortOptions });
}); });
afterEach(() => { afterEach(() => {
...@@ -48,10 +48,25 @@ describe('FilteredSearchBarRoot', () => { ...@@ -48,10 +48,25 @@ describe('FilteredSearchBarRoot', () => {
}); });
describe('data', () => { describe('data', () => {
it('initializes `filterValue`, `selectedSortOption` and `selectedSortDirection` data props', () => { it('initializes `filterValue`, `selectedSortOption` and `selectedSortDirection` data props and displays the sort dropdown', () => {
expect(wrapper.vm.filterValue).toEqual([]); expect(wrapper.vm.filterValue).toEqual([]);
expect(wrapper.vm.selectedSortOption).toBe(mockSortOptions[0].sortDirection.descending); expect(wrapper.vm.selectedSortOption).toBe(mockSortOptions[0].sortDirection.descending);
expect(wrapper.vm.selectedSortDirection).toBe(SortDirection.descending); expect(wrapper.vm.selectedSortDirection).toBe(SortDirection.descending);
expect(wrapper.contains(GlButtonGroup)).toBe(true);
expect(wrapper.contains(GlButton)).toBe(true);
expect(wrapper.contains(GlDropdown)).toBe(true);
expect(wrapper.contains(GlDropdownItem)).toBe(true);
});
it('does not initialize `selectedSortOption` and `selectedSortDirection` when `sortOptions` is not applied and hides the sort dropdown', () => {
const wrapperNoSort = createComponent();
expect(wrapperNoSort.vm.filterValue).toEqual([]);
expect(wrapperNoSort.vm.selectedSortOption).toBe(undefined);
expect(wrapperNoSort.contains(GlButtonGroup)).toBe(false);
expect(wrapperNoSort.contains(GlButton)).toBe(false);
expect(wrapperNoSort.contains(GlDropdown)).toBe(false);
expect(wrapperNoSort.contains(GlDropdownItem)).toBe(false);
}); });
}); });
...@@ -286,7 +301,7 @@ describe('FilteredSearchBarRoot', () => { ...@@ -286,7 +301,7 @@ describe('FilteredSearchBarRoot', () => {
}); });
it('renders search history items dropdown with formatting done using token symbols', async () => { it('renders search history items dropdown with formatting done using token symbols', async () => {
const wrapperFullMount = createComponent({ shallow: false }); const wrapperFullMount = createComponent({ sortOptions: mockSortOptions, shallow: false });
wrapperFullMount.vm.recentSearchesStore.addRecentSearch(mockHistoryItems[0]); wrapperFullMount.vm.recentSearchesStore.addRecentSearch(mockHistoryItems[0]);
await wrapperFullMount.vm.$nextTick(); await wrapperFullMount.vm.$nextTick();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment