Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-09-24 12:06:04 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-09-24 12:06:04 +0300
commitbc89882970d6a14b1f72eb9c715fae90b26d066c (patch)
treef5cb59d5130d7585980eb39437071e07ebc12f87
parent4a45a787703cb78c6101750cfbdc9f656b934b42 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/behaviors/preview_markdown.js46
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js10
-rw-r--r--app/assets/javascripts/build_artifacts.js10
-rw-r--r--app/assets/javascripts/commit/image_file.js12
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue25
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/region_dropdown.vue63
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/index.js1
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js20
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/actions.js26
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/index.js2
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js4
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js19
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/state.js9
-rw-r--r--app/assets/javascripts/create_label.js4
-rw-r--r--app/assets/javascripts/gl_dropdown.js113
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js4
-rw-r--r--app/assets/javascripts/jobs/components/log/collapsible_section.vue16
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue2
-rw-r--r--app/assets/javascripts/jobs/store/utils.js22
-rw-r--r--app/assets/javascripts/labels_select.js12
-rw-r--r--app/assets/javascripts/lib/utils/notify.js14
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js6
-rw-r--r--app/assets/javascripts/merge_request.js6
-rw-r--r--app/assets/javascripts/namespace_select.js4
-rw-r--r--app/assets/javascripts/network/branch_graph.js6
-rw-r--r--app/assets/javascripts/new_branch_form.js4
-rw-r--r--app/assets/javascripts/notes.js4
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js26
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js14
-rw-r--r--app/assets/javascripts/single_file_diff.js11
-rw-r--r--app/assets/javascripts/tree.js4
-rw-r--r--app/assets/javascripts/users_select.js20
-rw-r--r--app/assets/javascripts/zen_mode.js8
-rw-r--r--app/assets/stylesheets/framework/job_log.scss1
-rw-r--r--changelogs/unreleased/sh-handle-exceptions-sidekiq.yml5
-rw-r--r--config/initializers/sidekiq.rb4
-rw-r--r--lib/gitlab/sidekiq_logging/exception_handler.rb27
-rw-r--r--lib/gitlab/sidekiq_logging/structured_logger.rb7
-rw-r--r--lib/gitlab/tracking.rb2
-rw-r--r--locale/gitlab.pot32
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js74
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js55
-rw-r--r--spec/frontend/create_cluster/eks_cluster/store/actions_spec.js98
-rw-r--r--spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js40
-rw-r--r--spec/frontend/ide/components/jobs/list_spec.js115
-rw-r--r--spec/frontend/ide/components/pipelines/__snapshots__/list_spec.js.snap15
-rw-r--r--spec/frontend/ide/components/pipelines/list_spec.js193
-rw-r--r--spec/frontend/jobs/components/log/collapsible_section_spec.js29
-rw-r--r--spec/frontend/jobs/components/log/mock_data.js74
-rw-r--r--spec/frontend/jobs/store/utils_spec.js39
-rw-r--r--spec/javascripts/ide/components/jobs/list_spec.js67
-rw-r--r--spec/javascripts/ide/components/pipelines/list_spec.js137
-rw-r--r--spec/javascripts/ide/mock_data.js6
-rw-r--r--spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb44
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb3
-rw-r--r--spec/lib/gitlab/tracking_spec.rb4
56 files changed, 1118 insertions, 500 deletions
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js
index 1909830e9ed..a07942d87cb 100644
--- a/app/assets/javascripts/behaviors/preview_markdown.js
+++ b/app/assets/javascripts/behaviors/preview_markdown.js
@@ -1,4 +1,4 @@
-/* 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')) {
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 9f0680cc6a7..8acf0827c44 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -1,4 +1,4 @@
-/* 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());
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index b2c88e8c14e..2955f0f014b 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -1,4 +1,4 @@
-/* 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
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 9454f760df8..0e031de0dbc 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,4 +1,4 @@
-/* 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) {
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
index 6e74963dcb0..a700069d2c8 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
@@ -1,4 +1,7 @@
<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>
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/region_dropdown.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/region_dropdown.vue
new file mode 100644
index 00000000000..765955305c8
--- /dev/null
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/region_dropdown.vue
@@ -0,0 +1,63 @@
+<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>
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/index.js b/app/assets/javascripts/create_cluster/eks_cluster/index.js
index c62e5ec101d..9365fc41f4c 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/index.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/index.js
@@ -12,7 +12,6 @@ export default () =>
components: {
CreateEksCluster,
},
- data() {},
render(createElement) {
return createElement('create-eks-cluster');
},
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js
index e69de29bb2d..5a13d32e0d2 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js
@@ -0,0 +1,20 @@
+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 () => {};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
index 861bcddfcc7..cad9e5eb183 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
@@ -1,3 +1,27 @@
-// 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 () => {};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js
index 99e9e35fd1a..8f5ad109c2f 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js
@@ -9,7 +9,7 @@ const createStore = () =>
actions,
getters,
mutations,
- state,
+ state: state(),
});
export default createStore;
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js
index e69de29bb2d..d66d0e70dc0 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js
@@ -0,0 +1,4 @@
+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';
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js
index e69de29bb2d..a203dd3bf42 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js
@@ -0,0 +1,19 @@
+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;
+ },
+};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js
index 9754ccfeeaf..ffeb1963060 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js
@@ -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: '',
diff --git a/app/assets/javascripts/create_label.js b/app/assets/javascripts/create_label.js
index eac0e37bcaa..9c0ed7f79d4 100644
--- a/app/assets/javascripts/create_label.js
+++ b/app/assets/javascripts/create_label.js
@@ -1,4 +1,4 @@
-/* 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();
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index f49246cf07b..b0b061f41fd 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -1,4 +1,4 @@
-/* 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 {
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index c855f3973b0..45de287d44d 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -1,4 +1,4 @@
-/* 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);
}
diff --git a/app/assets/javascripts/jobs/components/log/collapsible_section.vue b/app/assets/javascripts/jobs/components/log/collapsible_section.vue
index 7c0deb08488..0c7b78a3da7 100644
--- a/app/assets/javascripts/jobs/components/log/collapsible_section.vue
+++ b/app/assets/javascripts/jobs/components/log/collapsible_section.vue
@@ -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>
diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue
index 4e09c85b25a..9fae541125e 100644
--- a/app/assets/javascripts/jobs/components/log/line.vue
+++ b/app/assets/javascripts/jobs/components/log/line.vue
@@ -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
diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js
index 261ec90cd12..3838c759354 100644
--- a/app/assets/javascripts/jobs/store/utils.js
+++ b/app/assets/javascripts/jobs/store/utils.js
@@ -9,6 +9,21 @@ export const parseLine = (line = {}, 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
*
* For collaspible lines (section_header = true):
@@ -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) {
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index b028e9564c9..ca7f6736714 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,4 +1,4 @@
-/* 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;
}
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index 3439db1e326..cd509a13193 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -1,12 +1,14 @@
-/* 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);
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 7873eaf059f..fec90956a86 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -1,4 +1,4 @@
-/* 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({
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 3b42a154af8..7223b5c0d43 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,4 +1,4 @@
-/* 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();
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index 4660e4397a2..af55106d48c 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -1,4 +1,4 @@
-/* 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'),
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index fcfc2570b3d..230d8f04b5e 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -1,4 +1,4 @@
-/* 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);
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 98522c67696..945472a9be6 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -1,4 +1,4 @@
-/* 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';
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index ed52eec8b18..82a95969a01 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,7 +1,7 @@
/* 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')
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
index 506e6075d16..e37cd83280d 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
@@ -1,4 +1,4 @@
-/* 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));
})
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
index 505ca938f40..a89a13fe37a 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
@@ -1,4 +1,4 @@
-/* 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;
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index b70e384fae5..de4a7f89449 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -1,4 +1,4 @@
-/* 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) {
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 3e659c9e7ea..69b3d20914a 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,4 +1,4 @@
-/* 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;
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index e78ca56be0e..ce587615bbb 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -1,4 +1,4 @@
-/* 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,
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index 5438572eadf..7a60ab1380f 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -1,4 +1,4 @@
-/* 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();
diff --git a/app/assets/stylesheets/framework/job_log.scss b/app/assets/stylesheets/framework/job_log.scss
index 074b2405217..b93b224ae13 100644
--- a/app/assets/stylesheets/framework/job_log.scss
+++ b/app/assets/stylesheets/framework/job_log.scss
@@ -13,6 +13,7 @@
.log-line {
padding: 1px $gl-padding 1px $job-log-line-padding;
+ min-height: $gl-line-height-20;
}
.line-number {
diff --git a/changelogs/unreleased/sh-handle-exceptions-sidekiq.yml b/changelogs/unreleased/sh-handle-exceptions-sidekiq.yml
new file mode 100644
index 00000000000..e2569d5beb5
--- /dev/null
+++ b/changelogs/unreleased/sh-handle-exceptions-sidekiq.yml
@@ -0,0 +1,5 @@
+---
+title: Log Sidekiq exceptions properly in JSON format
+merge_request: 17412
+author:
+type: fixed
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index e3505579204..b5d98399015 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -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|
diff --git a/lib/gitlab/sidekiq_logging/exception_handler.rb b/lib/gitlab/sidekiq_logging/exception_handler.rb
new file mode 100644
index 00000000000..fba74b6c9ed
--- /dev/null
+++ b/lib/gitlab/sidekiq_logging/exception_handler.rb
@@ -0,0 +1,27 @@
+# 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
diff --git a/lib/gitlab/sidekiq_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb
index 48b1524f9c7..0a21e529ceb 100644
--- a/lib/gitlab/sidekiq_logging/structured_logger.rb
+++ b/lib/gitlab/sidekiq_logging/structured_logger.rb
@@ -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)
diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb
index 78177c6d306..b167cb098b0 100644
--- a/lib/gitlab/tracking.rb
+++ b/lib/gitlab/tracking.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0ec169a081b..4e807abbde6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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."
diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
new file mode 100644
index 00000000000..5b04328bb78
--- /dev/null
+++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
@@ -0,0 +1,74 @@
+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);
+ });
+});
diff --git a/spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js b/spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js
new file mode 100644
index 00000000000..0ebb5026a4b
--- /dev/null
+++ b/spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js
@@ -0,0 +1,55 @@
+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',
+ );
+ });
+});
diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
new file mode 100644
index 00000000000..9a3970813ed
--- /dev/null
+++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
@@ -0,0 +1,98 @@
+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 } },
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js
new file mode 100644
index 00000000000..f2d48635f8c
--- /dev/null
+++ b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js
@@ -0,0 +1,40 @@
+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);
+ });
+});
diff --git a/spec/frontend/ide/components/jobs/list_spec.js b/spec/frontend/ide/components/jobs/list_spec.js
new file mode 100644
index 00000000000..ec2e5b05048
--- /dev/null
+++ b/spec/frontend/ide/components/jobs/list_spec.js
@@ -0,0 +1,115 @@
+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);
+ });
+ });
+});
diff --git a/spec/frontend/ide/components/pipelines/__snapshots__/list_spec.js.snap b/spec/frontend/ide/components/pipelines/__snapshots__/list_spec.js.snap
new file mode 100644
index 00000000000..5fbe6af750d
--- /dev/null
+++ b/spec/frontend/ide/components/pipelines/__snapshots__/list_spec.js.snap
@@ -0,0 +1,15 @@
+// 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>
+`;
diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js
new file mode 100644
index 00000000000..a974139a8f9
--- /dev/null
+++ b/spec/frontend/ide/components/pipelines/list_spec.js
@@ -0,0 +1,193 @@
+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);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js
index 6c1ebf0a7c1..01184a51193 100644
--- a/spec/frontend/jobs/components/log/collapsible_section_spec.js
+++ b/spec/frontend/jobs/components/log/collapsible_section_spec.js
@@ -1,6 +1,6 @@
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,
});
diff --git a/spec/frontend/jobs/components/log/mock_data.js b/spec/frontend/jobs/components/log/mock_data.js
index 0dae306dcc7..d375d82d3ca 100644
--- a/spec/frontend/jobs/components/log/mock_data.js
+++ b/spec/frontend/jobs/components/log/mock_data.js
@@ -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,
},
],
};
diff --git a/spec/frontend/jobs/store/utils_spec.js b/spec/frontend/jobs/store/utils_spec.js
index 780d42fd6a1..1e885b6b788 100644
--- a/spec/frontend/jobs/store/utils_spec.js
+++ b/spec/frontend/jobs/store/utils_spec.js
@@ -1,4 +1,9 @@
-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,
},
],
diff --git a/spec/javascripts/ide/components/jobs/list_spec.js b/spec/javascripts/ide/components/jobs/list_spec.js
deleted file mode 100644
index b24853c56fa..00000000000
--- a/spec/javascripts/ide/components/jobs/list_spec.js
+++ /dev/null
@@ -1,67 +0,0 @@
-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]]);
- });
-});
diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js
deleted file mode 100644
index 80829f2358a..00000000000
--- a/spec/javascripts/ide/components/pipelines/list_spec.js
+++ /dev/null
@@ -1,137 +0,0 @@
-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);
- });
- });
-});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index c02c7e5d45e..1c2e082489e 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -1,3 +1,5 @@
+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`,
},
];
diff --git a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
new file mode 100644
index 00000000000..24b6090cb19
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
@@ -0,0 +1,44 @@
+# 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
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 1b89c094a6b..6e6a8e14fc9 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index 3cce82e522b..6891349a1dc 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -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(