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 {
projects: [],
selectedProjects: this.defaultProjects || [],
searchTerm: '',
isDirty: false,
};
},
computed: {
......@@ -124,6 +125,24 @@ export default {
this.setSelectedProjects(project, !isSelected);
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() {
this.loading = true;
......@@ -158,12 +177,12 @@ export default {
},
};
</script>
<template>
<gl-dropdown
ref="projectsDropdown"
class="dropdown dropdown-projects"
toggle-class="gl-shadow-none"
@hide="onHide"
>
<template #button-content>
<div class="gl-display-flex gl-flex-grow-1">
......@@ -181,15 +200,18 @@ export default {
</div>
<gl-icon class="gl-ml-2" name="chevron-down" />
</template>
<gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header>
<gl-search-box-by-type v-model.trim="searchTerm" />
<template #header>
<gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header>
<gl-search-box-by-type v-model.trim="searchTerm" />
</template>
<gl-dropdown-item
v-for="project in availableProjects"
:key="project.id"
:is-check-item="true"
: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">
<gl-avatar
......@@ -203,7 +225,9 @@ export default {
/>
<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>
</gl-dropdown-item>
......
......@@ -121,7 +121,6 @@ export default {
]),
onProjectsSelect(projects) {
this.setSelectedProjects(projects);
this.fetchCycleAnalyticsData();
},
onStageSelect(stage) {
if (stage.slug === OVERVIEW_STAGE_ID) {
......
......@@ -27,8 +27,18 @@ export const setPaths = ({ dispatch }, options) => {
export const setFeatureFlags = ({ commit }, 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);
return refreshData({ dispatch, selectedStage, isOverviewStageSelected });
};
export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage);
......@@ -383,8 +393,7 @@ export const setFilters = ({
getters: { isOverviewStageSelected },
state: { selectedStage },
}) => {
if (selectedStage && !isOverviewStageSelected) dispatch('fetchStageData', selectedStage.id);
return dispatch('fetchCycleAnalyticsData');
return refreshData({ dispatch, isOverviewStageSelected, selectedStage });
};
export const updateStageTablePagination = (
......
......@@ -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 types from 'ee/analytics/cycle_analytics/store/mutation_types';
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 httpStatusCodes from '~/lib/utils/http_status';
import {
......@@ -47,6 +52,7 @@ jest.mock('~/flash');
describe('Value Stream Analytics actions', () => {
let state;
let stateWithOverview = null;
let mock;
beforeEach(() => {
......@@ -68,9 +74,8 @@ describe('Value Stream Analytics actions', () => {
});
it.each`
action | type | stateKey | payload
${'setFeatureFlags'} | ${'SET_FEATURE_FLAGS'} | ${'featureFlags'} | ${{ someFeatureFlag: true }}
${'setSelectedProjects'} | ${'SET_SELECTED_PROJECTS'} | ${'selectedProjectIds'} | ${[10, 20, 30, 40]}
action | type | stateKey | payload
${'setFeatureFlags'} | ${'SET_FEATURE_FLAGS'} | ${'featureFlags'} | ${{ someFeatureFlag: true }}
`('$action should set $stateKey with $payload and type $type', ({ action, type, payload }) => {
return testAction(
actions[action],
......@@ -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', () => {
const data = { id: 'someStageId' };
......@@ -961,8 +1003,6 @@ describe('Value Stream Analytics actions', () => {
${actions.setDateRange} | ${{ createdAfter, createdBefore }} | ${[{ type: 'SET_DATE_RANGE', payload: { createdAfter, createdBefore } }]}
${actions.setFilters} | ${''} | ${[]}
`('$action', ({ targetAction, payload, mutations }) => {
let stateWithOverview = null;
beforeEach(() => {
stateWithOverview = { ...state, isOverviewStageSelected: () => true };
});
......
......@@ -78,6 +78,8 @@ describe('ProjectsDropdownFilter component', () => {
const selectDropdownItemAtIndex = (index) =>
findDropdownAtIndex(index).find('button').trigger('click');
const selectedIds = () => wrapper.vm.selectedProjects.map(({ id }) => id);
describe('queryParams are applied when fetching data', () => {
beforeEach(() => {
createComponent({
......@@ -238,14 +240,15 @@ describe('ProjectsDropdownFilter component', () => {
selectDropdownItemAtIndex(0);
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', () => {
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 () => {
......
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