diff options
| author | Felipe Artur <felipefac@gmail.com> | 2019-06-17 15:22:44 -0300 |
|---|---|---|
| committer | Felipe Artur <felipefac@gmail.com> | 2019-06-17 15:22:44 -0300 |
| commit | d9df2f730b4eaab4e2d1b5f5045e34bb14e3486f (patch) | |
| tree | 050490a4e90601ad4d175ba6674b98f35937587e /spec/javascripts | |
| parent | 66b9ca952aa4104f99c1275566e8b5c7d28fce01 (diff) | |
| parent | d2929d6edb3a04054a5218cb1b21cb0759ec1ec8 (diff) | |
| download | gitlab-ce-issue_60515.tar.gz | |
Merge branch 'master' into issue_60515issue_60515
Diffstat (limited to 'spec/javascripts')
22 files changed, 557 insertions, 126 deletions
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 1aabf3c2132..fdf8bcee756 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -37,6 +37,8 @@ describe('diffs/components/app', () => { projectPath: 'namespace/project', currentUser: {}, changesEmptyStateIllustration: '', + dismissEndpoint: '', + showSuggestPopover: true, ...props, }, store, diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index f129fbb57a3..f973728cfe1 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -37,6 +37,7 @@ import actions, { toggleFullDiff, setFileCollapsed, setExpandedDiffLines, + setSuggestPopoverDismissed, } from '~/diffs/store/actions'; import eventHub from '~/notes/event_hub'; import * as types from '~/diffs/store/mutation_types'; @@ -68,12 +69,19 @@ describe('DiffsStoreActions', () => { it('should set given endpoint and project path', done => { const endpoint = '/diffs/set/endpoint'; const projectPath = '/root/project'; + const dismissEndpoint = '/-/user_callouts'; + const showSuggestPopover = false; testAction( setBaseConfig, - { endpoint, projectPath }, - { endpoint: '', projectPath: '' }, - [{ type: types.SET_BASE_CONFIG, payload: { endpoint, projectPath } }], + { endpoint, projectPath, dismissEndpoint, showSuggestPopover }, + { endpoint: '', projectPath: '', dismissEndpoint: '', showSuggestPopover: true }, + [ + { + type: types.SET_BASE_CONFIG, + payload: { endpoint, projectPath, dismissEndpoint, showSuggestPopover }, + }, + ], [], done, ); @@ -1080,4 +1088,30 @@ describe('DiffsStoreActions', () => { ); }); }); + + describe('setSuggestPopoverDismissed', () => { + it('commits SET_SHOW_SUGGEST_POPOVER', done => { + const state = { dismissEndpoint: `${gl.TEST_HOST}/-/user_callouts` }; + const mock = new MockAdapter(axios); + mock.onPost(state.dismissEndpoint).reply(200, {}); + + spyOn(axios, 'post').and.callThrough(); + + testAction( + setSuggestPopoverDismissed, + null, + state, + [{ type: types.SET_SHOW_SUGGEST_POPOVER }], + [], + () => { + expect(axios.post).toHaveBeenCalledWith(state.dismissEndpoint, { + feature_name: 'suggest_popover_dismissed', + }); + + mock.restore(); + done(); + }, + ); + }); + }); }); diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index fa193e1d3b9..9c13c7ceb7a 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -850,4 +850,14 @@ describe('DiffsStoreMutations', () => { expect(file.renderingLines).toBe(false); }); }); + + describe('SET_SHOW_SUGGEST_POPOVER', () => { + it('sets showSuggestPopover to false', () => { + const state = { showSuggestPopover: true }; + + mutations[types.SET_SHOW_SUGGEST_POPOVER](state); + + expect(state.showSuggestPopover).toBe(false); + }); + }); }); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index a72ea6ab547..0ee13faf841 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -118,7 +118,7 @@ describe('Filtered Search Visual Tokens', () => { describe('getEndpointWithQueryParams', () => { it('returns `endpoint` string as is when second param `endpointQueryParams` is undefined, null or empty string', () => { - const endpoint = 'foo/bar/labels.json'; + const endpoint = 'foo/bar/-/labels.json'; expect(subject.getEndpointWithQueryParams(endpoint)).toBe(endpoint); expect(subject.getEndpointWithQueryParams(endpoint, null)).toBe(endpoint); @@ -126,7 +126,7 @@ describe('Filtered Search Visual Tokens', () => { }); it('returns `endpoint` string with values of `endpointQueryParams`', () => { - const endpoint = 'foo/bar/labels.json'; + const endpoint = 'foo/bar/-/labels.json'; const singleQueryParams = '{"foo":"true"}'; const multipleQueryParams = '{"foo":"true","bar":"true"}'; diff --git a/spec/javascripts/filtered_search/visual_token_value_spec.js b/spec/javascripts/filtered_search/visual_token_value_spec.js index 14217d460cc..d1d16afc977 100644 --- a/spec/javascripts/filtered_search/visual_token_value_spec.js +++ b/spec/javascripts/filtered_search/visual_token_value_spec.js @@ -156,9 +156,11 @@ describe('Filtered Search Visual Tokens', () => { const filteredSearchInput = document.querySelector('.filtered-search'); filteredSearchInput.dataset.baseEndpoint = dummyEndpoint; + filteredSearchInput.dataset.labelsEndpoint = `${dummyEndpoint}/-/labels`; + filteredSearchInput.dataset.milestonesEndpoint = `${dummyEndpoint}/-/milestones`; AjaxCache.internalStorage = {}; - AjaxCache.internalStorage[`${dummyEndpoint}/labels.json`] = labelData; + AjaxCache.internalStorage[`${filteredSearchInput.dataset.labelsEndpoint}.json`] = labelData; }); const parseColor = color => { diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb index de6fcfe10f4..6b6b0eefab9 100644 --- a/spec/javascripts/fixtures/pipelines.rb +++ b/spec/javascripts/fixtures/pipelines.rb @@ -8,7 +8,7 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') } let(:commit) { create(:commit, project: project) } let(:commit_without_author) { RepoHelpers.another_sample_commit } - let!(:user) { create(:user, email: commit.author_email) } + let!(:user) { create(:user, developer_projects: [project], email: commit.author_email) } let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) } let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) } let!(:pipeline_without_commit) { create(:ci_pipeline, project: project, sha: '0000') } diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js index dc0f77ceb80..7e2ec2ec3f7 100644 --- a/spec/javascripts/jobs/components/job_log_spec.js +++ b/spec/javascripts/jobs/components/job_log_spec.js @@ -3,6 +3,7 @@ import component from '~/jobs/components/job_log.vue'; import createStore from '~/jobs/store'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../store/helpers'; +import { logWithCollapsibleSections } from '../mock_data'; describe('Job Log', () => { const Component = Vue.extend(component); @@ -62,4 +63,40 @@ describe('Job Log', () => { expect(vm.$el.querySelector('.js-log-animation')).toBeNull(); }); }); + + describe('Collapsible sections', () => { + beforeEach(() => { + vm = mountComponentWithStore(Component, { + props: { + trace: logWithCollapsibleSections.html, + isComplete: true, + }, + store, + }); + }); + + it('renders open arrow', () => { + expect(vm.$el.querySelector('.fa-caret-down')).not.toBeNull(); + }); + + it('toggles hidden class to the sibilings rows when arrow is clicked', done => { + vm.$nextTick() + .then(() => { + const { section } = vm.$el.querySelector('.js-section-start').dataset; + vm.$el.querySelector('.js-section-start').click(); + + vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { + expect(el.classList.contains('hidden')).toEqual(true); + }); + + vm.$el.querySelector('.js-section-start').click(); + + vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { + expect(el.classList.contains('hidden')).toEqual(false); + }); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index e98639bf21e..52bb5161123 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -9,7 +9,6 @@ describe('Stages Dropdown', () => { const mockPipelineData = { id: 28029444, - iid: 123, details: { status: { details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', @@ -78,8 +77,8 @@ describe('Stages Dropdown', () => { expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy'); }); - it(`renders the pipeline info text like "Pipeline #123 (#12) for source_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for ${pipeline.ref.name}`; + it(`renders the pipeline info text like "Pipeline #123 for source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for ${pipeline.ref.name}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); @@ -101,10 +100,10 @@ describe('Stages Dropdown', () => { }); }); - it(`renders the pipeline info text like "Pipeline #123 (#12) for !456 with source_branch into target_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${ - pipeline.merge_request.iid - } with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`; + it(`renders the pipeline info text like "Pipeline #123 for !456 with source_branch into target_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + } into ${pipeline.merge_request.target_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); @@ -144,10 +143,10 @@ describe('Stages Dropdown', () => { }); }); - it(`renders the pipeline info like "Pipeline #123 (#12) for !456 with source_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${ - pipeline.merge_request.iid - } with ${pipeline.merge_request.source_branch}`; + it(`renders the pipeline info like "Pipeline #123 for !456 with source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + }`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 88b0bb206ee..c5022d3e93d 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -960,7 +960,6 @@ export default { }, pipeline: { id: 140, - iid: 13, user: { name: 'Root', username: 'root', @@ -1190,3 +1189,18 @@ export const jobsInStage = { path: '/gitlab-org/gitlab-shell/pipelines/27#build', dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', }; + +export const logWithCollapsibleSections = { + append: false, + complete: true, + html: + '<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1559571405" data-section="after-script" role="button"></div><span class="term-fg-l-green term-bold section js-section-header js-s-after-script">Running after script...</span><span class="section js-section-header js-s-after-script"><br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script"></span><span class="term-fg-l-green term-bold section js-s-after-script">$ date</span><span class="section js-s-after-script"><br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script">Mon Jun 3 14:16:46 UTC 2019<br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script"></span><div class="section-end" data-section="after-script"></div><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"data-timestamp="1559571408" data-section="archive-cache" role="button" ></div><span class="term-fg-l-green term-bold section js-section-header js-s-archive-cache">Not uploading cache debian-stretch-ruby-2.6.3-node-10.x-3 due to policy</span><span class="section js-section-header js-s-archive-cache"><br /></span><span class="section s_archive-cache line"></span><span class="section js-s-archive-cache"></span><div class="section-end" data-section="archive-cache"></div><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1559571409" data-section="upload-artifacts-on-success" role="button"></div><span class="term-fg-l-green term-bold section js-section-header js-s-upload-artifacts-on-success">Uploading artifacts...</span><span class="section js-section-header js-s-upload-artifacts-on-success"><br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">coverage/: found 5 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">knapsack/: found 4 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">rspec_flaky/: found 4 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">rspec_profiling/: found 1 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><span class="term-fg-yellow section js-s-upload-artifacts-on-success">WARNING: tmp/capybara/: no matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">Uploading artifacts to coordinator... ok </span><span class="section js-s-upload-artifacts-on-success"> id</span><span class="section js-s-upload-artifacts-on-success">=224162288 responseStatus</span><span class="section js-s-upload-artifacts-on-success">=201 Created token</span><span class="section js-s-upload-artifacts-on-success">=bBmyXJNW<br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><span class="term-fg-l-green term-bold section js-s-upload-artifacts-on-success">Uploading artifacts...</span><span class="section js-s-upload-artifacts-on-success"><br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">junit_rspec.xml: found 1 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">Uploading artifacts to coordinator... ok </span><span class="section js-s-upload-artifacts-on-success"> id</span><span class="section js-s-upload-artifacts-on-success">=224162288 responseStatus</span><span class="section js-s-upload-artifacts-on-success">=201 Created token</span><span class="section js-s-upload-artifacts-on-success">=bBmyXJNW<br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><div class="section-end" data-section="upload-artifacts-on-success"></div><span class="term-fg-l-green term-bold">Job succeeded<br /><span class="term-fg-l-green term-bold"></span></span>', + id: 1385, + offset: 0, + size: 78815, + state: + 'eyJvZmZzZXQiOjc4ODE1LCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowLCJzZWN0aW9ucyI6W10sImxpbmVub19pbl9zZWN0aW9uIjoxMX0=', + status: 'success', + total: 78815, + truncated: false, +}; diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 1295d900de7..3a53ecacb88 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -46,15 +46,30 @@ describe('MergeRequestTabs', function() { describe('opensInNewTab', function() { var tabUrl; var windowTarget = '_blank'; + let clickTabParams; beforeEach(function() { loadFixtures('merge_requests/merge_request_with_task_list.html'); tabUrl = $('.commits-tab a').attr('href'); + + clickTabParams = { + metaKey: false, + ctrlKey: false, + which: 1, + stopImmediatePropagation: function() {}, + preventDefault: function() {}, + currentTarget: { + getAttribute: function(attr) { + return attr === 'href' ? tabUrl : null; + }, + }, + }; }); describe('meta click', () => { let metakeyEvent; + beforeEach(function() { metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true }); }); @@ -67,6 +82,8 @@ describe('MergeRequestTabs', function() { this.class.bindEvents(); $('.merge-request-tabs .commits-tab a').trigger(metakeyEvent); + + expect(window.open).toHaveBeenCalled(); }); it('opens page when commits badge is clicked', function() { @@ -77,6 +94,8 @@ describe('MergeRequestTabs', function() { this.class.bindEvents(); $('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent); + + expect(window.open).toHaveBeenCalled(); }); }); @@ -86,12 +105,9 @@ describe('MergeRequestTabs', function() { expect(name).toEqual(windowTarget); }); - this.class.clickTab({ - metaKey: false, - ctrlKey: true, - which: 1, - stopImmediatePropagation: function() {}, - }); + this.class.clickTab({ ...clickTabParams, metaKey: true }); + + expect(window.open).toHaveBeenCalled(); }); it('opens page tab in a new browser tab with Cmd+Click - Mac', function() { @@ -100,12 +116,9 @@ describe('MergeRequestTabs', function() { expect(name).toEqual(windowTarget); }); - this.class.clickTab({ - metaKey: true, - ctrlKey: false, - which: 1, - stopImmediatePropagation: function() {}, - }); + this.class.clickTab({ ...clickTabParams, ctrlKey: true }); + + expect(window.open).toHaveBeenCalled(); }); it('opens page tab in a new browser tab with Middle-click - Mac/PC', function() { @@ -114,12 +127,9 @@ describe('MergeRequestTabs', function() { expect(name).toEqual(windowTarget); }); - this.class.clickTab({ - metaKey: false, - ctrlKey: false, - which: 2, - stopImmediatePropagation: function() {}, - }); + this.class.clickTab({ ...clickTabParams, which: 2 }); + + expect(window.open).toHaveBeenCalled(); }); }); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index f9c3122088e..f4166987aed 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -20,7 +20,7 @@ const propsData = { tagsPath: '/path/to/tags', projectPath: '/path/to/project', metricsEndpoint: mockApiEndpoint, - deploymentEndpoint: null, + deploymentsEndpoint: null, emptyGettingStartedSvgPath: '/path/to/getting-started.svg', emptyLoadingSvgPath: '/path/to/loading.svg', emptyNoDataSvgPath: '/path/to/no-data.svg', @@ -49,9 +49,6 @@ describe('Dashboard', () => { window.gon = { ...window.gon, ee: false, - features: { - grafanaDashboardLink: true, - }, }; store = createStore(); @@ -382,52 +379,26 @@ describe('Dashboard', () => { describe('external dashboard link', () => { beforeEach(() => { mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - }); - - describe('with feature flag enabled', () => { - beforeEach(() => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - externalDashboardUrl: '/mockUrl', - }, - store, - }); - }); - it('shows the link', done => { - setTimeout(() => { - expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain( - 'View full dashboard', - ); - done(); - }); + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + externalDashboardUrl: '/mockUrl', + }, + store, }); }); - describe('without feature flage enabled', () => { - beforeEach(() => { - window.gon.features.grafanaDashboardLink = false; - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - externalDashboardUrl: '', - }, - store, - }); - }); - - it('does not show the link', done => { - setTimeout(() => { - expect(component.$el.querySelector('.js-external-dashboard-link')).toBe(null); - done(); - }); + it('shows the link', done => { + setTimeout(() => { + expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain( + 'View full dashboard', + ); + done(); }); }); }); diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index d9d8cb66749..82e42fe9ade 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -857,3 +857,68 @@ export const environmentData = [ updated_at: '2018-07-04T18:44:54.010Z', }, ]; + +export const metricsDashboardResponse = { + dashboard: { + dashboard: 'Environment metrics', + priority: 1, + panel_groups: [ + { + group: 'System metrics (Kubernetes)', + priority: 5, + panels: [ + { + title: 'Memory Usage (Total)', + type: 'area-chart', + y_label: 'Total Memory Used', + weight: 4, + metrics: [ + { + id: 'system_metrics_kubernetes_container_memory_total', + query_range: + 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', + label: 'Total', + unit: 'GB', + metric_id: 12, + prometheus_endpoint_path: 'http://test', + }, + ], + }, + { + title: 'Core Usage (Total)', + type: 'area-chart', + y_label: 'Total Cores', + weight: 3, + metrics: [ + { + id: 'system_metrics_kubernetes_container_cores_total', + query_range: + 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)', + label: 'Total', + unit: 'cores', + metric_id: 13, + }, + ], + }, + { + title: 'Memory Usage (Pod average)', + type: 'area-chart', + y_label: 'Memory Used per Pod', + weight: 2, + metrics: [ + { + id: 'system_metrics_kubernetes_container_memory_average', + query_range: + 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024', + label: 'Pod average', + unit: 'MB', + metric_id: 14, + }, + ], + }, + ], + }, + ], + }, + status: 'success', +}; diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js index a848cd24fe3..083a01c4d74 100644 --- a/spec/javascripts/monitoring/store/actions_spec.js +++ b/spec/javascripts/monitoring/store/actions_spec.js @@ -3,8 +3,13 @@ import MockAdapter from 'axios-mock-adapter'; import store from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; import { + fetchDashboard, + receiveMetricsDashboardSuccess, + receiveMetricsDashboardFailure, fetchDeploymentsData, fetchEnvironmentsData, + fetchPrometheusMetrics, + fetchPrometheusMetric, requestMetricsData, setEndpoints, setGettingStartedEmptyState, @@ -12,7 +17,12 @@ import { import storeState from '~/monitoring/stores/state'; import testAction from 'spec/helpers/vuex_action_helper'; import { resetStore } from '../helpers'; -import { deploymentData, environmentData } from '../mock_data'; +import { + deploymentData, + environmentData, + metricsDashboardResponse, + metricsGroupsAPIResponse, +} from '../mock_data'; describe('Monitoring store actions', () => { let mock; @@ -41,9 +51,9 @@ describe('Monitoring store actions', () => { it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => { const dispatch = jasmine.createSpy(); const { state } = store; - state.deploymentEndpoint = '/success'; + state.deploymentsEndpoint = '/success'; - mock.onGet(state.deploymentEndpoint).reply(200, { + mock.onGet(state.deploymentsEndpoint).reply(200, { deployments: deploymentData, }); @@ -58,9 +68,9 @@ describe('Monitoring store actions', () => { it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => { const dispatch = jasmine.createSpy(); const { state } = store; - state.deploymentEndpoint = '/error'; + state.deploymentsEndpoint = '/error'; - mock.onGet(state.deploymentEndpoint).reply(500); + mock.onGet(state.deploymentsEndpoint).reply(500); fetchDeploymentsData({ state, dispatch }) .then(() => { @@ -155,4 +165,158 @@ describe('Monitoring store actions', () => { ); }); }); + + describe('fetchDashboard', () => { + let dispatch; + let state; + const response = metricsDashboardResponse; + + beforeEach(() => { + dispatch = jasmine.createSpy(); + state = storeState(); + state.dashboardEndpoint = '/dashboard'; + }); + + it('dispatches receive and success actions', done => { + const params = {}; + mock.onGet(state.dashboardEndpoint).reply(200, response); + + fetchDashboard({ state, dispatch }, params) + .then(() => { + expect(dispatch).toHaveBeenCalledWith('requestMetricsDashboard'); + expect(dispatch).toHaveBeenCalledWith('receiveMetricsDashboardSuccess', { + response, + params, + }); + done(); + }) + .catch(done.fail); + }); + + it('dispatches failure action', done => { + const params = {}; + mock.onGet(state.dashboardEndpoint).reply(500); + + fetchDashboard({ state, dispatch }, params) + .then(() => { + expect(dispatch).toHaveBeenCalledWith( + 'receiveMetricsDashboardFailure', + new Error('Request failed with status code 500'), + ); + done(); + }) + .catch(done.fail); + }); + }); + + describe('receiveMetricsDashboardSuccess', () => { + let commit; + let dispatch; + + beforeEach(() => { + commit = jasmine.createSpy(); + dispatch = jasmine.createSpy(); + }); + + it('stores groups ', () => { + const params = {}; + const response = metricsDashboardResponse; + + receiveMetricsDashboardSuccess({ commit, dispatch }, { response, params }); + + expect(commit).toHaveBeenCalledWith( + types.RECEIVE_METRICS_DATA_SUCCESS, + metricsDashboardResponse.dashboard.panel_groups, + ); + + expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params); + }); + }); + + describe('receiveMetricsDashboardFailure', () => { + let commit; + + beforeEach(() => { + commit = jasmine.createSpy(); + }); + + it('commits failure action', () => { + receiveMetricsDashboardFailure({ commit }); + + expect(commit).toHaveBeenCalledWith(types.RECEIVE_METRICS_DATA_FAILURE, undefined); + }); + + it('commits failure action with error', () => { + receiveMetricsDashboardFailure({ commit }, 'uh-oh'); + + expect(commit).toHaveBeenCalledWith(types.RECEIVE_METRICS_DATA_FAILURE, 'uh-oh'); + }); + }); + + describe('fetchPrometheusMetrics', () => { + let commit; + let dispatch; + + beforeEach(() => { + commit = jasmine.createSpy(); + dispatch = jasmine.createSpy(); + }); + + it('commits empty state when state.groups is empty', done => { + const state = storeState(); + const params = {}; + + fetchPrometheusMetrics({ state, commit, dispatch }, params) + .then(() => { + expect(commit).toHaveBeenCalledWith(types.SET_NO_DATA_EMPTY_STATE); + expect(dispatch).not.toHaveBeenCalled(); + done(); + }) + .catch(done.fail); + }); + + it('dispatches fetchPrometheusMetric for each panel query', done => { + const params = {}; + const state = storeState(); + state.groups = metricsDashboardResponse.dashboard.panel_groups; + + const metric = state.groups[0].panels[0].metrics[0]; + + fetchPrometheusMetrics({ state, commit, dispatch }, params) + .then(() => { + expect(dispatch.calls.count()).toEqual(3); + expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', { metric, params }); + done(); + }) + .catch(done.fail); + + done(); + }); + }); + + describe('fetchPrometheusMetric', () => { + it('commits prometheus query result', done => { + const commit = jasmine.createSpy(); + const params = { + start: '1557216349.469', + end: '1557218149.469', + }; + const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0]; + const state = storeState(); + + const data = metricsGroupsAPIResponse.data[0].metrics[0].queries[0]; + const response = { data }; + mock.onGet('http://test').reply(200, response); + + fetchPrometheusMetric({ state, commit }, { metric, params }); + + setTimeout(() => { + expect(commit).toHaveBeenCalledWith(types.SET_QUERY_RESULT, { + metricId: metric.metric_id, + result: data.result, + }); + done(); + }); + }); + }); }); diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js index 882ee1dec14..02ff5847b34 100644 --- a/spec/javascripts/monitoring/store/mutations_spec.js +++ b/spec/javascripts/monitoring/store/mutations_spec.js @@ -1,7 +1,7 @@ import mutations from '~/monitoring/stores/mutations'; import * as types from '~/monitoring/stores/mutation_types'; import state from '~/monitoring/stores/state'; -import { metricsGroupsAPIResponse, deploymentData } from '../mock_data'; +import { metricsGroupsAPIResponse, deploymentData, metricsDashboardResponse } from '../mock_data'; describe('Monitoring mutations', () => { let stateCopy; @@ -11,14 +11,16 @@ describe('Monitoring mutations', () => { }); describe(types.RECEIVE_METRICS_DATA_SUCCESS, () => { + let groups; + beforeEach(() => { stateCopy.groups = []; - const groups = metricsGroupsAPIResponse.data; - - mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); + groups = metricsGroupsAPIResponse.data; }); it('normalizes values', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); + const expectedTimestamp = '2017-05-25T08:22:34.925Z'; const expectedValue = 0.0010794445585559514; const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0]; @@ -28,22 +30,27 @@ describe('Monitoring mutations', () => { }); it('contains two groups that contains, one of which has two queries sorted by priority', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); + expect(stateCopy.groups).toBeDefined(); expect(stateCopy.groups.length).toEqual(2); expect(stateCopy.groups[0].metrics.length).toEqual(2); }); it('assigns queries a metric id', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); + expect(stateCopy.groups[1].metrics[0].queries[0].metricId).toEqual('100'); }); it('removes the data if all the values from a query are not defined', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); + expect(stateCopy.groups[1].metrics[0].queries[0].result.length).toEqual(0); }); it('assigns metric id of null if metric has no id', () => { stateCopy.groups = []; - const groups = metricsGroupsAPIResponse.data; const noId = groups.map(group => ({ ...group, ...{ @@ -63,6 +70,26 @@ describe('Monitoring mutations', () => { }); }); }); + + describe('dashboard endpoint enabled', () => { + const dashboardGroups = metricsDashboardResponse.dashboard.panel_groups; + + beforeEach(() => { + stateCopy.useDashboardEndpoint = true; + }); + + it('aliases group panels to metrics for backwards compatibility', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups); + + expect(stateCopy.groups[0].metrics[0]).toBeDefined(); + }); + + it('aliases panel metrics to queries for backwards compatibility', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups); + + expect(stateCopy.groups[0].metrics[0].queries).toBeDefined(); + }); + }); }); describe(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, () => { @@ -82,11 +109,51 @@ describe('Monitoring mutations', () => { metricsEndpoint: 'additional_metrics.json', environmentsEndpoint: 'environments.json', deploymentsEndpoint: 'deployments.json', + dashboardEndpoint: 'dashboard.json', }); expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json'); expect(stateCopy.environmentsEndpoint).toEqual('environments.json'); expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json'); + expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json'); + }); + }); + + describe('SET_QUERY_RESULT', () => { + const metricId = 12; + const result = [{ values: [[0, 1], [1, 1], [1, 3]] }]; + + beforeEach(() => { + stateCopy.useDashboardEndpoint = true; + const dashboardGroups = metricsDashboardResponse.dashboard.panel_groups; + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups); + }); + + it('clears empty state', () => { + mutations[types.SET_QUERY_RESULT](stateCopy, { + metricId, + result, + }); + + expect(stateCopy.showEmptyState).toBe(false); + }); + + it('sets metricsWithData value', () => { + mutations[types.SET_QUERY_RESULT](stateCopy, { + metricId, + result, + }); + + expect(stateCopy.metricsWithData).toEqual([12]); + }); + + it('does not store empty results', () => { + mutations[types.SET_QUERY_RESULT](stateCopy, { + metricId, + result: [], + }); + + expect(stateCopy.metricsWithData).toEqual([]); }); }); }); diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js index 8eef9166b8d..03ead6cd8ba 100644 --- a/spec/javascripts/pipelines/mock_data.js +++ b/spec/javascripts/pipelines/mock_data.js @@ -1,6 +1,5 @@ export const pipelineWithStages = { id: 20333396, - iid: 304399, user: { id: 128633, name: 'Rémy Coutable', diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index 88c0137dc58..aa196af2f33 100644 --- a/spec/javascripts/pipelines/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -13,7 +13,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: {}, }, @@ -29,7 +28,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: {}, }, @@ -49,7 +47,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { latest: true, @@ -81,7 +78,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { latest: true, @@ -104,7 +100,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { failure_reason: true, diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index a2308b0dfdb..75017d20473 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -103,7 +103,7 @@ describe('MRWidgetPipeline', () => { it('should render pipeline ID', () => { expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`, + `#${mockData.pipeline.id}`, ); }); @@ -150,7 +150,7 @@ describe('MRWidgetPipeline', () => { it('should render pipeline ID', () => { expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`, + `#${mockData.pipeline.id}`, ); }); @@ -222,9 +222,9 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on ${mockCopy.source_branch_link}`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on ${mockCopy.source_branch_link}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); @@ -247,11 +247,11 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${ - pipeline.merge_request.source_branch - } into ${pipeline.merge_request.target_branch}`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${ + pipeline.merge_request.target_branch + }`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); @@ -274,11 +274,9 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${ - pipeline.merge_request.source_branch - }`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js index 39b879612ae..55073f5537c 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js @@ -23,11 +23,78 @@ describe('MRWidgetConflicts', () => { vm.destroy(); }); - describe('when allowed to merge', () => { + // There are two permissions we need to consider: + // + // 1. Is the user allowed to merge to the target branch? + // 2. Is the user allowed to push to the source branch? + // + // This yields 4 possible permutations that we need to test, and + // we test them below. A user who can push to the source + // branch should be allowed to resolve conflicts. This is + // consistent with what the backend does. + describe('when allowed to merge but not allowed to push to source branch', () => { beforeEach(() => { createComponent({ mr: { canMerge: true, + canPushToSourceBranch: false, + conflictResolutionPath: path, + conflictsDocsPath: '', + }, + }); + }); + + it('should tell you about conflicts without bothering other people', () => { + expect(vm.text()).toContain('There are merge conflicts'); + expect(vm.text()).not.toContain('ask someone with write access'); + }); + + it('should not allow you to resolve the conflicts', () => { + expect(vm.text()).not.toContain('Resolve conflicts'); + }); + + it('should have merge buttons', () => { + const mergeLocallyButton = vm.find('.js-merge-locally-button'); + + expect(mergeLocallyButton.text()).toContain('Merge locally'); + }); + }); + + describe('when not allowed to merge but allowed to push to source branch', () => { + beforeEach(() => { + createComponent({ + mr: { + canMerge: false, + canPushToSourceBranch: true, + conflictResolutionPath: path, + conflictsDocsPath: '', + }, + }); + }); + + it('should tell you about conflicts', () => { + expect(vm.text()).toContain('There are merge conflicts'); + expect(vm.text()).toContain('ask someone with write access'); + }); + + it('should allow you to resolve the conflicts', () => { + const resolveButton = vm.find('.js-resolve-conflicts-button'); + + expect(resolveButton.text()).toContain('Resolve conflicts'); + expect(resolveButton.attributes('href')).toEqual(path); + }); + + it('should not have merge buttons', () => { + expect(vm.text()).not.toContain('Merge locally'); + }); + }); + + describe('when allowed to merge and push to source branch', () => { + beforeEach(() => { + createComponent({ + mr: { + canMerge: true, + canPushToSourceBranch: true, conflictResolutionPath: path, conflictsDocsPath: '', }, @@ -53,11 +120,12 @@ describe('MRWidgetConflicts', () => { }); }); - describe('when user does not have permission to merge', () => { + describe('when user does not have permission to push to source branch', () => { it('should show proper message', () => { createComponent({ mr: { canMerge: false, + canPushToSourceBranch: false, conflictsDocsPath: '', }, }); @@ -74,6 +142,7 @@ describe('MRWidgetConflicts', () => { createComponent({ mr: { canMerge: false, + canPushToSourceBranch: false, conflictsDocsPath: '', }, }); @@ -115,6 +184,7 @@ describe('MRWidgetConflicts', () => { createComponent({ mr: { canMerge: true, + canPushToSourceBranch: true, conflictResolutionPath: gl.TEST_HOST, sourceBranchProtected: true, conflictsDocsPath: '', @@ -136,6 +206,7 @@ describe('MRWidgetConflicts', () => { createComponent({ mr: { canMerge: true, + canPushToSourceBranch: true, conflictResolutionPath: gl.TEST_HOST, sourceBranchProtected: false, conflictsDocsPath: '', diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 3c9a5cece90..48f812f0db4 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -61,7 +61,6 @@ export default { "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22", pipeline: { id: 172, - iid: 32, user: { name: 'Administrator', username: 'root', @@ -243,8 +242,6 @@ export default { export const mockStore = { pipeline: { id: 0, - iid: 0, - path: '/root/acets-app/pipelines/0', details: { status: { details_path: '/root/review-app-tester/pipelines/66', @@ -262,8 +259,6 @@ export const mockStore = { }, mergePipeline: { id: 1, - iid: 1, - path: '/root/acets-app/pipelines/0', details: { status: { details_path: '/root/review-app-tester/pipelines/66', diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 08f7a17515e..ac2fb16bd10 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -544,7 +544,6 @@ describe('mrWidgetOptions', () => { ]; const deploymentMockData = { id: 15, - iid: 7, name: 'review/diplo', url: '/root/acets-review-apps/environments/15', stop_url: '/root/acets-review-apps/environments/15/stop', @@ -591,7 +590,6 @@ describe('mrWidgetOptions', () => { vm.mr.state = 'merged'; vm.mr.mergePipeline = { id: 127, - iid: 35, user: { id: 1, name: 'Administrator', diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index d4be2451f0b..af92e5f5ae2 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -22,13 +22,13 @@ describe('Markdown field header component', () => { 'Add bold text', 'Add italic text', 'Insert a quote', + 'Insert suggestion', 'Insert code', 'Add a link', 'Add a bullet list', 'Add a numbered list', 'Add a task list', 'Add a table', - 'Insert suggestion', 'Go full screen', ]; const elements = vm.$el.querySelectorAll('.toolbar-btn'); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js index 70025f041a7..6564c012e67 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js @@ -48,8 +48,8 @@ export const mockConfig = { }, namespace: 'gitlab-org', updatePath: '/gitlab-org/my-project/issue/1', - labelsPath: '/gitlab-org/my-project/labels.json', - labelsWebUrl: '/gitlab-org/my-project/labels', + labelsPath: '/gitlab-org/my-project/-/labels.json', + labelsWebUrl: '/gitlab-org/my-project/-/labels', labelFilterBasePath: '/gitlab-org/my-project/issues', canEdit: true, suggestedColors: mockSuggestedColors, |
