Commit c01d0411 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '34021-environments-dropdown-dynamic-filtering' into 'master'

Add server-side search for environments dropdown

See merge request gitlab-org/gitlab!23834
parents ce5047a0 748b4e66
......@@ -6,8 +6,11 @@ import {
GlButton,
GlDropdown,
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
GlFormGroup,
GlModal,
GlLoadingIcon,
GlSearchBoxByType,
GlModalDirective,
GlTooltipDirective,
......@@ -41,7 +44,10 @@ export default {
Icon,
GlButton,
GlDropdown,
GlLoadingIcon,
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
GlSearchBoxByType,
GlFormGroup,
GlModal,
......@@ -210,6 +216,7 @@ export default {
'useDashboardEndpoint',
'allDashboards',
'additionalPanelTypesEnabled',
'environmentsLoading',
]),
...mapGetters('monitoringDashboard', ['getMetricStates', 'filteredEnvironments']),
firstDashboard() {
......@@ -235,6 +242,9 @@ export default {
shouldRenderSearchableEnvironmentsDropdown() {
return this.glFeatures.searchableEnvironmentsDropdown;
},
shouldShowEnvironmentsDropdownNoMatchedMsg() {
return !this.environmentsLoading && this.filteredEnvironments.length === 0;
},
},
created() {
this.setEndpoints({
......@@ -262,7 +272,7 @@ export default {
'setGettingStartedEmptyState',
'setEndpoints',
'setPanelGroupMetrics',
'setEnvironmentsSearchTerm',
'filterEnvironments',
]),
updatePanels(key, panels) {
this.setPanelGroupMetrics({
......@@ -305,7 +315,7 @@ export default {
this.formIsValid = isValid;
},
debouncedEnvironmentsSearch: debounce(function environmentsSearchOnInput(searchTerm) {
this.setEnvironmentsSearchTerm(searchTerm);
this.filterEnvironments(searchTerm);
}, 500),
submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit();
......@@ -390,16 +400,22 @@ export default {
toggle-class="dropdown-menu-toggle"
menu-class="monitor-environment-dropdown-menu"
:text="currentEnvironmentName"
:disabled="filteredEnvironments.length === 0"
>
<div class="d-flex flex-column overflow-hidden">
<gl-dropdown-header class="text-center">{{ __('Environment') }}</gl-dropdown-header>
<gl-dropdown-divider />
<gl-search-box-by-type
v-if="shouldRenderSearchableEnvironmentsDropdown"
ref="monitorEnvironmentsDropdownSearch"
class="m-2"
@input="debouncedEnvironmentsSearch"
/>
<div class="flex-fill overflow-auto">
<gl-loading-icon
v-if="environmentsLoading"
ref="monitorEnvironmentsDropdownLoading"
:inline="true"
/>
<div v-else class="flex-fill overflow-auto">
<gl-dropdown-item
v-for="environment in filteredEnvironments"
:key="environment.id"
......@@ -411,11 +427,11 @@ export default {
</div>
<div
v-if="shouldRenderSearchableEnvironmentsDropdown"
v-show="filteredEnvironments.length === 0"
v-show="shouldShowEnvironmentsDropdownNoMatchedMsg"
ref="monitorEnvironmentsDropdownMsg"
class="text-secondary no-matches-message"
>
{{ s__('No matching results') }}
{{ __('No matching results') }}
</div>
</div>
</gl-dropdown>
......
......@@ -32,8 +32,9 @@ export const setEndpoints = ({ commit }, endpoints) => {
commit(types.SET_ENDPOINTS, endpoints);
};
export const setEnvironmentsSearchTerm = ({ commit }, searchTerm) => {
commit(types.SET_ENVIRONMENTS_SEARCH_TERM, searchTerm);
export const filterEnvironments = ({ commit, dispatch }, searchTerm) => {
commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
dispatch('fetchEnvironmentsData');
};
export const setShowErrorBanner = ({ commit }, enabled) => {
......@@ -56,6 +57,7 @@ export const receiveDeploymentsDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, data);
export const receiveDeploymentsDataFailure = ({ commit }) =>
commit(types.RECEIVE_DEPLOYMENTS_DATA_FAILURE);
export const requestEnvironmentsData = ({ commit }) => commit(types.REQUEST_ENVIRONMENTS_DATA);
export const receiveEnvironmentsDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data);
export const receiveEnvironmentsDataFailure = ({ commit }) =>
......@@ -189,8 +191,9 @@ export const fetchDeploymentsData = ({ state, dispatch }) => {
});
};
export const fetchEnvironmentsData = ({ state, dispatch }) =>
gqClient
export const fetchEnvironmentsData = ({ state, dispatch }) => {
dispatch('requestEnvironmentsData');
return gqClient
.mutate({
mutation: getEnvironments,
variables: {
......@@ -207,12 +210,14 @@ export const fetchEnvironmentsData = ({ state, dispatch }) =>
s__('Metrics|There was an error fetching the environments data, please try again'),
);
}
dispatch('receiveEnvironmentsDataSuccess', environments);
})
.catch(() => {
dispatch('receiveEnvironmentsDataFailure');
createFlash(s__('Metrics|There was an error getting environments information.'));
});
};
/**
* Set a new array of metrics to a panel group
......
......@@ -22,4 +22,4 @@ export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
export const SET_ENVIRONMENTS_SEARCH_TERM = 'SET_ENVIRONMENTS_SEARCH_TERM';
export const SET_ENVIRONMENTS_FILTER = 'SET_ENVIRONMENTS_FILTER';
......@@ -123,10 +123,15 @@ export default {
[types.RECEIVE_DEPLOYMENTS_DATA_FAILURE](state) {
state.deploymentData = [];
},
[types.REQUEST_ENVIRONMENTS_DATA](state) {
state.environmentsLoading = true;
},
[types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS](state, environments) {
state.environmentsLoading = false;
state.environments = environments;
},
[types.RECEIVE_ENVIRONMENTS_DATA_FAILURE](state) {
state.environmentsLoading = false;
state.environments = [];
},
......@@ -195,7 +200,7 @@ export default {
const panelGroup = state.dashboard.panel_groups.find(pg => payload.key === pg.key);
panelGroup.panels = payload.panels;
},
[types.SET_ENVIRONMENTS_SEARCH_TERM](state, searchTerm) {
[types.SET_ENVIRONMENTS_FILTER](state, searchTerm) {
state.environmentsSearchTerm = searchTerm;
},
};
......@@ -15,6 +15,7 @@ export default () => ({
deploymentData: [],
environments: [],
environmentsSearchTerm: '',
environmentsLoading: false,
allDashboards: [],
currentDashboard: null,
projectPath: null,
......
......@@ -46,6 +46,20 @@
}
}
.prometheus-graphs-header {
.monitor-environment-dropdown-menu {
&.show {
display: flex;
flex-direction: column;
overflow: hidden;
}
.no-matches-message {
padding: $gl-padding-8 $gl-padding-12;
}
}
}
.prometheus-panel {
margin-top: 20px;
}
......
......@@ -31,10 +31,7 @@ describe('Dashboard', () => {
const findEnvironmentsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' });
const findAllEnvironmentsDropdownItems = () => findEnvironmentsDropdown().findAll(GlDropdownItem);
const setSearchTerm = searchTerm => {
wrapper.vm.$store.commit(
`monitoringDashboard/${types.SET_ENVIRONMENTS_SEARCH_TERM}`,
searchTerm,
);
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm);
};
const createShallowWrapper = (props = {}, options = {}) => {
......@@ -313,6 +310,25 @@ describe('Dashboard', () => {
expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownMsg' }).isVisible()).toBe(true);
});
});
it('shows loading element when environments fetch is still loading', () => {
wrapper.vm.$store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`);
return wrapper.vm
.$nextTick()
.then(() => {
expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownLoading' }).exists()).toBe(true);
})
.then(() => {
wrapper.vm.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
})
.then(() => {
expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownLoading' }).exists()).toBe(false);
});
});
});
describe('drag and drop function', () => {
......
......@@ -17,10 +17,12 @@ import {
fetchPrometheusMetrics,
fetchPrometheusMetric,
setEndpoints,
filterEnvironments,
setGettingStartedEmptyState,
duplicateSystemDashboard,
} from '~/monitoring/stores/actions';
import { gqClient, parseEnvironmentsResponse } from '~/monitoring/stores/utils';
import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql';
import storeState from '~/monitoring/stores/state';
import {
deploymentData,
......@@ -105,12 +107,70 @@ describe('Monitoring store actions', () => {
.catch(done.fail);
});
});
describe('fetchEnvironmentsData', () => {
it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', () => {
const dispatch = jest.fn();
const { state } = store;
state.projectPath = '/gitlab-org/gitlab-test';
const dispatch = jest.fn();
const { state } = store;
state.projectPath = 'gitlab-org/gitlab-test';
afterEach(() => {
resetStore(store);
jest.restoreAllMocks();
});
it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
jest.spyOn(gqClient, 'mutate').mockReturnValue(
Promise.resolve({
data: {
project: {
data: {
environments: [],
},
},
},
}),
);
return testAction(
filterEnvironments,
{},
state,
[
{
type: 'SET_ENVIRONMENTS_FILTER',
payload: {},
},
],
[
{
type: 'fetchEnvironmentsData',
},
],
);
});
it('fetch environments data call takes in search param', () => {
const mockMutate = jest.spyOn(gqClient, 'mutate');
const searchTerm = 'Something';
const mutationVariables = {
mutation: getEnvironments,
variables: {
projectPath: state.projectPath,
search: searchTerm,
},
};
state.environmentsSearchTerm = searchTerm;
mockMutate.mockReturnValue(Promise.resolve());
return fetchEnvironmentsData({
state,
dispatch,
}).then(() => {
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
});
});
it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on success', () => {
jest.spyOn(gqClient, 'mutate').mockReturnValue(
Promise.resolve({
data: {
......@@ -135,9 +195,6 @@ describe('Monitoring store actions', () => {
});
it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', () => {
const dispatch = jest.fn();
const { state } = store;
state.projectPath = '/gitlab-org/gitlab-test';
jest.spyOn(gqClient, 'mutate').mockReturnValue(Promise.reject());
return fetchEnvironmentsData({
......@@ -148,6 +205,7 @@ describe('Monitoring store actions', () => {
});
});
});
describe('Set endpoints', () => {
let mockedState;
beforeEach(() => {
......
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