Commit dcea86d0 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '232465-migrate-code-review-to-use-generic-filter-bar' into 'master'

Feat(Code Review): use general filter bar

Closes #217720

See merge request gitlab-org/gitlab!38908
parents 27ea4c9c 2f8795eb
......@@ -44,7 +44,8 @@ export default {
},
sortOptions: {
type: Array,
required: true,
default: () => [],
required: false,
},
initialFilterValue: {
type: Array,
......@@ -63,7 +64,7 @@ export default {
},
},
data() {
let selectedSortOption = this.sortOptions[0].sortDirection.descending;
let selectedSortOption = this.sortOptions[0]?.sortDirection?.descending;
let selectedSortDirection = SortDirection.descending;
// Extract correct sortBy value based on initialSortBy
......@@ -267,7 +268,7 @@ export default {
</template>
</template>
</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-item
v-for="sortBy in sortOptions"
......
......@@ -6,12 +6,12 @@ export default () => {
const container = document.getElementById('js-code-review-analytics');
const {
projectId,
projectPath,
newMergeRequestUrl,
emptyStateSvgPath,
milestonePath,
labelsPath,
} = container.dataset;
if (!container) return;
// eslint-disable-next-line no-new
......@@ -22,6 +22,7 @@ export default () => {
return h(CodeAnalyticsApp, {
props: {
projectId: Number(projectId),
projectPath,
newMergeRequestUrl,
emptyStateSvgPath,
milestonePath,
......
......@@ -21,6 +21,10 @@ export default {
type: Number,
required: true,
},
projectPath: {
type: String,
required: true,
},
newMergeRequestUrl: {
type: String,
required: true,
......@@ -79,7 +83,7 @@ export default {
<template>
<div>
<filter-bar v-if="codeReviewAnalyticsHasNewSearch" />
<filter-bar v-if="codeReviewAnalyticsHasNewSearch" :project-path="projectPath" />
<div class="mt-2">
<gl-loading-icon v-show="isLoading" size="md" class="mt-3" />
<template v-if="!isLoading">
......
<script>
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 MilestoneToken from '../../shared/components/tokens/milestone_token.vue';
import LabelToken from '../../shared/components/tokens/label_token.vue';
export default {
components: {
GlFilteredSearch,
FilteredSearchBar,
},
props: {
projectPath: {
type: String,
required: true,
},
},
data() {
return {
searchTerms: [],
initialFilterValue: [],
};
},
computed: {
......@@ -78,7 +84,7 @@ export default {
return acc;
}, {});
},
filteredSearchSubmit(filters) {
handleFilter(filters) {
const { label: labelNames, milestone } = this.processFilters(filters);
const milestoneTitle = milestone ? milestone[0] : null;
this.setFilters({ labelNames, milestoneTitle });
......@@ -88,14 +94,13 @@ export default {
</script>
<template>
<div class="bg-secondary-50 p-3 border-top border-bottom">
<gl-filtered-search
:v-model="searchTerms"
:placeholder="__('Filter results')"
:clear-button-title="__('Clear')"
:close-button-title="__('Close')"
:available-tokens="tokens"
@submit="filteredSearchSubmit"
/>
</div>
<filtered-search-bar
:namespace="projectPath"
recent-searches-storage-key="code-review-analytics"
:search-input-placeholder="__('Filter results')"
:tokens="tokens"
:initial-filter-value="initialFilterValue"
class="row-content-block"
@onFilter="handleFilter"
/>
</template>
......@@ -7,4 +7,4 @@
%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)
= 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', () => {
newMergeRequestUrl: 'new_merge_request',
emptyStateSvgPath: 'svg',
milestonePath: `${TEST_HOST}/milestones`,
projectPath: TEST_HOST,
labelsPath: `${TEST_HOST}/labels`,
},
provide: {
......
import { createLocalVue, shallowMount } from '@vue/test-utils';
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 createFiltersState from 'ee/analytics/code_review_analytics/store/modules/filters/state';
import { mockMilestones, mockLabels } from '../mock_data';
......@@ -42,19 +42,22 @@ describe('FilteredSearchBar', () => {
shallowMount(FilterBar, {
localVue,
store,
propsData: {
projectPath: 'foo',
},
});
afterEach(() => {
wrapper.destroy();
});
const findFilteredSearch = () => wrapper.find(GlFilteredSearch);
const findFilteredSearch = () => wrapper.find(FilteredSearchBar);
const getSearchToken = type =>
findFilteredSearch()
.props('availableTokens')
.filter(token => token.type === type)[0];
.props('tokens')
.find(token => token.type === type);
it('renders GlFilteredSearch component', () => {
it('renders FilteredSearchBar component', () => {
vuexStore = createStore();
wrapper = createComponent(vuexStore);
......@@ -71,7 +74,7 @@ describe('FilteredSearchBar', () => {
});
it('displays the milestone and label token', () => {
const tokens = findFilteredSearch().props('availableTokens');
const tokens = findFilteredSearch().props('tokens');
expect(tokens).toHaveLength(2);
expect(tokens[0].type).toBe(milestoneTokenType);
......@@ -101,7 +104,7 @@ describe('FilteredSearchBar', () => {
});
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: 'label', value: { data: 'my-label', operator: '=' } },
]);
......@@ -117,7 +120,7 @@ describe('FilteredSearchBar', () => {
});
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: '=' } },
]);
......@@ -132,7 +135,7 @@ describe('FilteredSearchBar', () => {
});
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: '=' } },
]);
......@@ -147,7 +150,7 @@ describe('FilteredSearchBar', () => {
});
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: '=' } },
]);
......
......@@ -20,7 +20,7 @@ const createComponent = ({
namespace = 'gitlab-org/gitlab-test',
recentSearchesStorageKey = 'requirements',
tokens = mockAvailableTokens,
sortOptions = mockSortOptions,
sortOptions,
searchInputPlaceholder = 'Filter requirements',
} = {}) => {
const mountMethod = shallow ? shallowMount : mount;
......@@ -40,7 +40,7 @@ describe('FilteredSearchBarRoot', () => {
let wrapper;
beforeEach(() => {
wrapper = createComponent();
wrapper = createComponent({ sortOptions: mockSortOptions });
});
afterEach(() => {
......@@ -48,10 +48,25 @@ describe('FilteredSearchBarRoot', () => {
});
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.selectedSortOption).toBe(mockSortOptions[0].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', () => {
});
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]);
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