diff options
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" |