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