Commit dac0003a authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '330314-prevent-multiselect-dropdown-from-autoclosing' into 'master'

Prevents autoclose for the shared projects filter

See merge request gitlab-org/gitlab!66062
parents 230856ff cda29c62
...@@ -62,6 +62,7 @@ export default { ...@@ -62,6 +62,7 @@ export default {
projects: [], projects: [],
selectedProjects: this.defaultProjects || [], selectedProjects: this.defaultProjects || [],
searchTerm: '', searchTerm: '',
isDirty: false,
}; };
}, },
computed: { computed: {
...@@ -124,6 +125,24 @@ export default { ...@@ -124,6 +125,24 @@ export default {
this.setSelectedProjects(project, !isSelected); this.setSelectedProjects(project, !isSelected);
this.$emit('selected', this.selectedProjects); this.$emit('selected', this.selectedProjects);
}, },
onMultiSelectClick({ project, isSelected }) {
this.setSelectedProjects(project, !isSelected);
this.isDirty = true;
},
onSelected(ev) {
if (this.multiSelect) {
this.onMultiSelectClick(ev);
} else {
this.onClick(ev);
}
},
onHide() {
if (this.multiSelect && this.isDirty) {
this.$emit('selected', this.selectedProjects);
}
this.searchTerm = '';
this.isDirty = false;
},
fetchData() { fetchData() {
this.loading = true; this.loading = true;
...@@ -158,12 +177,12 @@ export default { ...@@ -158,12 +177,12 @@ export default {
}, },
}; };
</script> </script>
<template> <template>
<gl-dropdown <gl-dropdown
ref="projectsDropdown" ref="projectsDropdown"
class="dropdown dropdown-projects" class="dropdown dropdown-projects"
toggle-class="gl-shadow-none" toggle-class="gl-shadow-none"
@hide="onHide"
> >
<template #button-content> <template #button-content>
<div class="gl-display-flex gl-flex-grow-1"> <div class="gl-display-flex gl-flex-grow-1">
...@@ -181,15 +200,18 @@ export default { ...@@ -181,15 +200,18 @@ export default {
</div> </div>
<gl-icon class="gl-ml-2" name="chevron-down" /> <gl-icon class="gl-ml-2" name="chevron-down" />
</template> </template>
<template #header>
<gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header> <gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header>
<gl-search-box-by-type v-model.trim="searchTerm" /> <gl-search-box-by-type v-model.trim="searchTerm" />
</template>
<gl-dropdown-item <gl-dropdown-item
v-for="project in availableProjects" v-for="project in availableProjects"
:key="project.id" :key="project.id"
:is-check-item="true" :is-check-item="true"
:is-checked="isProjectSelected(project.id)" :is-checked="isProjectSelected(project.id)"
@click.prevent="onClick({ project, isSelected: isProjectSelected(project.id) })" @click.native.capture.stop="
onSelected({ project, isSelected: isProjectSelected(project.id) })
"
> >
<div class="gl-display-flex"> <div class="gl-display-flex">
<gl-avatar <gl-avatar
...@@ -203,7 +225,9 @@ export default { ...@@ -203,7 +225,9 @@ export default {
/> />
<div> <div>
<div data-testid="project-name">{{ project.name }}</div> <div data-testid="project-name">{{ project.name }}</div>
<div class="gl-text-gray-500" data-testid="project-full-path">{{ project.fullPath }}</div> <div class="gl-text-gray-500" data-testid="project-full-path">
{{ project.fullPath }}
</div>
</div> </div>
</div> </div>
</gl-dropdown-item> </gl-dropdown-item>
......
...@@ -121,7 +121,6 @@ export default { ...@@ -121,7 +121,6 @@ export default {
]), ]),
onProjectsSelect(projects) { onProjectsSelect(projects) {
this.setSelectedProjects(projects); this.setSelectedProjects(projects);
this.fetchCycleAnalyticsData();
}, },
onStageSelect(stage) { onStageSelect(stage) {
if (stage.slug === OVERVIEW_STAGE_ID) { if (stage.slug === OVERVIEW_STAGE_ID) {
......
...@@ -27,8 +27,18 @@ export const setPaths = ({ dispatch }, options) => { ...@@ -27,8 +27,18 @@ export const setPaths = ({ dispatch }, options) => {
export const setFeatureFlags = ({ commit }, featureFlags) => export const setFeatureFlags = ({ commit }, featureFlags) =>
commit(types.SET_FEATURE_FLAGS, featureFlags); commit(types.SET_FEATURE_FLAGS, featureFlags);
export const setSelectedProjects = ({ commit }, projects) => const refreshData = ({ selectedStage, isOverviewStageSelected, dispatch }) => {
if (selectedStage && !isOverviewStageSelected) dispatch('fetchStageData', selectedStage.id);
return dispatch('fetchCycleAnalyticsData');
};
export const setSelectedProjects = (
{ commit, dispatch, getters: { isOverviewStageSelected }, state: { selectedStage } },
projects,
) => {
commit(types.SET_SELECTED_PROJECTS, projects); commit(types.SET_SELECTED_PROJECTS, projects);
return refreshData({ dispatch, selectedStage, isOverviewStageSelected });
};
export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage); export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage);
...@@ -383,8 +393,7 @@ export const setFilters = ({ ...@@ -383,8 +393,7 @@ export const setFilters = ({
getters: { isOverviewStageSelected }, getters: { isOverviewStageSelected },
state: { selectedStage }, state: { selectedStage },
}) => { }) => {
if (selectedStage && !isOverviewStageSelected) dispatch('fetchStageData', selectedStage.id); return refreshData({ dispatch, isOverviewStageSelected, selectedStage });
return dispatch('fetchCycleAnalyticsData');
}; };
export const updateStageTablePagination = ( export const updateStageTablePagination = (
......
...@@ -5,7 +5,12 @@ import * as actions from 'ee/analytics/cycle_analytics/store/actions'; ...@@ -5,7 +5,12 @@ import * as actions from 'ee/analytics/cycle_analytics/store/actions';
import * as getters from 'ee/analytics/cycle_analytics/store/getters'; import * as getters from 'ee/analytics/cycle_analytics/store/getters';
import * as types from 'ee/analytics/cycle_analytics/store/mutation_types'; import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { createdAfter, createdBefore, currentGroup } from 'jest/cycle_analytics/mock_data'; import {
createdAfter,
createdBefore,
currentGroup,
selectedProjects,
} from 'jest/cycle_analytics/mock_data';
import createFlash from '~/flash'; import createFlash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import { import {
...@@ -47,6 +52,7 @@ jest.mock('~/flash'); ...@@ -47,6 +52,7 @@ jest.mock('~/flash');
describe('Value Stream Analytics actions', () => { describe('Value Stream Analytics actions', () => {
let state; let state;
let stateWithOverview = null;
let mock; let mock;
beforeEach(() => { beforeEach(() => {
...@@ -70,7 +76,6 @@ describe('Value Stream Analytics actions', () => { ...@@ -70,7 +76,6 @@ describe('Value Stream Analytics actions', () => {
it.each` it.each`
action | type | stateKey | payload action | type | stateKey | payload
${'setFeatureFlags'} | ${'SET_FEATURE_FLAGS'} | ${'featureFlags'} | ${{ someFeatureFlag: true }} ${'setFeatureFlags'} | ${'SET_FEATURE_FLAGS'} | ${'featureFlags'} | ${{ someFeatureFlag: true }}
${'setSelectedProjects'} | ${'SET_SELECTED_PROJECTS'} | ${'selectedProjectIds'} | ${[10, 20, 30, 40]}
`('$action should set $stateKey with $payload and type $type', ({ action, type, payload }) => { `('$action should set $stateKey with $payload and type $type', ({ action, type, payload }) => {
return testAction( return testAction(
actions[action], actions[action],
...@@ -86,6 +91,43 @@ describe('Value Stream Analytics actions', () => { ...@@ -86,6 +91,43 @@ describe('Value Stream Analytics actions', () => {
); );
}); });
describe('setSelectedProjects', () => {
describe('with `overview` stage selected', () => {
beforeEach(() => {
stateWithOverview = { ...state, isOverviewStageSelected: () => true };
});
it('will dispatch the "fetchCycleAnalyticsData" action', () => {
return testAction(
actions.setSelectedProjects,
selectedProjects,
stateWithOverview,
[{ type: types.SET_SELECTED_PROJECTS, payload: selectedProjects }],
[{ type: 'fetchCycleAnalyticsData' }],
);
});
});
describe('with non overview stage selected', () => {
beforeEach(() => {
state = { ...state, selectedStage };
});
it('will dispatch the "fetchStageData" and "fetchCycleAnalyticsData" actions', () => {
return testAction(
actions.setSelectedProjects,
selectedProjects,
state,
[{ type: types.SET_SELECTED_PROJECTS, payload: selectedProjects }],
[
{ type: 'fetchStageData', payload: selectedStage.id },
{ type: 'fetchCycleAnalyticsData' },
],
);
});
});
});
describe('setSelectedStage', () => { describe('setSelectedStage', () => {
const data = { id: 'someStageId' }; const data = { id: 'someStageId' };
...@@ -961,8 +1003,6 @@ describe('Value Stream Analytics actions', () => { ...@@ -961,8 +1003,6 @@ describe('Value Stream Analytics actions', () => {
${actions.setDateRange} | ${{ createdAfter, createdBefore }} | ${[{ type: 'SET_DATE_RANGE', payload: { createdAfter, createdBefore } }]} ${actions.setDateRange} | ${{ createdAfter, createdBefore }} | ${[{ type: 'SET_DATE_RANGE', payload: { createdAfter, createdBefore } }]}
${actions.setFilters} | ${''} | ${[]} ${actions.setFilters} | ${''} | ${[]}
`('$action', ({ targetAction, payload, mutations }) => { `('$action', ({ targetAction, payload, mutations }) => {
let stateWithOverview = null;
beforeEach(() => { beforeEach(() => {
stateWithOverview = { ...state, isOverviewStageSelected: () => true }; stateWithOverview = { ...state, isOverviewStageSelected: () => true };
}); });
......
...@@ -78,6 +78,8 @@ describe('ProjectsDropdownFilter component', () => { ...@@ -78,6 +78,8 @@ describe('ProjectsDropdownFilter component', () => {
const selectDropdownItemAtIndex = (index) => const selectDropdownItemAtIndex = (index) =>
findDropdownAtIndex(index).find('button').trigger('click'); findDropdownAtIndex(index).find('button').trigger('click');
const selectedIds = () => wrapper.vm.selectedProjects.map(({ id }) => id);
describe('queryParams are applied when fetching data', () => { describe('queryParams are applied when fetching data', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
...@@ -238,14 +240,15 @@ describe('ProjectsDropdownFilter component', () => { ...@@ -238,14 +240,15 @@ describe('ProjectsDropdownFilter component', () => {
selectDropdownItemAtIndex(0); selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(1); selectDropdownItemAtIndex(1);
expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[projects[0], projects[1]]]]); expect(selectedIds()).toEqual([projects[0].id, projects[1].id]);
}); });
it('should remove from selection when clicked again', () => { it('should remove from selection when clicked again', () => {
selectDropdownItemAtIndex(0); selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(0); expect(selectedIds()).toEqual([projects[0].id]);
expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[]]]); selectDropdownItemAtIndex(0);
expect(selectedIds()).toEqual([]);
}); });
it('renders the correct placeholder text when multiple projects are selected', async () => { it('renders the correct placeholder text when multiple projects are selected', async () => {
......
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