Commit bc898829 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 4a45a787
/* eslint-disable func-names, no-var, prefer-arrow-callback */
/* eslint-disable func-names, no-var */
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
......@@ -45,26 +45,22 @@ MarkdownPreview.prototype.showPreview = function($form) {
this.hideReferencedUsers($form);
} else {
preview.addClass('md-preview-loading').text(__('Loading...'));
this.fetchMarkdownPreview(
mdText,
url,
function(response) {
var body;
if (response.body.length > 0) {
({ body } = response);
} else {
body = this.emptyMessage;
}
preview.removeClass('md-preview-loading').html(body);
preview.renderGFM();
this.renderReferencedUsers(response.references.users, $form);
if (response.references.commands) {
this.renderReferencedCommands(response.references.commands, $form);
}
}.bind(this),
);
this.fetchMarkdownPreview(mdText, url, response => {
var body;
if (response.body.length > 0) {
({ body } = response);
} else {
body = this.emptyMessage;
}
preview.removeClass('md-preview-loading').html(body);
preview.renderGFM();
this.renderReferencedUsers(response.references.users, $form);
if (response.references.commands) {
this.renderReferencedCommands(response.references.commands, $form);
}
});
}
};
......@@ -132,12 +128,12 @@ const markdownToolbar = $('.md-header-toolbar');
$.fn.setupMarkdownPreview = function() {
var $form = $(this);
$form.find('textarea.markdown-area').on('input', function() {
$form.find('textarea.markdown-area').on('input', () => {
markdownPreview.hideReferencedUsers($form);
});
};
$(document).on('markdown-preview:show', function(e, $form) {
$(document).on('markdown-preview:show', (e, $form) => {
if (!$form) {
return;
}
......@@ -162,7 +158,7 @@ $(document).on('markdown-preview:show', function(e, $form) {
markdownPreview.showPreview($form);
});
$(document).on('markdown-preview:hide', function(e, $form) {
$(document).on('markdown-preview:hide', (e, $form) => {
if (!$form) {
return;
}
......@@ -191,7 +187,7 @@ $(document).on('markdown-preview:hide', function(e, $form) {
markdownPreview.hideReferencedCommands($form);
});
$(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
$(document).on('markdown-preview:toggle', (e, keyboardEvent) => {
var $target;
$target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) {
......
/* eslint-disable func-names, prefer-arrow-callback */
/* eslint-disable func-names */
import $ from 'jquery';
import Dropzone from 'dropzone';
......@@ -43,18 +43,18 @@ export default class BlobFileDropzone {
previewsContainer: '.dropzone-previews',
headers: csrf.headers,
init() {
this.on('addedfile', function() {
this.on('addedfile', () => {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.addClass(HIDDEN_CLASS);
$('.dropzone-alerts')
.html('')
.hide();
});
this.on('removedfile', function() {
this.on('removedfile', () => {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.removeClass(HIDDEN_CLASS);
});
this.on('success', function(header, response) {
this.on('success', (header, response) => {
$('#modal-upload-blob').modal('hide');
visitUrl(response.filePath);
});
......@@ -62,7 +62,7 @@ export default class BlobFileDropzone {
dropzoneMessage.addClass(HIDDEN_CLASS);
this.removeFile(file);
});
this.on('sending', function(file, xhr, formData) {
this.on('sending', (file, xhr, formData) => {
formData.append('branch_name', form.find('.js-branch-name').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
......
/* eslint-disable func-names, prefer-arrow-callback */
/* eslint-disable func-names */
import $ from 'jquery';
import { visitUrl } from './lib/utils/url_utility';
......@@ -12,11 +12,11 @@ export default class BuildArtifacts {
}
// eslint-disable-next-line class-methods-use-this
disablePropagation() {
$('.top-block').on('click', '.download', function(e) {
return e.stopPropagation();
$('.top-block').on('click', '.download', e => {
e.stopPropagation();
});
return $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
return e.stopImmediatePropagation();
return $('.tree-holder').on('click', 'tr[data-link] a', e => {
e.stopImmediatePropagation();
});
}
// eslint-disable-next-line class-methods-use-this
......
/* eslint-disable func-names, no-var, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, one-var, no-return-assign, no-unused-expressions, no-sequences */
/* eslint-disable func-names, no-var, no-else-return, consistent-return, prefer-template, one-var, no-return-assign, no-unused-expressions, no-sequences */
import $ from 'jquery';
......@@ -13,14 +13,14 @@ export default class ImageFile {
$('.two-up.view .frame.deleted img', this.file),
(function(_this) {
return function() {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function() {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), () => {
_this.initViewModes();
// Load two-up view after images are loaded
// so that we can display the correct width and height information
const $images = $('.two-up.view img', _this.file);
$images.waitForImages(function() {
$images.waitForImages(() => {
_this.initView('two-up');
});
});
......@@ -138,7 +138,7 @@ export default class ImageFile {
return $(this).width(availWidth / 2);
}
});
return _this.requestImageInfo($('img', wrap), function(width, height) {
return _this.requestImageInfo($('img', wrap), (width, height) => {
$('.image-info .meta-width', wrap).text(width + 'px');
$('.image-info .meta-height', wrap).text(height + 'px');
return $('.image-info', wrap).removeClass('hide');
......@@ -175,7 +175,7 @@ export default class ImageFile {
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
_this.initDraggable($swipeBar, wrapPadding, function(e, left) {
_this.initDraggable($swipeBar, wrapPadding, (e, left) => {
if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) {
$swipeWrap.width(maxWidth + 1 - left);
$swipeBar.css('left', left);
......@@ -215,7 +215,7 @@ export default class ImageFile {
$frameAdded.css('opacity', 1);
framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
_this.initDraggable($dragger, framePadding, function(e, left) {
_this.initDraggable($dragger, framePadding, (e, left) => {
var opacity = left / dragTrackWidth;
if (opacity >= 0 && opacity <= 1) {
......
<script>
import { mapActions, mapState } from 'vuex';
import RegionDropdown from './region_dropdown.vue';
import RoleNameDropdown from './role_name_dropdown.vue';
import SecurityGroupDropdown from './security_group_dropdown.vue';
import SubnetDropdown from './subnet_dropdown.vue';
......@@ -6,11 +9,21 @@ import VPCDropdown from './vpc_dropdown.vue';
export default {
components: {
RegionDropdown,
RoleNameDropdown,
SecurityGroupDropdown,
SubnetDropdown,
VPCDropdown,
},
computed: {
...mapState(['isLoadingRegions', 'loadingRegionsError', 'regions', 'selectedRegion']),
},
mounted() {
this.fetchRegions();
},
methods: {
...mapActions(['fetchRegions', 'setRegion']),
},
};
</script>
<template>
......@@ -21,5 +34,17 @@ export default {
</label>
<role-name-dropdown />
</div>
<div class="form-group">
<label class="label-bold" name="role" for="eks-role">
{{ s__('ClusterIntegration|Region') }}
</label>
<region-dropdown
:value="selectedRegion"
:regions="regions"
:error="loadingRegionsError"
:loading="isLoadingRegions"
@input="setRegion({ region: $event })"
/>
</div>
</form>
</template>
<script>
import { sprintf, s__ } from '~/locale';
import ClusterFormDropdown from './cluster_form_dropdown.vue';
export default {
components: {
ClusterFormDropdown,
},
props: {
regions: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
error: {
type: Object,
required: false,
default: null,
},
},
computed: {
hasErrors() {
return Boolean(this.error);
},
helpText() {
return sprintf(
s__('ClusterIntegration|Learn more about %{startLink}Regions%{endLink}.'),
{
startLink:
'<a href="https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/" target="_blank" rel="noopener noreferrer">',
endLink: '</a>',
},
false,
);
},
},
};
</script>
<template>
<div>
<cluster-form-dropdown
field-id="eks-region"
field-name="eks-region"
:items="regions"
:loading="loading"
:loading-text="s__('ClusterIntegration|Loading Regions')"
:placeholder="s__('ClusterIntergation|Select a region')"
:search-field-placeholder="s__('ClusterIntegration|Search regions')"
:empty-text="s__('ClusterIntegration|No region found')"
:has-errors="hasErrors"
:error-message="s__('ClusterIntegration|Could not load regions from your AWS account')"
v-bind="$attrs"
v-on="$listeners"
/>
<p class="form-text text-muted" v-html="helpText"></p>
</div>
</template>
......@@ -12,7 +12,6 @@ export default () =>
components: {
CreateEksCluster,
},
data() {},
render(createElement) {
return createElement('create-eks-cluster');
},
......
import EC2 from 'aws-sdk/clients/ec2';
export const fetchRegions = () =>
new Promise((resolve, reject) => {
const ec2 = new EC2();
ec2
.describeRegions()
.on('success', ({ data: { Regions: regions } }) => {
const transformedRegions = regions.map(({ RegionName: name }) => ({ name }));
resolve(transformedRegions);
})
.on('error', error => {
reject(error);
})
.send();
});
export default () => {};
// import awsServices from '../services/aws_services_facade';
import * as awsServices from '../services/aws_services_facade';
import * as types from './mutation_types';
export const requestRegions = ({ commit }) => commit(types.REQUEST_REGIONS);
export const receiveRegionsSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_REGIONS_SUCCESS, payload);
};
export const receiveRegionsError = ({ commit }, payload) => {
commit(types.RECEIVE_REGIONS_ERROR, payload);
};
export const fetchRegions = ({ dispatch }) => {
dispatch('requestRegions');
return awsServices
.fetchRegions()
.then(regions => dispatch('receiveRegionsSuccess', { regions }))
.catch(error => dispatch('receiveRegionsError', { error }));
};
export const setRegion = ({ commit }, payload) => {
commit(types.SET_REGION, payload);
};
export default () => {};
......@@ -9,7 +9,7 @@ const createStore = () =>
actions,
getters,
mutations,
state,
state: state(),
});
export default createStore;
export const REQUEST_REGIONS = 'REQUEST_REGIONS';
export const RECEIVE_REGIONS_SUCCESS = 'REQUEST_REGIONS_SUCCESS';
export const RECEIVE_REGIONS_ERROR = 'RECEIVE_REGIONS_ERROR';
export const SET_REGION = 'SET_REGION';
import * as types from './mutation_types';
export default {
[types.REQUEST_REGIONS](state) {
state.isLoadingRegions = true;
state.loadingRegionsError = null;
},
[types.RECEIVE_REGIONS_SUCCESS](state, { regions }) {
state.isLoadingRegions = false;
state.regions = regions;
},
[types.RECEIVE_REGIONS_ERROR](state, { error }) {
state.isLoadingRegions = false;
state.loadingRegionsError = error;
},
[types.SET_REGION](state, { region }) {
state.selectedRegion = region;
},
};
......@@ -2,16 +2,25 @@ export default () => ({
isValidatingCredentials: false,
validCredentials: false,
isLoadingRegions: false,
isLoadingRoles: false,
isLoadingVPCs: false,
isLoadingSubnets: false,
isLoadingSecurityGroups: false,
regions: [],
roles: [],
vpcs: [],
subnets: [],
securityGroups: [],
loadingRegionsError: null,
loadingRolesError: null,
loadingVPCsError: null,
loadingSubnetsError: null,
loadingSecurityGroupsError: null,
selectedRegion: '',
selectedRole: '',
selectedVPC: '',
selectedSubnet: '',
......
/* eslint-disable func-names, prefer-arrow-callback */
/* eslint-disable func-names */
import $ from 'jquery';
import Api from './api';
......@@ -50,7 +50,7 @@ export default class CreateLabelDropdown {
this.$dropdownBack.on('click', this.resetForm.bind(this));
this.$cancelButton.on('click', function(e) {
this.$cancelButton.on('click', e => {
e.preventDefault();
e.stopPropagation();
......
/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */
/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-shadow, no-cond-assign, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */
import $ from 'jquery';
import _ from 'underscore';
......@@ -35,13 +35,13 @@ GitLabDropdownInput = (function() {
);
this.input
.on('keydown', function(e) {
.on('keydown', e => {
var keyCode = e.which;
if (keyCode === 13 && !options.elIsInput) {
e.preventDefault();
}
})
.on('input', function(e) {
.on('input', e => {
var val = e.currentTarget.value || _this.options.inputFieldName;
val = val
.split(' ')
......@@ -95,42 +95,33 @@ GitLabDropdownFilter = (function() {
// Key events
timeout = '';
this.input
.on('keydown', function(e) {
.on('keydown', e => {
var keyCode = e.which;
if (keyCode === 13 && !options.elIsInput) {
e.preventDefault();
}
})
.on(
'input',
function() {
if (this.input.val() !== '' && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.addClass(HAS_VALUE_CLASS);
} else if (this.input.val() === '' && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.removeClass(HAS_VALUE_CLASS);
}
// Only filter asynchronously only if option remote is set
if (this.options.remote) {
clearTimeout(timeout);
return (timeout = setTimeout(
function() {
$inputContainer.parent().addClass('is-loading');
return this.options.query(
this.input.val(),
function(data) {
$inputContainer.parent().removeClass('is-loading');
return this.options.callback(data);
}.bind(this),
);
}.bind(this),
250,
));
} else {
return this.filter(this.input.val());
}
}.bind(this),
);
.on('input', () => {
if (this.input.val() !== '' && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.addClass(HAS_VALUE_CLASS);
} else if (this.input.val() === '' && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.removeClass(HAS_VALUE_CLASS);
}
// Only filter asynchronously only if option remote is set
if (this.options.remote) {
clearTimeout(timeout);
return (timeout = setTimeout(() => {
$inputContainer.parent().addClass('is-loading');
return this.options.query(this.input.val(), data => {
$inputContainer.parent().removeClass('is-loading');
return this.options.callback(data);
});
}, 250));
} else {
return this.filter(this.input.val());
}
});
}
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
......@@ -175,9 +166,7 @@ GitLabDropdownFilter = (function() {
key: this.options.keys,
});
if (tmp.length) {
results[key] = tmp.map(function(item) {
return item;
});
results[key] = tmp.map(item => item);
}
}
}
......@@ -453,32 +442,28 @@ GitLabDropdown = (function() {
if (this.dropdown.find('.dropdown-toggle-page').length) {
selector = '.dropdown-page-one .dropdown-content a';
}
this.dropdown.on(
'click',
selector,
function(e) {
var $el, selected, selectedObj, isMarking;
$el = $(e.currentTarget);
selected = self.rowClicked($el);
selectedObj = selected ? selected[0] : null;
isMarking = selected ? selected[1] : null;
if (this.options.clicked) {
this.options.clicked.call(this, {
selectedObj,
$el,
e,
isMarking,
});
}
this.dropdown.on('click', selector, e => {
var $el, selected, selectedObj, isMarking;
$el = $(e.currentTarget);
selected = self.rowClicked($el);
selectedObj = selected ? selected[0] : null;
isMarking = selected ? selected[1] : null;
if (this.options.clicked) {
this.options.clicked.call(this, {
selectedObj,
$el,
e,
isMarking,
});
}
// Update label right after all modifications in dropdown has been done
if (this.options.toggleLabel) {
this.updateLabel(selectedObj, $el, this);
}
// Update label right after all modifications in dropdown has been done
if (this.options.toggleLabel) {
this.updateLabel(selectedObj, $el, this);
}
$el.trigger('blur');
}.bind(this),
);
$el.trigger('blur');
});
}
}
......@@ -525,9 +510,7 @@ GitLabDropdown = (function() {
name,
),
);
this.renderData(groupData, name).map(function(item) {
return html.push(item);
});
this.renderData(groupData, name).map(item => html.push(item));
}
} else {
// Render each row
......@@ -708,7 +691,7 @@ GitLabDropdown = (function() {
return text
.split('')
.map(function(character, i) {
.map((character, i) => {
if (indexOf.call(occurrences, i) !== -1) {
return '<b>' + character + '</b>';
} else {
......
/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback */
/* eslint-disable consistent-return, func-names, array-callback-return */
import $ from 'jquery';
import _ from 'underscore';
......@@ -45,7 +45,7 @@ export default {
this.getSelectedIssues().map(function() {
const labelsData = $(this).data('labels');
if (labelsData) {
return labelsData.map(function(labelId) {
return labelsData.map(labelId => {
if (labels.indexOf(labelId) === -1) {
return labels.push(labelId);
}
......
......@@ -40,16 +40,12 @@ export default {
@toggleLine="handleOnClickCollapsibleLine(section)"
/>
<template v-if="!section.isClosed">
<template v-for="line in section.lines">
<collpasible-log-section
v-if="line.isHeader"
:key="`collapsible-nested-${line.offset}`"
:section="line"
:trace-endpoint="traceEndpoint"
@toggleLine="handleOnClickCollapsibleLine"
/>
<log-line v-else :key="line.offset" :line="line" :path="traceEndpoint" />
</template>
<log-line
v-for="line in section.lines"
:key="line.offset"
:line="line"
:path="traceEndpoint"
/>
</template>
</div>
</template>
......@@ -19,7 +19,7 @@ export default {
</script>
<template>
<div class="log-line">
<div class="js-line log-line">
<line-number :line-number="line.lineNumber" :path="path" />
<span v-for="(content, i) in line.content" :key="i" :class="content.style">{{
content.text
......
......@@ -8,6 +8,21 @@ export const parseLine = (line = {}, lineNumber) => ({
lineNumber,
});
/**
* When a line has `section_header` set to true, we create a new
* structure to allow to nest the lines that belong to the
* collpasible section
*
* @param Object line
* @param Number lineNumber
*/
export const parseHeaderLine = (line = {}, lineNumber) => ({
isClosed: true,
isHeader: true,
line: parseLine(line, lineNumber),
lines: [],
});
/**
* Parses the job log content into a structure usable by the template
*
......@@ -28,12 +43,7 @@ export const logLinesParser = (lines = [], lineNumberStart) =>
const last = acc[acc.length - 1];
if (line.section_header) {
acc.push({
isClosed: true,
isHeader: true,
line: parseLine(line, lineNumber),
lines: [],
});
acc.push(parseHeaderLine(line, lineNumber));
} else if (acc.length && last.isHeader && !line.section_duration && line.content.length) {
last.lines.push(parseLine(line, lineNumber));
} else if (acc.length && last.isHeader && line.section_duration) {
......
/* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, prefer-arrow-callback, one-var, prefer-template, no-new, consistent-return, no-shadow, no-param-reassign, vars-on-top, no-lonely-if, no-else-return, dot-notation, no-empty */
/* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, one-var, prefer-template, no-new, consistent-return, no-shadow, no-param-reassign, vars-on-top, no-lonely-if, no-else-return, dot-notation, no-empty */
/* global Issuable */
/* global ListLabel */
......@@ -24,7 +24,7 @@ export default class LabelsSelect {
$els = $('.js-label-select');
}
$els.each(function(i, dropdown) {
$els.each((i, dropdown) => {
var $block,
$dropdown,
$form,
......@@ -172,9 +172,7 @@ export default class LabelsSelect {
$sidebarCollapsedValue.text(labelCount);
if (data.labels.length) {
labelTitles = data.labels.map(function(label) {
return label.title;
});
labelTitles = data.labels.map(label => label.title);
if (labelTitles.length > 5) {
labelTitles = labelTitles.slice(0, 5);
......@@ -456,9 +454,7 @@ export default class LabelsSelect {
);
} else {
var { labels } = boardsStore.detail.issue;
labels = labels.filter(function(selectedLabel) {
return selectedLabel.id !== label.id;
});
labels = labels.filter(selectedLabel => selectedLabel.id !== label.id);
boardsStore.detail.issue.labels = labels;
}
......
/* eslint-disable func-names, no-var, consistent-return, prefer-arrow-callback, no-return-assign */
/* eslint-disable no-var, consistent-return, no-return-assign */
function notificationGranted(message, opts, onclick) {
var notification;
notification = new Notification(message, opts);
setTimeout(function() {
// Hide the notification after X amount of seconds
return notification.close();
}, 8000);
setTimeout(
() =>
// Hide the notification after X amount of seconds
notification.close(),
8000,
);
return (notification.onclick = onclick || notification.close);
}
......@@ -32,7 +34,7 @@ function notifyMe(message, body, icon, onclick) {
// If it's okay let's create a notification
return notificationGranted(message, opts, onclick);
} else if (Notification.permission !== 'denied') {
return Notification.requestPermission(function(permission) {
return Notification.requestPermission(permission => {
// If the user accepts, let's create a notification
if (permission === 'granted') {
return notificationGranted(message, opts, onclick);
......
/* eslint-disable func-names, no-var, no-param-reassign, one-var, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, consistent-return */
/* eslint-disable func-names, no-var, no-param-reassign, one-var, operator-assignment, no-else-return, prefer-template, consistent-return */
import $ from 'jquery';
import { insertText } from '~/lib/utils/common_utils';
......@@ -218,7 +218,7 @@ export function insertMarkdownText({
: blockTagText(text, textArea, blockTag, selected);
} else {
textToInsert = selectedSplit
.map(function(val) {
.map(val => {
if (tag.indexOf(textPlaceholder) > -1) {
return tag.replace(textPlaceholder, val);
}
......@@ -301,7 +301,7 @@ export function addMarkdownListeners(form) {
export function addEditorMarkdownListeners(editor) {
$('.js-md')
.off('click')
.on('click', function(e) {
.on('click', e => {
const { mdTag, mdBlock, mdPrepend, mdSelect } = $(e.currentTarget).data();
insertMarkdownText({
......
/* eslint-disable func-names, no-var, no-underscore-dangle, one-var, consistent-return, prefer-arrow-callback */
/* eslint-disable func-names, no-var, no-underscore-dangle, one-var, consistent-return */
import $ from 'jquery';
import { __ } from '~/locale';
......@@ -105,7 +105,7 @@ MergeRequest.prototype.submitNoteForm = function(form, $button) {
};
MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) {
$(document).on('click', 'a.js-with-description-link', e => {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
......@@ -114,7 +114,7 @@ MergeRequest.prototype.initCommitMessageListeners = function() {
$('.js-without-description-hint').show();
});
$(document).on('click', 'a.js-without-description-link', function(e) {
$(document).on('click', 'a.js-without-description-link', e => {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
......
/* eslint-disable func-names, no-else-return, prefer-template, prefer-arrow-callback */
/* eslint-disable no-else-return, prefer-template */
import $ from 'jquery';
import '~/gl_dropdown';
......@@ -28,7 +28,7 @@ export default class NamespaceSelect {
}
},
data(term, dataCallback) {
return Api.namespaces(term, function(namespaces) {
return Api.namespaces(term, namespaces => {
if (isFilter) {
const anyNamespace = {
text: __('Any namespace'),
......
/* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, prefer-template, prefer-arrow-callback, camelcase */
/* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, prefer-template, camelcase */
import $ from 'jquery';
import { __ } from '../locale';
......@@ -259,9 +259,7 @@ export default (function() {
opacity: 0,
cursor: 'pointer',
})
.click(function() {
return window.open(options.commit_url.replace('%s', commit.id), '_blank');
})
.click(() => window.open(options.commit_url.replace('%s', commit.id), '_blank'))
.hover(
function() {
this.tooltip = r.commitTooltip(x + 5, y, commit);
......
/* eslint-disable func-names, no-var, one-var, consistent-return, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, @gitlab/i18n/no-non-i18n-strings */
/* eslint-disable func-names, no-var, one-var, consistent-return, no-return-assign, prefer-template, no-shadow, no-else-return, @gitlab/i18n/no-non-i18n-strings */
import $ from 'jquery';
import RefSelectDropdown from './ref_select_dropdown';
......@@ -63,7 +63,7 @@ export default class NewBranchForm {
};
formatter = function(values, restriction) {
var formatted;
formatted = values.map(function(value) {
formatted = values.map(value => {
switch (false) {
case !/\s/.test(value):
return 'spaces';
......
/* eslint-disable no-restricted-properties, func-names, no-var, camelcase,
no-unused-expressions, one-var, default-case,
prefer-template, consistent-return, no-alert, no-return-assign,
no-param-reassign, prefer-arrow-callback, no-else-return, vars-on-top,
no-param-reassign, no-else-return, vars-on-top,
no-shadow, no-useless-escape, class-methods-use-this */
/* global ResolveService */
......@@ -1370,7 +1370,7 @@ export default class Notes {
.find('li.system-note')
.has('ul');
$.each(systemNotes, function(index, systemNote) {
$.each(systemNotes, (index, systemNote) => {
const $systemNote = $(systemNote);
const headerMessage = $systemNote
.find('.note-text')
......
/* eslint-disable func-names, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, no-return-assign, prefer-arrow-callback, prefer-template, no-else-return, no-shadow */
/* eslint-disable func-names, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, no-return-assign, prefer-template, no-else-return, no-shadow */
import $ from 'jquery';
import _ from 'underscore';
......@@ -69,24 +69,18 @@ export const ContributorsGraph = (function() {
ContributorsGraph.set_y_domain = function(data) {
return (ContributorsGraph.prototype.y_domain = [
0,
d3.max(data, function(d) {
return (d.commits = d.commits || d.additions || d.deletions);
}),
d3.max(data, d => (d.commits = d.commits || d.additions || d.deletions)),
]);
};
ContributorsGraph.init_x_domain = function(data) {
return (ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
return d.date;
}));
return (ContributorsGraph.prototype.x_domain = d3.extent(data, d => d.date));
};
ContributorsGraph.init_y_domain = function(data) {
return (ContributorsGraph.prototype.y_domain = [
0,
d3.max(data, function(d) {
return (d.commits = d.commits || d.additions || d.deletions);
}),
d3.max(data, d => (d.commits = d.commits || d.additions || d.deletions)),
]);
};
......@@ -180,9 +174,7 @@ export const ContributorsMasterGraph = (function(superClass) {
ContributorsMasterGraph.prototype.parse_dates = function(data) {
const parseDate = d3.timeParse('%Y-%m-%d');
return data.forEach(function(d) {
return (d.date = parseDate(d.date));
});
return data.forEach(d => (d.date = parseDate(d.date)));
};
ContributorsMasterGraph.prototype.create_scale = function() {
......@@ -216,11 +208,9 @@ export const ContributorsMasterGraph = (function(superClass) {
ContributorsMasterGraph.prototype.create_area = function(x, y) {
return (this.area = d3
.area()
.x(function(d) {
return x(d.date);
})
.x(d => x(d.date))
.y0(this.height)
.y1(function(d) {
.y1(d => {
d.commits = d.commits || d.additions || d.deletions;
return y(d.commits);
}));
......@@ -330,7 +320,7 @@ export const ContributorsAuthorGraph = (function(superClass) {
ContributorsAuthorGraph.prototype.create_area = function(x, y) {
return (this.area = d3
.area()
.x(function(d) {
.x(d => {
const parseDate = d3.timeParse('%Y-%m-%d');
return x(parseDate(d));
})
......
/* eslint-disable func-names, no-var, one-var, camelcase, no-param-reassign, no-return-assign, prefer-arrow-callback, consistent-return, no-cond-assign, no-else-return */
/* eslint-disable func-names, no-var, one-var, camelcase, no-param-reassign, no-return-assign, consistent-return, no-cond-assign, no-else-return */
import _ from 'underscore';
export default {
......@@ -76,16 +76,12 @@ export default {
var log, total_data;
log = parsed_log.total;
total_data = this.pick_field(log, field);
return _.sortBy(total_data, function(d) {
return d.date;
});
return _.sortBy(total_data, d => d.date);
},
pick_field(log, field) {
var total_data;
total_data = [];
_.each(log, function(d) {
return total_data.push(_.pick(d, [field, 'date']));
});
_.each(log, d => total_data.push(_.pick(d, [field, 'date'])));
return total_data;
},
get_author_data(parsed_log, field, date_range) {
......@@ -107,9 +103,7 @@ export default {
};
})(this),
);
return _.sortBy(author_data, function(d) {
return d[field];
}).reverse();
return _.sortBy(author_data, d => d[field]).reverse();
},
parse_log_entry(log_entry, field, date_range) {
var parsed_entry;
......
/* eslint-disable func-names, prefer-arrow-callback, consistent-return, */
/* eslint-disable consistent-return */
import $ from 'jquery';
import { __ } from './locale';
......@@ -40,12 +40,9 @@ export default class SingleFileDiff {
this.$toggleIcon.addClass('fa-caret-down');
}
$('.js-file-title, .click-to-expand', this.file).on(
'click',
function(e) {
this.toggleDiff($(e.target));
}.bind(this),
);
$('.js-file-title, .click-to-expand', this.file).on('click', e => {
this.toggleDiff($(e.target));
});
}
toggleDiff($target, cb) {
......
/* eslint-disable func-names, consistent-return, no-var, one-var, no-else-return, prefer-arrow-callback, class-methods-use-this */
/* eslint-disable func-names, consistent-return, no-var, one-var, no-else-return, class-methods-use-this */
import $ from 'jquery';
import { visitUrl } from './lib/utils/url_utility';
......@@ -29,7 +29,7 @@ export default class TreeView {
var li, liSelected;
li = $('tr.tree-item');
liSelected = null;
return $('body').keydown(function(e) {
return $('body').keydown(e => {
var next, path;
if ($('input:focus').length > 0 && (e.which === 38 || e.which === 40)) {
return false;
......
/* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, prefer-arrow-callback, consistent-return, no-shadow, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
/* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, consistent-return, no-shadow, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
/* global Issuable */
/* global emitSidebarEvent */
......@@ -250,16 +250,12 @@ function UsersSelect(currentUser, els, options = {}) {
return $dropdown.glDropdown({
showMenuAbove,
data(term, callback) {
return _this.users(
term,
options,
function(users) {
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
const glDropdown = this.instance || this.options.instance;
glDropdown.options.processData(term, users, callback);
}.bind(this),
);
return _this.users(term, options, users => {
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
const glDropdown = this.instance || this.options.instance;
glDropdown.options.processData(term, users, callback);
});
},
processData(term, data, callback) {
let users = data;
......@@ -606,7 +602,7 @@ function UsersSelect(currentUser, els, options = {}) {
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query(query) {
return _this.users(query.term, options, function(users) {
return _this.users(query.term, options, users => {
var anyUser, data, emailUser, index, len, name, nullUser, obj, ref;
data = {
results: users,
......
/* eslint-disable func-names, prefer-arrow-callback, consistent-return, camelcase, class-methods-use-this */
/* eslint-disable func-names, consistent-return, camelcase, class-methods-use-this */
// Zen Mode (full screen) textarea
//
......@@ -39,11 +39,11 @@ export default class ZenMode {
constructor() {
this.active_backdrop = null;
this.active_textarea = null;
$(document).on('click', '.js-zen-enter', function(e) {
$(document).on('click', '.js-zen-enter', e => {
e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:enter');
});
$(document).on('click', '.js-zen-leave', function(e) {
$(document).on('click', '.js-zen-leave', e => {
e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:leave');
});
......@@ -67,7 +67,7 @@ export default class ZenMode {
};
})(this),
);
$(document).on('keydown', function(e) {
$(document).on('keydown', e => {
// Esc
if (e.keyCode === 27) {
e.preventDefault();
......
......@@ -13,6 +13,7 @@
.log-line {
padding: 1px $gl-padding 1px $job-log-line-padding;
min-height: $gl-line-height-20;
}
.line-number {
......
---
title: Log Sidekiq exceptions properly in JSON format
merge_request: 17412
author:
type: fixed
......@@ -50,6 +50,10 @@ Sidekiq.configure_server do |config|
if enable_json_logs
Sidekiq.logger.formatter = Gitlab::SidekiqLogging::JSONFormatter.new
config.options[:job_logger] = Gitlab::SidekiqLogging::StructuredLogger
# Remove the default-provided handler
config.error_handlers.reject! { |handler| handler.is_a?(Sidekiq::ExceptionHandler::Logger) }
config.error_handlers << Gitlab::SidekiqLogging::ExceptionHandler.new
end
config.client_middleware do |chain|
......
# frozen_string_literal: true
module Gitlab
module SidekiqLogging
class ExceptionHandler
def call(job_exception, context)
data = {
error_class: job_exception.class.name,
error_message: job_exception.message
}
if context.is_a?(Hash)
data.merge!(context)
# correlation_id, jid, and class are available inside the job
# Hash, so promote these arguments to the root tree so that
# can be searched alongside other Sidekiq log messages.
job_data = data.delete(:job)
data.merge!(job_data) if job_data.present?
end
data[:error_backtrace] = Gitlab::Profiler.clean_backtrace(job_exception.backtrace) if job_exception.backtrace.present?
Sidekiq.logger.warn(data)
end
end
end
end
......@@ -58,8 +58,7 @@ module Gitlab
payload['message'] = "#{message}: fail: #{payload['duration']} sec"
payload['job_status'] = 'fail'
payload['error_message'] = job_exception.message
payload['error'] = job_exception.class
payload['error_backtrace'] = backtrace_cleaner.clean(job_exception.backtrace)
payload['error_class'] = job_exception.class.name
else
payload['message'] = "#{message}: done: #{payload['duration']} sec"
payload['job_status'] = 'done'
......@@ -127,10 +126,6 @@ module Gitlab
Gitlab::Metrics::System.monotonic_time
end
def backtrace_cleaner
@backtrace_cleaner ||= ActiveSupport::BacktraceCleaner.new
end
def format_time(timestamp)
return timestamp if timestamp.is_a?(String)
......
......@@ -33,7 +33,7 @@ module Gitlab
def snowplow
@snowplow ||= SnowplowTracker::Tracker.new(
SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname),
SnowplowTracker::AsyncEmitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname, protocol: 'https'),
SnowplowTracker::Subject.new,
SNOWPLOW_NAMESPACE,
Gitlab::CurrentSettings.snowplow_site_id
......
......@@ -3367,6 +3367,9 @@ msgstr ""
msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Could not load regions from your AWS account"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr ""
......@@ -3532,6 +3535,9 @@ msgstr ""
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
msgstr ""
msgid "ClusterIntegration|Learn more about %{startLink}Regions%{endLink}."
msgstr ""
msgid "ClusterIntegration|Learn more about Kubernetes"
msgstr ""
......@@ -3547,6 +3553,9 @@ msgstr ""
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
msgid "ClusterIntegration|Loading Regions"
msgstr ""
msgid "ClusterIntegration|Machine type"
msgstr ""
......@@ -3568,6 +3577,9 @@ msgstr ""
msgid "ClusterIntegration|No projects matched your search"
msgstr ""
msgid "ClusterIntegration|No region found"
msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
......@@ -3607,6 +3619,9 @@ msgstr ""
msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
msgstr ""
msgid "ClusterIntegration|Region"
msgstr ""
msgid "ClusterIntegration|Remove Kubernetes cluster integration"
msgstr ""
......@@ -3640,6 +3655,9 @@ msgstr ""
msgid "ClusterIntegration|Search projects"
msgstr ""
msgid "ClusterIntegration|Search regions"
msgstr ""
msgid "ClusterIntegration|Search zones"
msgstr ""
......@@ -3790,6 +3808,9 @@ msgstr ""
msgid "ClusterIntegration|sign up"
msgstr ""
msgid "ClusterIntergation|Select a region"
msgstr ""
msgid "ClusterIntergation|Select role name"
msgstr ""
......@@ -15707,9 +15728,6 @@ msgstr ""
msgid "There is no data available. Please change your selection."
msgstr ""
msgid "There is no data for the selected metric. Please change your selection."
msgstr ""
msgid "There was a problem communicating with your device."
msgstr ""
......@@ -17214,7 +17232,7 @@ msgstr ""
msgid "UserOnboardingTour|That's it for the Repository. Let's take a look at the %{emphasisStart}Issues%{emphasisEnd}."
msgstr ""
msgid "UserOnboardingTour|The structure of this page is very similar to the onfe of issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Below, alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
msgid "UserOnboardingTour|The structure of this page is very similar to the one of issues. Status, description, discussion and the sidebar are all here.%{lineBreak}%{lineBreak}But take a look below the description and you'll notice that there's more information about the merge request, the CI/CD pipeline and the options for approving it.%{lineBreak}%{lineBreak}Below, alongside the discussion you can also see more information about commits in this merge request, the status of pipelines and review all changes that were made."
msgstr ""
msgid "UserOnboardingTour|There's a lot of information here but don't worry, we'll go through it.%{lineBreak}%{lineBreak}On the top you can see the status of the issue and when it was opened and by whom. Directly below it is the issue description and below that are other %{emphasisStart}related issues%{emphasisEnd} and %{emphasisStart}merge requests%{emphasisEnd} (if any). Then below that is the %{emphasisStart}discussion%{emphasisEnd}, that's where most of the communication happens.%{lineBreak}%{lineBreak}On the right, there's a sidebar where you can view/change the %{emphasisStart}assignee, milestone, due date, labels, weight%{emphasisEnd}, etc."
......@@ -17229,10 +17247,10 @@ msgstr ""
msgid "UserOnboardingTour|This is an overview of all merge requests in this project. Similarly to the issues overview it can be filtered down by things like labels, milestones, authors, assignees, etc."
msgstr ""
msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore around and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure, you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down, below the file strcture, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
msgid "UserOnboardingTour|This is the repository for the %{emphasisStart}%{projectName}%{emphasisEnd} project. All our code is stored here. Feel free to explore around and take a closer look at folders and files.%{lineBreak}%{lineBreak}Above the file structure, you can see the latest commit, who the author is and the status of the CI/CD pipeline.%{lineBreak}%{lineBreak}If you scroll down, below the file structure, you'll find the Readme of this project. This is defined in the README.md file at the root of the repository."
msgstr ""
msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we uese to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaboreate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
msgid "UserOnboardingTour|Welcome to the project overview of the %{emphasisStart}%{projectName}%{emphasisEnd} project. This is the project that we use to work on GitLab. At first, a project seems like a simple repository, but at GitLab, a project is so much more.%{lineBreak}%{lineBreak}You can create projects for hosting your codebase, use it as an issue tracker, collaboreate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
msgstr ""
msgid "UserProfile|Activity"
......@@ -17960,7 +17978,7 @@ msgstr ""
msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
msgstr ""
msgid "You can filter by \"days to merge\" by clicking on the columns in the chart."
msgid "You can filter by 'days to merge' by clicking on the columns in the chart."
msgstr ""
msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import Vue from 'vue';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
import RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('EksClusterConfigurationForm', () => {
let store;
let actions;
let state;
let vm;
beforeEach(() => {
actions = {
fetchRegions: jest.fn(),
setRegion: jest.fn(),
};
state = {
regions: [{ name: 'region 1' }],
isLoadingRegions: false,
loadingRegionsError: { message: '' },
};
store = new Vuex.Store({
state,
actions,
});
});
beforeEach(() => {
vm = shallowMount(EksClusterConfigurationForm, {
localVue,
store,
});
});
afterEach(() => {
vm.destroy();
});
const findRegionDropdown = () => vm.find(RegionDropdown);
describe('when mounted', () => {
it('fetches available regions', () => {
expect(actions.fetchRegions).toHaveBeenCalled();
});
});
it('sets isLoadingRegions to RegionDropdown loading property', () => {
state.isLoadingRegions = true;
return Vue.nextTick().then(() => {
expect(findRegionDropdown().props('loading')).toEqual(state.isLoadingRegions);
});
});
it('sets regions to RegionDropdown regions property', () => {
expect(findRegionDropdown().props('regions')).toEqual(state.regions);
});
it('sets loadingRegionsError to RegionDropdown error property', () => {
expect(findRegionDropdown().props('error')).toEqual(state.loadingRegionsError);
});
it('dispatches setRegion action when region is selected', () => {
const region = { region: 'us-west-2' };
findRegionDropdown().vm.$emit('input', region);
expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region }, undefined);
});
});
import { shallowMount } from '@vue/test-utils';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
import RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue';
describe('RegionDropdown', () => {
let vm;
const getClusterFormDropdown = () => vm.find(ClusterFormDropdown);
beforeEach(() => {
vm = shallowMount(RegionDropdown);
});
afterEach(() => vm.destroy());
it('renders a cluster-form-dropdown', () => {
expect(getClusterFormDropdown().exists()).toBe(true);
});
it('sets regions to cluster-form-dropdown items property', () => {
const regions = [{ name: 'basic' }];
vm.setProps({ regions });
expect(getClusterFormDropdown().props('items')).toEqual(regions);
});
it('sets a loading text', () => {
expect(getClusterFormDropdown().props('loadingText')).toEqual('Loading Regions');
});
it('sets a placeholder', () => {
expect(getClusterFormDropdown().props('placeholder')).toEqual('Select a region');
});
it('sets an empty results text', () => {
expect(getClusterFormDropdown().props('emptyText')).toEqual('No region found');
});
it('sets a search field placeholder', () => {
expect(getClusterFormDropdown().props('searchFieldPlaceholder')).toEqual('Search regions');
});
it('sets hasErrors property', () => {
vm.setProps({ error: {} });
expect(getClusterFormDropdown().props('hasErrors')).toEqual(true);
});
it('sets an error message', () => {
expect(getClusterFormDropdown().props('errorMessage')).toEqual(
'Could not load regions from your AWS account',
);
});
});
import testAction from 'helpers/vuex_action_helper';
import * as awsServicesFacade from '~/create_cluster/eks_cluster/services/aws_services_facade';
import createState from '~/create_cluster/eks_cluster/store/state';
import * as types from '~/create_cluster/eks_cluster/store/mutation_types';
import * as actions from '~/create_cluster/eks_cluster/store/actions';
describe('EKS Cluster Store Actions', () => {
const regions = [{ name: 'region 1' }];
describe('fetchRegions', () => {
describe('on success', () => {
beforeEach(() => {
jest.spyOn(awsServicesFacade, 'fetchRegions').mockResolvedValueOnce(regions);
});
it('dispatches success with received regions', () =>
testAction(
actions.fetchRegions,
null,
createState(),
[],
[
{ type: 'requestRegions' },
{
type: 'receiveRegionsSuccess',
payload: { regions },
},
],
));
});
describe('on failure', () => {
const error = new Error('Could not fetch regions');
beforeEach(() => {
jest.spyOn(awsServicesFacade, 'fetchRegions').mockRejectedValueOnce(error);
});
it('dispatches success with received regions', () =>
testAction(
actions.fetchRegions,
null,
createState(),
[],
[
{ type: 'requestRegions' },
{
type: 'receiveRegionsError',
payload: { error },
},
],
));
});
});
describe('requestRegions', () => {
it(`commits ${types.REQUEST_REGIONS} mutation`, () =>
testAction(actions.requestRegions, null, createState(), [{ type: types.REQUEST_REGIONS }]));
});
describe('receiveRegionsSuccess', () => {
it(`commits ${types.RECEIVE_REGIONS_SUCCESS} mutation`, () =>
testAction(actions.receiveRegionsSuccess, { regions }, createState(), [
{
type: types.RECEIVE_REGIONS_SUCCESS,
payload: {
regions,
},
},
]));
});
describe('receiveRegionsError', () => {
it(`commits ${types.RECEIVE_REGIONS_ERROR} mutation`, () => {
const error = new Error('Error fetching regions');
testAction(actions.receiveRegionsError, { error }, createState(), [
{
type: types.RECEIVE_REGIONS_ERROR,
payload: {
error,
},
},
]);
});
});
describe('setRegion', () => {
it(`commits ${types.SET_REGION} mutation`, () => {
const region = { name: 'west-1' };
testAction(actions.setRegion, { region }, createState(), [
{ type: types.SET_REGION, payload: { region } },
]);
});
});
});
import {
REQUEST_REGIONS,
RECEIVE_REGIONS_ERROR,
RECEIVE_REGIONS_SUCCESS,
SET_REGION,
} from '~/create_cluster/eks_cluster/store/mutation_types';
import createState from '~/create_cluster/eks_cluster/store/state';
import mutations from '~/create_cluster/eks_cluster/store/mutations';
describe('Create EKS cluster store mutations', () => {
let state;
let emptyPayload;
let regions;
let region;
let error;
beforeEach(() => {
emptyPayload = {};
region = { name: 'regions-1' };
regions = [region];
error = new Error('could not load error');
state = createState();
});
it.each`
mutation | mutatedProperty | payload | expectedValue | expectedValueDescription
${REQUEST_REGIONS} | ${'isLoadingRegions'} | ${emptyPayload} | ${true} | ${true}
${REQUEST_REGIONS} | ${'loadingRegionsError'} | ${emptyPayload} | ${null} | ${null}
${RECEIVE_REGIONS_SUCCESS} | ${'isLoadingRegions'} | ${{ regions }} | ${false} | ${false}
${RECEIVE_REGIONS_SUCCESS} | ${'regions'} | ${{ regions }} | ${regions} | ${'regions payload'}
${RECEIVE_REGIONS_ERROR} | ${'isLoadingRegions'} | ${{ error }} | ${false} | ${false}
${RECEIVE_REGIONS_ERROR} | ${'error'} | ${{ error }} | ${error} | ${'received error object'}
${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'}
`(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => {
const { mutation, mutatedProperty, payload, expectedValue } = data;
mutations[mutation](state, payload);
expect(state[mutatedProperty]).toBe(expectedValue);
});
});
import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import Vuex from 'vuex';
import StageList from '~/ide/components/jobs/list.vue';
import Stage from '~/ide/components/jobs/stage.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const storeActions = {
fetchJobs: jest.fn(),
toggleStageCollapsed: jest.fn(),
setDetailJob: jest.fn(),
};
const store = new Vuex.Store({
modules: {
pipelines: {
namespaced: true,
actions: storeActions,
},
},
});
describe('IDE stages list', () => {
let wrapper;
const defaultProps = {
stages: [],
loading: false,
};
const stages = ['build', 'test', 'deploy'].map((name, id) => ({
id,
name,
jobs: [],
status: { icon: 'status_success' },
}));
const createComponent = props => {
wrapper = shallowMount(StageList, {
propsData: {
...defaultProps,
...props,
},
localVue,
store,
sync: false,
});
};
afterEach(() => {
Object.values(storeActions).forEach(actionMock => actionMock.mockClear());
});
afterAll(() => {
wrapper.destroy();
wrapper = null;
});
it('renders loading icon when no stages & loading', () => {
createComponent({ loading: true, stages: [] });
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('renders stages components for each stage', () => {
createComponent({ stages });
expect(wrapper.findAll(Stage).length).toBe(stages.length);
});
it('triggers fetchJobs action when stage emits fetch event', () => {
createComponent({ stages });
wrapper.find(Stage).vm.$emit('fetch');
expect(storeActions.fetchJobs).toHaveBeenCalled();
});
it('triggers toggleStageCollapsed action when stage emits toggleCollapsed event', () => {
createComponent({ stages });
wrapper.find(Stage).vm.$emit('toggleCollapsed');
expect(storeActions.toggleStageCollapsed).toHaveBeenCalled();
});
it('triggers setDetailJob action when stage emits clickViewLog event', () => {
createComponent({ stages });
wrapper.find(Stage).vm.$emit('clickViewLog');
expect(storeActions.setDetailJob).toHaveBeenCalled();
});
describe('integration tests', () => {
const findCardHeader = () => wrapper.find('.card-header');
beforeEach(() => {
wrapper = mount(StageList, {
propsData: { ...defaultProps, stages },
store,
sync: false,
localVue,
});
});
it('calls toggleStageCollapsed when clicking stage header', () => {
findCardHeader().trigger('click');
expect(storeActions.toggleStageCollapsed).toHaveBeenCalledWith(
expect.any(Object),
0,
undefined,
);
});
it('calls fetchJobs when stage is mounted', () => {
expect(storeActions.fetchJobs.mock.calls.map(([, stage]) => stage)).toEqual(stages);
});
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IDE pipelines list when loaded renders empty state when no latestPipeline 1`] = `
<div
class="ide-pipeline"
>
<!---->
<emptystate-stub
cansetci="true"
emptystatesvgpath="http://test.host"
helppagepath="http://test.host"
/>
</div>
`;
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import List from '~/ide/components/pipelines/list.vue';
import JobsList from '~/ide/components/jobs/list.vue';
import Tab from '~/vue_shared/components/tabs/tab.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import { pipelines } from '../../../../javascripts/ide/mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('IDE pipelines list', () => {
let wrapper;
const defaultState = {
links: { ciHelpPagePath: TEST_HOST },
pipelinesEmptyStateSvgPath: TEST_HOST,
pipelines: {
stages: [],
failedStages: [],
isLoadingJobs: false,
},
};
const fetchLatestPipelineMock = jest.fn();
const failedStagesGetterMock = jest.fn().mockReturnValue([]);
const createComponent = (state = {}) => {
const { pipelines: pipelinesState, ...restOfState } = state;
const { defaultPipelines, ...defaultRestOfState } = defaultState;
const fakeStore = new Vuex.Store({
getters: { currentProject: () => ({ web_url: 'some/url ' }) },
state: {
...defaultRestOfState,
...restOfState,
},
modules: {
pipelines: {
namespaced: true,
state: {
...defaultPipelines,
...pipelinesState,
},
actions: {
fetchLatestPipeline: fetchLatestPipelineMock,
},
getters: {
jobsCount: () => 1,
failedJobsCount: () => 1,
failedStages: failedStagesGetterMock,
pipelineFailed: () => false,
},
methods: {
fetchLatestPipeline: jest.fn(),
},
},
},
});
wrapper = shallowMount(List, {
localVue,
store: fakeStore,
sync: false,
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('fetches latest pipeline', () => {
createComponent();
expect(fetchLatestPipelineMock).toHaveBeenCalled();
});
describe('when loading', () => {
let defaultPipelinesLoadingState;
beforeAll(() => {
defaultPipelinesLoadingState = {
...defaultState.pipelines,
isLoadingPipeline: true,
};
});
it('does not render when pipeline has loaded before', () => {
createComponent({
pipelines: {
...defaultPipelinesLoadingState,
hasLoadedPipeline: true,
},
});
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
it('renders loading state', () => {
createComponent({
pipelines: {
...defaultPipelinesLoadingState,
hasLoadedPipeline: false,
},
});
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
});
describe('when loaded', () => {
let defaultPipelinesLoadedState;
beforeAll(() => {
defaultPipelinesLoadedState = {
...defaultState.pipelines,
isLoadingPipeline: false,
hasLoadedPipeline: true,
};
});
it('renders empty state when no latestPipeline', () => {
createComponent({ pipelines: { ...defaultPipelinesLoadedState, latestPipeline: null } });
expect(wrapper.element).toMatchSnapshot();
});
describe('with latest pipeline loaded', () => {
let withLatestPipelineState;
beforeAll(() => {
withLatestPipelineState = {
...defaultPipelinesLoadedState,
latestPipeline: pipelines[0],
};
});
it('renders ci icon', () => {
createComponent({ pipelines: withLatestPipelineState });
expect(wrapper.find(CiIcon).exists()).toBe(true);
});
it('renders pipeline data', () => {
createComponent({ pipelines: withLatestPipelineState });
expect(wrapper.text()).toContain('#1');
});
it('renders list of jobs', () => {
const stages = [];
const isLoadingJobs = true;
createComponent({ pipelines: { ...withLatestPipelineState, stages, isLoadingJobs } });
const jobProps = wrapper
.findAll(Tab)
.at(0)
.find(JobsList)
.props();
expect(jobProps.stages).toBe(stages);
expect(jobProps.loading).toBe(isLoadingJobs);
});
it('renders list of failed jobs', () => {
const failedStages = [];
failedStagesGetterMock.mockReset().mockReturnValue(failedStages);
const isLoadingJobs = true;
createComponent({ pipelines: { ...withLatestPipelineState, isLoadingJobs } });
const jobProps = wrapper
.findAll(Tab)
.at(1)
.find(JobsList)
.props();
expect(jobProps.stages).toBe(failedStages);
expect(jobProps.loading).toBe(isLoadingJobs);
});
describe('with YAML error', () => {
it('renders YAML error', () => {
const yamlError = 'test yaml error';
createComponent({
pipelines: {
...defaultPipelinesLoadedState,
latestPipeline: { ...pipelines[0], yamlError },
},
});
expect(wrapper.text()).toContain('Found errors in your .gitlab-ci.yml:');
expect(wrapper.text()).toContain(yamlError);
});
});
});
});
});
import { mount } from '@vue/test-utils';
import CollpasibleSection from '~/jobs/components/log/collapsible_section.vue';
import { nestedSectionOpened, nestedSectionClosed } from './mock_data';
import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data';
describe('Job Log Collapsible Section', () => {
let wrapper;
......@@ -8,6 +8,7 @@ describe('Job Log Collapsible Section', () => {
const traceEndpoint = 'jobs/335';
const findCollapsibleLine = () => wrapper.find('.collapsible-line');
const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg');
const createComponent = (props = {}) => {
wrapper = mount(CollpasibleSection, {
......@@ -22,10 +23,10 @@ describe('Job Log Collapsible Section', () => {
wrapper.destroy();
});
describe('with closed nested section', () => {
describe('with closed section', () => {
beforeEach(() => {
createComponent({
section: nestedSectionClosed,
section: collapsibleSectionClosed,
traceEndpoint,
});
});
......@@ -33,24 +34,36 @@ describe('Job Log Collapsible Section', () => {
it('renders clickable header line', () => {
expect(findCollapsibleLine().attributes('role')).toBe('button');
});
it('renders an icon with the closed state', () => {
expect(findCollapsibleLineSvg().classes()).toContain('ic-angle-right');
});
});
describe('with opened nested section', () => {
describe('with opened section', () => {
beforeEach(() => {
createComponent({
section: nestedSectionOpened,
section: collapsibleSectionOpened,
traceEndpoint,
});
});
it('renders all sections opened', () => {
expect(wrapper.findAll('.collapsible-line').length).toBe(2);
it('renders clickable header line', () => {
expect(findCollapsibleLine().attributes('role')).toBe('button');
});
it('renders an icon with the open state', () => {
expect(findCollapsibleLineSvg().classes()).toContain('ic-angle-down');
});
it('renders collapsible lines content', () => {
expect(wrapper.findAll('.js-line').length).toEqual(collapsibleSectionOpened.lines.length);
});
});
it('emits onClickCollapsibleLine on click', () => {
createComponent({
section: nestedSectionOpened,
section: collapsibleSectionOpened,
traceEndpoint,
});
......
......@@ -14,13 +14,13 @@ export const jobLog = [
text: 'Using Docker executor with image dev.gitlab.org3',
},
],
sections: ['prepare-executor'],
section: 'prepare-executor',
section_header: true,
},
{
offset: 1003,
content: [{ text: 'Starting service postgres:9.6.14 ...', style: 'text-green' }],
sections: ['prepare-executor'],
section: 'prepare-executor',
},
];
......@@ -37,23 +37,23 @@ export const utilsMockData = [
'Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33',
},
],
sections: ['prepare-executor'],
section: 'prepare-executor',
section_header: true,
},
{
offset: 1003,
content: [{ text: 'Starting service postgres:9.6.14 ...' }],
sections: ['prepare-executor'],
section: 'prepare-executor',
},
{
offset: 1004,
content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }],
sections: ['prepare-executor'],
section: 'prepare-executor',
},
{
offset: 1005,
content: [],
sections: ['prepare-executor'],
section: 'prepare-executor',
section_duration: '10:00',
},
];
......@@ -100,7 +100,7 @@ export const headerTrace = [
text: 'log line',
},
],
sections: ['section'],
section: 'section',
},
];
......@@ -113,7 +113,7 @@ export const headerTraceIncremental = [
text: 'updated log line',
},
],
sections: ['section'],
section: 'section',
},
];
......@@ -126,7 +126,7 @@ export const collapsibleTrace = [
text: 'log line',
},
],
sections: ['section'],
section: 'section',
},
{
offset: 2,
......@@ -135,7 +135,7 @@ export const collapsibleTrace = [
text: 'log line',
},
],
sections: ['section'],
section: 'section',
},
];
......@@ -147,76 +147,48 @@ export const collapsibleTraceIncremental = [
text: 'updated log line',
},
],
sections: ['section'],
section: 'section',
},
];
export const nestedSectionClosed = {
export const collapsibleSectionClosed = {
offset: 5,
section_header: true,
isHeader: true,
isClosed: true,
line: {
content: [{ text: 'foo' }],
sections: ['prepare-script'],
section: 'prepare-script',
lineNumber: 1,
},
section_duration: '00:03',
lines: [
{
section_header: true,
section_duration: '00:02',
isHeader: true,
isClosed: true,
line: {
offset: 52,
content: [{ text: 'bar' }],
sections: ['prepare-script', 'prepare-script-nested'],
lineNumber: 2,
},
lines: [
{
offset: 80,
content: [{ text: 'this is a collapsible nested section' }],
sections: ['prepare-script', 'prepare-script-nested'],
lineNumber: 3,
},
],
offset: 80,
content: [{ text: 'this is a collapsible nested section' }],
section: 'prepare-script',
lineNumber: 3,
},
],
};
export const nestedSectionOpened = {
export const collapsibleSectionOpened = {
offset: 5,
section_header: true,
isHeader: true,
isClosed: false,
line: {
content: [{ text: 'foo' }],
sections: ['prepare-script'],
section: 'prepare-script',
lineNumber: 1,
},
section_duration: '00:03',
lines: [
{
section_header: true,
section_duration: '00:02',
isHeader: true,
isClosed: false,
line: {
offset: 52,
content: [{ text: 'bar' }],
sections: ['prepare-script', 'prepare-script-nested'],
lineNumber: 2,
},
lines: [
{
offset: 80,
content: [{ text: 'this is a collapsible nested section' }],
sections: ['prepare-script', 'prepare-script-nested'],
lineNumber: 3,
},
],
offset: 80,
content: [{ text: 'this is a collapsible nested section' }],
section: 'prepare-script',
lineNumber: 3,
},
],
};
import { logLinesParser, updateIncrementalTrace } from '~/jobs/store/utils';
import {
logLinesParser,
updateIncrementalTrace,
parseHeaderLine,
parseLine,
} from '~/jobs/store/utils';
import {
utilsMockData,
originalTrace,
......@@ -11,6 +16,32 @@ import {
} from '../components/log/mock_data';
describe('Jobs Store Utils', () => {
describe('parseHeaderLine', () => {
it('returns a new object with the header keys and the provided line parsed', () => {
const headerLine = { content: [{ text: 'foo' }] };
const parsedHeaderLine = parseHeaderLine(headerLine, 2);
expect(parsedHeaderLine).toEqual({
isClosed: true,
isHeader: true,
line: {
...headerLine,
lineNumber: 2,
},
lines: [],
});
});
});
describe('parseLine', () => {
it('returns a new object with the lineNumber key added to the provided line object', () => {
const line = { content: [{ text: 'foo' }] };
const parsed = parseLine(line, 1);
expect(parsed.content).toEqual(line.content);
expect(parsed.lineNumber).toEqual(1);
});
});
describe('logLinesParser', () => {
let result;
......@@ -117,7 +148,7 @@ describe('Jobs Store Utils', () => {
text: 'updated log line',
},
],
sections: ['section'],
section: 'section',
lineNumber: 0,
},
lines: [],
......@@ -147,7 +178,7 @@ describe('Jobs Store Utils', () => {
text: 'log line',
},
],
sections: ['section'],
section: 'section',
lineNumber: 0,
},
lines: [
......@@ -158,7 +189,7 @@ describe('Jobs Store Utils', () => {
text: 'updated log line',
},
],
sections: ['section'],
section: 'section',
lineNumber: 1,
},
],
......
import Vue from 'vue';
import StageList from '~/ide/components/jobs/list.vue';
import { createStore } from '~/ide/stores';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { stages, jobs } from '../../mock_data';
describe('IDE stages list', () => {
const Component = Vue.extend(StageList);
let vm;
beforeEach(() => {
const store = createStore();
vm = createComponentWithStore(Component, store, {
stages: stages.map((mappedState, i) => ({
...mappedState,
id: i,
dropdownPath: mappedState.dropdown_path,
jobs: [...jobs],
isLoading: false,
isCollapsed: false,
})),
loading: false,
});
spyOn(vm, 'fetchJobs');
spyOn(vm, 'toggleStageCollapsed');
vm.$mount();
});
afterEach(() => {
vm.$destroy();
});
it('renders list of stages', () => {
expect(vm.$el.querySelectorAll('.card').length).toBe(2);
});
it('renders loading icon when no stages & is loading', done => {
vm.stages = [];
vm.loading = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
done();
});
});
it('calls toggleStageCollapsed when clicking stage header', done => {
vm.$el.querySelector('.card-header').click();
vm.$nextTick(() => {
expect(vm.toggleStageCollapsed).toHaveBeenCalledWith(0);
done();
});
});
it('calls fetchJobs when stage is mounted', () => {
expect(vm.fetchJobs.calls.count()).toBe(stages.length);
expect(vm.fetchJobs.calls.argsFor(0)).toEqual([vm.stages[0]]);
expect(vm.fetchJobs.calls.argsFor(1)).toEqual([vm.stages[1]]);
});
});
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { createStore } from '~/ide/stores';
import List from '~/ide/components/pipelines/list.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { pipelines, projectData, stages, jobs } from '../../mock_data';
describe('IDE pipelines list', () => {
const Component = Vue.extend(List);
let vm;
let mock;
const findLoadingState = () => vm.$el.querySelector('.loading-container');
beforeEach(done => {
const store = createStore();
mock = new MockAdapter(axios);
store.state.currentProjectId = 'abc/def';
store.state.currentBranchId = 'master';
store.state.projects['abc/def'] = {
...projectData,
path_with_namespace: 'abc/def',
branches: {
master: { commit: { id: '123' } },
},
};
store.state.links = { ciHelpPagePath: gl.TEST_HOST };
store.state.pipelinesEmptyStateSvgPath = gl.TEST_HOST;
store.state.pipelines.stages = stages.map((mappedState, i) => ({
...mappedState,
id: i,
dropdownPath: mappedState.dropdown_path,
jobs: [...jobs],
isLoading: false,
isCollapsed: false,
}));
mock
.onGet('/abc/def/commit/123/pipelines')
.replyOnce(200, { pipelines: [...pipelines] }, { 'poll-interval': '-1' });
vm = createComponentWithStore(Component, store).$mount();
setTimeout(done);
});
afterEach(done => {
vm.$destroy();
mock.restore();
vm.$store
.dispatch('pipelines/stopPipelinePolling')
.then(() => vm.$store.dispatch('pipelines/clearEtagPoll'))
.then(done)
.catch(done.fail);
});
it('renders pipeline data', () => {
expect(vm.$el.textContent).toContain('#1');
});
it('renders CI icon', () => {
expect(vm.$el.querySelector('.ci-status-icon-failed')).not.toBe(null);
});
it('renders list of jobs', () => {
expect(vm.$el.querySelectorAll('.tab-pane:first-child .ide-job-item').length).toBe(
jobs.length * stages.length,
);
});
it('renders list of failed jobs on failed jobs tab', done => {
vm.$el.querySelectorAll('.tab-links a')[1].click();
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('.tab-pane.active .ide-job-item').length).toBe(2);
done();
});
});
describe('YAML error', () => {
it('renders YAML error', done => {
vm.$store.state.pipelines.latestPipeline.yamlError = 'test yaml error';
vm.$nextTick(() => {
expect(vm.$el.textContent).toContain('Found errors in your .gitlab-ci.yml:');
expect(vm.$el.textContent).toContain('test yaml error');
done();
});
});
});
describe('empty state', () => {
it('renders pipelines empty state', done => {
vm.$store.state.pipelines.latestPipeline = null;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.empty-state')).not.toBe(null);
done();
});
});
});
describe('loading state', () => {
beforeEach(() => {
vm.$store.state.pipelines.isLoadingPipeline = true;
});
it('does not render when pipeline has loaded before', done => {
vm.$store.state.pipelines.hasLoadedPipeline = true;
vm.$nextTick()
.then(() => {
expect(findLoadingState()).toBe(null);
})
.then(done)
.catch(done.fail);
});
it('renders loading state when there is no latest pipeline', done => {
vm.$store.state.pipelines.hasLoadedPipeline = false;
vm.$nextTick()
.then(() => {
expect(findLoadingState()).not.toBe(null);
})
.then(done)
.catch(done.fail);
});
});
});
import { TEST_HOST } from '../test_constants';
export const projectData = {
id: 1,
name: 'abcproject',
......@@ -50,7 +52,7 @@ export const pipelines = [
export const stages = [
{
dropdown_path: `${gl.TEST_HOST}/testing`,
dropdown_path: `${TEST_HOST}/testing`,
name: 'build',
status: {
icon: 'status_failed',
......@@ -163,7 +165,7 @@ export const mergeRequests = [
iid: 1,
title: 'Test merge request',
project_id: 1,
web_url: `${gl.TEST_HOST}/namespace/project-path/merge_requests/1`,
web_url: `${TEST_HOST}/namespace/project-path/merge_requests/1`,
},
];
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqLogging::ExceptionHandler do
describe '#call' do
let(:job) do
{
"class" => "TestWorker",
"args" => [1234, 'hello'],
"retry" => false,
"queue" => "cronjob:test_queue",
"queue_namespace" => "cronjob",
"jid" => "da883554ee4fe414012f5f42",
"correlation_id" => 'cid'
}
end
let(:exception_message) { 'An error was thrown' }
let(:backtrace) { caller }
let(:exception) { RuntimeError.new(exception_message) }
let(:logger) { double }
before do
allow(Sidekiq).to receive(:logger).and_return(logger)
allow(exception).to receive(:backtrace).and_return(backtrace)
end
subject { described_class.new.call(exception, { context: 'Test', job: job }) }
it 'logs job data into root tree' do
expected_data = job.merge(
error_class: 'RuntimeError',
error_message: exception_message,
context: 'Test',
error_backtrace: Gitlab::Profiler.clean_backtrace(backtrace)
)
expect(logger).to receive(:warn).with(expected_data)
subject
end
end
end
......@@ -47,7 +47,7 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end_payload.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
'job_status' => 'fail',
'error' => ArgumentError,
'error_class' => 'ArgumentError',
'error_message' => 'some exception'
)
end
......@@ -86,7 +86,6 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
it 'logs an exception in job' do
Timecop.freeze(timestamp) do
expect(logger).to receive(:info).with(start_payload)
# This excludes the exception_backtrace
expect(logger).to receive(:warn).with(hash_including(exception_payload))
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
......
......@@ -49,8 +49,8 @@ describe Gitlab::Tracking do
it 'can track events' do
tracker = double
expect(SnowplowTracker::Emitter).to receive(:new).with(
'gitfoo.com'
expect(SnowplowTracker::AsyncEmitter).to receive(:new).with(
'gitfoo.com', { protocol: 'https' }
).and_return('_emitter_')
expect(SnowplowTracker::Tracker).to receive(:new).with(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment