diff options
Diffstat (limited to 'spec')
22 files changed, 558 insertions, 432 deletions
diff --git a/spec/controllers/groups/registry/repositories_controller_spec.rb b/spec/controllers/groups/registry/repositories_controller_spec.rb index eb702d65325..3a74aa1dac0 100644 --- a/spec/controllers/groups/registry/repositories_controller_spec.rb +++ b/spec/controllers/groups/registry/repositories_controller_spec.rb @@ -93,7 +93,7 @@ describe Groups::Registry::RepositoriesController do context 'with :vue_container_registry_explorer feature flag disabled' do before do - stub_feature_flags(vue_container_registry_explorer: false) + stub_feature_flags(vue_container_registry_explorer: { enabled: false, thing: group }) end it 'has the correct response schema' do diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb index 5b9c0211b39..a64673a7f87 100644 --- a/spec/controllers/projects/registry/repositories_controller_spec.rb +++ b/spec/controllers/projects/registry/repositories_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::Registry::RepositoriesController do - let(:user) { create(:user) } + let(:user) { create(:user) } let(:project) { create(:project, :private) } before do @@ -88,7 +88,7 @@ describe Projects::Registry::RepositoriesController do context 'with :vue_container_registry_explorer feature flag disabled' do before do - stub_feature_flags(vue_container_registry_explorer: false) + stub_feature_flags(vue_container_registry_explorer: { enabled: false, thing: project.group }) stub_container_registry_tags(repository: project.full_path, tags: %w[rc1 latest]) end diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb index 02b2d03a880..b99dab39c34 100644 --- a/spec/features/projects/container_registry_spec.rb +++ b/spec/features/projects/container_registry_spec.rb @@ -19,7 +19,7 @@ describe 'Container Registry', :js do describe 'Registry explorer is off' do before do - stub_feature_flags(vue_container_registry_explorer: false) + stub_feature_flags(vue_container_registry_explorer: { enabled: false, thing: project.group }) end it 'has a page title set' do diff --git a/spec/fixtures/api/schemas/environment.json b/spec/fixtures/api/schemas/environment.json index 321c495a575..7e7e5ce37e3 100644 --- a/spec/fixtures/api/schemas/environment.json +++ b/spec/fixtures/api/schemas/environment.json @@ -26,7 +26,6 @@ "stop_path": { "type": "string" }, "cancel_auto_stop_path": { "type": "string" }, "folder_path": { "type": "string" }, - "project_path": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" }, "auto_stop_at": { "type": "string", "format": "date-time" }, diff --git a/spec/frontend/helpers/dom_shims/get_client_rects.js b/spec/frontend/helpers/dom_shims/get_client_rects.js index d740c1bf154..7ba60dd7936 100644 --- a/spec/frontend/helpers/dom_shims/get_client_rects.js +++ b/spec/frontend/helpers/dom_shims/get_client_rects.js @@ -8,14 +8,16 @@ function hasHiddenStyle(node) { return false; } -function createDefaultClientRect() { +function createDefaultClientRect(node) { + const { outerWidth: width, outerHeight: height } = node; + return { - bottom: 0, - height: 0, + bottom: height, + height, left: 0, - right: 0, + right: width, top: 0, - width: 0, + width, x: 0, y: 0, }; @@ -46,5 +48,5 @@ window.Element.prototype.getClientRects = function getClientRects() { return []; } - return [createDefaultClientRect()]; + return [createDefaultClientRect(node)]; }; diff --git a/spec/frontend/helpers/dom_shims/index.js b/spec/frontend/helpers/dom_shims/index.js index 63850b62ff7..1b73f0e2ef5 100644 --- a/spec/frontend/helpers/dom_shims/index.js +++ b/spec/frontend/helpers/dom_shims/index.js @@ -2,3 +2,5 @@ import './element_scroll_into_view'; import './get_client_rects'; import './inner_text'; import './window_scroll_to'; +import './scroll_by'; +import './size_properties'; diff --git a/spec/frontend/helpers/dom_shims/scroll_by.js b/spec/frontend/helpers/dom_shims/scroll_by.js new file mode 100644 index 00000000000..90387e51765 --- /dev/null +++ b/spec/frontend/helpers/dom_shims/scroll_by.js @@ -0,0 +1,7 @@ +window.scrollX = 0; +window.scrollY = 0; + +window.scrollBy = (x, y) => { + window.scrollX += x; + window.scrollY += y; +}; diff --git a/spec/frontend/helpers/dom_shims/size_properties.js b/spec/frontend/helpers/dom_shims/size_properties.js new file mode 100644 index 00000000000..a2d5940bd1e --- /dev/null +++ b/spec/frontend/helpers/dom_shims/size_properties.js @@ -0,0 +1,19 @@ +const convertFromStyle = style => { + if (style.match(/[0-9](px|rem)/g)) { + return Number(style.replace(/[^0-9]/g, '')); + } + + return 0; +}; + +Object.defineProperty(global.HTMLElement.prototype, 'offsetWidth', { + get() { + return convertFromStyle(this.style.width || '0px'); + }, +}); + +Object.defineProperty(global.HTMLElement.prototype, 'offsetHeight', { + get() { + return convertFromStyle(this.style.height || '0px'); + }, +}); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js index 504d4a3e01a..d0d45b153af 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/frontend/lib/utils/common_utils_spec.js @@ -1,27 +1,4 @@ -import MockAdapter from 'axios-mock-adapter'; -import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils'; -import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; -import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data'; - -const PIXEL_TOLERANCE = 0.2; - -/** - * Loads a data URL as the src of an - * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image|Image} - * and resolves to that Image once loaded. - * - * @param url - * @returns {Promise} - */ -const urlToImage = url => - new Promise(resolve => { - const img = new Image(); - img.onload = function() { - resolve(img); - }; - img.src = url; - }); describe('common_utils', () => { describe('parseUrl', () => { @@ -87,13 +64,11 @@ describe('common_utils', () => { describe('handleLocationHash', () => { beforeEach(() => { - spyOn(window.document, 'getElementById').and.callThrough(); - jasmine.clock().install(); + jest.spyOn(window.document, 'getElementById'); }); afterEach(() => { window.history.pushState({}, null, ''); - jasmine.clock().uninstall(); }); function expectGetElementIdToHaveBeenCalledWith(elementId) { @@ -162,7 +137,7 @@ describe('common_utils', () => { }); it('scrolls to element with offset from navbar', () => { - spyOn(window, 'scrollBy').and.callThrough(); + jest.spyOn(window, 'scrollBy'); document.body.innerHTML += ` <div id="parent"> <div class="navbar-gitlab" style="position: fixed; top: 0; height: 50px;"></div> @@ -173,7 +148,7 @@ describe('common_utils', () => { window.history.pushState({}, null, '#test'); commonUtils.handleLocationHash(); - jasmine.clock().tick(1); + jest.advanceTimersByTime(1); expectGetElementIdToHaveBeenCalledWith('test'); expectGetElementIdToHaveBeenCalledWith('user-content-test'); @@ -191,12 +166,12 @@ describe('common_utils', () => { }); it('should call pushState with the correct path', () => { - spyOn(window.history, 'pushState'); + jest.spyOn(window.history, 'pushState').mockImplementation(() => {}); commonUtils.historyPushState('newpath?page=2'); expect(window.history.pushState).toHaveBeenCalled(); - expect(window.history.pushState.calls.allArgs()[0][2]).toContain('newpath?page=2'); + expect(window.history.pushState.mock.calls[0][2]).toContain('newpath?page=2'); }); }); @@ -238,7 +213,7 @@ describe('common_utils', () => { describe('debounceByAnimationFrame', () => { it('debounces a function to allow a maximum of one call per animation frame', done => { - const spy = jasmine.createSpy('spy'); + const spy = jest.fn(); const debouncedSpy = commonUtils.debounceByAnimationFrame(spy); window.requestAnimationFrame(() => { debouncedSpy(); @@ -302,25 +277,24 @@ describe('common_utils', () => { }); describe('normalizeCRLFHeaders', () => { - beforeEach(function() { - this.CLRFHeaders = + const testContext = {}; + beforeEach(() => { + testContext.CLRFHeaders = 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE'; - spyOn(String.prototype, 'split').and.callThrough(); - this.normalizeCRLFHeaders = commonUtils.normalizeCRLFHeaders(this.CLRFHeaders); + jest.spyOn(String.prototype, 'split'); + testContext.normalizeCRLFHeaders = commonUtils.normalizeCRLFHeaders(testContext.CLRFHeaders); }); - it('should split by newline', function() { + it('should split by newline', () => { expect(String.prototype.split).toHaveBeenCalledWith('\n'); }); - it('should split by colon+space for each header', function() { - expect(String.prototype.split.calls.allArgs().filter(args => args[0] === ': ').length).toBe( - 3, - ); + it('should split by colon+space for each header', () => { + expect(String.prototype.split.mock.calls.filter(args => args[0] === ': ').length).toBe(3); }); - it('should return a normalized headers object', function() { - expect(this.normalizeCRLFHeaders).toEqual({ + it('should return a normalized headers object', () => { + expect(testContext.normalizeCRLFHeaders).toEqual({ 'A-HEADER': 'a-value', 'ANOTHER-HEADER': 'ANOTHER-VALUE', 'LAST-HEADER': 'last-VALUE', @@ -384,38 +358,6 @@ describe('common_utils', () => { }); }); - describe('contentTop', () => { - it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => { - spyOn(breakpointInstance, 'isDesktop').and.returnValue(false); - - setFixtures(` - <div class="diff-file file-title-flex-parent"> - blah blah blah - </div> - <div class="mr-version-controls"> - more blah blah blah - </div> - `); - - expect(commonUtils.contentTop()).toBe(0); - }); - - it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => { - spyOn(breakpointInstance, 'isDesktop').and.returnValue(true); - - setFixtures(` - <div class="diff-file file-title-flex-parent"> - blah blah blah - </div> - <div class="mr-version-controls"> - more blah blah blah - </div> - `); - - expect(commonUtils.contentTop()).toBe(18); - }); - }); - describe('parseBoolean', () => { const { parseBoolean } = commonUtils; @@ -448,8 +390,7 @@ describe('common_utils', () => { describe('backOff', () => { beforeEach(() => { // shortcut our timeouts otherwise these tests will take a long time to finish - const origSetTimeout = window.setTimeout; - spyOn(window, 'setTimeout').and.callFake(cb => origSetTimeout(cb, 0)); + jest.spyOn(window, 'setTimeout').mockImplementation(cb => setImmediate(cb, 0)); }); it('solves the promise from the callback', done => { @@ -507,7 +448,7 @@ describe('common_utils', () => { .catch(done.fail), ) .then(respBackoff => { - const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout); + const timeouts = window.setTimeout.mock.calls.map(([, timeout]) => timeout); expect(timeouts).toEqual([2000, 4000]); expect(respBackoff).toBe(expectedResponseValue); @@ -520,7 +461,7 @@ describe('common_utils', () => { commonUtils .backOff(next => next(), 64000) .catch(errBackoffResp => { - const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout); + const timeouts = window.setTimeout.mock.calls.map(([, timeout]) => timeout); expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]); expect(errBackoffResp instanceof Error).toBe(true); @@ -572,90 +513,6 @@ describe('common_utils', () => { }); }); - describe('createOverlayIcon', () => { - it('should return the favicon with the overlay', done => { - commonUtils - .createOverlayIcon(faviconDataUrl, overlayDataUrl) - .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)])) - .then(([actual, expected]) => { - expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE); - done(); - }) - .catch(done.fail); - }); - }); - - describe('setFaviconOverlay', () => { - beforeEach(() => { - const favicon = document.createElement('link'); - favicon.setAttribute('id', 'favicon'); - favicon.setAttribute('data-original-href', faviconDataUrl); - document.body.appendChild(favicon); - }); - - afterEach(() => { - document.body.removeChild(document.getElementById('favicon')); - }); - - it('should set page favicon to provided favicon overlay', done => { - commonUtils - .setFaviconOverlay(overlayDataUrl) - .then(() => document.getElementById('favicon').getAttribute('href')) - .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)])) - .then(([actual, expected]) => { - expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE); - done(); - }) - .catch(done.fail); - }); - }); - - describe('setCiStatusFavicon', () => { - const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`; - let mock; - - beforeEach(() => { - const favicon = document.createElement('link'); - favicon.setAttribute('id', 'favicon'); - favicon.setAttribute('href', 'null'); - favicon.setAttribute('data-original-href', faviconDataUrl); - document.body.appendChild(favicon); - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - document.body.removeChild(document.getElementById('favicon')); - }); - - it('should reset favicon in case of error', done => { - mock.onGet(BUILD_URL).replyOnce(500); - - commonUtils.setCiStatusFavicon(BUILD_URL).catch(() => { - const favicon = document.getElementById('favicon'); - - expect(favicon.getAttribute('href')).toEqual(faviconDataUrl); - done(); - }); - }); - - it('should set page favicon to CI status favicon based on provided status', done => { - mock.onGet(BUILD_URL).reply(200, { - favicon: overlayDataUrl, - }); - - commonUtils - .setCiStatusFavicon(BUILD_URL) - .then(() => document.getElementById('favicon').getAttribute('href')) - .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)])) - .then(([actual, expected]) => { - expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE); - done(); - }) - .catch(done.fail); - }); - }); - describe('spriteIcon', () => { let beforeGon; @@ -894,32 +751,6 @@ describe('common_utils', () => { }); }); - describe('isInViewport', () => { - let el; - - beforeEach(() => { - el = document.createElement('div'); - }); - - afterEach(() => { - document.body.removeChild(el); - }); - - it('returns true when provided `el` is in viewport', () => { - el.setAttribute('style', `position: absolute; right: ${window.innerWidth + 0.2};`); - document.body.appendChild(el); - - expect(commonUtils.isInViewport(el)).toBe(true); - }); - - it('returns false when provided `el` is not in viewport', () => { - el.setAttribute('style', 'position: absolute; top: -1000px; left: -1000px;'); - document.body.appendChild(el); - - expect(commonUtils.isInViewport(el)).toBe(false); - }); - }); - describe('searchBy', () => { const searchSpace = { iid: 1, @@ -937,14 +768,14 @@ describe('common_utils', () => { it('returns object with matching props based on `query` & `searchSpace` params', () => { // String `omnis` is found only in `title` prop so return just that expect(commonUtils.searchBy('omnis', searchSpace)).toEqual( - jasmine.objectContaining({ + expect.objectContaining({ title: searchSpace.title, }), ); // String `1` is found in both `iid` and `reference` props so return both expect(commonUtils.searchBy('1', searchSpace)).toEqual( - jasmine.objectContaining({ + expect.objectContaining({ iid: searchSpace.iid, reference: searchSpace.reference, }), @@ -952,7 +783,7 @@ describe('common_utils', () => { // String `/epics/1` is found in `url` prop so return just that expect(commonUtils.searchBy('/epics/1', searchSpace)).toEqual( - jasmine.objectContaining({ + expect.objectContaining({ url: searchSpace.url, }), ); diff --git a/spec/frontend/lib/utils/mock_data.js b/spec/frontend/lib/utils/mock_data.js new file mode 100644 index 00000000000..c466b0cd1ed --- /dev/null +++ b/spec/frontend/lib/utils/mock_data.js @@ -0,0 +1,8 @@ +export const faviconDataUrl = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACcFBMVEX////iQyniQyniQyniQyniQyniQyniQyniQynhRiriQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniRCniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQynhQiniQiniQiniQinhQinpUSjqUSjqTyjqTyjqTyjlSCniRCniQynjRCjqTyjsZSjrWyj8oib9kSb8pyb9pib8oyb8fyb3ZSb4Zib8fCb8oyb8oyb8oyb8pCb8cSbiQyn7bCb8cib8oyb8oSb8bSbtVSjpTij8nyb8oyb8oyb8lCb2Yyf3ZCf8mCb8oyb8oyb8oyb8iib8bSbiRCn8gyb8oyb8eCbpTinrUSj8oyb8oyb8oyb8pSb8bib4Zif0YCf8byb8oyb8oyb8oyb7oib8oyb8nCbjRSn9bib8ayb8nib8oyb8oyb8oyb8kSbpTyjpTyj8jib8oyb8oyb8oyb8fib0Xyf2ZSb8gCb8oyb6pSb8oyb8dib+cCbgQCnjRSn8cCb8oib8oyb8oyb8oybqUCjnSyn8bCb8oyb8oyb8oyb8myb2YyfyXyf8oyb8oyb8hibhQSn+bib8iSb8oyb8qCb+fSbmSSnqTyj8oib9pCb1YifxXyf7pSb8oCb8pCb+mCb0fCf8pSb7hSXvcSjiQyniQinqTyj9kCb9bib9byb+cCbqUSjiRCnsVCj+cSb8pib8bCb8bSbgQCn7bCb8bibjRSn8oyb8ayb8oib8aib8pCbjRCn8pybhQinhQSn8pSb7ayb7aSb6aib8eib///8IbM+7AAAAr3RSTlMBA3NtX2vT698HGQcRLwWLiXnv++3V+eEd/R8HE2V/Y5HjyefdFw99YWfJ+/3nwQP78/HvX1VTQ/kdA2HzbQXj9fX79/3DGf379/33T/v99/f7ba33+/f1+9/18/v59V339flzF/H9+fX3/fMhBwOh9/v5/fmvBV/z+fP3Awnp9/f38+UFgff7+/37+4c77/f7/flFz/f59dFr7/v98Wnr+/f3I5/197EDBU1ZAwUD8/kLUwAAAAFiS0dEAIgFHUgAAAAHdElNRQfhBQoLHiBV6/1lAAACHUlEQVQ4y41TZXsTQRCe4FAIUigN7m7FXY+iLRQKBG2x4g7BjhZ3Le7uMoEkFJprwyQk0CC/iZnNhUZaHt4vt6/szO7cHcD/wFKjZrJWq3YMq1M3eVc9rFzXR2yQkuA3RGxkjZLGiEk9miA2tURJs1RsnhhokYYtzaU13WZDbBVnW1sjo43J2vI6tZ0lLtFeAh1M0lECneI7dGYtrUtk3RUVIKaEJR25qw27yT0s3W0qEHuPlB4RradivXo7GX36xnbo51SQ+fWHARmCgYMGDxkaxbD3SssYPmIkwKgPLrfA87EETTg/fVaSa/SYsQDjSsd7DcGEsr+BieVKmaRNBsjUtClTfUI900y/5Mt05c8oJQKYSURZ2UqYFa0w283M588JEM2BuRwI5EqT8nmmXzZf4l8XsGNfCIv4QcHFklhiBpaqAsuC4tghj+ySyOdjeJYrP7RCCuR/E5tWAqxaLcmCNSyujdxjHZdbn8UHoA0bN/GoNm8hjQJb/ZzYpo6w3TB27JRduxxqrA7YzbWCezixN8RD2Oc2/Ptlfx7o5uT1A4XMiwzj4HfEikNe7+Ew0ZGjeuW70eEYaeHjxomTiKd++E4XnKGz8d+HDufOB3Ky3RcwdNF1qZiKLyf/B44r2tWf15wV143cwI2qfi8dbtKtX6Hbd+6G74EDqkTm/QcPH/0ufFyNLXjy9NnzF9Xb8BJevYY38C+8fZcg/AF3QTYemVkCwwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wNS0xMFQxMTozMDozMiswMjowMMzup8UAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDUtMTBUMTE6MzA6MzIrMDI6MDC9sx95AAAAAElFTkSuQmCC'; + +export const overlayDataUrl = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA85JREFUWAntVllIVGEUPv/9b46O41KplYN7PeRkti8TjQlhCUGh3MmeQugpIsGKAi2soIcIooiohxYKK2daqDAlIpIiWwxtQaJcaHE0d5tMrbn37z9XRqfR0TvVW56Hudf//uec72zfEWBCJjIwkYGJDPzvGSD/KgExN3Oi2Q+2DJgSDYQEMwItVGH1iZGmJw/Si1y+/PwVAMYYib22MYc/8hVQFgKDEfYoId0KYzagAQebsos/ewMZoeB9wdffcTYpQSaCTWHKoqSQaDk7zkIt0+aCUR8BelEHrf3dUNv9AcqbnsHtT5UKB/hTASh0SLYjnjb/CIDRJi0XiFAaJOpCD8zLpdb4NB66b1OfelthX815dtdRRfiti2aAXLvVLiMQ6olGyztGDkSo4JGGXk8/QFdGpYzpHG2GBQTDhtgVhPEaVbbVpvI6GJz22rv4TcAfrYI1x7Rj5MWWAppomKFVVb2302SFzUkZHAbkG+0b1+Gh77yNYjrmqnWTrLBLRxdvBWv8qlFujH/kYjJYyvLkj71t78zAUvzMAMnHhpN4zf9UREJhd8omyssxu1IgazQDwDnHUcNuH6vhPIE1fmuBzHt74Hn7W89jWGtcAjoaIDOFrdcMYJBkgOCoaRF0Lj0oglddDbCj6tRvKjphEpgjkzEQs2YAKsNxMzjn3nKurhzK+Ly7xe28ua8TwgMMcHJZnvvT0BPtEEKM4tDJ+C8GvIIk4ylINIXVZ0EUKJxYuh3mhCeokbudl6TtVc88dfBdLwbyaWB6zQCYQJpBYSrDGQxBQ/ZWRM2B+VNmQnVnHWx7elyNuL2/R336co7KyJR8CL9oLgEuFlREevWUkEl6uGwpVEG4FBm0OEf9N10NMgPlvWYAuNVwsWDKvcUNYsHUWTCZ13ysyFEXe6TO6aC8CUr9IiK+A05TQrc8yjwmxARHeeMAPlfQJw+AQRwu0YhL/GDXi9NwufG+S8dYkuYMqIb4SsWthotlNMOUCOM6r+G9cqXxPmd1dqrBav/o1zJy2l5/NUjJA/VORwYuFnOUaTQcPs9wMqwV++Xv8oADxKAcZ8nLPr8AoGW+xR6HSqYk3GodAz2QNj0V+Gr26dT9ASNH5239Pf0gktVNWZca8ZvfAFBprWS6hSu1pqt++Y0PD+WIwDAhIWQGtzvSHDbcodfFUFB9hg1Gjs5LXqIdFL+acFBl+FddqYwdxsWC3I70OvgfUaA65zhq2O2c8VxYcyIGFTVlXegYtvCXANCQZJMobjVcLMjtSK/IcEgyOOe8Ve5w7ryKDefp2P3+C/5ohv8HZmVLAAAAAElFTkSuQmCC'; + +export const faviconWithOverlayDataUrl = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGtElEQVRYR8WXf3CT9R3H35/nSdIQIktrCf0RStI0FYRjVBAccxTq5MDBKUoz4ZyjbPO87q4yBsPDMdExTjlvIsdQexyI0oMBeuKhdjsNHhwcMgpjIlublLIm/UlJKZSSJs/z/e6+T5v0CQ22wB/7/pPck8/383l9fj6fEOec8H88NAjAS1LwknsFSVLU8WXd1rtm85LUeKnwGQKzjj3s33azvsEAAEIlnn8ByHL4/Pa7BgAQLCm8QOBOh88vDQkQeMxjMkcQEYKqYsyJWWPhgs/80TsFafzROJtkNIXFfYI0pfXqPeennjqlxPUNikBoTuEmEF+lCRBV3G0aQiWFrwH8d30AWJubGdiEfZzdGqDEEwbICnADQGGHry7zTr0X94IlnnMACggwAWh0+PxOvb5EBGqmTTNkj7ySxWS62C+g5Usm1Zn95YXG24UQ+r5n75Li6Ux4LBkyc7/4t5YSLSr6Lgg9UvBLcKocMEYKON/gGB3YoA/bcGFCczzLQdieLE9bHL66FakBSjzCU0cSAHDa4at7aLhG9XLBEk8zAVnxZxyIEhBy+PwFgwAafpxvNzK5NZUhrX28JA07Cl6SmtvcOUwm4ZAouHj7ad+jMrN1dqb3iG7oS4EYPh2etQS+XiesC8TQ3ZD3yZJsHuUPgbMcI+ej5v3ncv5PasNlk1p7JJnzJL+I0/O5h+u0VCdqIDi78AQRHuirft3hYJzQPvawPydVdPI+/OnTnNNKBjYVXHRa8rFFGeb4w1he0wZ7d/84IXTEhxzxUsgitB2LPFGwvgGUfLSeZUpEXqEqrIdz0nr4iHOUfeOccb/tNMtutzWHPeWcJc0aMxm5lkxYDGloj1zB+Sv/RXXTSXzaeBwSY3j+bHNv2bdtMYCbpHtRkNFd36xFQN3tXkZhvgP1fdPi5kMEXL4oIXKVAA58M8aCVQs84BYLXi5aDq+zGJTqYr+i4PV2vHxmJ/7WUoOn2i/jz6yhW7JjrdSV8U4fQFV+I2Q4UIsedMCSSlcsgp72WtnSajOhzDsBNtsYfFD8e+Rbs4fdIG98uw9vnj+AX7FWvk4NHZOXXphF/INx2SpJIU2L8L4GDAoMwlP9kWSg6awcKVs83tyUnY5Dj75+W8bjutae3o5d9X/HTiWAuUtOS6RUOR8Hp48TxjgU/AMSeKJ1Ej/tMWXG1sxwGt98sBxe5+xhe64XVLiK2Z9XwNgdRLXyzQsC4ENwelIHAFxDBOdh1qdCdNLCoon8RnY+HZ6/+TtzPhTZweAxlJ94C5VqoI2U3a7rACzJjQqgBd24CGscos1kxPQZ38fqSU/jhQkDvN9lrKG7FeUnNuPVKcvwYOb4hGgvi2HSx8vwRKyJkVLl+hk43gdBAcfADBD1cA4RXIdZ1EN1Zjqem+DGoUc2oigjMUlvaV8YL/1qPVpuhOG+JwdH5m1Okn3m6Eacaz3V2jeI9uTbVYY6AKOSKw8MX0MBg2lXjh3r3Hk4s7ASdrMtSWxnoBpZIzIwP3e69lxv3Gay4q/F6zDJ5kq6s6amEnsafJ0Db8P9JKkx1w5wPJuY36IToojgNMzb8rLwmsuB2kW7YDWMSCgTg+YXx9+AQZKxdUaFZiju+a2Mi8uvnH0f2/2f9g4AVE4z4LlTilrlehag9xIpEam4jO4DXfdaV97nwtH5byW137VYD5Yc2YAz4YAGIYx2RLq0z1Sex8l//fUWfBI83jh4Kd1PEuAwqVGjWEwSS+nJJmt0sWu86d0frMQCR/LbWQ8hDAxlXMgUV69Q67ubv0q5FUNAlHKmVLnXE/gfREpUiaQHqAizXbO0UN98BMTSo39Cw7UW7E2Rc728qJGHP68ASbQyNYCQTkAUzCSwQ+CwvSjnsQPGLOnI/C0YO3Lwxq5yhhtqb1KNpGqT1TXvigJU0jh33xpAf7NymoGNDJ9sJtPkYuNkqTh7KnY8vGaoeZPy93+GA1joe4kzzv/SVLqvYngA/dFgVfnlb8tjtm6Ux+I39y/Gqone24IQM+GxL15UO3q7WrhsnhJatCs8PAC9md3OrPK0goaDyEj7uXsuXi0qg4HkIUGE52XHNqmXIl0RGOiHoUV7xb+v5K14SC39At79Ximdhc8ekjImuiyjsXryUszLnY40yThIhSi4bbUHsbfBJ6ZKE5dpQdz4HQOgf2a8tLvklY+M6cuvSnJummxSZ46+X+7biMzaRnSu84IauNYsE5HCOX+HDCPWi7DrKW8/BTcVZ2UN8Me57kc5448TaCYR5XJwC0BtHMwPjs/SgAP1pfuCqSL8Pxhr/wunLWAOAAAAAElFTkSuQmCC'; diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index 4dd376faac0..e9322d6b5a9 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -12,6 +12,7 @@ import { deploymentData, metricsDashboardPayload, mockedQueryResultPayload, + metricsDashboardViewModel, mockProjectDir, mockHost, } from '../../mock_data'; @@ -65,7 +66,7 @@ describe('Time series component', () => { ); // Pick the second panel group and the first panel in it - [mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; + [mockGraphData] = store.state.monitoringDashboard.dashboard.panelGroups[0].panels; }); describe('general functions', () => { @@ -188,7 +189,7 @@ describe('Time series component', () => { }); it('formats tooltip content', () => { - const name = 'Pod average'; + const name = 'Total'; const value = '5.556'; const dataIndex = 0; const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel); @@ -439,7 +440,7 @@ describe('Time series component', () => { it('constructs a label for the chart y-axis', () => { const { yAxis } = getChartOptions(); - expect(yAxis[0].name).toBe('Memory Used per Pod'); + expect(yAxis[0].name).toBe('Total Memory Used'); }); }); }); @@ -535,48 +536,24 @@ describe('Time series component', () => { }); describe('with multiple time series', () => { - const mockedResultMultipleSeries = []; - const [, , panelData] = metricsDashboardPayload.panel_groups[1].panels; - - for (let i = 0; i < panelData.metrics.length; i += 1) { - mockedResultMultipleSeries.push(cloneDeep(mockedQueryResultPayload)); - mockedResultMultipleSeries[ - i - ].metricId = `${panelData.metrics[i].metric_id}_${panelData.metrics[i].id}`; - } - - beforeEach(() => { - setTestTimeout(1000); - - store = createStore(); - - store.commit( - `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, - metricsDashboardPayload, - ); - - store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); - - // Mock data contains the metric_id for a multiple time series panel - for (let i = 0; i < panelData.metrics.length; i += 1) { - store.commit( - `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, - mockedResultMultipleSeries[i], - ); - } - - // Pick the second panel group and the second panel in it - [, , mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; - }); - describe('General functions', () => { let timeSeriesChart; beforeEach(done => { - timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); + store = createStore(); + const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]); + graphData.metrics.forEach(metric => + Object.assign(metric, { result: mockedQueryResultPayload.result }), + ); + + timeSeriesChart = makeTimeSeriesChart(graphData, 'area-chart'); timeSeriesChart.vm.$nextTick(done); }); + afterEach(() => { + timeSeriesChart.destroy(); + }); + describe('computed', () => { let chartData; diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index fcf70a1af63..6f05207204e 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -17,12 +17,13 @@ import { setupComponentStore, propsData } from '../init_utils'; import { metricsDashboardPayload, mockedQueryResultPayload, + metricsDashboardViewModel, environmentData, dashboardGitResponse, } from '../mock_data'; const localVue = createLocalVue(); -const expectedPanelCount = 3; +const expectedPanelCount = 4; describe('Dashboard', () => { let store; @@ -366,7 +367,7 @@ describe('Dashboard', () => { it('metrics can be swapped', () => { const firstDraggable = findDraggables().at(0); - const mockMetrics = [...metricsDashboardPayload.panel_groups[1].panels]; + const mockMetrics = [...metricsDashboardViewModel.panelGroups[0].panels]; const firstTitle = mockMetrics[0].title; const secondTitle = mockMetrics[1].title; @@ -376,7 +377,7 @@ describe('Dashboard', () => { firstDraggable.vm.$emit('input', mockMetrics); return wrapper.vm.$nextTick(() => { - const { panels } = wrapper.vm.dashboard.panel_groups[1]; + const { panels } = wrapper.vm.dashboard.panelGroups[0]; expect(panels[1].title).toEqual(firstTitle); expect(panels[0].title).toEqual(secondTitle); diff --git a/spec/frontend/monitoring/embed/embed_spec.js b/spec/frontend/monitoring/embed/embed_spec.js index 3bb70a02bd9..850092c4a72 100644 --- a/spec/frontend/monitoring/embed/embed_spec.js +++ b/spec/frontend/monitoring/embed/embed_spec.js @@ -69,8 +69,8 @@ describe('Embed', () => { describe('metrics are available', () => { beforeEach(() => { - store.state.monitoringDashboard.dashboard.panel_groups = groups; - store.state.monitoringDashboard.dashboard.panel_groups[0].panels = metricsData; + store.state.monitoringDashboard.dashboard.panelGroups = groups; + store.state.monitoringDashboard.dashboard.panelGroups[0].panels = metricsData; metricsWithDataGetter.mockReturnValue(metricsWithData); diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js index bad3962dd8f..32daf990ad3 100644 --- a/spec/frontend/monitoring/mock_data.js +++ b/spec/frontend/monitoring/mock_data.js @@ -1,3 +1,5 @@ +import { mapToDashboardViewModel } from '~/monitoring/stores/utils'; + // This import path needs to be relative for now because this mock data is used in // Karma specs too, where the helpers/test_constants alias can not be resolved import { TEST_HOST } from '../helpers/test_constants'; @@ -246,7 +248,7 @@ export const mockedEmptyResult = { }; export const mockedQueryResultPayload = { - metricId: '17_system_metrics_kubernetes_container_memory_average', + metricId: '12_system_metrics_kubernetes_container_memory_total', result: [ { metric: {}, @@ -378,122 +380,28 @@ export const environmentData = [ }, ].concat(extraEnvironmentData); -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: 'line-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', -}; - export const metricsDashboardPayload = { dashboard: 'Environment metrics', + priority: 1, panel_groups: [ { - group: 'Response metrics (NGINX Ingress VTS)', - priority: 10, - panels: [ - { - metrics: [ - { - id: 'response_metrics_nginx_ingress_throughput_status_code', - label: 'Status Code', - metric_id: 1, - prometheus_endpoint_path: - '/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29', - query_range: - 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)', - unit: 'req / sec', - }, - ], - title: 'Throughput', - type: 'area-chart', - weight: 1, - y_label: 'Requests / Sec', - }, - ], - }, - { group: 'System metrics (Kubernetes)', priority: 5, panels: [ { - title: 'Memory Usage (Pod average)', + title: 'Memory Usage (Total)', type: 'area-chart', - y_label: 'Memory Used per Pod', - weight: 2, + y_label: 'Total Memory Used', + weight: 4, metrics: [ { - id: 'system_metrics_kubernetes_container_memory_average', + id: 'system_metrics_kubernetes_container_memory_total', query_range: - 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024', - label: 'Pod average', - unit: 'MB', - metric_id: 17, - prometheus_endpoint_path: - '/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024', - appearance: { - line: { - width: 2, - }, - }, + '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', }, ], }, @@ -514,6 +422,22 @@ export const metricsDashboardPayload = { ], }, { + title: 'Memory Usage (Pod average)', + type: 'line-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, + }, + ], + }, + { title: 'memories', type: 'area-chart', y_label: 'memories', @@ -557,9 +481,45 @@ export const metricsDashboardPayload = { }, ], }, + { + group: 'Response metrics (NGINX Ingress VTS)', + priority: 10, + panels: [ + { + metrics: [ + { + id: 'response_metrics_nginx_ingress_throughput_status_code', + label: 'Status Code', + metric_id: 1, + prometheus_endpoint_path: + '/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29', + query_range: + 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)', + unit: 'req / sec', + }, + ], + title: 'Throughput', + type: 'area-chart', + weight: 1, + y_label: 'Requests / Sec', + }, + ], + }, ], }; +/** + * Mock of response of metrics_dashboard.json + */ +export const metricsDashboardResponse = { + all_dashboards: [], + dashboard: metricsDashboardPayload, + metrics_data: {}, + status: 'success', +}; + +export const metricsDashboardViewModel = mapToDashboardViewModel(metricsDashboardPayload); + const customDashboardsData = new Array(30).fill(null).map((_, idx) => ({ default: false, display_name: `Custom Dashboard ${idx}`, diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 11d3109fcd1..211950facd7 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -3,7 +3,7 @@ import testAction from 'helpers/vuex_action_helper'; import Tracking from '~/tracking'; import axios from '~/lib/utils/axios_utils'; import statusCodes from '~/lib/utils/http_status'; -import { backOff } from '~/lib/utils/common_utils'; +import * as commonUtils from '~/lib/utils/common_utils'; import createFlash from '~/flash'; import store from '~/monitoring/stores'; @@ -28,11 +28,10 @@ import { deploymentData, environmentData, metricsDashboardResponse, - metricsDashboardPayload, + metricsDashboardViewModel, dashboardGitResponse, } from '../mock_data'; -jest.mock('~/lib/utils/common_utils'); jest.mock('~/flash'); const resetStore = str => { @@ -44,14 +43,17 @@ const resetStore = str => { }; describe('Monitoring store actions', () => { + const { convertObjectPropsToCamelCase } = commonUtils; + let mock; + beforeEach(() => { mock = new MockAdapter(axios); // Mock `backOff` function to remove exponential algorithm delay. jest.useFakeTimers(); - backOff.mockImplementation(callback => { + jest.spyOn(commonUtils, 'backOff').mockImplementation(callback => { const q = new Promise((resolve, reject) => { const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg)); const next = () => callback(next, stop); @@ -69,7 +71,7 @@ describe('Monitoring store actions', () => { resetStore(store); mock.reset(); - backOff.mockReset(); + commonUtils.backOff.mockReset(); createFlash.mockReset(); }); @@ -115,7 +117,6 @@ describe('Monitoring store actions', () => { afterEach(() => { resetStore(store); - jest.restoreAllMocks(); }); it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => { @@ -365,6 +366,7 @@ describe('Monitoring store actions', () => { ); expect(commit).toHaveBeenCalledWith( types.RECEIVE_METRICS_DATA_SUCCESS, + metricsDashboardResponse.dashboard, ); expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params); @@ -443,8 +445,11 @@ describe('Monitoring store actions', () => { .catch(done.fail); }); it('dispatches fetchPrometheusMetric for each panel query', done => { - state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups; - const [metric] = state.dashboard.panel_groups[0].panels[0].metrics; + state.dashboard.panelGroups = convertObjectPropsToCamelCase( + metricsDashboardResponse.dashboard.panel_groups, + ); + + const [metric] = state.dashboard.panelGroups[0].panels[0].metrics; const getters = { metricsWithData: () => [metric.id], }; @@ -473,16 +478,16 @@ describe('Monitoring store actions', () => { }); it('dispatches fetchPrometheusMetric for each panel query, handles an error', done => { - state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups; - const metric = state.dashboard.panel_groups[0].panels[0].metrics[0]; + state.dashboard.panelGroups = metricsDashboardViewModel.panelGroups; + const metric = state.dashboard.panelGroups[0].panels[0].metrics[0]; - // Mock having one out of three metrics failing + // Mock having one out of four metrics failing dispatch.mockRejectedValueOnce(new Error('Error fetching this metric')); dispatch.mockResolvedValue(); fetchPrometheusMetrics({ state, commit, dispatch }, params) .then(() => { - expect(dispatch).toHaveBeenCalledTimes(3); + expect(dispatch).toHaveBeenCalledTimes(9); // one per metric expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', { metric, params, @@ -508,7 +513,12 @@ describe('Monitoring store actions', () => { beforeEach(() => { state = storeState(); [metric] = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics; - [data] = metricsDashboardPayload.panel_groups[0].panels[0].metrics; + metric = convertObjectPropsToCamelCase(metric, { deep: true }); + + data = { + metricId: metric.metricId, + result: [1582065167.353, 5, 1582065599.353], + }; }); it('commits result', done => { @@ -522,13 +532,13 @@ describe('Monitoring store actions', () => { { type: types.REQUEST_METRIC_RESULT, payload: { - metricId: metric.metric_id, + metricId: metric.metricId, }, }, { type: types.RECEIVE_METRIC_RESULT_SUCCESS, payload: { - metricId: metric.metric_id, + metricId: metric.metricId, result: data.result, }, }, @@ -556,13 +566,13 @@ describe('Monitoring store actions', () => { { type: types.REQUEST_METRIC_RESULT, payload: { - metricId: metric.metric_id, + metricId: metric.metricId, }, }, { type: types.RECEIVE_METRIC_RESULT_SUCCESS, payload: { - metricId: metric.metric_id, + metricId: metric.metricId, result: data.result, }, }, @@ -592,13 +602,13 @@ describe('Monitoring store actions', () => { { type: types.REQUEST_METRIC_RESULT, payload: { - metricId: metric.metric_id, + metricId: metric.metricId, }, }, { type: types.RECEIVE_METRIC_RESULT_FAILURE, payload: { - metricId: metric.metric_id, + metricId: metric.metricId, error, }, }, diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js index 263050b462f..64601e892ad 100644 --- a/spec/frontend/monitoring/store/getters_spec.js +++ b/spec/frontend/monitoring/store/getters_spec.js @@ -32,7 +32,7 @@ describe('Monitoring store Getters', () => { it('when dashboard has no panel groups, returns empty', () => { setupState({ dashboard: { - panel_groups: [], + panelGroups: [], }, }); @@ -43,10 +43,10 @@ describe('Monitoring store Getters', () => { let groups; beforeEach(() => { setupState({ - dashboard: { panel_groups: [] }, + dashboard: { panelGroups: [] }, }); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload); - groups = state.dashboard.panel_groups; + groups = state.dashboard.panelGroups; }); it('no loaded metric returns empty', () => { @@ -84,8 +84,8 @@ describe('Monitoring store Getters', () => { expect(getMetricStates()).toEqual([metricStates.OK]); // Filtered by groups - expect(getMetricStates(state.dashboard.panel_groups[0].key)).toEqual([]); - expect(getMetricStates(state.dashboard.panel_groups[1].key)).toEqual([metricStates.OK]); + expect(getMetricStates(state.dashboard.panelGroups[0].key)).toEqual([metricStates.OK]); + expect(getMetricStates(state.dashboard.panelGroups[1].key)).toEqual([]); }); it('on multiple metrics errors', () => { mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload); @@ -94,10 +94,10 @@ describe('Monitoring store Getters', () => { metricId: groups[0].panels[0].metrics[0].metricId, }); mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { - metricId: groups[1].panels[0].metrics[0].metricId, + metricId: groups[0].panels[0].metrics[0].metricId, }); mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { - metricId: groups[1].panels[1].metrics[0].metricId, + metricId: groups[1].panels[0].metrics[0].metricId, }); // Entire dashboard fails @@ -113,18 +113,18 @@ describe('Monitoring store Getters', () => { mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); // An error in 2 groups mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { - metricId: groups[0].panels[0].metrics[0].metricId, + metricId: groups[0].panels[1].metrics[0].metricId, }); mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { - metricId: groups[1].panels[1].metrics[0].metricId, + metricId: groups[1].panels[0].metrics[0].metricId, }); expect(getMetricStates()).toEqual([metricStates.OK, metricStates.UNKNOWN_ERROR]); - expect(getMetricStates(groups[0].key)).toEqual([metricStates.UNKNOWN_ERROR]); - expect(getMetricStates(groups[1].key)).toEqual([ + expect(getMetricStates(groups[0].key)).toEqual([ metricStates.OK, metricStates.UNKNOWN_ERROR, ]); + expect(getMetricStates(groups[1].key)).toEqual([metricStates.UNKNOWN_ERROR]); }); }); }); @@ -154,7 +154,7 @@ describe('Monitoring store Getters', () => { it('when dashboard has no panel groups, returns empty', () => { setupState({ dashboard: { - panel_groups: [], + panelGroups: [], }, }); @@ -164,7 +164,7 @@ describe('Monitoring store Getters', () => { describe('when the dashboard is set', () => { beforeEach(() => { setupState({ - dashboard: { panel_groups: [] }, + dashboard: { panelGroups: [] }, }); }); @@ -204,14 +204,14 @@ describe('Monitoring store Getters', () => { mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal); - // First group has no metrics - expect(metricsWithData(state.dashboard.panel_groups[0].key)).toEqual([]); - - // Second group has metrics - expect(metricsWithData(state.dashboard.panel_groups[1].key)).toEqual([ + // First group has metrics + expect(metricsWithData(state.dashboard.panelGroups[0].key)).toEqual([ mockedQueryResultPayload.metricId, mockedQueryResultPayloadCoresTotal.metricId, ]); + + // Second group has no metrics + expect(metricsWithData(state.dashboard.panelGroups[1].key)).toEqual([]); }); }); }); diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js index 3fb7b84fae5..76efc68788d 100644 --- a/spec/frontend/monitoring/store/mutations_spec.js +++ b/spec/frontend/monitoring/store/mutations_spec.js @@ -4,12 +4,8 @@ import mutations from '~/monitoring/stores/mutations'; import * as types from '~/monitoring/stores/mutation_types'; import state from '~/monitoring/stores/state'; import { metricStates } from '~/monitoring/constants'; -import { - metricsDashboardPayload, - deploymentData, - metricsDashboardResponse, - dashboardGitResponse, -} from '../mock_data'; + +import { metricsDashboardPayload, deploymentData, dashboardGitResponse } from '../mock_data'; describe('Monitoring mutations', () => { let stateCopy; @@ -17,27 +13,29 @@ describe('Monitoring mutations', () => { beforeEach(() => { stateCopy = state(); }); + describe('RECEIVE_METRICS_DATA_SUCCESS', () => { let payload; - const getGroups = () => stateCopy.dashboard.panel_groups; + const getGroups = () => stateCopy.dashboard.panelGroups; beforeEach(() => { - stateCopy.dashboard.panel_groups = []; + stateCopy.dashboard.panelGroups = []; payload = metricsDashboardPayload; }); it('adds a key to the group', () => { mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); const groups = getGroups(); - expect(groups[0].key).toBe('response-metrics-nginx-ingress-vts-0'); - expect(groups[1].key).toBe('system-metrics-kubernetes-1'); + expect(groups[0].key).toBe('system-metrics-kubernetes-0'); + expect(groups[1].key).toBe('response-metrics-nginx-ingress-vts-1'); }); it('normalizes values', () => { mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); const expectedLabel = 'Pod average'; - const { label, query_range } = getGroups()[1].panels[0].metrics[0]; + + const { label, queryRange } = getGroups()[0].panels[2].metrics[0]; expect(label).toEqual(expectedLabel); - expect(query_range.length).toBeGreaterThan(0); + expect(queryRange.length).toBeGreaterThan(0); }); it('contains two groups, with panels with a metric each', () => { mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); @@ -47,13 +45,14 @@ describe('Monitoring mutations', () => { expect(groups).toBeDefined(); expect(groups).toHaveLength(2); - expect(groups[0].panels).toHaveLength(1); + expect(groups[0].panels).toHaveLength(4); expect(groups[0].panels[0].metrics).toHaveLength(1); + expect(groups[0].panels[1].metrics).toHaveLength(1); + expect(groups[0].panels[2].metrics).toHaveLength(1); + expect(groups[0].panels[3].metrics).toHaveLength(5); - expect(groups[1].panels).toHaveLength(3); + expect(groups[1].panels).toHaveLength(1); expect(groups[1].panels[0].metrics).toHaveLength(1); - expect(groups[1].panels[1].metrics).toHaveLength(1); - expect(groups[1].panels[2].metrics).toHaveLength(5); }); it('assigns metrics a metric id', () => { mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); @@ -61,10 +60,10 @@ describe('Monitoring mutations', () => { const groups = getGroups(); expect(groups[0].panels[0].metrics[0].metricId).toEqual( - '1_response_metrics_nginx_ingress_throughput_status_code', + '12_system_metrics_kubernetes_container_memory_total', ); expect(groups[1].panels[0].metrics[0].metricId).toEqual( - '17_system_metrics_kubernetes_container_memory_average', + '1_response_metrics_nginx_ingress_throughput_status_code', ); }); }); @@ -130,8 +129,8 @@ describe('Monitoring mutations', () => { values: [[0, 1], [1, 1], [1, 3]], }, ]; - const { dashboard } = metricsDashboardResponse; - const getMetric = () => stateCopy.dashboard.panel_groups[0].panels[0].metrics[0]; + const dashboard = metricsDashboardPayload; + const getMetric = () => stateCopy.dashboard.panelGroups[0].panels[0].metrics[0]; describe('REQUEST_METRIC_RESULT', () => { beforeEach(() => { diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js index d322d45457e..57418e90470 100644 --- a/spec/frontend/monitoring/store/utils_spec.js +++ b/spec/frontend/monitoring/store/utils_spec.js @@ -1,27 +1,169 @@ import { - normalizeMetric, uniqMetricsId, parseEnvironmentsResponse, removeLeadingSlash, + mapToDashboardViewModel, } from '~/monitoring/stores/utils'; const projectPath = 'gitlab-org/gitlab-test'; -describe('normalizeMetric', () => { - [ - { args: [], expected: 'undefined_undefined' }, - { args: [undefined], expected: 'undefined_undefined' }, - { args: [{ id: 'something' }], expected: 'undefined_something' }, - { args: [{ id: 45 }], expected: 'undefined_45' }, - { args: [{ metric_id: 5 }], expected: '5_undefined' }, - { args: [{ metric_id: 'something' }], expected: 'something_undefined' }, - { - args: [{ metric_id: 5, id: 'system_metrics_kubernetes_container_memory_total' }], - expected: '5_system_metrics_kubernetes_container_memory_total', - }, - ].forEach(({ args, expected }) => { - it(`normalizes metric to "${expected}" with args=${JSON.stringify(args)}`, () => { - expect(normalizeMetric(...args)).toEqual({ metric_id: expected, metricId: expected }); +describe('mapToDashboardViewModel', () => { + it('maps an empty dashboard', () => { + expect(mapToDashboardViewModel({})).toEqual({ + dashboard: '', + panelGroups: [], + }); + }); + + it('maps a simple dashboard', () => { + const response = { + dashboard: 'Dashboard Name', + panel_groups: [ + { + group: 'Group 1', + panels: [ + { + title: 'Title A', + type: 'chart-type', + y_label: 'Y Label A', + metrics: [], + }, + ], + }, + ], + }; + + expect(mapToDashboardViewModel(response)).toEqual({ + dashboard: 'Dashboard Name', + panelGroups: [ + { + group: 'Group 1', + key: 'group-1-0', + panels: [ + { + title: 'Title A', + type: 'chart-type', + y_label: 'Y Label A', + metrics: [], + }, + ], + }, + ], + }); + }); + + describe('panel groups mapping', () => { + it('key', () => { + const response = { + dashboard: 'Dashboard Name', + panel_groups: [ + { + group: 'Group A', + }, + { + group: 'Group B', + }, + { + group: '', + unsupported_property: 'This should be removed', + }, + ], + }; + + expect(mapToDashboardViewModel(response).panelGroups).toEqual([ + { + group: 'Group A', + key: 'group-a-0', + panels: [], + }, + { + group: 'Group B', + key: 'group-b-1', + panels: [], + }, + { + group: '', + key: 'default-2', + panels: [], + }, + ]); + }); + }); + + describe('metrics mapping', () => { + const defaultLabel = 'Panel Label'; + const dashboardWithMetric = (metric, label = defaultLabel) => ({ + panel_groups: [ + { + panels: [ + { + y_label: label, + metrics: [metric], + }, + ], + }, + ], + }); + + const getMappedMetric = dashboard => { + return mapToDashboardViewModel(dashboard).panelGroups[0].panels[0].metrics[0]; + }; + + it('creates a metric', () => { + const dashboard = dashboardWithMetric({}); + + expect(getMappedMetric(dashboard)).toEqual({ + label: expect.any(String), + metricId: expect.any(String), + metric_id: expect.any(String), + }); + }); + + it('creates a metric with a correct ids', () => { + const dashboard = dashboardWithMetric({ + id: 'http_responses', + metric_id: 1, + }); + + expect(getMappedMetric(dashboard)).toMatchObject({ + metricId: '1_http_responses', + metric_id: '1_http_responses', + }); + }); + + it('creates a metric with a default label', () => { + const dashboard = dashboardWithMetric({}); + + expect(getMappedMetric(dashboard)).toMatchObject({ + label: defaultLabel, + }); + }); + + it('creates a metric with an endpoint and query', () => { + const dashboard = dashboardWithMetric({ + prometheus_endpoint_path: 'http://test', + query_range: 'http_responses', + }); + + expect(getMappedMetric(dashboard)).toMatchObject({ + prometheusEndpointPath: 'http://test', + queryRange: 'http_responses', + }); + }); + + it('creates a metric with an ad-hoc property', () => { + // This behavior is deprecated and should be removed + // https://gitlab.com/gitlab-org/gitlab/issues/207198 + + const dashboard = dashboardWithMetric({ + x_label: 'Another label', + unkown_option: 'unkown_data', + }); + + expect(getMappedMetric(dashboard)).toMatchObject({ + x_label: 'Another label', + unkown_option: 'unkown_data', + }); }); }); }); diff --git a/spec/javascripts/lib/utils/browser_spec.js b/spec/javascripts/lib/utils/browser_spec.js new file mode 100644 index 00000000000..6b1074a3b4f --- /dev/null +++ b/spec/javascripts/lib/utils/browser_spec.js @@ -0,0 +1,175 @@ +/** + * This file should only contain browser specific specs. + * If you need to add or update a spec, please see spec/frontend/lib/utils/*.js + * https://gitlab.com/gitlab-org/gitlab/issues/194242#note_292137135 + * https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment + */ + +import MockAdapter from 'axios-mock-adapter'; +import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils'; +import axios from '~/lib/utils/axios_utils'; +import * as commonUtils from '~/lib/utils/common_utils'; +import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data'; + +const PIXEL_TOLERANCE = 0.2; + +/** + * Loads a data URL as the src of an + * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image|Image} + * and resolves to that Image once loaded. + * + * @param url + * @returns {Promise} + */ +const urlToImage = url => + new Promise(resolve => { + const img = new Image(); + img.onload = function() { + resolve(img); + }; + img.src = url; + }); + +describe('common_utils browser specific specs', () => { + describe('contentTop', () => { + it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => { + spyOn(breakpointInstance, 'isDesktop').and.returnValue(false); + + setFixtures(` + <div class="diff-file file-title-flex-parent"> + blah blah blah + </div> + <div class="mr-version-controls"> + more blah blah blah + </div> + `); + + expect(commonUtils.contentTop()).toBe(0); + }); + + it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => { + spyOn(breakpointInstance, 'isDesktop').and.returnValue(true); + + setFixtures(` + <div class="diff-file file-title-flex-parent"> + blah blah blah + </div> + <div class="mr-version-controls"> + more blah blah blah + </div> + `); + + expect(commonUtils.contentTop()).toBe(18); + }); + }); + + describe('createOverlayIcon', () => { + it('should return the favicon with the overlay', done => { + commonUtils + .createOverlayIcon(faviconDataUrl, overlayDataUrl) + .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)])) + .then(([actual, expected]) => { + expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE); + done(); + }) + .catch(done.fail); + }); + }); + + describe('setFaviconOverlay', () => { + beforeEach(() => { + const favicon = document.createElement('link'); + favicon.setAttribute('id', 'favicon'); + favicon.setAttribute('data-original-href', faviconDataUrl); + document.body.appendChild(favicon); + }); + + afterEach(() => { + document.body.removeChild(document.getElementById('favicon')); + }); + + it('should set page favicon to provided favicon overlay', done => { + commonUtils + .setFaviconOverlay(overlayDataUrl) + .then(() => document.getElementById('favicon').getAttribute('href')) + .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)])) + .then(([actual, expected]) => { + expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE); + done(); + }) + .catch(done.fail); + }); + }); + + describe('setCiStatusFavicon', () => { + const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`; + let mock; + + beforeEach(() => { + const favicon = document.createElement('link'); + favicon.setAttribute('id', 'favicon'); + favicon.setAttribute('href', 'null'); + favicon.setAttribute('data-original-href', faviconDataUrl); + document.body.appendChild(favicon); + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + document.body.removeChild(document.getElementById('favicon')); + }); + + it('should reset favicon in case of error', done => { + mock.onGet(BUILD_URL).replyOnce(500); + + commonUtils.setCiStatusFavicon(BUILD_URL).catch(() => { + const favicon = document.getElementById('favicon'); + + expect(favicon.getAttribute('href')).toEqual(faviconDataUrl); + done(); + }); + }); + + it('should set page favicon to CI status favicon based on provided status', done => { + mock.onGet(BUILD_URL).reply(200, { + favicon: overlayDataUrl, + }); + + commonUtils + .setCiStatusFavicon(BUILD_URL) + .then(() => document.getElementById('favicon').getAttribute('href')) + .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)])) + .then(([actual, expected]) => { + expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE); + done(); + }) + .catch(done.fail); + }); + }); + + describe('isInViewport', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + }); + + afterEach(() => { + document.body.removeChild(el); + }); + + it('returns true when provided `el` is in viewport', () => { + el.setAttribute('style', `position: absolute; right: ${window.innerWidth + 0.2};`); + document.body.appendChild(el); + + expect(commonUtils.isInViewport(el)).toBe(true); + }); + + it('returns false when provided `el` is not in viewport', () => { + el.setAttribute('style', 'position: absolute; top: -1000px; left: -1000px;'); + document.body.appendChild(el); + + expect(commonUtils.isInViewport(el)).toBe(false); + }); + }); +}); diff --git a/spec/javascripts/lib/utils/mock_data.js b/spec/javascripts/lib/utils/mock_data.js index c466b0cd1ed..c2f79a32377 100644 --- a/spec/javascripts/lib/utils/mock_data.js +++ b/spec/javascripts/lib/utils/mock_data.js @@ -1,8 +1 @@ -export const faviconDataUrl = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACcFBMVEX////iQyniQyniQyniQyniQyniQyniQyniQynhRiriQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniRCniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQynhQiniQiniQiniQinhQinpUSjqUSjqTyjqTyjqTyjlSCniRCniQynjRCjqTyjsZSjrWyj8oib9kSb8pyb9pib8oyb8fyb3ZSb4Zib8fCb8oyb8oyb8oyb8pCb8cSbiQyn7bCb8cib8oyb8oSb8bSbtVSjpTij8nyb8oyb8oyb8lCb2Yyf3ZCf8mCb8oyb8oyb8oyb8iib8bSbiRCn8gyb8oyb8eCbpTinrUSj8oyb8oyb8oyb8pSb8bib4Zif0YCf8byb8oyb8oyb8oyb7oib8oyb8nCbjRSn9bib8ayb8nib8oyb8oyb8oyb8kSbpTyjpTyj8jib8oyb8oyb8oyb8fib0Xyf2ZSb8gCb8oyb6pSb8oyb8dib+cCbgQCnjRSn8cCb8oib8oyb8oyb8oybqUCjnSyn8bCb8oyb8oyb8oyb8myb2YyfyXyf8oyb8oyb8hibhQSn+bib8iSb8oyb8qCb+fSbmSSnqTyj8oib9pCb1YifxXyf7pSb8oCb8pCb+mCb0fCf8pSb7hSXvcSjiQyniQinqTyj9kCb9bib9byb+cCbqUSjiRCnsVCj+cSb8pib8bCb8bSbgQCn7bCb8bibjRSn8oyb8ayb8oib8aib8pCbjRCn8pybhQinhQSn8pSb7ayb7aSb6aib8eib///8IbM+7AAAAr3RSTlMBA3NtX2vT698HGQcRLwWLiXnv++3V+eEd/R8HE2V/Y5HjyefdFw99YWfJ+/3nwQP78/HvX1VTQ/kdA2HzbQXj9fX79/3DGf379/33T/v99/f7ba33+/f1+9/18/v59V339flzF/H9+fX3/fMhBwOh9/v5/fmvBV/z+fP3Awnp9/f38+UFgff7+/37+4c77/f7/flFz/f59dFr7/v98Wnr+/f3I5/197EDBU1ZAwUD8/kLUwAAAAFiS0dEAIgFHUgAAAAHdElNRQfhBQoLHiBV6/1lAAACHUlEQVQ4y41TZXsTQRCe4FAIUigN7m7FXY+iLRQKBG2x4g7BjhZ3Le7uMoEkFJprwyQk0CC/iZnNhUZaHt4vt6/szO7cHcD/wFKjZrJWq3YMq1M3eVc9rFzXR2yQkuA3RGxkjZLGiEk9miA2tURJs1RsnhhokYYtzaU13WZDbBVnW1sjo43J2vI6tZ0lLtFeAh1M0lECneI7dGYtrUtk3RUVIKaEJR25qw27yT0s3W0qEHuPlB4RradivXo7GX36xnbo51SQ+fWHARmCgYMGDxkaxbD3SssYPmIkwKgPLrfA87EETTg/fVaSa/SYsQDjSsd7DcGEsr+BieVKmaRNBsjUtClTfUI900y/5Mt05c8oJQKYSURZ2UqYFa0w283M588JEM2BuRwI5EqT8nmmXzZf4l8XsGNfCIv4QcHFklhiBpaqAsuC4tghj+ySyOdjeJYrP7RCCuR/E5tWAqxaLcmCNSyujdxjHZdbn8UHoA0bN/GoNm8hjQJb/ZzYpo6w3TB27JRduxxqrA7YzbWCezixN8RD2Oc2/Ptlfx7o5uT1A4XMiwzj4HfEikNe7+Ew0ZGjeuW70eEYaeHjxomTiKd++E4XnKGz8d+HDufOB3Ky3RcwdNF1qZiKLyf/B44r2tWf15wV143cwI2qfi8dbtKtX6Hbd+6G74EDqkTm/QcPH/0ufFyNLXjy9NnzF9Xb8BJevYY38C+8fZcg/AF3QTYemVkCwwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wNS0xMFQxMTozMDozMiswMjowMMzup8UAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDUtMTBUMTE6MzA6MzIrMDI6MDC9sx95AAAAAElFTkSuQmCC'; - -export const overlayDataUrl = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA85JREFUWAntVllIVGEUPv/9b46O41KplYN7PeRkti8TjQlhCUGh3MmeQugpIsGKAi2soIcIooiohxYKK2daqDAlIpIiWwxtQaJcaHE0d5tMrbn37z9XRqfR0TvVW56Hudf//uec72zfEWBCJjIwkYGJDPzvGSD/KgExN3Oi2Q+2DJgSDYQEMwItVGH1iZGmJw/Si1y+/PwVAMYYib22MYc/8hVQFgKDEfYoId0KYzagAQebsos/ewMZoeB9wdffcTYpQSaCTWHKoqSQaDk7zkIt0+aCUR8BelEHrf3dUNv9AcqbnsHtT5UKB/hTASh0SLYjnjb/CIDRJi0XiFAaJOpCD8zLpdb4NB66b1OfelthX815dtdRRfiti2aAXLvVLiMQ6olGyztGDkSo4JGGXk8/QFdGpYzpHG2GBQTDhtgVhPEaVbbVpvI6GJz22rv4TcAfrYI1x7Rj5MWWAppomKFVVb2302SFzUkZHAbkG+0b1+Gh77yNYjrmqnWTrLBLRxdvBWv8qlFujH/kYjJYyvLkj71t78zAUvzMAMnHhpN4zf9UREJhd8omyssxu1IgazQDwDnHUcNuH6vhPIE1fmuBzHt74Hn7W89jWGtcAjoaIDOFrdcMYJBkgOCoaRF0Lj0oglddDbCj6tRvKjphEpgjkzEQs2YAKsNxMzjn3nKurhzK+Ly7xe28ua8TwgMMcHJZnvvT0BPtEEKM4tDJ+C8GvIIk4ylINIXVZ0EUKJxYuh3mhCeokbudl6TtVc88dfBdLwbyaWB6zQCYQJpBYSrDGQxBQ/ZWRM2B+VNmQnVnHWx7elyNuL2/R336co7KyJR8CL9oLgEuFlREevWUkEl6uGwpVEG4FBm0OEf9N10NMgPlvWYAuNVwsWDKvcUNYsHUWTCZ13ysyFEXe6TO6aC8CUr9IiK+A05TQrc8yjwmxARHeeMAPlfQJw+AQRwu0YhL/GDXi9NwufG+S8dYkuYMqIb4SsWthotlNMOUCOM6r+G9cqXxPmd1dqrBav/o1zJy2l5/NUjJA/VORwYuFnOUaTQcPs9wMqwV++Xv8oADxKAcZ8nLPr8AoGW+xR6HSqYk3GodAz2QNj0V+Gr26dT9ASNH5239Pf0gktVNWZca8ZvfAFBprWS6hSu1pqt++Y0PD+WIwDAhIWQGtzvSHDbcodfFUFB9hg1Gjs5LXqIdFL+acFBl+FddqYwdxsWC3I70OvgfUaA65zhq2O2c8VxYcyIGFTVlXegYtvCXANCQZJMobjVcLMjtSK/IcEgyOOe8Ve5w7ryKDefp2P3+C/5ohv8HZmVLAAAAAElFTkSuQmCC'; - -export const faviconWithOverlayDataUrl = - 'data:image/png;base64,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'; +export * from '../../../frontend/lib/utils/mock_data.js'; diff --git a/spec/javascripts/monitoring/components/dashboard_resize_spec.js b/spec/javascripts/monitoring/components/dashboard_resize_spec.js index 2422934f4b3..6a35069ccff 100644 --- a/spec/javascripts/monitoring/components/dashboard_resize_spec.js +++ b/spec/javascripts/monitoring/components/dashboard_resize_spec.js @@ -112,7 +112,7 @@ describe('Dashboard', () => { setupComponentStore(component); return Vue.nextTick().then(() => { - [, promPanel] = component.$el.querySelectorAll('.prometheus-panel'); + [promPanel] = component.$el.querySelectorAll('.prometheus-panel'); promGroup = promPanel.querySelector('.prometheus-graph-group'); panelToggle = promPanel.querySelector('.js-graph-group-toggle'); chart = promGroup.querySelector('.position-relative svg'); diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 7d98f8a0c3e..1cd3071ac68 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -72,6 +72,7 @@ Note: - resolved_by_push - discussion_id - original_discussion_id +- confidential LabelLink: - id - target_type |