Commit d3711900 authored by Mark Florian's avatar Mark Florian

Merge branch '207912-implementing-filtered-search-advanced-filters' into 'master'

Log explorer: split filters into separate simple and advanced components

Closes #207912

See merge request gitlab-org/gitlab!27484
parents 95c0aa51 54a0fa36
......@@ -7,17 +7,15 @@ import {
GlAlert,
GlDropdown,
GlDropdownHeader,
GlDropdownDivider,
GlDropdownItem,
GlFormGroup,
GlSearchBoxByClick,
GlInfiniteScroll,
} from '@gitlab/ui';
import { s__ } from '~/locale';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import LogSimpleFilters from './log_simple_filters.vue';
import LogAdvancedFilters from './log_advanced_filters.vue';
import LogControlButtons from './log_control_buttons.vue';
import { timeRanges, defaultTimeRange } from '~/vue_shared/constants';
import { defaultTimeRange } from '~/vue_shared/constants';
import { timeRangeFromUrl } from '~/monitoring/utils';
import { formatDate } from '../utils';
......@@ -28,12 +26,10 @@ export default {
GlAlert,
GlDropdown,
GlDropdownHeader,
GlDropdownDivider,
GlDropdownItem,
GlFormGroup,
GlSearchBoxByClick,
GlInfiniteScroll,
DateTimePicker,
LogSimpleFilters,
LogAdvancedFilters,
LogControlButtons,
},
filters: {
......@@ -63,49 +59,22 @@ export default {
traceHeight: 600,
data() {
return {
searchQuery: '',
timeRanges,
isElasticStackCalloutDismissed: false,
scrollDownButtonDisabled: true,
};
},
computed: {
...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods']),
...mapGetters('environmentLogs', ['trace']),
timeRangeModel: {
get() {
return this.timeRange.selected;
},
set(val) {
this.setTimeRange(val);
},
},
...mapGetters('environmentLogs', ['trace', 'showAdvancedFilters']),
showLoader() {
return this.logs.isLoading;
},
advancedFeaturesEnabled() {
const environment = this.environments.options.find(
({ name }) => name === this.environments.current,
);
return environment && environment.enable_advanced_logs_querying;
},
disableAdvancedControls() {
return this.environments.isLoading || !this.advancedFeaturesEnabled;
},
shouldShowElasticStackCallout() {
return !this.isElasticStackCalloutDismissed && this.disableAdvancedControls;
},
podDropdownText() {
if (this.pods.current) {
return this.pods.current;
} else if (this.advancedFeaturesEnabled) {
// "All pods" is a valid option when advanced querying is available
return s__('Environments|All pods');
}
return s__('Environments|No pod selected');
return (
!this.isElasticStackCalloutDismissed &&
(this.environments.isLoading || !this.showAdvancedFilters)
);
},
},
mounted() {
......@@ -121,7 +90,6 @@ export default {
...mapActions('environmentLogs', [
'setInitData',
'setSearch',
'setTimeRange',
'showPodLogs',
'showEnvironment',
'fetchEnvironments',
......@@ -131,9 +99,6 @@ export default {
isCurrentEnvironment(envName) {
return envName === this.environments.current;
},
isCurrentPod(podName) {
return podName === this.pods.current;
},
topReached() {
if (!this.logs.isLoading) {
this.fetchMoreLogsPrepend();
......@@ -167,123 +132,49 @@ export default {
</strong>
</a>
</gl-alert>
<div class="top-bar js-top-bar d-flex">
<div class="row mx-n1">
<gl-form-group
id="environments-dropdown-fg"
label-size="sm"
label-for="environments-dropdown"
class="col-3 px-1"
<div class="top-bar d-md-flex border bg-secondary-50 pt-2 pr-1 pb-0 pl-2">
<div class="flex-grow-0">
<gl-dropdown
id="environments-dropdown"
:text="environments.current"
:disabled="environments.isLoading"
class="mb-2 gl-h-32 pr-2 d-flex d-md-block js-environments-dropdown"
>
<gl-dropdown
id="environments-dropdown"
:text="environments.current"
:disabled="environments.isLoading"
class="d-flex gl-h-32 js-environments-dropdown"
toggle-class="dropdown-menu-toggle"
<gl-dropdown-header class="text-center">
{{ s__('Environments|Select environment') }}
</gl-dropdown-header>
<gl-dropdown-item
v-for="env in environments.options"
:key="env.id"
@click="showEnvironment(env.name)"
>
<gl-dropdown-header class="text-center">
{{ s__('Environments|Select environment') }}
</gl-dropdown-header>
<gl-dropdown-item
v-for="env in environments.options"
:key="env.id"
@click="showEnvironment(env.name)"
>
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentEnvironment(env.name) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ env.name }}</div>
</div>
</gl-dropdown-item>
</gl-dropdown>
</gl-form-group>
<gl-form-group
id="pods-dropdown-fg"
label-size="sm"
label-for="pods-dropdown"
class="col-3 px-1"
>
<gl-dropdown
id="pods-dropdown"
:text="podDropdownText"
:disabled="environments.isLoading"
class="d-flex gl-h-32 js-pods-dropdown"
toggle-class="dropdown-menu-toggle"
>
<gl-dropdown-header class="text-center">
{{ s__('Environments|Filter by pod') }}
</gl-dropdown-header>
<template v-if="advancedFeaturesEnabled">
<gl-dropdown-item key="all-pods" @click="showPodLogs(null)">
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentPod(null) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ s__('Environments|All pods') }}</div>
</div>
</gl-dropdown-item>
<gl-dropdown-divider />
</template>
<gl-dropdown-item v-if="!pods.options.length" :disabled="true">
<span class="text-muted">
{{ s__('Environments|No pods to display') }}
</span>
</gl-dropdown-item>
<gl-dropdown-item
v-for="podName in pods.options"
:key="podName"
class="text-nowrap"
@click="showPodLogs(podName)"
>
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentPod(podName) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ podName }}</div>
</div>
</gl-dropdown-item>
</gl-dropdown>
</gl-form-group>
<gl-form-group id="search-fg" label-size="sm" label-for="search" class="col-3 px-1">
<gl-search-box-by-click
v-model.trim="searchQuery"
:disabled="disableAdvancedControls"
:placeholder="s__('Environments|Search')"
class="js-logs-search"
type="search"
autofocus
@submit="setSearch(searchQuery)"
/>
</gl-form-group>
<gl-form-group
id="dates-fg"
label-size="sm"
label-for="time-window-dropdown"
class="col-3 px-1"
>
<date-time-picker
ref="dateTimePicker"
v-model="timeRangeModel"
class="w-100 gl-h-32"
right
:disabled="disableAdvancedControls"
:options="timeRanges"
/>
</gl-form-group>
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentEnvironment(env.name) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ env.name }}</div>
</div>
</gl-dropdown-item>
</gl-dropdown>
</div>
<log-advanced-filters
v-if="showAdvancedFilters"
ref="log-advanced-filters"
class="d-md-flex flex-grow-1"
:disabled="environments.isLoading"
/>
<log-simple-filters
v-else
ref="log-simple-filters"
class="d-md-flex flex-grow-1"
:disabled="environments.isLoading"
/>
<log-control-buttons
ref="scrollButtons"
class="controllers"
class="flex-grow-0 pr-2 mb-2 controllers"
:scroll-down-button-disabled="scrollDownButtonDisabled"
@refresh="showPodLogs(pods.current)"
@scrollDown="scrollDown"
......
<script>
import { s__ } from '~/locale';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import { mapActions, mapState } from 'vuex';
import {
GlIcon,
GlDropdown,
GlDropdownHeader,
GlDropdownDivider,
GlDropdownItem,
GlSearchBoxByClick,
} from '@gitlab/ui';
import { timeRanges } from '~/vue_shared/constants';
export default {
components: {
GlIcon,
GlDropdown,
GlDropdownHeader,
GlDropdownDivider,
GlDropdownItem,
GlSearchBoxByClick,
DateTimePicker,
},
props: {
disabled: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
timeRanges,
searchQuery: '',
};
},
computed: {
...mapState('environmentLogs', ['timeRange', 'pods']),
timeRangeModel: {
get() {
return this.timeRange.selected;
},
set(val) {
this.setTimeRange(val);
},
},
podDropdownText() {
return this.pods.current || s__('Environments|All pods');
},
},
methods: {
...mapActions('environmentLogs', ['setSearch', 'showPodLogs', 'setTimeRange']),
isCurrentPod(podName) {
return podName === this.pods.current;
},
},
};
</script>
<template>
<div>
<gl-dropdown
ref="podsDropdown"
:text="podDropdownText"
:disabled="disabled"
class="mb-2 gl-h-32 pr-2 d-flex d-md-block flex-grow-0 qa-pods-dropdown"
>
<gl-dropdown-header class="text-center">
{{ s__('Environments|Filter by pod') }}
</gl-dropdown-header>
<gl-dropdown-item v-if="!pods.options.length" disabled>
<span ref="noPodsMsg" class="text-muted">
{{ s__('Environments|No pods to display') }}
</span>
</gl-dropdown-item>
<template v-else>
<gl-dropdown-item ref="allPodsOption" key="all-pods" @click="showPodLogs(null)">
<div class="d-flex">
<gl-icon
:class="{ invisible: pods.current !== null }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ s__('Environments|All pods') }}</div>
</div>
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-item
v-for="podName in pods.options"
:key="podName"
class="text-nowrap"
@click="showPodLogs(podName)"
>
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentPod(podName) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ podName }}</div>
</div>
</gl-dropdown-item>
</template>
</gl-dropdown>
<gl-search-box-by-click
ref="searchBox"
v-model.trim="searchQuery"
:disabled="disabled"
:placeholder="s__('Environments|Search')"
class="mb-2 pr-2 flex-grow-1"
type="search"
autofocus
@submit="setSearch(searchQuery)"
/>
<date-time-picker
ref="dateTimePicker"
v-model="timeRangeModel"
:disabled="disabled"
:options="timeRanges"
class="mb-2 gl-h-32 pr-2 d-block date-time-picker-wrapper"
right
/>
</div>
</template>
<script>
import { s__ } from '~/locale';
import { mapActions, mapState } from 'vuex';
import { GlIcon, GlDropdown, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui';
export default {
components: {
GlIcon,
GlDropdown,
GlDropdownHeader,
GlDropdownItem,
},
props: {
disabled: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
searchQuery: '',
};
},
computed: {
...mapState('environmentLogs', ['pods']),
podDropdownText() {
return this.pods.current || s__('Environments|No pod selected');
},
},
methods: {
...mapActions('environmentLogs', ['showPodLogs']),
isCurrentPod(podName) {
return podName === this.pods.current;
},
},
};
</script>
<template>
<div>
<gl-dropdown
ref="podsDropdown"
:text="podDropdownText"
:disabled="disabled"
class="mb-2 gl-h-32 pr-2 d-flex d-md-block flex-grow-0 qa-pods-dropdown"
>
<gl-dropdown-header class="text-center">
{{ s__('Environments|Select pod') }}
</gl-dropdown-header>
<gl-dropdown-item v-if="!pods.options.length" disabled>
<span ref="noPodsMsg" class="text-muted">
{{ s__('Environments|No pods to display') }}
</span>
</gl-dropdown-item>
<gl-dropdown-item
v-for="podName in pods.options"
:key="podName"
class="text-nowrap"
@click="showPodLogs(podName)"
>
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentPod(podName) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ podName }}</div>
</div>
</gl-dropdown-item>
</gl-dropdown>
</div>
</template>
......@@ -5,5 +5,9 @@ const mapTrace = ({ timestamp = null, pod = '', message = '' }) =>
export const trace = state => state.logs.lines.map(mapTrace).join('\n');
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const showAdvancedFilters = state => {
const environment = state.environments.options.find(
({ name }) => name === state.environments.current,
);
return Boolean(environment?.enable_advanced_logs_querying);
};
......@@ -379,25 +379,19 @@
}
.top-bar {
@include build-trace-top-bar($gl-line-height * 3);
position: relative;
top: 0;
.dropdown-menu-toggle {
width: 200px;
.date-time-picker-wrapper,
.dropdown-toggle {
@include media-breakpoint-up(md) {
width: 140px;
}
@include media-breakpoint-up(sm) {
width: 300px;
@include media-breakpoint-up(lg) {
width: 160px;
}
}
.controllers {
@include build-controllers(16px, flex-end, true, 2);
}
.refresh-control {
@include build-controllers(16px, flex-end, true, 0);
margin-left: 2px;
@include build-controllers(16px, flex-end, false, 2);
}
}
......
---
title: Improve logs filters on mobile, simplify kubernetes API logs filters
merge_request: 27484
author:
type: added
......@@ -7825,6 +7825,9 @@ msgstr ""
msgid "Environments|Select environment"
msgstr ""
msgid "Environments|Select pod"
msgstr ""
msgid "Environments|Show all"
msgstr ""
......
......@@ -29,7 +29,7 @@ describe 'Environment > Pod Logs', :js do
wait_for_requests
page.within('.js-environments-dropdown') do
toggle = find(".dropdown-menu-toggle:not([disabled])")
toggle = find(".dropdown-toggle:not([disabled])")
expect(toggle).to have_content(environment.name)
......@@ -47,8 +47,8 @@ describe 'Environment > Pod Logs', :js do
wait_for_requests
page.within('.js-pods-dropdown') do
find(".dropdown-menu-toggle:not([disabled])").click
page.within('.qa-pods-dropdown') do
find(".dropdown-toggle:not([disabled])").click
dropdown_items = find(".dropdown-menu").all(".dropdown-item:not([disabled])")
expect(dropdown_items.size).to eq(1)
......
import Vue from 'vue';
import { GlSprintf, GlIcon, GlDropdown, GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui';
import { GlSprintf, GlIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import EnvironmentLogs from '~/logs/components/environment_logs.vue';
import { createStore } from '~/logs/stores';
......@@ -13,7 +11,6 @@ import {
mockLogsResult,
mockTrace,
mockPodName,
mockSearch,
mockEnvironmentsEndpoint,
mockDocumentationPath,
} from '../mock_data';
......@@ -29,7 +26,6 @@ jest.mock('lodash/throttle', () =>
);
describe('EnvironmentLogs', () => {
let EnvironmentLogsComponent;
let store;
let dispatch;
let wrapper;
......@@ -44,13 +40,9 @@ describe('EnvironmentLogs', () => {
const updateControlBtnsMock = jest.fn();
const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown');
const findPodsDropdown = () => wrapper.find('.js-pods-dropdown');
const findPodsDropdownItems = () =>
findPodsDropdown()
.findAll(GlDropdownItem)
.filter(itm => !itm.attributes('disabled'));
const findSearchBar = () => wrapper.find('.js-logs-search');
const findTimeRangePicker = () => wrapper.find({ ref: 'dateTimePicker' });
const findSimpleFilters = () => wrapper.find({ ref: 'log-simple-filters' });
const findAdvancedFilters = () => wrapper.find({ ref: 'log-advanced-filters' });
const findInfoAlert = () => wrapper.find('.js-elasticsearch-alert');
const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' });
......@@ -79,7 +71,7 @@ describe('EnvironmentLogs', () => {
};
const initWrapper = () => {
wrapper = shallowMount(EnvironmentLogsComponent, {
wrapper = shallowMount(EnvironmentLogs, {
propsData,
store,
stubs: {
......@@ -111,7 +103,6 @@ describe('EnvironmentLogs', () => {
beforeEach(() => {
store = createStore();
state = store.state.environmentLogs;
EnvironmentLogsComponent = Vue.extend(EnvironmentLogs);
jest.spyOn(store, 'dispatch').mockResolvedValue();
......@@ -132,17 +123,10 @@ describe('EnvironmentLogs', () => {
expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.isEmpty()).toBe(false);
// top bar
expect(findEnvironmentsDropdown().is(GlDropdown)).toBe(true);
expect(findPodsDropdown().is(GlDropdown)).toBe(true);
expect(findSimpleFilters().exists()).toBe(true);
expect(findLogControlButtons().exists()).toBe(true);
expect(findSearchBar().exists()).toBe(true);
expect(findSearchBar().is(GlSearchBoxByClick)).toBe(true);
expect(findTimeRangePicker().exists()).toBe(true);
expect(findTimeRangePicker().is(DateTimePicker)).toBe(true);
// log trace
expect(findInfiniteScroll().exists()).toBe(true);
expect(findLogTrace().exists()).toBe(true);
});
......@@ -181,20 +165,6 @@ describe('EnvironmentLogs', () => {
expect(findEnvironmentsDropdown().findAll(GlDropdownItem).length).toBe(0);
});
it('displays a disabled pods dropdown', () => {
expect(findPodsDropdown().attributes('disabled')).toBe('true');
expect(findPodsDropdownItems()).toHaveLength(0);
});
it('displays a disabled search bar', () => {
expect(findSearchBar().exists()).toBe(true);
expect(findSearchBar().attributes('disabled')).toBe('true');
});
it('displays a disabled time window dropdown', () => {
expect(findTimeRangePicker().attributes('disabled')).toBe('true');
});
it('does not update buttons state', () => {
expect(updateControlBtnsMock).not.toHaveBeenCalled();
});
......@@ -237,17 +207,14 @@ describe('EnvironmentLogs', () => {
initWrapper();
});
it('displays a disabled time window dropdown', () => {
expect(findTimeRangePicker().attributes('disabled')).toBe('true');
});
it('displays a disabled search bar', () => {
expect(findSearchBar().attributes('disabled')).toBe('true');
});
it('displays an alert to upgrade to ES', () => {
expect(findInfoAlert().exists()).toBe(true);
});
it('displays simple filters for kubernetes logs API', () => {
expect(findSimpleFilters().exists()).toBe(true);
expect(findAdvancedFilters().exists()).toBe(false);
});
});
describe('state with data', () => {
......@@ -271,21 +238,6 @@ describe('EnvironmentLogs', () => {
updateControlBtnsMock.mockReset();
});
it('displays an enabled search bar', () => {
expect(findSearchBar().attributes('disabled')).toBeFalsy();
// input a query and click `search`
findSearchBar().vm.$emit('input', mockSearch);
findSearchBar().vm.$emit('submit');
expect(dispatch).toHaveBeenCalledWith(`${module}/setInitData`, expect.any(Object));
expect(dispatch).toHaveBeenCalledWith(`${module}/setSearch`, mockSearch);
});
it('displays an enabled time window dropdown', () => {
expect(findTimeRangePicker().attributes('disabled')).toBeFalsy();
});
it('does not display an alert to upgrade to ES', () => {
expect(findInfoAlert().exists()).toBe(false);
});
......@@ -306,24 +258,16 @@ describe('EnvironmentLogs', () => {
const item = items.at(i);
if (item.text() !== mockEnvName) {
expect(item.find(GlIcon).classes()).toContain('invisible');
expect(item.find(GlIcon).classes('invisible')).toBe(true);
} else {
// selected
expect(item.find(GlIcon).classes()).not.toContain('invisible');
expect(item.find(GlIcon).classes('invisible')).toBe(false);
}
});
});
it('populates pods dropdown', () => {
const items = findPodsDropdownItems();
expect(findPodsDropdown().props('text')).toBe(mockPodName);
expect(items.length).toBe(mockPods.length + 1);
expect(items.at(0).text()).toBe('All pods');
mockPods.forEach((pod, i) => {
const item = items.at(i + 1);
expect(item.text()).toBe(pod);
});
it('displays advanced filters for elasticsearch logs API', () => {
expect(findSimpleFilters().exists()).toBe(false);
expect(findAdvancedFilters().exists()).toBe(true);
});
it('shows infinite scroll with height and no content', () => {
......@@ -331,19 +275,6 @@ describe('EnvironmentLogs', () => {
expect(getInfiniteScrollAttr('fetched-items')).toBe(mockTrace.length);
});
it('dropdown has one pod selected', () => {
const items = findPodsDropdownItems();
mockPods.forEach((pod, i) => {
const item = items.at(i);
if (item.text() !== mockPodName) {
expect(item.find(GlIcon).classes()).toContain('invisible');
} else {
// selected
expect(item.find(GlIcon).classes()).not.toContain('invisible');
}
});
});
it('populates logs trace', () => {
const trace = findLogTrace();
expect(trace.text().split('\n').length).toBe(mockTrace.length);
......@@ -371,17 +302,6 @@ describe('EnvironmentLogs', () => {
);
});
it('pod name, trace is refreshed', () => {
const items = findPodsDropdownItems();
const index = 2; // any pod
expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything());
items.at(index + 1).vm.$emit('click');
expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]);
});
it('refresh button, trace is refreshed', () => {
expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything());
......
import { GlIcon, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { defaultTimeRange } from '~/vue_shared/constants';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { createStore } from '~/logs/stores';
import { mockPods, mockSearch } from '../mock_data';
import LogAdvancedFilters from '~/logs/components/log_advanced_filters.vue';
const module = 'environmentLogs';
describe('LogAdvancedFilters', () => {
let store;
let dispatch;
let wrapper;
let state;
const findPodsDropdown = () => wrapper.find({ ref: 'podsDropdown' });
const findPodsNoPodsText = () => wrapper.find({ ref: 'noPodsMsg' });
const findPodsDropdownItems = () =>
findPodsDropdown()
.findAll(GlDropdownItem)
.filter(item => !item.is('[disabled]'));
const findPodsDropdownItemsSelected = () =>
findPodsDropdownItems()
.filter(item => {
return !item.find(GlIcon).classes('invisible');
})
.at(0);
const findSearchBox = () => wrapper.find({ ref: 'searchBox' });
const findTimeRangePicker = () => wrapper.find({ ref: 'dateTimePicker' });
const mockStateLoading = () => {
state.timeRange.selected = defaultTimeRange;
state.timeRange.current = convertToFixedRange(defaultTimeRange);
state.pods.options = [];
state.pods.current = null;
};
const mockStateWithData = () => {
state.timeRange.selected = defaultTimeRange;
state.timeRange.current = convertToFixedRange(defaultTimeRange);
state.pods.options = mockPods;
state.pods.current = null;
};
const initWrapper = (propsData = {}) => {
wrapper = shallowMount(LogAdvancedFilters, {
propsData: {
...propsData,
},
store,
});
};
beforeEach(() => {
store = createStore();
state = store.state.environmentLogs;
jest.spyOn(store, 'dispatch').mockResolvedValue();
dispatch = store.dispatch;
});
afterEach(() => {
store.dispatch.mockReset();
if (wrapper) {
wrapper.destroy();
}
});
it('displays UI elements', () => {
initWrapper();
expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.isEmpty()).toBe(false);
expect(findPodsDropdown().exists()).toBe(true);
expect(findSearchBox().exists()).toBe(true);
expect(findTimeRangePicker().exists()).toBe(true);
});
describe('disabled state', () => {
beforeEach(() => {
mockStateLoading();
initWrapper({
disabled: true,
});
});
it('displays disabled filters', () => {
expect(findPodsDropdown().props('text')).toBe('All pods');
expect(findPodsDropdown().attributes('disabled')).toBeTruthy();
expect(findSearchBox().attributes('disabled')).toBeTruthy();
expect(findTimeRangePicker().attributes('disabled')).toBeTruthy();
});
});
describe('when the state is loading', () => {
beforeEach(() => {
mockStateLoading();
initWrapper();
});
it('displays a enabled filters', () => {
expect(findPodsDropdown().props('text')).toBe('All pods');
expect(findPodsDropdown().attributes('disabled')).toBeFalsy();
expect(findSearchBox().attributes('disabled')).toBeFalsy();
expect(findTimeRangePicker().attributes('disabled')).toBeFalsy();
});
it('displays an empty pods dropdown', () => {
expect(findPodsNoPodsText().exists()).toBe(true);
expect(findPodsDropdownItems()).toHaveLength(0);
});
});
describe('when the state has data', () => {
beforeEach(() => {
mockStateWithData();
initWrapper();
});
it('displays an enabled pods dropdown', () => {
expect(findPodsDropdown().attributes('disabled')).toBeFalsy();
expect(findPodsDropdown().props('text')).toBe('All pods');
});
it('displays options in a pods dropdown', () => {
const items = findPodsDropdownItems();
expect(items).toHaveLength(mockPods.length + 1);
});
it('displays "all pods" selected in a pods dropdown', () => {
const selected = findPodsDropdownItemsSelected();
expect(selected.text()).toBe('All pods');
});
it('displays options in date time picker', () => {
const options = findTimeRangePicker().props('options');
expect(options).toEqual(expect.any(Array));
expect(options.length).toBeGreaterThan(0);
});
describe('when the user interacts', () => {
it('clicks on a all options, showPodLogs is dispatched with null', () => {
const items = findPodsDropdownItems();
items.at(0).vm.$emit('click');
expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, null);
});
it('clicks on a pod name, showPodLogs is dispatched with pod name', () => {
const items = findPodsDropdownItems();
const index = 2; // any pod
items.at(index + 1).vm.$emit('click'); // skip "All pods" option
expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]);
});
it('clicks on search, a serches is done', () => {
expect(findSearchBox().attributes('disabled')).toBeFalsy();
// input a query and click `search`
findSearchBox().vm.$emit('input', mockSearch);
findSearchBox().vm.$emit('submit');
expect(dispatch).toHaveBeenCalledWith(`${module}/setSearch`, mockSearch);
});
it('selects a new time range', () => {
expect(findTimeRangePicker().attributes('disabled')).toBeFalsy();
const mockRange = { start: 'START_DATE', end: 'END_DATE' };
findTimeRangePicker().vm.$emit('input', mockRange);
expect(dispatch).toHaveBeenCalledWith(`${module}/setTimeRange`, mockRange);
});
});
});
});
import { GlIcon, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { createStore } from '~/logs/stores';
import { mockPods, mockPodName } from '../mock_data';
import LogSimpleFilters from '~/logs/components/log_simple_filters.vue';
const module = 'environmentLogs';
describe('LogSimpleFilters', () => {
let store;
let dispatch;
let wrapper;
let state;
const findPodsDropdown = () => wrapper.find({ ref: 'podsDropdown' });
const findPodsNoPodsText = () => wrapper.find({ ref: 'noPodsMsg' });
const findPodsDropdownItems = () =>
findPodsDropdown()
.findAll(GlDropdownItem)
.filter(item => !item.is('[disabled]'));
const mockPodsLoading = () => {
state.pods.options = [];
state.pods.current = null;
};
const mockPodsLoaded = () => {
state.pods.options = mockPods;
state.pods.current = mockPodName;
};
const initWrapper = (propsData = {}) => {
wrapper = shallowMount(LogSimpleFilters, {
propsData: {
...propsData,
},
store,
});
};
beforeEach(() => {
store = createStore();
state = store.state.environmentLogs;
jest.spyOn(store, 'dispatch').mockResolvedValue();
dispatch = store.dispatch;
});
afterEach(() => {
store.dispatch.mockReset();
if (wrapper) {
wrapper.destroy();
}
});
it('displays UI elements', () => {
initWrapper();
expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.isEmpty()).toBe(false);
expect(findPodsDropdown().exists()).toBe(true);
});
describe('disabled state', () => {
beforeEach(() => {
mockPodsLoading();
initWrapper({
disabled: true,
});
});
it('displays a disabled pods dropdown', () => {
expect(findPodsDropdown().props('text')).toBe('No pod selected');
expect(findPodsDropdown().attributes('disabled')).toBeTruthy();
});
});
describe('loading state', () => {
beforeEach(() => {
mockPodsLoading();
initWrapper();
});
it('displays an enabled pods dropdown', () => {
expect(findPodsDropdown().attributes('disabled')).toBeFalsy();
expect(findPodsDropdown().props('text')).toBe('No pod selected');
});
it('displays an empty pods dropdown', () => {
expect(findPodsNoPodsText().exists()).toBe(true);
expect(findPodsDropdownItems()).toHaveLength(0);
});
});
describe('pods available state', () => {
beforeEach(() => {
mockPodsLoaded();
initWrapper();
});
it('displays an enabled pods dropdown', () => {
expect(findPodsDropdown().attributes('disabled')).toBeFalsy();
expect(findPodsDropdown().props('text')).toBe(mockPods[0]);
});
it('displays a pods dropdown with items', () => {
expect(findPodsNoPodsText().exists()).toBe(false);
expect(findPodsDropdownItems()).toHaveLength(mockPods.length);
});
it('dropdown has one pod selected', () => {
const items = findPodsDropdownItems();
mockPods.forEach((pod, i) => {
const item = items.at(i);
if (item.text() !== mockPodName) {
expect(item.find(GlIcon).classes('invisible')).toBe(true);
} else {
expect(item.find(GlIcon).classes('invisible')).toBe(false);
}
});
});
it('when the user clicks on a pod, showPodLogs is dispatched', () => {
const items = findPodsDropdownItems();
const index = 2; // any pod
expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything());
items.at(index).vm.$emit('click');
expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]);
});
});
});
import * as getters from '~/logs/stores/getters';
import { trace, showAdvancedFilters } from '~/logs/stores/getters';
import logsPageState from '~/logs/stores/state';
import { mockLogsResult, mockTrace } from '../mock_data';
import { mockLogsResult, mockTrace, mockEnvName, mockEnvironments } from '../mock_data';
describe('Logs Store getters', () => {
let state;
......@@ -13,7 +13,7 @@ describe('Logs Store getters', () => {
describe('trace', () => {
describe('when state is initialized', () => {
it('returns an empty string', () => {
expect(getters.trace(state)).toEqual('');
expect(trace(state)).toEqual('');
});
});
......@@ -23,7 +23,7 @@ describe('Logs Store getters', () => {
});
it('returns an empty string', () => {
expect(getters.trace(state)).toEqual('');
expect(trace(state)).toEqual('');
});
});
......@@ -33,7 +33,42 @@ describe('Logs Store getters', () => {
});
it('returns an empty string', () => {
expect(getters.trace(state)).toEqual(mockTrace.join('\n'));
expect(trace(state)).toEqual(mockTrace.join('\n'));
});
});
});
describe('showAdvancedFilters', () => {
describe('when no environments are set', () => {
beforeEach(() => {
state.environments.current = mockEnvName;
state.environments.options = [];
});
it('returns false', () => {
expect(showAdvancedFilters(state)).toBe(false);
});
});
describe('when the environment supports filters', () => {
beforeEach(() => {
state.environments.current = mockEnvName;
state.environments.options = mockEnvironments;
});
it('returns true', () => {
expect(showAdvancedFilters(state)).toBe(true);
});
});
describe('when the environment does not support filters', () => {
beforeEach(() => {
state.environments.options = mockEnvironments;
state.environments.current = mockEnvironments[1].name;
});
it('returns true', () => {
expect(showAdvancedFilters(state)).toBe(false);
});
});
});
......
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