summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/groups/registry/repositories_controller_spec.rb2
-rw-r--r--spec/controllers/projects/registry/repositories_controller_spec.rb4
-rw-r--r--spec/features/projects/container_registry_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/environment.json1
-rw-r--r--spec/frontend/helpers/dom_shims/get_client_rects.js14
-rw-r--r--spec/frontend/helpers/dom_shims/index.js2
-rw-r--r--spec/frontend/helpers/dom_shims/scroll_by.js7
-rw-r--r--spec/frontend/helpers/dom_shims/size_properties.js19
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js (renamed from spec/javascripts/lib/utils/common_utils_spec.js)213
-rw-r--r--spec/frontend/lib/utils/mock_data.js8
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js53
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js7
-rw-r--r--spec/frontend/monitoring/embed/embed_spec.js4
-rw-r--r--spec/frontend/monitoring/mock_data.js170
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js48
-rw-r--r--spec/frontend/monitoring/store/getters_spec.js36
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js39
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js174
-rw-r--r--spec/javascripts/lib/utils/browser_spec.js175
-rw-r--r--spec/javascripts/lib/utils/mock_data.js9
-rw-r--r--spec/javascripts/monitoring/components/dashboard_resize_spec.js2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
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,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';
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