diff options
Diffstat (limited to 'spec/frontend/monitoring/components/dashboard_header_spec.js')
-rw-r--r-- | spec/frontend/monitoring/components/dashboard_header_spec.js | 372 |
1 files changed, 282 insertions, 90 deletions
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js index 5a1a615c703..5cf24706ebd 100644 --- a/spec/frontend/monitoring/components/dashboard_header_spec.js +++ b/spec/frontend/monitoring/components/dashboard_header_spec.js @@ -1,16 +1,23 @@ import { shallowMount } from '@vue/test-utils'; +import { GlNewDropdownItem, GlSearchBoxByType, GlLoadingIcon, GlButton } from '@gitlab/ui'; import { createStore } from '~/monitoring/stores'; +import * as types from '~/monitoring/stores/mutation_types'; +import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; +import RefreshButton from '~/monitoring/components/refresh_button.vue'; import DashboardHeader from '~/monitoring/components/dashboard_header.vue'; -import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue'; -import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue'; -import { setupAllDashboards } from '../store_utils'; +import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue'; +import ActionsMenu from '~/monitoring/components/dashboard_actions_menu.vue'; +import { setupAllDashboards, setupStoreWithDashboard, setupStoreWithData } from '../store_utils'; import { + environmentData, dashboardGitResponse, selfMonitoringDashboardGitResponse, dashboardHeaderProps, } from '../mock_data'; import { redirectTo } from '~/lib/utils/url_utility'; +const mockProjectPath = 'https://path/to/project'; + jest.mock('~/lib/utils/url_utility', () => ({ redirectTo: jest.fn(), queryToObject: jest.fn(), @@ -21,13 +28,22 @@ describe('Dashboard header', () => { let store; let wrapper; - const findActionsMenu = () => wrapper.find('[data-testid="actions-menu"]'); - const findCreateDashboardMenuItem = () => - findActionsMenu().find('[data-testid="action-create-dashboard"]'); - const findCreateDashboardDuplicateItem = () => - findActionsMenu().find('[data-testid="action-duplicate-dashboard"]'); - const findDuplicateDashboardModal = () => wrapper.find(DuplicateDashboardModal); - const findCreateDashboardModal = () => wrapper.find('[data-testid="create-dashboard-modal"]'); + const findDashboardDropdown = () => wrapper.find(DashboardsDropdown); + + const findEnvsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' }); + const findEnvsDropdownItems = () => findEnvsDropdown().findAll(GlNewDropdownItem); + const findEnvsDropdownSearch = () => findEnvsDropdown().find(GlSearchBoxByType); + const findEnvsDropdownSearchMsg = () => wrapper.find({ ref: 'monitorEnvironmentsDropdownMsg' }); + const findEnvsDropdownLoadingIcon = () => findEnvsDropdown().find(GlLoadingIcon); + + const findDateTimePicker = () => wrapper.find(DateTimePicker); + const findRefreshButton = () => wrapper.find(RefreshButton); + + const findActionsMenu = () => wrapper.find(ActionsMenu); + + const setSearchTerm = searchTerm => { + store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm); + }; const createShallowWrapper = (props = {}, options = {}) => { wrapper = shallowMount(DashboardHeader, { @@ -45,139 +61,315 @@ describe('Dashboard header', () => { wrapper.destroy(); }); - describe('when a dashboard has been duplicated in the duplicate dashboard modal', () => { + describe('dashboards dropdown', () => { beforeEach(() => { - store.state.monitoringDashboard.projectPath = 'root/sandbox'; + store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, { + projectPath: mockProjectPath, + }); + + createShallowWrapper(); }); - /** - * The duplicate dashboard modal gets called both by a menu item from the - * dashboards dropdown and by an item from the actions menu. - * - * This spec is context agnostic, so it addresses all cases where the - * duplicate dashboard modal gets called. - */ - it('redirects to the newly created dashboard', () => { - delete window.location; - window.location = new URL('https://localhost'); - const newDashboard = dashboardGitResponse[1]; + it('shows the dashboard dropdown', () => { + expect(findDashboardDropdown().exists()).toBe(true); + }); - createShallowWrapper(); + it('when an out of the box dashboard is selected, encodes dashboard path', () => { + findDashboardDropdown().vm.$emit('selectDashboard', { + path: '.gitlab/dashboards/dashboard©.yml', + out_of_the_box_dashboard: true, + display_name: 'A display name', + }); - const newDashboardUrl = 'root/sandbox/-/metrics/dashboard.yml'; - findDuplicateDashboardModal().vm.$emit('dashboardDuplicated', newDashboard); + expect(redirectTo).toHaveBeenCalledWith( + `${mockProjectPath}/-/metrics/.gitlab%2Fdashboards%2Fdashboard%26copy.yml`, + ); + }); - return wrapper.vm.$nextTick().then(() => { - expect(redirectTo).toHaveBeenCalled(); - expect(redirectTo).toHaveBeenCalledWith(newDashboardUrl); + it('when a custom dashboard is selected, encodes dashboard display name', () => { + findDashboardDropdown().vm.$emit('selectDashboard', { + path: '.gitlab/dashboards/file&path.yml', + display_name: 'dashboard©.yml', }); + + expect(redirectTo).toHaveBeenCalledWith(`${mockProjectPath}/-/metrics/dashboard%26copy.yml`); }); }); - describe('actions menu', () => { + describe('environments dropdown', () => { beforeEach(() => { - store.state.monitoringDashboard.projectPath = ''; createShallowWrapper(); }); - it('is rendered if projectPath is set in store', () => { - store.state.monitoringDashboard.projectPath = 'https://path/to/project'; + it('shows the environments dropdown', () => { + expect(findEnvsDropdown().exists()).toBe(true); + }); - return wrapper.vm.$nextTick().then(() => { - expect(findActionsMenu().exists()).toBe(true); + it('renders a search input', () => { + expect(findEnvsDropdownSearch().exists()).toBe(true); + }); + + describe('when environments data is not loaded', () => { + beforeEach(() => { + setupStoreWithDashboard(store); + return wrapper.vm.$nextTick(); + }); + + it('there are no environments listed', () => { + expect(findEnvsDropdownItems()).toHaveLength(0); + }); + }); + + describe('when environments data is loaded', () => { + const currentDashboard = dashboardGitResponse[0].path; + const currentEnvironmentName = environmentData[0].name; + + beforeEach(() => { + setupStoreWithData(store); + store.state.monitoringDashboard.projectPath = mockProjectPath; + store.state.monitoringDashboard.currentDashboard = currentDashboard; + store.state.monitoringDashboard.currentEnvironmentName = currentEnvironmentName; + + return wrapper.vm.$nextTick(); + }); + + it('renders dropdown items with the environment name', () => { + const path = `${mockProjectPath}/-/metrics/${encodeURIComponent(currentDashboard)}`; + + findEnvsDropdownItems().wrappers.forEach((itemWrapper, index) => { + const { name, id } = environmentData[index]; + const idParam = encodeURIComponent(id); + + expect(itemWrapper.text()).toBe(name); + expect(itemWrapper.attributes('href')).toBe(`${path}?environment=${idParam}`); + }); + }); + + it('environments dropdown items can be checked', () => { + const items = findEnvsDropdownItems(); + const checkItems = findEnvsDropdownItems().filter(item => item.props('isCheckItem')); + + expect(items).toHaveLength(checkItems.length); + }); + + it('checks the currently selected environment', () => { + const selectedItems = findEnvsDropdownItems().filter(item => item.props('isChecked')); + + expect(selectedItems).toHaveLength(1); + expect(selectedItems.at(0).text()).toBe(currentEnvironmentName); + }); + + it('filters rendered dropdown items', () => { + const searchTerm = 'production'; + const resultEnvs = environmentData.filter(({ name }) => name.indexOf(searchTerm) !== -1); + setSearchTerm(searchTerm); + + return wrapper.vm.$nextTick().then(() => { + expect(findEnvsDropdownItems()).toHaveLength(resultEnvs.length); + }); + }); + + it('does not filter dropdown items if search term is empty string', () => { + const searchTerm = ''; + setSearchTerm(searchTerm); + + return wrapper.vm.$nextTick(() => { + expect(findEnvsDropdownItems()).toHaveLength(environmentData.length); + }); + }); + + it("shows error message if search term doesn't match", () => { + const searchTerm = 'does-not-exist'; + setSearchTerm(searchTerm); + + return wrapper.vm.$nextTick(() => { + expect(findEnvsDropdownSearchMsg().isVisible()).toBe(true); + }); + }); + + it('shows loading element when environments fetch is still loading', () => { + store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`); + + return wrapper.vm + .$nextTick() + .then(() => { + expect(findEnvsDropdownLoadingIcon().exists()).toBe(true); + }) + .then(() => { + store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); + }) + .then(() => { + expect(findEnvsDropdownLoadingIcon().exists()).toBe(false); + }); }); }); + }); - it('is not rendered if projectPath is not set in store', () => { - expect(findActionsMenu().exists()).toBe(false); + describe('date time picker', () => { + beforeEach(() => { + createShallowWrapper(); }); - it('contains a modal', () => { - store.state.monitoringDashboard.projectPath = 'https://path/to/project'; + it('is rendered', () => { + expect(findDateTimePicker().exists()).toBe(true); + }); - return wrapper.vm.$nextTick().then(() => { - expect(findActionsMenu().contains(CreateDashboardModal)).toBe(true); + describe('timezone setting', () => { + const setupWithTimezone = value => { + store = createStore({ dashboardTimezone: value }); + createShallowWrapper(); + }; + + describe('local timezone is enabled by default', () => { + it('shows the data time picker in local timezone', () => { + expect(findDateTimePicker().props('utc')).toBe(false); + }); + }); + + describe('when LOCAL timezone is enabled', () => { + beforeEach(() => { + setupWithTimezone('LOCAL'); + }); + + it('shows the data time picker in local timezone', () => { + expect(findDateTimePicker().props('utc')).toBe(false); + }); + }); + + describe('when UTC timezone is enabled', () => { + beforeEach(() => { + setupWithTimezone('UTC'); + }); + + it('shows the data time picker in UTC format', () => { + expect(findDateTimePicker().props('utc')).toBe(true); + }); }); }); + }); + + describe('refresh button', () => { + beforeEach(() => { + createShallowWrapper(); + }); + + it('is rendered', () => { + expect(findRefreshButton().exists()).toBe(true); + }); + }); + + describe('external dashboard link', () => { + beforeEach(() => { + store.state.monitoringDashboard.externalDashboardUrl = '/mockUrl'; + createShallowWrapper(); + + return wrapper.vm.$nextTick(); + }); + + it('shows the link', () => { + const externalDashboardButton = wrapper.find('.js-external-dashboard-link'); + + expect(externalDashboardButton.exists()).toBe(true); + expect(externalDashboardButton.is(GlButton)).toBe(true); + expect(externalDashboardButton.text()).toContain('View full dashboard'); + }); + }); - const duplicableCases = [ - null, // When no path is specified, it uses the default dashboard path. + describe('actions menu', () => { + const ootbDashboards = [ dashboardGitResponse[0].path, - dashboardGitResponse[2].path, selfMonitoringDashboardGitResponse[0].path, ]; + const customDashboards = [ + dashboardGitResponse[1].path, + selfMonitoringDashboardGitResponse[1].path, + ]; - describe.each(duplicableCases)( - 'when the selected dashboard can be duplicated', - dashboardPath => { - it('contains a "Create New" menu item and a "Duplicate Dashboard" menu item', () => { - store.state.monitoringDashboard.projectPath = 'https://path/to/project'; - setupAllDashboards(store, dashboardPath); + it('is rendered', () => { + createShallowWrapper(); - return wrapper.vm.$nextTick().then(() => { - expect(findCreateDashboardMenuItem().exists()).toBe(true); - expect(findCreateDashboardDuplicateItem().exists()).toBe(true); - }); + expect(findActionsMenu().exists()).toBe(true); + }); + + describe('adding metrics prop', () => { + it.each(ootbDashboards)('gets passed true if current dashboard is OOTB', dashboardPath => { + createShallowWrapper({ customMetricsAvailable: true }); + + store.state.monitoringDashboard.emptyState = false; + setupAllDashboards(store, dashboardPath); + + return wrapper.vm.$nextTick().then(() => { + expect(findActionsMenu().props('addingMetricsAvailable')).toBe(true); }); - }, - ); + }); - const nonDuplicableCases = [ - dashboardGitResponse[1].path, - selfMonitoringDashboardGitResponse[1].path, - ]; + it.each(customDashboards)( + 'gets passed false if current dashboard is custom', + dashboardPath => { + createShallowWrapper({ customMetricsAvailable: true }); - describe.each(nonDuplicableCases)( - 'when the selected dashboard cannot be duplicated', - dashboardPath => { - it('contains a "Create New" menu item and no "Duplicate Dashboard" menu item', () => { - store.state.monitoringDashboard.projectPath = 'https://path/to/project'; + store.state.monitoringDashboard.emptyState = false; setupAllDashboards(store, dashboardPath); return wrapper.vm.$nextTick().then(() => { - expect(findCreateDashboardMenuItem().exists()).toBe(true); - expect(findCreateDashboardDuplicateItem().exists()).toBe(false); + expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false); }); + }, + ); + + it('gets passed false if empty state is shown', () => { + createShallowWrapper({ customMetricsAvailable: true }); + + store.state.monitoringDashboard.emptyState = true; + setupAllDashboards(store, ootbDashboards[0]); + + return wrapper.vm.$nextTick().then(() => { + expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false); }); - }, - ); - }); + }); - describe('actions menu modals', () => { - const url = 'https://path/to/project'; + it('gets passed false if custom metrics are not available', () => { + createShallowWrapper({ customMetricsAvailable: false }); - beforeEach(() => { - store.state.monitoringDashboard.projectPath = url; - setupAllDashboards(store); + store.state.monitoringDashboard.emptyState = false; + setupAllDashboards(store, ootbDashboards[0]); - createShallowWrapper(); + return wrapper.vm.$nextTick().then(() => { + expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false); + }); + }); }); - it('Clicking on "Create New" opens up a modal', () => { - const modalId = 'createDashboard'; - const modalTrigger = findCreateDashboardMenuItem(); - const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit'); + it('custom metrics path gets passed', () => { + const path = 'https://path/to/customMetrics'; - modalTrigger.trigger('click'); + createShallowWrapper({ customMetricsPath: path }); return wrapper.vm.$nextTick().then(() => { - expect(rootEmit.mock.calls[0]).toContainEqual(modalId); + expect(findActionsMenu().props('customMetricsPath')).toBe(path); }); }); - it('"Create new dashboard" modal contains correct buttons', () => { - expect(findCreateDashboardModal().props('projectPath')).toBe(url); + it('validate query path gets passed', () => { + const path = 'https://path/to/validateQuery'; + + createShallowWrapper({ validateQueryPath: path }); + + return wrapper.vm.$nextTick().then(() => { + expect(findActionsMenu().props('validateQueryPath')).toBe(path); + }); }); - it('"Duplicate Dashboard" opens up a modal', () => { - const modalId = 'duplicateDashboard'; - const modalTrigger = findCreateDashboardDuplicateItem(); - const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit'); + it('default branch gets passed', () => { + const branch = 'branchName'; - modalTrigger.trigger('click'); + createShallowWrapper({ defaultBranch: branch }); return wrapper.vm.$nextTick().then(() => { - expect(rootEmit.mock.calls[0]).toContainEqual(modalId); + expect(findActionsMenu().props('defaultBranch')).toBe(branch); }); }); }); |