summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/diffs/constants.js2
-rw-r--r--app/assets/javascripts/diffs/store/actions.js15
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js9
-rw-r--r--app/assets/javascripts/diffs/store/utils.js63
-rw-r--r--app/assets/javascripts/diffs/workers/tree_worker.js14
-rw-r--r--app/assets/javascripts/error_tracking/index.js4
-rw-r--r--app/assets/javascripts/frequent_items/components/app.vue6
-rw-r--r--app/assets/javascripts/frequent_items/index.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue2
-rw-r--r--app/controllers/projects/badges_controller.rb13
-rw-r--r--app/controllers/projects/error_tracking_controller.rb10
-rw-r--r--app/controllers/projects/lfs_locks_api_controller.rb10
-rw-r--r--app/helpers/members_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/models/ci/build.rb9
-rw-r--r--app/models/group.rb4
-rw-r--r--app/services/projects/create_from_template_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb24
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/projects/badges/badge_flat-square.svg.erb17
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml2
-rw-r--r--changelogs/unreleased/30120-add-flat-square-badge-style.yml5
-rw-r--r--changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml5
-rw-r--r--changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml5
-rw-r--r--changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml5
-rw-r--r--changelogs/unreleased/api-nested-group-permission.yml5
-rw-r--r--changelogs/unreleased/api-tags-search.yml5
-rw-r--r--changelogs/unreleased/diff-tree-collapse-directories.yml5
-rw-r--r--changelogs/unreleased/fix-runner-eternal-loop-when-update-job-result.yml5
-rw-r--r--changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml5
-rw-r--r--changelogs/unreleased/raise-on-unfiltered-params.yml5
-rw-r--r--changelogs/unreleased/sh-preload-associations-for-group-api.yml5
-rw-r--r--config/application.rb3
-rw-r--r--config/initializers/new_framework_defaults.rb2
-rw-r--r--doc/api/tags.md3
-rw-r--r--doc/ci/yaml/README.md5
-rw-r--r--doc/development/contributing/issue_workflow.md2
-rw-r--r--doc/development/fe_guide/vuex.md4
-rw-r--r--doc/update/11.6-to-11.7.md2
-rw-r--r--doc/user/discussions/index.md2
-rw-r--r--doc/user/permissions.md4
-rw-r--r--doc/user/project/integrations/prometheus_library/index.md3
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md26
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md58
-rw-r--r--doc/user/project/pipelines/settings.md22
-rw-r--r--lib/api/entities.rb10
-rw-r--r--lib/api/tags.rb7
-rw-r--r--lib/gitlab/tracing/jaeger_factory.rb2
-rw-r--r--package.json2
-rw-r--r--qa/qa/resource/base.rb12
-rw-r--r--qa/qa/resource/fork.rb9
-rw-r--r--qa/qa/resource/merge_request_from_fork.rb2
-rw-r--r--qa/qa/resource/project.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb8
-rw-r--r--qa/spec/resource/base_spec.rb36
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb41
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb12
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb1
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb1
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb24
-rw-r--r--spec/features/markdown/math_spec.rb4
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb24
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js22
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js119
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestions_spec.js2
-rw-r--r--spec/lib/gitlab/tracing/jaeger_factory_spec.rb58
-rw-r--r--spec/migrations/README.md76
-rw-r--r--spec/models/ci/build_spec.rb18
-rw-r--r--spec/models/group_spec.rb36
-rw-r--r--spec/requests/api/groups_spec.rb14
-rw-r--r--spec/requests/api/projects_spec.rb34
-rw-r--r--spec/requests/api/tags_spec.rb12
-rw-r--r--spec/requests/lfs_locks_api_spec.rb11
-rw-r--r--spec/services/projects/destroy_service_spec.rb34
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb22
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb2
-rw-r--r--yarn.lock17
78 files changed, 895 insertions, 200 deletions
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 78a39baa4cb..0af1ba13d36 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -32,3 +32,5 @@ export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
export const MAX_LINES_TO_BE_RENDERED = 2000;
export const MR_TREE_SHOW_KEY = 'mr_tree_show';
+
+export const TREE_TYPE = 'tree';
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 00a4bb6d3a3..196c9dfb1c2 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -5,6 +5,7 @@ import createFlash from '~/flash';
import { s__ } from '~/locale';
import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/utils/common_utils';
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
+import TreeWorker from '../workers/tree_worker';
import eventHub from '../../notes/event_hub';
import { getDiffPositionByLineCode, getNoteFormData } from './utils';
import * as types from './mutation_types';
@@ -21,17 +22,29 @@ export const setBaseConfig = ({ commit }, options) => {
};
export const fetchDiffFiles = ({ state, commit }) => {
+ const worker = new TreeWorker();
+
commit(types.SET_LOADING, true);
+ worker.addEventListener('message', ({ data }) => {
+ commit(types.SET_TREE_DATA, data);
+
+ worker.terminate();
+ });
+
return axios
.get(state.endpoint)
.then(res => {
commit(types.SET_LOADING, false);
commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []);
commit(types.SET_DIFF_DATA, res.data);
+
+ worker.postMessage(state.diffFiles);
+
return Vue.nextTick();
})
- .then(handleLocationHash);
+ .then(handleLocationHash)
+ .catch(() => worker.terminate());
};
export const setHighlightedRow = ({ commit }, lineCode) => {
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 0338cde3658..6ed8c5709a8 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -18,3 +18,5 @@ export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM';
export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM';
export const CLOSE_DIFF_FILE_COMMENT_FORM = 'CLOSE_DIFF_FILE_COMMENT_FORM';
export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW';
+
+export const SET_TREE_DATA = 'SET_TREE_DATA';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index ed4203cf5e0..00095997ba3 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -1,5 +1,4 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { sortTree } from '~/ide/stores/utils';
import {
findDiffFile,
addLineReferences,
@@ -7,7 +6,6 @@ import {
addContextLines,
prepareDiffData,
isDiscussionApplicableToLine,
- generateTreeList,
} from './utils';
import * as types from './mutation_types';
@@ -23,12 +21,9 @@ export default {
[types.SET_DIFF_DATA](state, data) {
prepareDiffData(data);
- const { tree, treeEntries } = generateTreeList(data.diff_files);
Object.assign(state, {
...convertObjectPropsToCamelCase(data),
- tree: sortTree(tree),
- treeEntries,
});
},
@@ -239,4 +234,8 @@ export default {
[types.SET_HIGHLIGHTED_ROW](state, lineCode) {
state.highlightedRow = lineCode;
},
+ [types.SET_TREE_DATA](state, { treeEntries, tree }) {
+ state.treeEntries = treeEntries;
+ state.tree = tree;
+ },
};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index f427367c11e..ada93b570b0 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -11,6 +11,7 @@ import {
MATCH_LINE_TYPE,
LINES_TO_BE_RENDERED_DIRECTLY,
MAX_LINES_TO_BE_RENDERED,
+ TREE_TYPE,
} from '../constants';
export function findDiffFile(files, hash) {
@@ -289,8 +290,63 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD
return latestDiff && discussion.active && line_code === discussion.line_code;
}
-export const generateTreeList = files =>
- files.reduce(
+export const getLowestSingleFolder = folder => {
+ const getFolder = (blob, start = []) =>
+ blob.tree.reduce(
+ (acc, file) => {
+ const shouldGetFolder = file.tree.length === 1 && file.tree[0].type === TREE_TYPE;
+ const currentFileTypeTree = file.type === TREE_TYPE;
+ const path = shouldGetFolder || currentFileTypeTree ? acc.path.concat(file.name) : acc.path;
+ const tree = shouldGetFolder || currentFileTypeTree ? acc.tree.concat(file) : acc.tree;
+
+ if (shouldGetFolder) {
+ const firstFolder = getFolder(file);
+
+ path.push(firstFolder.path);
+ tree.push(...firstFolder.tree);
+ }
+
+ return {
+ ...acc,
+ path,
+ tree,
+ };
+ },
+ { path: start, tree: [] },
+ );
+ const { path, tree } = getFolder(folder, [folder.name]);
+
+ return {
+ path: path.join('/'),
+ treeAcc: tree.length ? tree[tree.length - 1].tree : null,
+ };
+};
+
+export const flattenTree = tree => {
+ const flatten = blobTree =>
+ blobTree.reduce((acc, file) => {
+ const blob = file;
+ let treeToFlatten = blob.tree;
+
+ if (file.type === TREE_TYPE && file.tree.length === 1) {
+ const { treeAcc, path } = getLowestSingleFolder(file);
+
+ if (treeAcc) {
+ blob.name = path;
+ treeToFlatten = flatten(treeAcc);
+ }
+ }
+
+ blob.tree = flatten(treeToFlatten);
+
+ return acc.concat(blob);
+ }, []);
+
+ return flatten(tree);
+};
+
+export const generateTreeList = files => {
+ const { treeEntries, tree } = files.reduce(
(acc, file) => {
const split = file.new_path.split('/');
@@ -335,6 +391,9 @@ export const generateTreeList = files =>
{ treeEntries: {}, tree: [] },
);
+ return { treeEntries, tree: flattenTree(tree) };
+};
+
export const getDiffMode = diffFile => {
const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]);
return (
diff --git a/app/assets/javascripts/diffs/workers/tree_worker.js b/app/assets/javascripts/diffs/workers/tree_worker.js
new file mode 100644
index 00000000000..534d737c77e
--- /dev/null
+++ b/app/assets/javascripts/diffs/workers/tree_worker.js
@@ -0,0 +1,14 @@
+import { sortTree } from '~/ide/stores/utils';
+import { generateTreeList } from '../store/utils';
+
+// eslint-disable-next-line no-restricted-globals
+self.addEventListener('message', e => {
+ const { data } = e;
+ const { treeEntries, tree } = generateTreeList(data);
+
+ // eslint-disable-next-line no-restricted-globals
+ self.postMessage({
+ treeEntries,
+ tree: sortTree(tree),
+ });
+});
diff --git a/app/assets/javascripts/error_tracking/index.js b/app/assets/javascripts/error_tracking/index.js
index 808ae2c9a41..3d609448efe 100644
--- a/app/assets/javascripts/error_tracking/index.js
+++ b/app/assets/javascripts/error_tracking/index.js
@@ -4,10 +4,6 @@ import store from './store';
import ErrorTrackingList from './components/error_tracking_list.vue';
export default () => {
- if (!gon.features.errorTracking) {
- return;
- }
-
// eslint-disable-next-line no-new
new Vue({
el: '#js-error_tracking',
diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue
index 63531f1f246..968e255e1fc 100644
--- a/app/assets/javascripts/frequent_items/components/app.vue
+++ b/app/assets/javascripts/frequent_items/components/app.vue
@@ -47,6 +47,12 @@ export default {
}
eventHub.$on(`${this.namespace}-dropdownOpen`, this.dropdownOpenHandler);
+
+ // As we init it through requestIdleCallback it could be that the dropdown is already open
+ const namespaceDropdown = document.getElementById(`nav-${this.namespace}-dropdown`);
+ if (namespaceDropdown && namespaceDropdown.classList.contains('show')) {
+ this.dropdownOpenHandler();
+ }
},
beforeDestroy() {
eventHub.$off(`${this.namespace}-dropdownOpen`, this.dropdownOpenHandler);
diff --git a/app/assets/javascripts/frequent_items/index.js b/app/assets/javascripts/frequent_items/index.js
index 5157ff211dc..6263acbab8e 100644
--- a/app/assets/javascripts/frequent_items/index.js
+++ b/app/assets/javascripts/frequent_items/index.js
@@ -17,7 +17,7 @@ const frequentItemDropdowns = [
},
];
-document.addEventListener('DOMContentLoaded', () => {
+const initFrequentItemDropdowns = () => {
frequentItemDropdowns.forEach(dropdown => {
const { namespace, key } = dropdown;
const el = document.getElementById(`js-${namespace}-dropdown`);
@@ -66,4 +66,8 @@ document.addEventListener('DOMContentLoaded', () => {
},
});
});
+};
+
+document.addEventListener('DOMContentLoaded', () => {
+ requestIdleCallback(initFrequentItemDropdowns);
});
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index 721f0276ac8..dcda701f049 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -129,7 +129,7 @@ export default {
<template>
<div>
- <div class="flash-container mt-3"></div>
+ <div class="flash-container js-suggestions-flash"></div>
<div v-show="isRendered" ref="container" v-html="noteHtml"></div>
</div>
</template>
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index c24bf211760..09a384e89ab 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -21,11 +21,22 @@ class Projects::BadgesController < Projects::ApplicationController
private
+ def badge_layout
+ case params[:style]
+ when 'flat'
+ 'badge'
+ when 'flat-square'
+ 'badge_flat-square'
+ else
+ 'badge'
+ end
+ end
+
def render_badge(badge)
respond_to do |format|
format.html { render_404 }
format.svg do
- render 'badge', locals: { badge: badge.template }
+ render badge_layout, locals: { badge: badge.template }
end
end
end
diff --git a/app/controllers/projects/error_tracking_controller.rb b/app/controllers/projects/error_tracking_controller.rb
index 4596b6c91f2..9e403e1d25b 100644
--- a/app/controllers/projects/error_tracking_controller.rb
+++ b/app/controllers/projects/error_tracking_controller.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
class Projects::ErrorTrackingController < Projects::ApplicationController
- before_action :check_feature_flag!
before_action :authorize_read_sentry_issue!
- before_action :push_feature_flag_to_frontend
POLLING_INTERVAL = 10_000
@@ -43,12 +41,4 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
.new(project: project, user: current_user)
.represent(errors)
end
-
- def check_feature_flag!
- render_404 unless Feature.enabled?(:error_tracking, project)
- end
-
- def push_feature_flag_to_frontend
- push_frontend_feature_flag(:error_tracking, current_user)
- end
end
diff --git a/app/controllers/projects/lfs_locks_api_controller.rb b/app/controllers/projects/lfs_locks_api_controller.rb
index fc67cd72faa..6aacb9d9a56 100644
--- a/app/controllers/projects/lfs_locks_api_controller.rb
+++ b/app/controllers/projects/lfs_locks_api_controller.rb
@@ -4,19 +4,19 @@ class Projects::LfsLocksApiController < Projects::GitHttpClientController
include LfsRequest
def create
- @result = Lfs::LockFileService.new(project, user, params).execute
+ @result = Lfs::LockFileService.new(project, user, lfs_params).execute
render_json(@result[:lock])
end
def unlock
- @result = Lfs::UnlockFileService.new(project, user, params).execute
+ @result = Lfs::UnlockFileService.new(project, user, lfs_params).execute
render_json(@result[:lock])
end
def index
- @result = Lfs::LocksFinderService.new(project, user, params).execute
+ @result = Lfs::LocksFinderService.new(project, user, lfs_params).execute
render_json(@result[:locks])
end
@@ -69,4 +69,8 @@ class Projects::LfsLocksApiController < Projects::GitHttpClientController
def upload_request?
%w(create unlock verify).include?(params[:action])
end
+
+ def lfs_params
+ params.permit(:id, :path, :force)
+ end
end
diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb
index 5a21403bc5e..ab4a1ccc0d1 100644
--- a/app/helpers/members_helper.rb
+++ b/app/helpers/members_helper.rb
@@ -32,7 +32,7 @@ module MembersHelper
end
def filter_group_project_member_path(options = {})
- options = params.slice(:search, :sort).merge(options)
+ options = params.slice(:search, :sort).merge(options).permit!
"#{request.path}?#{options.to_param}"
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index ebbed08f78a..eceee054ede 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -285,7 +285,7 @@ module ProjectsHelper
# overridden in EE
def settings_operations_available?
- Feature.enabled?(:error_tracking, @project) && can?(current_user, :read_environment, @project)
+ can?(current_user, :read_environment, @project)
end
private
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index dc6f8ae1a7f..cfdb3c0d719 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -224,8 +224,15 @@ module Ci
before_transition any => [:failed] do |build|
next unless build.project
+ next unless build.deployment
- build.deployment&.drop
+ begin
+ build.deployment.drop!
+ rescue => e
+ Gitlab::Sentry.track_exception(e, extra: { build_id: build.id })
+ end
+
+ true
end
after_transition any => [:failed] do |build|
diff --git a/app/models/group.rb b/app/models/group.rb
index edac2444c4d..22814e35ca8 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -382,6 +382,10 @@ class Group < Namespace
end
end
+ def highest_group_member(user)
+ GroupMember.where(source_id: self_and_ancestors_ids, user_id: user.id).order(:access_level).last
+ end
+
def hashed_storage?(_feature)
false
end
diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb
index 8306d43ca7c..678bc0d24c3 100644
--- a/app/services/projects/create_from_template_service.rb
+++ b/app/services/projects/create_from_template_service.rb
@@ -5,7 +5,7 @@ module Projects
include Gitlab::Utils::StrongMemoize
def initialize(user, params)
- @current_user, @params = user, params.dup
+ @current_user, @params = user, params.to_h.dup
end
def execute
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 336d029d330..b14b31302f5 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -7,9 +7,16 @@ module Projects
DestroyError = Class.new(StandardError)
DELETED_FLAG = '+deleted'.freeze
+ REPO_REMOVAL_DELAY = 5.minutes.to_i
def async_execute
project.update_attribute(:pending_delete, true)
+
+ # Ensure no repository +deleted paths are kept,
+ # regardless of any issue with the ProjectDestroyWorker
+ # job process.
+ schedule_stale_repos_removal
+
job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params)
Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}")
end
@@ -92,14 +99,23 @@ module Projects
log_info(%Q{Repository "#{path}" moved to "#{new_path}" for project "#{project.full_path}"})
project.run_after_commit do
- # self is now project
- GitlabShellWorker.perform_in(5.minutes, :remove_repository, self.repository_storage, new_path)
+ GitlabShellWorker.perform_in(REPO_REMOVAL_DELAY, :remove_repository, self.repository_storage, new_path)
end
else
false
end
end
+ def schedule_stale_repos_removal
+ repo_paths = [removal_path(repo_path), removal_path(wiki_path)]
+
+ # Ideally it should wait until the regular removal phase finishes,
+ # so let's delay it a bit further.
+ repo_paths.each do |path|
+ GitlabShellWorker.perform_in(REPO_REMOVAL_DELAY * 2, :remove_repository, project.repository_storage, path)
+ end
+ end
+
def rollback_repository(old_path, new_path)
# There is a possibility project does not have repository or wiki
return true unless repo_exists?(old_path)
@@ -113,13 +129,11 @@ module Projects
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def mv_repository(from_path, to_path)
- return true unless gitlab_shell.exists?(project.repository_storage, from_path + '.git')
+ return true unless repo_exists?(from_path)
gitlab_shell.mv_repository(project.repository_storage, from_path, to_path)
end
- # rubocop: enable CodeReuse/ActiveRecord
def attempt_rollback(project, message)
return unless project
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 4b67069d9ac..1eab7813865 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -227,7 +227,7 @@
%span
= _('Environments')
- - if project_nav_tab?(:error_tracking) && Feature.enabled?(:error_tracking, @project)
+ - if project_nav_tab?(:error_tracking)
= nav_link(controller: :error_tracking) do
= link_to project_error_tracking_index_path(@project), title: _('Error Tracking'), class: 'shortcuts-tracking qa-operations-tracking-link' do
%span
diff --git a/app/views/projects/badges/badge_flat-square.svg.erb b/app/views/projects/badges/badge_flat-square.svg.erb
new file mode 100644
index 00000000000..5b90da15ef5
--- /dev/null
+++ b/app/views/projects/badges/badge_flat-square.svg.erb
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="<%= badge.width %>" height="20">
+ <g shape-rendering="crispEdges">
+ <path fill="<%= badge.key_color %>" d="M0 0 h<%= badge.key_width %> v20 H0 z"/>
+ <path fill="<%= badge.value_color %>" d="M<%= badge.key_width %> 0 h<%= badge.value_width %> v20 H<%= badge.key_width %> z"/>
+ </g>
+
+ <g fill="#fff" text-anchor="middle">
+ <g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
+ <text x="<%= badge.key_text_anchor %>" y="14">
+ <%= badge.key_text %>
+ </text>
+ <text x="<%= badge.value_text_anchor %>" y="14">
+ <%= badge.value_text %>
+ </text>
+ </g>
+ </g>
+</svg>
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
index 871b60f05ba..4911e8d3770 100644
--- a/app/views/projects/settings/operations/_error_tracking.html.haml
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -1,4 +1,4 @@
-- return unless Feature.enabled?(:error_tracking, @project) && can?(current_user, :read_environment, @project)
+- return unless can?(current_user, :read_environment, @project)
- setting = error_tracking_setting
diff --git a/changelogs/unreleased/30120-add-flat-square-badge-style.yml b/changelogs/unreleased/30120-add-flat-square-badge-style.yml
new file mode 100644
index 00000000000..a542a58d3fc
--- /dev/null
+++ b/changelogs/unreleased/30120-add-flat-square-badge-style.yml
@@ -0,0 +1,5 @@
+---
+title: Add flat-square badge style
+merge_request: 24172
+author: Fabian Schneider @fabsrc
+type: added
diff --git a/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml b/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml
new file mode 100644
index 00000000000..3494feb9be1
--- /dev/null
+++ b/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unwanted margin above suggested changes.
+merge_request: 24419
+author:
+type: fixed
diff --git a/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml b/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml
new file mode 100644
index 00000000000..671e204da21
--- /dev/null
+++ b/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade KaTeX to version 0.10.0
+merge_request: 24478
+author: Andrew Harmon
+type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml b/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml
new file mode 100644
index 00000000000..5365260cbae
--- /dev/null
+++ b/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid overwriting default jaeger values with nil
+merge_request: 24482
+author:
+type: fixed
diff --git a/changelogs/unreleased/api-nested-group-permission.yml b/changelogs/unreleased/api-nested-group-permission.yml
new file mode 100644
index 00000000000..3ec0df6893f
--- /dev/null
+++ b/changelogs/unreleased/api-nested-group-permission.yml
@@ -0,0 +1,5 @@
+---
+title: Return the maximum group access level in the projects API
+merge_request: 24403
+author:
+type: changed
diff --git a/changelogs/unreleased/api-tags-search.yml b/changelogs/unreleased/api-tags-search.yml
new file mode 100644
index 00000000000..1501acd5a9e
--- /dev/null
+++ b/changelogs/unreleased/api-tags-search.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Support searching for tags'
+merge_request: 24385
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/diff-tree-collapse-directories.yml b/changelogs/unreleased/diff-tree-collapse-directories.yml
new file mode 100644
index 00000000000..6eae48f2352
--- /dev/null
+++ b/changelogs/unreleased/diff-tree-collapse-directories.yml
@@ -0,0 +1,5 @@
+---
+title: Collapse directory structure in merge request file tree
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-runner-eternal-loop-when-update-job-result.yml b/changelogs/unreleased/fix-runner-eternal-loop-when-update-job-result.yml
new file mode 100644
index 00000000000..5a6c36e6f5f
--- /dev/null
+++ b/changelogs/unreleased/fix-runner-eternal-loop-when-update-job-result.yml
@@ -0,0 +1,5 @@
+---
+title: Fix runner eternal loop when update job result
+merge_request: 24481
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml b/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml
new file mode 100644
index 00000000000..6a2a67e7aa8
--- /dev/null
+++ b/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml
@@ -0,0 +1,5 @@
+---
+title: Cleanup stale +deleted repo paths on project removal (adjusts project removal bug)
+merge_request: 24269
+author:
+type: fixed
diff --git a/changelogs/unreleased/raise-on-unfiltered-params.yml b/changelogs/unreleased/raise-on-unfiltered-params.yml
new file mode 100644
index 00000000000..531e9ba807e
--- /dev/null
+++ b/changelogs/unreleased/raise-on-unfiltered-params.yml
@@ -0,0 +1,5 @@
+---
+title: Actually set raise_on_unfiltered_parameters to true
+merge_request: 24443
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/sh-preload-associations-for-group-api.yml b/changelogs/unreleased/sh-preload-associations-for-group-api.yml
new file mode 100644
index 00000000000..24e424b7efb
--- /dev/null
+++ b/changelogs/unreleased/sh-preload-associations-for-group-api.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate N+1 queries in /api/groups/:id
+merge_request: 24513
+author:
+type: performance
diff --git a/config/application.rb b/config/application.rb
index 349c7258852..92a3d031c63 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -162,6 +162,9 @@ module Gitlab
config.action_view.sanitized_allowed_protocols = %w(smb)
+ # Can be removed once upgraded to Rails 5.1 or higher
+ config.action_controller.raise_on_unfiltered_parameters = true
+
# Nokogiri is significantly faster and uses less memory than REXML
ActiveSupport::XmlMini.backend = 'Nokogiri'
diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb
index a1e0667bc6f..115ee08dbb6 100644
--- a/config/initializers/new_framework_defaults.rb
+++ b/config/initializers/new_framework_defaults.rb
@@ -8,8 +8,6 @@
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-Rails.application.config.action_controller.raise_on_unfiltered_parameters = true
-
# Enable per-form CSRF tokens. Previous versions had false.
Rails.application.config.action_controller.per_form_csrf_tokens = false
diff --git a/doc/api/tags.md b/doc/api/tags.md
index fc86aaa6757..23dbf2d9ff7 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -17,6 +17,9 @@ Parameters:
| `id` | integer/string| yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user|
| `order_by` | string | no | Return tags ordered by `name` or `updated` fields. Default is `updated` |
| `sort` | string | no | Return tags sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Return list of tags matching the search criteria |
+
+> Support for `search` was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/54401) in GitLab 11.8.
```json
[
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index d7ab6fc506d..fb69d888b94 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -2026,6 +2026,11 @@ variables:
GIT_STRATEGY: none
```
+NOTE: **Note:** `GIT_STRATEGY` is not supported for
+[Kubernetes executor](https://docs.gitlab.com/runner/executors/kubernetes.html),
+but may be in the future. See the [support Git strategy with Kubernetes executor feature proposal](https://gitlab.com/gitlab-org/gitlab-runner/issues/3847)
+for updates.
+
### Git submodule strategy
> Requires GitLab Runner v1.10+.
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 6d9149004fe..24feb1378a1 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -67,7 +67,7 @@ The current team labels are:
- ~Geo
- ~Gitaly
- ~Manage
-- ~Monitoring
+- ~Monitor
- ~Plan
- ~Quality
- ~Release
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index 65963b959f7..4b60ec80cb8 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -120,8 +120,8 @@ create:
1. An action `receiveSomethingError`, to handle the error callback
1. An action `fetchSomething` to make the request.
1. In case your application does more than a `GET` request you can use these as examples:
- - `PUT`: `createSomething`
- - `POST`: `updateSomething`
+ - `POST`: `createSomething`
+ - `PUT`: `updateSomething`
- `DELETE`: `deleteSomething`
The component MUST only dispatch the `fetchNamespace` action. Actions namespaced with `request` or `receive` should not be called from the component
diff --git a/doc/update/11.6-to-11.7.md b/doc/update/11.6-to-11.7.md
index f5f671c1946..f9e3f565e0b 100644
--- a/doc/update/11.6-to-11.7.md
+++ b/doc/update/11.6-to-11.7.md
@@ -66,7 +66,7 @@ from source at the nodejs.org website.
<https://nodejs.org/en/download/>
-GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript
+GitLab also requires the use of yarn `>= v1.10.0` to manage JavaScript
dependencies.
```bash
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 9379d047fca..84f4b0b3922 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -280,7 +280,7 @@ Additionally locked issues can not be reopened.
For issues with many comments like activity notes and user comments, sometimes
finding useful information can be hard. There is a way to filter comments from single notes and discussions for merge requests and issues.
-From a merge request's **Discussion** tab, or from an issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options:
+From a merge request's **Discussion** tab, or from an epic/issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options:
- **Show all activity**: displays all user comments and system notes
(issue updates, mentions from other issues, changes to the description, etc).
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 2a1c8cc5bc0..0c358390046 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -178,9 +178,7 @@ group.
| Remove group | | | | | ✓ |
| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
-| View private group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
-| View internal group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View public group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
| Delete group epic **[ULTIMATE]** | | | | | ✓ |
| View group Audit Events | | | | | ✓ |
diff --git a/doc/user/project/integrations/prometheus_library/index.md b/doc/user/project/integrations/prometheus_library/index.md
index 9b9b4f6c8ca..a79bc2bce06 100644
--- a/doc/user/project/integrations/prometheus_library/index.md
+++ b/doc/user/project/integrations/prometheus_library/index.md
@@ -10,7 +10,8 @@ Currently supported exporters are:
- [Kubernetes](kubernetes.md)
- [NGINX](nginx.md)
-- [NGINX Ingress Controller](nginx_ingress.md)
+- [NGINX Ingress Controller 0.9.0-0.15.x](nginx_ingress_vts.md)
+- [NGINX Ingress Controller 0.16.0+](nginx_ingress.md)
- [HAProxy](haproxy.md)
- [Amazon Cloud Watch](cloudwatch.md)
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index d5f77d622be..b7601f26802 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -1,8 +1,10 @@
# Monitoring NGINX Ingress Controller
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22133) in GitLab 11.7.
-GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built in Prometheus metrics included in [version 0.9.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#09-beta1) and above of the ingress.
+NOTE: **Note:** NGINX Ingress versions prior to 0.16.0 offer an included [VTS Prometheus metrics exporter](nginx_ingress_vts.md), which exports metrics different than the built-in metrics.
+
+GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built-in Prometheus metrics included starting with [version 0.16.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0160).
## Requirements
@@ -12,9 +14,9 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
| Name | Query |
| ---- | ----- |
-| Throughput (req/sec) | sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code) |
-| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
-| HTTP Error Rate (%) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100 |
+| Throughput (req/sec) | sum(label_replace(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m]), "status_code", "${1}xx", "status", "(.)..")) by (status_code) |
+| Latency (ms) | sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000 |
+| HTTP Error Rate (%) | sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100 |
## Configuring NGINX ingress monitoring
@@ -22,9 +24,9 @@ If you have deployed NGINX Ingress using GitLab's [Kubernetes cluster integratio
For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
-- NGINX Ingress should be version 0.9.0 or above, with metrics enabled
-- NGINX Ingress should be annotated for Prometheus monitoring
-- Prometheus should be configured to monitor annotated pods
+- NGINX Ingress should be version 0.16.0 or above, with metrics enabled.
+- NGINX Ingress should be annotated for Prometheus monitoring.
+- Prometheus should be configured to monitor annotated pods.
### About managed NGINX Ingress deployments
@@ -32,9 +34,9 @@ NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [o
NGINX is configured for Prometheus monitoring, by setting:
-- `enable-vts-status: "true"`, to export Prometheus metrics
-- `prometheus.io/scrape: "true"`, to enable automatic discovery
-- `prometheus.io/port: "10254"`, to specify the metrics port
+- `enable-vts-status: "true"`, to export Prometheus metrics.
+- `prometheus.io/scrape: "true"`, to enable automatic discovery.
+- `prometheus.io/port: "10254"`, to specify the metrics port.
When used in conjunction with the GitLab deployed Prometheus service, response metrics will be automatically collected.
@@ -51,6 +53,6 @@ Managing these settings depends on how NGINX ingress has been deployed. If you h
## Specifying the Environment label
-In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
+In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `ingress` label must `<CI_ENVIRONMENT_SLUG>`.
If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
new file mode 100644
index 00000000000..081eb8732ad
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
@@ -0,0 +1,58 @@
+# Monitoring NGINX Ingress Controller with VTS metrics
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5.
+
+NOTE: **Note:** [NGINX Ingress version 0.16](nginx_ingress.md) and above have built-in Prometheus metrics, which are different than the VTS based metrics.
+
+GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the included VTS Prometheus metrics exporter in [version 0.9.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#09-beta1) through [0.15.x](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0150).
+
+## Requirements
+
+[Prometheus integration](../prometheus.md) must be active.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code) |
+| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
+| HTTP Error Rate (%) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100 |
+
+## Configuring NGINX ingress monitoring
+
+If you have deployed NGINX Ingress using GitLab's [Kubernetes cluster integration](../../clusters/index.md#installing-applications), it will [automatically be monitored](#about-managed-nginx-ingress-deployments) by Prometheus.
+
+For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
+
+- NGINX Ingress should be version 0.9.0 or above, with metrics enabled.
+- NGINX Ingress should be annotated for Prometheus monitoring.
+- Prometheus should be configured to monitor annotated pods.
+
+### About managed NGINX Ingress deployments
+
+NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address).
+
+NGINX is configured for Prometheus monitoring, by setting:
+
+- `enable-vts-status: "true"`, to export Prometheus metrics.
+- `prometheus.io/scrape: "true"`, to enable automatic discovery.
+- `prometheus.io/port: "10254"`, to specify the metrics port.
+
+When used in conjunction with the GitLab deployed Prometheus service, response metrics will be automatically collected.
+
+### Manually setting up NGINX Ingress for Prometheus monitoring
+
+Version 0.9.0 and above of [NGINX ingress](https://github.com/kubernetes/ingress-nginx) have built-in support for exporting Prometheus metrics. To enable, a ConfigMap setting must be passed: `enable-vts-status: "true"`. Once enabled, a Prometheus metrics endpoint will start running on port 10254.
+
+Next, the ingress needs to be annotated for Prometheus monitoring. Two new annotations need to be added:
+
+- `prometheus.io/scrape: "true"`
+- `prometheus.io/port: "10254"`
+
+Managing these settings depends on how NGINX ingress has been deployed. If you have deployed via the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress), metrics can be enabled with `controller.stats.enabled` along with the required annotations. Alternatively it is possible edit the NGINX ingress YML directly in the [Kubernetes dashboard](https://github.com/kubernetes/dashboard).
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
+
+If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 88d745b0ce4..5aa26af5265 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -157,7 +157,27 @@ into your `README.md`:
![coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)
```
-### Environment Variables
+### Badge styles
+
+Pipeline badges can be rendered in different styles by adding the `style=style_name` parameter to the URL. Currently two styles are available:
+
+#### Flat (default)
+
+```
+https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat
+```
+
+![Badge flat style](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage&style=flat)
+
+#### Flat square
+
+```
+https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat-square
+```
+
+![Badge flat square style](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage&style=flat-square)
+
+## Environment Variables
[Environment variables](../../../ci/variables/README.html#variables) can be set in an environment to be available to a runner.
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 57a48d0c0fa..e0a48908122 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -344,19 +344,23 @@ module API
class GroupDetail < Group
expose :projects, using: Entities::Project do |group, options|
- GroupProjectsFinder.new(
+ projects = GroupProjectsFinder.new(
group: group,
current_user: options[:current_user],
options: { only_owned: true }
).execute
+
+ Entities::Project.prepare_relation(projects)
end
expose :shared_projects, using: Entities::Project do |group, options|
- GroupProjectsFinder.new(
+ projects = GroupProjectsFinder.new(
group: group,
current_user: options[:current_user],
options: { only_shared: true }
).execute
+
+ Entities::Project.prepare_relation(projects)
end
end
@@ -964,7 +968,7 @@ module API
if options[:group_members]
options[:group_members].find { |member| member.source_id == project.namespace_id }
else
- project.group.group_member(options[:current_user])
+ project.group.highest_group_member(options[:current_user])
end
end
end
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index aacdca3871a..f5359fd316c 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -20,12 +20,15 @@ module API
desc: 'Return tags sorted in updated by `asc` or `desc` order.'
optional :order_by, type: String, values: %w[name updated], default: 'updated',
desc: 'Return tags ordered by `name` or `updated` fields.'
+ optional :search, type: String, desc: 'Return list of tags matching the search criteria'
use :pagination
end
get ':id/repository/tags' do
- tags = ::Kaminari.paginate_array(::TagsFinder.new(user_project.repository, sort: "#{params[:order_by]}_#{params[:sort]}").execute)
+ tags = ::TagsFinder.new(user_project.repository,
+ sort: "#{params[:order_by]}_#{params[:sort]}",
+ search: params[:search]).execute
- present paginate(tags), with: Entities::Tag, project: user_project
+ present paginate(::Kaminari.paginate_array(tags)), with: Entities::Tag, project: user_project
end
desc 'Get a single repository tag' do
diff --git a/lib/gitlab/tracing/jaeger_factory.rb b/lib/gitlab/tracing/jaeger_factory.rb
index 0726f6b67f4..2682007302a 100644
--- a/lib/gitlab/tracing/jaeger_factory.rb
+++ b/lib/gitlab/tracing/jaeger_factory.rb
@@ -22,7 +22,7 @@ module Gitlab
service_name: service_name,
sampler: get_sampler(options[:sampler], options[:sampler_param]),
reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint])
- }
+ }.compact
extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug) # rubocop: disable CodeReuse/ActiveRecord
if extra_params.present?
diff --git a/package.json b/package.json
index 75df0ec3ff6..1fd7e53d62c 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,7 @@
"js-cookie": "^2.1.3",
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
- "katex": "^0.9.0",
+ "katex": "^0.10.0",
"marked": "^0.3.12",
"mermaid": "^8.0.0-rc.8",
"monaco-editor": "^0.14.3",
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index f325162d1c0..ffe8633dd16 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -116,23 +116,13 @@ module QA
end
private_class_method :evaluator
- def self.dynamic_attributes
- const_get(:DynamicAttributes)
- rescue NameError
- mod = const_set(:DynamicAttributes, Module.new)
-
- include mod
-
- mod
- end
-
class DSL
def initialize(base)
@base = base
end
def attribute(name, &block)
- @base.dynamic_attributes.module_eval do
+ @base.module_eval do
attr_writer(name)
define_method(name) do
diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb
index 9fd66f3a36a..c6243ff43fa 100644
--- a/qa/qa/resource/fork.rb
+++ b/qa/qa/resource/fork.rb
@@ -3,6 +3,13 @@
module QA
module Resource
class Fork < Base
+ attribute :project do
+ Resource::Project.fabricate! do |resource|
+ resource.name = push.project.name
+ resource.path_with_namespace = "#{user.name}/#{push.project.name}"
+ end
+ end
+
attribute :push do
Repository::ProjectPush.fabricate!
end
@@ -37,6 +44,8 @@ module QA
Page::Layout::Banner.perform do |page|
page.has_notice?('The project was successfully forked.')
end
+
+ populate(:project)
end
end
end
diff --git a/qa/qa/resource/merge_request_from_fork.rb b/qa/qa/resource/merge_request_from_fork.rb
index f91ae299d76..5d20a6e9c75 100644
--- a/qa/qa/resource/merge_request_from_fork.rb
+++ b/qa/qa/resource/merge_request_from_fork.rb
@@ -11,7 +11,7 @@ module QA
attribute :push do
Repository::ProjectPush.fabricate! do |resource|
- resource.project = fork
+ resource.project = fork.project
resource.branch_name = fork_branch
resource.file_name = 'file2.txt'
resource.user = fork.user
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 1fafbf5d73e..433e5a8f7c9 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -12,6 +12,10 @@ module QA
Group.fabricate!
end
+ attribute :path_with_namespace do
+ "#{group.sandbox.path}/#{group.path}/#{name}" if group
+ end
+
attribute :repository_ssh_location do
Page::Project::Show.perform do |page|
page.repository_clone_ssh_location
@@ -46,8 +50,14 @@ module QA
end
end
+ def fabricate_via_api!
+ resource_web_url(api_get)
+ rescue ResourceNotFoundError
+ super
+ end
+
def api_get_path
- "/projects/#{name}"
+ "/projects/#{CGI.escape(path_with_namespace)}"
end
def api_post_path
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 6dcd74471fe..6ca7af8a3af 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -5,18 +5,18 @@ module QA
describe 'Merge request creation from fork' do
it 'user forks a project, submits a merge request and maintainer merges it' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
merge_request = Resource::MergeRequestFromFork.fabricate! do |merge_request|
merge_request.fork_branch = 'feature-branch'
end
- Page::Main::Menu.perform { |main| main.sign_out }
- Page::Main::Login.perform { |login| login.sign_in_using_credentials }
+ Page::Main::Menu.perform(&:sign_out)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
merge_request.visit!
- Page::MergeRequest::Show.perform { |show| show.merge! }
+ Page::MergeRequest::Show.perform(&:merge!)
expect(page).to have_content('The changes were merged')
end
diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb
index b8c406ae72a..a2a3ad01749 100644
--- a/qa/spec/resource/base_spec.rb
+++ b/qa/spec/resource/base_spec.rb
@@ -213,6 +213,42 @@ describe QA::Resource::Base do
.to raise_error(described_class::NoValueError, "No value was computed for no_block of #{resource.class.name}.")
end
end
+
+ context 'when multiple resources have the same attribute name' do
+ let(:base) do
+ Class.new(QA::Resource::Base) do
+ def fabricate!
+ 'any'
+ end
+
+ def self.current_url
+ 'http://stub'
+ end
+ end
+ end
+ let(:first_resource) do
+ Class.new(base) do
+ attribute :test do
+ 'first block'
+ end
+ end
+ end
+ let(:second_resource) do
+ Class.new(base) do
+ attribute :test do
+ 'second block'
+ end
+ end
+ end
+
+ it 'has unique attribute values' do
+ first_result = first_resource.fabricate!(resource: first_resource.new)
+ second_result = second_resource.fabricate!(resource: second_resource.new)
+
+ expect(first_result.test).to eq 'first block'
+ expect(second_result.test).to eq 'second block'
+ end
+ end
end
describe '#web_url' do
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
index 2556bc3ae50..8eac3d9a459 100644
--- a/spec/controllers/projects/badges_controller_spec.rb
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -22,7 +22,44 @@ describe Projects::BadgesController do
expect(response).to have_gitlab_http_status(:ok)
end
- def get_badge(badge)
- get badge, params: { namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref }, format: :svg
+ it 'renders the `flat` badge layout by default' do
+ get_badge(:coverage)
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+
+ context 'when style param is set to `flat`' do
+ it 'renders the `flat` badge layout' do
+ get_badge(:coverage, 'flat')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
+
+ context 'when style param is set to an invalid type' do
+ it 'renders the `flat` (default) badge layout' do
+ get_badge(:coverage, 'xxx')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
+
+ context 'when style param is set to `flat-square`' do
+ it 'renders the `flat-square` badge layout' do
+ get_badge(:coverage, 'flat-square')
+
+ expect(response).to render_template('projects/badges/badge_flat-square')
+ end
+ end
+
+ def get_badge(badge, style = nil)
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ ref: pipeline.ref,
+ style: style
+ }
+
+ get badge, params: params, format: :svg
end
end
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
index 729e71b87a6..6464398cea1 100644
--- a/spec/controllers/projects/error_tracking_controller_spec.rb
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -20,18 +20,6 @@ describe Projects::ErrorTrackingController do
expect(response).to render_template(:index)
end
- context 'with feature flag disabled' do
- before do
- stub_feature_flags(error_tracking: false)
- end
-
- it 'returns 404' do
- get :index, params: project_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
context 'with insufficient permissions' do
before do
project.add_guest(user)
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 5b3256bf409..a2c3bb2919d 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1118,6 +1118,7 @@ describe Projects::IssuesController do
context 'when user is setting notes filters' do
let(:issuable) { issue }
+ let(:issuable_parent) { project }
let!(:discussion_note) { create(:discussion_note_on_issue, :system, noteable: issuable, project: project) }
it_behaves_like 'issuable notes filter'
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 4f4d3ca226f..4451fd227e8 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -78,6 +78,7 @@ describe Projects::MergeRequestsController do
context 'when user is setting notes filters' do
let(:issuable) { merge_request }
+ let(:issuable_parent) { project }
let!(:discussion_note) { create(:discussion_note_on_merge_request, :system, noteable: issuable, project: project) }
let!(:discussion_comment) { create(:discussion_note_on_merge_request, noteable: issuable, project: project) }
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index 810f5bb64ba..d989ec22481 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -41,18 +41,6 @@ describe Projects::Settings::OperationsController do
end
end
- context 'with feature flag disabled' do
- before do
- stub_feature_flags(error_tracking: false)
- end
-
- it 'renders 404' do
- get :show, params: project_params(project)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
context 'with insufficient permissions' do
before do
project.add_reporter(user)
@@ -121,18 +109,6 @@ describe Projects::Settings::OperationsController do
end
end
- context 'with feature flag disabled' do
- before do
- stub_feature_flags(error_tracking: false)
- end
-
- it 'renders 404' do
- patch :update, params: project_params(project)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
context 'with insufficient permissions' do
before do
project.add_reporter(user)
diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb
index 6a23d6b78ab..678ce80b382 100644
--- a/spec/features/markdown/math_spec.rb
+++ b/spec/features/markdown/math_spec.rb
@@ -16,7 +16,7 @@ describe 'Math rendering', :js do
visit project_issue_path(project, issue)
- expect(page).to have_selector('.katex .mord.mathit', text: 'b')
- expect(page).to have_selector('.katex-display .mord.mathit', text: 'b')
+ expect(page).to have_selector('.katex .mord.mathdefault', text: 'b')
+ expect(page).to have_selector('.katex-display .mord.mathdefault', text: 'b')
end
end
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
index 1f2328a6dd8..06290c67c70 100644
--- a/spec/features/projects/settings/operations_settings_spec.rb
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -8,32 +8,16 @@ describe 'Projects > Settings > For a forked project', :js do
let(:role) { :maintainer }
before do
- stub_feature_flags(error_tracking: true)
sign_in(user)
project.add_role(user, role)
end
describe 'Sidebar > Operations' do
- context 'when sidebar feature flag enabled' do
- it 'renders the settings link in the sidebar' do
- visit project_path(project)
- wait_for_requests
+ it 'renders the settings link in the sidebar' do
+ visit project_path(project)
+ wait_for_requests
- expect(page).to have_selector('a[title="Operations"]', visible: false)
- end
- end
-
- context 'when sidebar feature flag disabled' do
- before do
- stub_feature_flags(error_tracking: false)
- end
-
- it 'does not render the settings link in the sidebar' do
- visit project_path(project)
- wait_for_requests
-
- expect(page).not_to have_selector('a[title="Operations"]', visible: false)
- end
+ expect(page).to have_selector('a[title="Operations"]', visible: false)
end
end
end
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index d8733941181..c595c38ef55 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -628,4 +628,26 @@ describe('DiffsStoreMutations', () => {
expect(file.parallel_diff_lines[1].right.hasForm).toBe(false);
});
});
+
+ describe('SET_TREE_DATA', () => {
+ it('sets treeEntries and tree in state', () => {
+ const state = {
+ treeEntries: {},
+ tree: [],
+ };
+
+ mutations[types.SET_TREE_DATA](state, {
+ treeEntries: { file: { name: 'index.js' } },
+ tree: ['tree'],
+ });
+
+ expect(state.treeEntries).toEqual({
+ file: {
+ name: 'index.js',
+ },
+ });
+
+ expect(state.tree).toEqual(['tree']);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 036b320b314..ea86844ddca 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -601,4 +601,123 @@ describe('DiffsStoreUtils', () => {
expect(utils.getDiffMode({})).toBe('replaced');
});
});
+
+ describe('getLowestSingleFolder', () => {
+ it('returns path and tree of lowest single folder tree', () => {
+ const folder = {
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ name: 'javascripts',
+ type: 'tree',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ },
+ ],
+ },
+ ],
+ };
+ const { path, treeAcc } = utils.getLowestSingleFolder(folder);
+
+ expect(path).toEqual('app/javascripts');
+ expect(treeAcc).toEqual([
+ {
+ type: 'blob',
+ name: 'index.js',
+ },
+ ]);
+ });
+
+ it('returns passed in folders path & tree when more than tree exists', () => {
+ const folder = {
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ name: 'spec',
+ type: 'blob',
+ tree: [],
+ },
+ ],
+ };
+ const { path, treeAcc } = utils.getLowestSingleFolder(folder);
+
+ expect(path).toEqual('app');
+ expect(treeAcc).toBeNull();
+ });
+ });
+
+ describe('flattenTree', () => {
+ it('returns flattened directory structure', () => {
+ const tree = [
+ {
+ type: 'tree',
+ name: 'app',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ tree: [],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'spec',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [],
+ },
+ {
+ type: 'blob',
+ name: 'index_spec.js',
+ tree: [],
+ },
+ ],
+ },
+ ];
+ const flattened = utils.flattenTree(tree);
+
+ expect(flattened).toEqual([
+ {
+ type: 'tree',
+ name: 'app/javascripts',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ tree: [],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'spec',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [],
+ },
+ {
+ type: 'blob',
+ name: 'index_spec.js',
+ tree: [],
+ },
+ ],
+ },
+ ]);
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
index 423cd6dee0f..33be63a3a1e 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
@@ -61,7 +61,7 @@ describe('Suggestion component', () => {
describe('mounted', () => {
it('renders a flash container', () => {
- expect(vm.$el.querySelector('.flash-container')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-suggestions-flash')).not.toBeNull();
});
it('renders a container for suggestions', () => {
diff --git a/spec/lib/gitlab/tracing/jaeger_factory_spec.rb b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
index 3bffeb28830..3d6a007cfd9 100644
--- a/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
+++ b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
@@ -6,36 +6,62 @@ describe Gitlab::Tracing::JaegerFactory do
describe '.create_tracer' do
let(:service_name) { 'rspec' }
- it 'processes default connections' do
- expect(described_class.create_tracer(service_name, {})).to respond_to(:active_span)
+ shared_examples_for 'a jaeger tracer' do
+ it 'responds to active_span methods' do
+ expect(tracer).to respond_to(:active_span)
+ end
+
+ it 'yields control' do
+ expect { |b| tracer.start_active_span('operation_name', &b) }.to yield_control
+ end
+ end
+
+ context 'processes default connections' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, {}) }
+ end
end
- it 'handles debug options' do
- expect(described_class.create_tracer(service_name, { debug: "1" })).to respond_to(:active_span)
+ context 'handles debug options' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { debug: "1" }) }
+ end
end
- it 'handles const sampler' do
- expect(described_class.create_tracer(service_name, { sampler: "const", sampler_param: "1" })).to respond_to(:active_span)
+ context 'handles const sampler' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { sampler: "const", sampler_param: "1" }) }
+ end
end
- it 'handles probabilistic sampler' do
- expect(described_class.create_tracer(service_name, { sampler: "probabilistic", sampler_param: "0.5" })).to respond_to(:active_span)
+ context 'handles probabilistic sampler' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { sampler: "probabilistic", sampler_param: "0.5" }) }
+ end
end
- it 'handles http_endpoint configurations' do
- expect(described_class.create_tracer(service_name, { http_endpoint: "http://localhost:1234" })).to respond_to(:active_span)
+ context 'handles http_endpoint configurations' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { http_endpoint: "http://localhost:1234" }) }
+ end
end
- it 'handles udp_endpoint configurations' do
- expect(described_class.create_tracer(service_name, { udp_endpoint: "localhost:4321" })).to respond_to(:active_span)
+ context 'handles udp_endpoint configurations' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { udp_endpoint: "localhost:4321" }) }
+ end
end
- it 'ignores invalid parameters' do
- expect(described_class.create_tracer(service_name, { invalid: "true" })).to respond_to(:active_span)
+ context 'ignores invalid parameters' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { invalid: "true" }) }
+ end
end
- it 'accepts the debug parameter when strict_parser is set' do
- expect(described_class.create_tracer(service_name, { debug: "1", strict_parsing: "1" })).to respond_to(:active_span)
+ context 'accepts the debug parameter when strict_parser is set' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { debug: "1", strict_parsing: "1" }) }
+ end
end
it 'rejects invalid parameters when strict_parser is set' do
diff --git a/spec/migrations/README.md b/spec/migrations/README.md
index 49760fa62b8..5df44dbc355 100644
--- a/spec/migrations/README.md
+++ b/spec/migrations/README.md
@@ -22,39 +22,33 @@ migrate the database **down** to the previous migration version.
With this approach you can test a migration against a database schema that this
migration has been written for.
-Use `migrate!` helper to run the migration that is under test.
-
The `after` hook will migrate the database **up** and reinstitutes the latest
schema version, so that the process does not affect subsequent specs and
ensures proper isolation.
-## Testing a class that is not an ActiveRecord::Migration
-
-In order to test a class that is not a migration itself, you will need to
-manually provide a required schema version. Please add a `schema` tag to a
-context that you want to switch the database schema within.
-
-Example: `describe SomeClass, :migration, schema: 20170608152748`.
-
## Available helpers
Use `table` helper to create a temporary `ActiveRecord::Base` derived model
for a table.
-Use `migrate!` helper to run the migration that is under test. It will not only
+See `spec/support/helpers/migrations_helpers.rb` for all the available helpers.
+
+## Testing a class that is an ActiveRecord::Migration
+
+In order to test a class that is an `ActiveRecord::Migration`, you will need to
+manually `require` the migration file because it is not autoloaded with Rails.
+
+Use `migrate!` helper to run the migration that is under test. It will not only
run migration, but will also bump the schema version in the `schema_migrations`
table. It is necessary because in the `after` hook we trigger the rest of
the migrations, and we need to know where to start.
-See `spec/support/migrations_helpers.rb` for all the available helpers.
+### Example
-## An example
+This spec tests the [`db/post_migrate/20170526185842_migrate_pipeline_stages.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.6.5/db/post_migrate/20170526185842_migrate_pipeline_stages.rb) migration. You can find the complete spec on [`spec/migrations/migrate_pipeline_stages_spec.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.6.5/spec/migrations/migrate_pipeline_stages_spec.rb).
```ruby
require 'spec_helper'
-
-# Load a migration class.
-
require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb')
describe MigratePipelineStages, :migration do
@@ -86,6 +80,56 @@ describe MigratePipelineStages, :migration do
end
```
+## Testing a class that is not an ActiveRecord::Migration
+
+To test a class that is not an `ActiveRecord::Migration` (a background migration),
+you will need to manually provide a required schema version. Please add a
+schema tag to a context that you want to switch the database schema within.
+
+Example: `describe SomeClass, :migration, schema: 20170608152748`.
+
+### Example
+
+This spec tests the [`lib/gitlab/background_migration/archive_legacy_traces.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.6.5/lib/gitlab/background_migration/archive_legacy_traces.rb)
+background migration. You can find the complete spec on
+[`spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.6.5/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb)
+
+```ruby
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do
+ include TraceHelpers
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:builds) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+
+ before do
+ namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123)
+ @build = builds.create!(id: 1, project_id: 123, status: 'success', type: 'Ci::Build')
+ end
+
+ context 'when trace file exsits at the right place' do
+ before do
+ create_legacy_trace(@build, 'trace in file')
+ end
+
+ it 'correctly archive legacy traces' do
+ expect(job_artifacts.count).to eq(0)
+ expect(File.exist?(legacy_trace_path(@build))).to be_truthy
+
+ described_class.new.perform(1, 1)
+
+ expect(job_artifacts.count).to eq(1)
+ expect(File.exist?(legacy_trace_path(@build))).to be_falsy
+ expect(File.read(archived_trace_path(job_artifacts.first))).to eq('trace in file')
+ end
+ end
+end
+```
+
## Best practices
1. Note that this type of tests do not run within the transaction, we use
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 1afc2436bb5..60d89313f07 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -3028,6 +3028,24 @@ describe Ci::Build do
subject.drop!
end
end
+
+ context 'when associated deployment failed to update its status' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, deployable: build) }
+
+ before do
+ allow_any_instance_of(Deployment)
+ .to receive(:drop!).and_raise('Unexpected error')
+ end
+
+ it 'can drop the build' do
+ expect(Gitlab::Sentry).to receive(:track_exception)
+
+ expect { build.drop! }.not_to raise_error
+
+ expect(build).to be_failed
+ end
+ end
end
describe '.matches_tag_ids' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index e63881242f6..9dc32a815d8 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -722,6 +722,42 @@ describe Group do
end
end
+ describe '#highest_group_member', :nested_groups do
+ let(:nested_group) { create(:group, parent: group) }
+ let(:nested_group_2) { create(:group, parent: nested_group) }
+ let(:user) { create(:user) }
+
+ subject(:highest_group_member) { nested_group_2.highest_group_member(user) }
+
+ context 'when the user is not a member of any group in the hierarchy' do
+ it 'returns nil' do
+ expect(highest_group_member).to be_nil
+ end
+ end
+
+ context 'when the user is only a member of one group in the hierarchy' do
+ before do
+ nested_group.add_developer(user)
+ end
+
+ it 'returns that group member' do
+ expect(highest_group_member.access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when the user is a member of several groups in the hierarchy' do
+ before do
+ group.add_owner(user)
+ nested_group.add_developer(user)
+ nested_group_2.add_maintainer(user)
+ end
+
+ it 'returns the group member with the highest access level' do
+ expect(highest_group_member.access_level).to eq(Gitlab::Access::OWNER)
+ end
+ end
+ end
+
describe '#has_parent?' do
context 'when the group has a parent' do
it 'should be truthy' do
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index c9dfc5c4a7e..7176bc23e34 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -382,6 +382,20 @@ describe API::Groups do
expect(response_project_ids(json_response, 'shared_projects'))
.to contain_exactly(projects[:public].id, projects[:internal].id)
end
+
+ it 'avoids N+1 queries' do
+ get api("/groups/#{group1.id}", admin)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/groups/#{group1.id}", admin)
+ end.count
+
+ create(:project, namespace: group1)
+
+ expect do
+ get api("/groups/#{group1.id}", admin)
+ end.not_to exceed_query_limit(control_count)
+ end
end
context "when authenticated as admin" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a01b494f615..7248908b494 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1147,6 +1147,40 @@ describe API::Projects do
.to eq(Gitlab::Access::OWNER)
end
end
+
+ context 'nested group project', :nested_groups do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+ let(:project2) { create(:project, group: nested_group) }
+
+ before do
+ project2.group.parent.add_owner(user)
+ end
+
+ it 'sets group access and return 200' do
+ get api("/projects/#{project2.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['permissions']['project_access']).to be_nil
+ expect(json_response['permissions']['group_access']['access_level'])
+ .to eq(Gitlab::Access::OWNER)
+ end
+
+ context 'with various access levels across nested groups' do
+ before do
+ project2.group.add_maintainer(user)
+ end
+
+ it 'sets the maximum group access and return 200' do
+ get api("/projects/#{project2.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['permissions']['project_access']).to be_nil
+ expect(json_response['permissions']['group_access']['access_level'])
+ .to eq(Gitlab::Access::OWNER)
+ end
+ end
+ end
end
end
end
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index d09b6fe72b1..fffe878ddbd 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -54,6 +54,18 @@ describe API::Tags do
end
end
+ context 'searching' do
+ it 'only returns searched tags' do
+ get api("#{route}", user), params: { search: 'v1.1.0' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]['name']).to eq('v1.1.0')
+ end
+ end
+
shared_examples_for 'repository tags' do
it 'returns the repository tags' do
get api(route, current_user)
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 28cb90e450e..c63fbcdd84e 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -132,6 +132,17 @@ describe 'Git LFS File Locking API' do
expect(json_response['lock'].keys).to match_array(%w(id path locked_at owner))
end
+
+ context 'when a maintainer uses force' do
+ let(:authorization) { authorize_user(maintainer) }
+
+ it 'deletes the lock' do
+ project.add_maintainer(maintainer)
+ post_lfs_json url, { force: true }, headers
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
end
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 12ddf8447bd..dfbdfa2ab69 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -281,6 +281,40 @@ describe Projects::DestroyService do
end
end
+ context 'repository +deleted path removal' do
+ def removal_path(path)
+ "#{path}+#{project.id}#{described_class::DELETED_FLAG}"
+ end
+
+ context 'regular phase' do
+ it 'schedules +deleted removal of existing repos' do
+ service = described_class.new(project, user, {})
+ allow(service).to receive(:schedule_stale_repos_removal)
+
+ expect(GitlabShellWorker).to receive(:perform_in)
+ .with(5.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path))
+
+ service.execute
+ end
+ end
+
+ context 'stale cleanup' do
+ let!(:async) { true }
+
+ it 'schedules +deleted wiki and repo removal' do
+ allow(ProjectDestroyWorker).to receive(:perform_async)
+
+ expect(GitlabShellWorker).to receive(:perform_in)
+ .with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path))
+
+ expect(GitlabShellWorker).to receive(:perform_in)
+ .with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.wiki.disk_path))
+
+ destroy_project(project, user, {})
+ end
+ end
+ end
+
context '#attempt_restore_repositories' do
let(:path) { project.disk_path + '.git' }
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index dbdca99b5aa..0acc9e2a836 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -1,8 +1,16 @@
shared_examples 'issuable notes filter' do
+ let(:params) do
+ if issuable_parent.is_a?(Project)
+ { namespace_id: issuable_parent.namespace, project_id: issuable_parent, id: issuable.iid }
+ else
+ { group_id: issuable_parent, id: issuable.to_param }
+ end
+ end
+
it 'sets discussion filter' do
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
expect(user.reload.notes_filter_for(issuable)).to eq(notes_filter)
expect(UserPreference.count).to eq(1)
@@ -13,7 +21,7 @@ shared_examples 'issuable notes filter' do
expect_any_instance_of(issuable.class).to receive(:expire_note_etag_cache)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
end
it 'does not expires notes e-tag cache for issuable if filter did not change' do
@@ -22,14 +30,14 @@ shared_examples 'issuable notes filter' do
expect_any_instance_of(issuable.class).not_to receive(:expire_note_etag_cache)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
end
it 'does not set notes filter when database is in read only mode' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
expect(user.reload.notes_filter_for(issuable)).to eq(0)
end
@@ -37,7 +45,7 @@ shared_examples 'issuable notes filter' do
it 'returns only user comments' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
@@ -47,7 +55,7 @@ shared_examples 'issuable notes filter' do
it 'returns only activity notes' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
@@ -60,7 +68,7 @@ shared_examples 'issuable notes filter' do
expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
end
end
end
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index 752fd82c5e8..8e34521c7c8 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -13,8 +13,6 @@ describe 'projects/settings/operations/show' do
describe 'Operations > Error Tracking' do
before do
- stub_feature_flags(error_tracking: true)
-
project.add_reporter(user)
allow(view).to receive(:error_tracking_setting)
diff --git a/yarn.lock b/yarn.lock
index fadfeb3dc49..7b3144fca16 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2282,7 +2282,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2, commander@^2.10.0, commander@^2.18.0, commander@^2.19.0:
+commander@2, commander@^2.10.0, commander@^2.16.0, commander@^2.18.0, commander@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
@@ -6330,12 +6330,12 @@ karma@^3.0.0:
tmp "0.0.33"
useragent "2.2.1"
-katex@^0.9.0:
- version "0.9.0"
- resolved "https://registry.yarnpkg.com/katex/-/katex-0.9.0.tgz#26a7d082c21d53725422d2d71da9b2d8455fbd4a"
- integrity sha512-lp3x90LT1tDZBW2tjLheJ98wmRMRjUHwk4QpaswT9bhqoQZ+XA4cPcjcQBxgOQNwaOSt6ZeL/a6GKQ1of3LFxQ==
+katex@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/katex/-/katex-0.10.0.tgz#da562e5d0d5cc3aa602e27af8a9b8710bfbce765"
+ integrity sha512-/WRvx+L1eVBrLwX7QzKU1dQuaGnE7E8hDvx3VWfZh9HbMiCfsKWJNnYZ0S8ZMDAfAyDSofdyXIrH/hujF1fYXg==
dependencies:
- match-at "^0.1.1"
+ commander "^2.16.0"
keyv@3.0.0:
version "3.0.0"
@@ -6672,11 +6672,6 @@ marked@^0.3.12, marked@~0.3.6:
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
-match-at@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.1.tgz#25d040d291777704d5e6556bbb79230ec2de0540"
- integrity sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q==
-
math-random@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"