diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-12 18:08:32 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-12 18:08:32 +0300 |
commit | b17f0b91a66f2101a54dd1efed0c4973f04b1daf (patch) | |
tree | 55d2a3dc5581b90bd1304c5d53d4d5b5da0076c9 | |
parent | b47e7cd6b2049c1fb87e7c4ffd1de898cda52364 (diff) |
Add latest changes from gitlab-org/gitlab@master
125 files changed, 1345 insertions, 1152 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index 181e8c8811c..fea3956bfe8 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -94,7 +94,8 @@ - name: postgres:11.6 command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] - name: redis:4.0-alpine - - name: elasticsearch:6.4.2 + - name: elasticsearch:7.9.2 + command: ["elasticsearch", "-E", "discovery.type=single-node"] variables: POSTGRES_HOST_AUTH_METHOD: trust @@ -104,7 +105,8 @@ - name: postgres:12 command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] - name: redis:4.0-alpine - - name: elasticsearch:6.4.2 + - name: elasticsearch:7.9.2 + command: ["elasticsearch", "-E", "discovery.type=single-node"] variables: POSTGRES_HOST_AUTH_METHOD: trust diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 70fc7c8d6c3..84c4fe93ed8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1165,11 +1165,6 @@ Rails/SaveBang: - 'spec/services/users/repair_ldap_blocked_service_spec.rb' - 'spec/services/verify_pages_domain_service_spec.rb' - 'spec/sidekiq/cron/job_gem_dependency_spec.rb' - - 'spec/support/migrations_helpers/cluster_helpers.rb' - - 'spec/support/migrations_helpers/namespaces_helper.rb' - - 'spec/support/shared_contexts/email_shared_context.rb' - - 'spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb' - - 'spec/support/shared_contexts/mailers/notify_shared_context.rb' # Offense count: 187 # Cop supports --auto-correct. @@ -290,7 +290,7 @@ gem 'gitlab_chronic_duration', '~> 0.10.6.2' gem 'rack-proxy', '~> 0.6.0' gem 'sassc-rails', '~> 2.1.0' -gem 'uglifier', '~> 2.7.2' +gem 'terser', '~> 1.0' gem 'addressable', '~> 2.7' gem 'font-awesome-rails', '~> 4.7' @@ -430,7 +430,7 @@ end gem 'octokit', '~> 4.15' # https://gitlab.com/gitlab-org/gitlab/issues/207207 -gem 'gitlab-mail_room', '~> 0.0.6', require: 'mail_room' +gem 'gitlab-mail_room', '~> 0.0.7', require: 'mail_room' gem 'email_reply_trimmer', '~> 0.1' gem 'html2text' diff --git a/Gemfile.lock b/Gemfile.lock index 7a24130096b..97aa43db2a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -312,7 +312,7 @@ GEM tzinfo eventmachine (1.2.7) excon (0.71.1) - execjs (2.6.0) + execjs (2.7.0) expression_parser (0.9.0) extended-markdown-filter (0.6.0) html-pipeline (~> 2.0) @@ -436,7 +436,7 @@ GEM opentracing (~> 0.4) redis (> 3.0.0, < 5.0.0) gitlab-license (1.0.0) - gitlab-mail_room (0.0.6) + gitlab-mail_room (0.0.7) gitlab-markup (1.7.1) gitlab-net-dns (0.9.1) gitlab-puma (4.3.5.gitlab.3) @@ -1130,6 +1130,8 @@ GEM temple (0.8.2) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + terser (1.0.1) + execjs (>= 0.3.0, < 3) test-prof (0.12.0) text (1.3.1) thin (1.7.2) @@ -1157,9 +1159,6 @@ GEM thread_safe (~> 0.1) u2f (0.2.1) uber (0.1.0) - uglifier (2.7.2) - execjs (>= 0.3.0) - json (>= 1.8.0) unf (0.1.4) unf_ext unf_ext (0.0.7.5) @@ -1327,7 +1326,7 @@ DEPENDENCIES gitlab-fog-azure-rm (~> 1.0) gitlab-labkit (= 0.12.1) gitlab-license (~> 1.0) - gitlab-mail_room (~> 0.0.6) + gitlab-mail_room (~> 0.0.7) gitlab-markup (~> 1.7.1) gitlab-net-dns (~> 0.9.1) gitlab-puma (~> 4.3.3.gitlab.2) @@ -1483,13 +1482,13 @@ DEPENDENCIES stackprof (~> 0.2.15) state_machines-activerecord (~> 0.6.0) sys-filesystem (~> 1.1.6) + terser (~> 1.0) test-prof (~> 0.12.0) thin (~> 1.7.0) timecop (~> 0.9.1) toml-rb (~> 1.0.0) truncato (~> 0.7.11) u2f (~> 0.2.1) - uglifier (~> 2.7.2) unf (~> 0.1.4) unicorn (~> 5.5) unicorn-worker-killer (~> 0.4.4) diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js new file mode 100644 index 00000000000..bbcc40ab9fe --- /dev/null +++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js @@ -0,0 +1,96 @@ +import { flatten } from 'lodash'; +import { s__ } from '~/locale'; +import AccessorUtilities from '~/lib/utils/accessor'; +import { shouldDisableShortcuts } from './shortcuts_toggle'; + +export const LOCAL_STORAGE_KEY = 'gl-keyboard-shortcuts-customizations'; + +let parsedCustomizations = {}; +const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe(); + +if (localStorageIsSafe) { + try { + parsedCustomizations = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || '{}'); + } catch (e) { + /* do nothing */ + } +} + +/** + * A map of command => keys of all keyboard shortcuts + * that have been customized by the user. + * + * @example + * { "globalShortcuts.togglePerformanceBar": ["p e r f"] } + * + * @type { Object.<string, string[]> } + */ +export const customizations = parsedCustomizations; + +// All available commands +export const TOGGLE_PERFORMANCE_BAR = 'globalShortcuts.togglePerformanceBar'; + +/** All keybindings, grouped and ordered with descriptions */ +export const keybindingGroups = [ + { + groupId: 'globalShortcuts', + name: s__('KeyboardShortcuts|Global Shortcuts'), + keybindings: [ + { + description: s__('KeyboardShortcuts|Toggle the Performance Bar'), + command: TOGGLE_PERFORMANCE_BAR, + // eslint-disable-next-line @gitlab/require-i18n-strings + defaultKeys: ['p b'], + }, + ], + }, +] + + // For each keybinding object, add a `customKeys` property populated with the + // user's custom keybindings (if the command has been customized). + // `customKeys` will be `undefined` if the command hasn't been customized. + .map(group => { + return { + ...group, + keybindings: group.keybindings.map(binding => ({ + ...binding, + customKeys: customizations[binding.command], + })), + }; + }); + +/** + * A simple map of command => keys. All user customizations are included in this map. + * This mapping is used to simplify `keysFor` below. + * + * @example + * { "globalShortcuts.togglePerformanceBar": ["p e r f"] } + */ +const commandToKeys = flatten(keybindingGroups.map(group => group.keybindings)).reduce( + (acc, binding) => { + acc[binding.command] = binding.customKeys || binding.defaultKeys; + return acc; + }, + {}, +); + +/** + * Gets keyboard shortcuts associated with a command + * + * @param {string} command The command string. All command + * strings are available as imports from this file. + * + * @returns {string[]} An array of keyboard shortcut strings bound to the command + * + * @example + * import { keysFor, TOGGLE_PERFORMANCE_BAR } from '~/behaviors/shortcuts/keybindings' + * + * Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), handler); + */ +export const keysFor = command => { + if (shouldDisableShortcuts()) { + return []; + } + + return commandToKeys[command]; +}; diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 3cb2d6719c8..a53150f8d61 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -9,6 +9,7 @@ import axios from '../../lib/utils/axios_utils'; import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility'; import findAndFollowLink from '../../lib/utils/navigation_utility'; import { parseBoolean, getCspNonceValue } from '~/lib/utils/common_utils'; +import { keysFor, TOGGLE_PERFORMANCE_BAR } from './keybindings'; const defaultStopCallback = Mousetrap.prototype.stopCallback; Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) { @@ -70,7 +71,7 @@ export default class Shortcuts { Mousetrap.bind('s', Shortcuts.focusSearch); Mousetrap.bind('/', Shortcuts.focusSearch); Mousetrap.bind('f', this.focusFilter.bind(this)); - Mousetrap.bind('p b', Shortcuts.onTogglePerfBar); + Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), Shortcuts.onTogglePerfBar); const findFileURL = document.body.dataset.findFile; diff --git a/app/assets/javascripts/design_management/components/list/item.vue b/app/assets/javascripts/design_management/components/list/item.vue index b179b1b5e79..fa09c7c15cc 100644 --- a/app/assets/javascripts/design_management/components/list/item.vue +++ b/app/assets/javascripts/design_management/components/list/item.vue @@ -132,7 +132,13 @@ export default { > <div v-if="icon.name" data-testid="designEvent" class="design-event gl-absolute"> <span :title="icon.tooltip" :aria-label="icon.tooltip"> - <gl-icon :name="icon.name" :size="18" :class="icon.classes" /> + <gl-icon + :name="icon.name" + :size="18" + :class="icon.classes" + data-qa-selector="design_status_icon" + :data-qa-status="icon.name" + /> </span> </div> <gl-intersection-observer @appear="onAppear"> diff --git a/app/assets/javascripts/diffs/components/diff_row_utils.js b/app/assets/javascripts/diffs/components/diff_row_utils.js index 998320c3245..08b87a4bade 100644 --- a/app/assets/javascripts/diffs/components/diff_row_utils.js +++ b/app/assets/javascripts/diffs/components/diff_row_utils.js @@ -1,4 +1,3 @@ -import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; import { MATCH_LINE_TYPE, @@ -23,21 +22,8 @@ export const isMatchLine = type => type === MATCH_LINE_TYPE; export const isMetaLine = type => [OLD_NO_NEW_LINE_TYPE, NEW_NO_NEW_LINE_TYPE, EMPTY_CELL_TYPE].includes(type); -export const shouldRenderCommentButton = ( - isLoggedIn, - isCommentButtonRendered, - featureMergeRefHeadComments = false, -) => { - if (!isCommentButtonRendered) { - return false; - } - - if (isLoggedIn) { - const isDiffHead = parseBoolean(getParameterByName('diff_head')); - return !isDiffHead || featureMergeRefHeadComments; - } - - return false; +export const shouldRenderCommentButton = (isLoggedIn, isCommentButtonRendered) => { + return isCommentButtonRendered && isLoggedIn; }; export const hasDiscussions = line => line?.discussions?.length > 0; diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue index f9d491603cb..99cf79a70d4 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue @@ -81,11 +81,7 @@ export default { return utils.addCommentTooltip(this.line); }, shouldRenderCommentButton() { - return utils.shouldRenderCommentButton( - this.isLoggedIn, - true, - gon.features?.mergeRefHeadComments, - ); + return utils.shouldRenderCommentButton(this.isLoggedIn, true); }, shouldShowCommentButton() { return utils.shouldShowCommentButton( diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue index 06dcadb2dc1..cdc6db791f0 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue @@ -102,11 +102,7 @@ export default { return utils.addCommentTooltip(this.line.right); }, shouldRenderCommentButton() { - return utils.shouldRenderCommentButton( - this.isLoggedIn, - this.isCommentButtonRendered, - gon.features?.mergeRefHeadComments, - ); + return utils.shouldRenderCommentButton(this.isLoggedIn, this.isCommentButtonRendered); }, shouldShowCommentButtonLeft() { return utils.shouldShowCommentButton( diff --git a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js index 1bfbab9ef96..f8b47727921 100644 --- a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js +++ b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js @@ -1,6 +1,6 @@ import { __ } from '~/locale'; -export default IssuableTokenKeys => { +export default (IssuableTokenKeys, disableTargetBranchFilter = false) => { const draftToken = { token: { formattedKey: __('Draft'), @@ -51,18 +51,20 @@ export default IssuableTokenKeys => { IssuableTokenKeys.tokenKeysWithAlternative.push(draftToken.token); IssuableTokenKeys.conditions.push(...draftToken.conditions); - const targetBranchToken = { - formattedKey: __('Target-Branch'), - key: 'target-branch', - type: 'string', - param: '', - symbol: '', - icon: 'arrow-right', - tag: 'branch', - }; + if (!disableTargetBranchFilter) { + const targetBranchToken = { + formattedKey: __('Target-Branch'), + key: 'target-branch', + type: 'string', + param: '', + symbol: '', + icon: 'arrow-right', + tag: 'branch', + }; - IssuableTokenKeys.tokenKeys.push(targetBranchToken); - IssuableTokenKeys.tokenKeysWithAlternative.push(targetBranchToken); + IssuableTokenKeys.tokenKeys.push(targetBranchToken); + IssuableTokenKeys.tokenKeysWithAlternative.push(targetBranchToken); + } const approvedBy = { token: { diff --git a/app/assets/javascripts/issuable_show/components/issuable_header.vue b/app/assets/javascripts/issuable_show/components/issuable_header.vue new file mode 100644 index 00000000000..3815c50cac6 --- /dev/null +++ b/app/assets/javascripts/issuable_show/components/issuable_header.vue @@ -0,0 +1,120 @@ +<script> +import { GlIcon, GlButton, GlTooltipDirective, GlAvatarLink, GlAvatarLabeled } from '@gitlab/ui'; + +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; + +export default { + components: { + GlIcon, + GlButton, + GlAvatarLink, + GlAvatarLabeled, + TimeAgoTooltip, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + createdAt: { + type: String, + required: true, + }, + author: { + type: Object, + required: true, + }, + statusBadgeClass: { + type: String, + required: false, + default: '', + }, + statusIcon: { + type: String, + required: false, + default: '', + }, + blocked: { + type: Boolean, + required: false, + default: false, + }, + confidential: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + authorId() { + return getIdFromGraphQLId(`${this.author.id}`); + }, + }, + mounted() { + this.toggleSidebarButtonEl = document.querySelector('.js-toggle-right-sidebar-button'); + }, + methods: { + handleRightSidebarToggleClick() { + if (this.toggleSidebarButtonEl) { + this.toggleSidebarButtonEl.dispatchEvent(new Event('click')); + } + }, + }, +}; +</script> + +<template> + <div class="detail-page-header"> + <div class="detail-page-header-body"> + <div data-testid="status" class="issuable-status-box status-box" :class="statusBadgeClass"> + <gl-icon v-if="statusIcon" :name="statusIcon" class="d-block d-sm-none" /> + <span class="d-none d-sm-block"><slot name="status-badge"></slot></span> + </div> + <div class="issuable-meta gl-display-flex gl-align-items-center"> + <div class="gl-display-inline-block"> + <div v-if="blocked" data-testid="blocked" class="issuable-warning-icon inline"> + <gl-icon name="lock" :aria-label="__('Blocked')" /> + </div> + <div v-if="confidential" data-testid="confidential" class="issuable-warning-icon inline"> + <gl-icon name="eye-slash" :aria-label="__('Confidential')" /> + </div> + </div> + <span> + {{ __('Opened') }} + <time-ago-tooltip data-testid="startTimeItem" :time="createdAt" /> + {{ __('by') }} + </span> + <gl-avatar-link + data-testid="avatar" + :data-user-id="authorId" + :data-username="author.username" + :data-name="author.name" + :href="author.webUrl" + target="_blank" + class="js-user-link gl-ml-2" + > + <gl-avatar-labeled + :size="24" + :src="author.avatarUrl" + :label="author.name" + class="d-none d-sm-inline-flex gl-ml-1" + /> + <strong class="author d-sm-none d-inline">@{{ author.username }}</strong> + </gl-avatar-link> + </div> + <gl-button + data-testid="sidebar-toggle" + icon="chevron-double-lg-left" + class="d-block d-sm-none gutter-toggle issuable-gutter-toggle" + :aria-label="__('Expand sidebar')" + @click="handleRightSidebarToggleClick" + /> + </div> + <div + data-testid="header-actions" + class="detail-page-header-actions js-issuable-actions js-issuable-buttons gl-display-flex gl-display-md-block" + > + <slot name="header-actions"></slot> + </div> + </div> +</template> diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 261f76a0f2d..103ea839a4b 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -86,6 +86,21 @@ export const getDayName = date => ][date.getDay()]; /** + * Returns the i18n month name from a given date + * @example + * formatDateAsMonth(new Date('2020-06-28')) -> 'Jun' + * @param {String} datetime where month is extracted from + * @param {Object} options + * @param {Boolean} options.abbreviated whether to use the abbreviated month string, or not + * @return {String} the i18n month name + */ +export function formatDateAsMonth(datetime, options = {}) { + const { abbreviated = true } = options; + const month = new Date(datetime).getMonth(); + return getMonthNames(abbreviated)[month]; +} + +/** * @example * dateFormat('2017-12-05','mmm d, yyyy h:MMtt Z' ) -> "Dec 5, 2017 12:00am GMT+0000" * @param {date} datetime diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js index 10df18c85e7..7adae2cdb05 100644 --- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js +++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js @@ -5,7 +5,7 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered import { FILTERED_SEARCH } from '~/pages/constants'; document.addEventListener('DOMContentLoaded', () => { - addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys); + addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys, true); initFilteredSearch({ page: FILTERED_SEARCH.MERGE_REQUESTS, diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 3c432fe09c0..7d622dda070 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -38,6 +38,14 @@ top: $mr-file-header-top; z-index: 120; + .with-system-header & { + top: $mr-file-header-top + $system-header-height; + } + + .with-system-header.with-performance-bar & { + top: $mr-file-header-top + $system-header-height + $performance-bar-height; + } + &::before { content: ''; position: absolute; @@ -1078,6 +1086,14 @@ table.code { max-height: calc(100vh - #{$top-pos}); z-index: 202; + .with-system-header & { + top: $top-pos + $system-header-height; + } + + .with-system-header.with-performance-bar & { + top: $top-pos + $system-header-height + $performance-bar-height; + } + .with-performance-bar & { $performance-bar-top-pos: $performance-bar-height + $top-pos; top: $performance-bar-top-pos; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index ddec04b1b0c..5835f665ada 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -771,6 +771,14 @@ $mr-widget-min-height: 69px; position: sticky; top: $header-height + $mr-tabs-height; + .with-system-header & { + top: $header-height + $mr-tabs-height + $system-header-height; + } + + .with-system-header.with-performance-bar & { + top: $header-height + $mr-tabs-height + $system-header-height + $performance-bar-height; + } + .mr-version-menus-container { flex-wrap: nowrap; } @@ -788,6 +796,14 @@ $mr-widget-min-height: 69px; background-color: $white; border-bottom: 1px solid $border-color; + .with-system-header & { + top: $header-height + $system-header-height; + } + + .with-system-header.with-performance-bar & { + top: $header-height + $system-header-height + $performance-bar-height; + } + @include media-breakpoint-up(sm) { position: -webkit-sticky; position: sticky; diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index 4548595d968..e4c3df6ccc3 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -17,13 +17,7 @@ module SnippetsActions respond_to :html end - def edit - # We need to load some info from the existing blob - snippet.content = blob.data - snippet.file_name = blob.path - - render 'edit' - end + def edit; end # This endpoint is being replaced by Snippets::BlobController#raw # Support for old raw links will be maintainted via this action but @@ -55,7 +49,6 @@ module SnippetsActions def show respond_to do |format| format.html do - conditionally_expand_blob(blob) @note = Note.new(noteable: @snippet, project: @snippet.project) @noteable = @snippet @@ -80,29 +73,6 @@ module SnippetsActions end end end - - def update - update_params = snippet_params.merge(spammable_params) - - service_response = Snippets::UpdateService.new(@snippet.project, current_user, update_params).execute(@snippet) - @snippet = service_response.payload[:snippet] - - handle_repository_error(:edit) - end - - def destroy - service_response = Snippets::DestroyService.new(current_user, @snippet).execute - - if service_response.success? - redirect_to gitlab_dashboard_snippets_path(@snippet), status: :found - elsif service_response.http_status == 403 - access_denied! - else - redirect_to gitlab_snippet_path(@snippet), - status: :found, - alert: service_response.message - end - end # rubocop:enable Gitlab/ModuleWithInstanceVariables private @@ -124,12 +94,4 @@ module SnippetsActions def convert_line_endings(content) params[:line_ending] == 'raw' ? content : content.gsub(/\r\n/, "\n") end - - def handle_repository_error(action) - errors = Array(snippet.errors.delete(:repository)) - - flash.now[:alert] = errors.first if errors.present? - - recaptcha_check_with_fallback(errors.empty?) { render action } - end end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 28aef6f4328..07c38431f0f 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -173,7 +173,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic end def update_diff_discussion_positions! - return unless Feature.enabled?(:merge_ref_head_comments, @merge_request.target_project, default_enabled: true) return unless Feature.enabled?(:merge_red_head_comments_position_on_demand, @merge_request.target_project, default_enabled: true) return if @merge_request.has_any_diff_note_positions? diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8ca70602c89..ae055e9494c 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -29,7 +29,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action only: [:show] do push_frontend_experiment(:suggest_pipeline) push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true) - push_frontend_feature_flag(:merge_ref_head_comments, @project, default_enabled: true) push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true) push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true) push_frontend_feature_flag(:file_identifier_hash) diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 49840e847f2..779e149bb9c 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -7,12 +7,11 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController before_action :check_snippets_available! - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] + before_action :snippet, only: [:show, :edit, :raw, :toggle_award_emoji, :mark_as_spam] - before_action :authorize_create_snippet!, only: [:new, :create] - before_action :authorize_read_snippet!, except: [:new, :create, :index] - before_action :authorize_update_snippet!, only: [:edit, :update] - before_action :authorize_admin_snippet!, only: [:destroy] + before_action :authorize_create_snippet!, only: :new + before_action :authorize_read_snippet!, except: [:new, :index] + before_action :authorize_update_snippet!, only: :edit def index @snippet_counts = ::Snippets::CountService @@ -33,14 +32,6 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController @snippet = @noteable = @project.snippets.build end - def create - create_params = snippet_params.merge(spammable_params) - service_response = ::Snippets::CreateService.new(project, current_user, create_params).execute - @snippet = service_response.payload[:snippet] - - handle_repository_error(:new) - end - protected alias_method :awardable, :snippet @@ -49,8 +40,4 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController def spammable_path project_snippet_path(@project, @snippet) end - - def snippet_params - params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description) - end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index e68b821459d..913b1e3bb6e 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -6,12 +6,11 @@ class SnippetsController < Snippets::ApplicationController include ToggleAwardEmoji include SpammableActions - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] + before_action :snippet, only: [:show, :edit, :raw, :toggle_award_emoji, :mark_as_spam] - before_action :authorize_create_snippet!, only: [:new, :create] + before_action :authorize_create_snippet!, only: :new before_action :authorize_read_snippet!, only: [:show, :raw] - before_action :authorize_update_snippet!, only: [:edit, :update] - before_action :authorize_admin_snippet!, only: [:destroy] + before_action :authorize_update_snippet!, only: :edit skip_before_action :authenticate_user!, only: [:index, :show, :raw] @@ -40,18 +39,6 @@ class SnippetsController < Snippets::ApplicationController @snippet = PersonalSnippet.new end - def create - create_params = snippet_params.merge(files: params.delete(:files)) - service_response = Snippets::CreateService.new(nil, current_user, create_params).execute - @snippet = service_response.payload[:snippet] - - if service_response.error? && @snippet.errors[:repository].present? - handle_repository_error(:new) - else - recaptcha_check_with_fallback { render :new } - end - end - protected alias_method :awardable, :snippet @@ -60,8 +47,4 @@ class SnippetsController < Snippets::ApplicationController def spammable_path snippet_path(@snippet) end - - def snippet_params - params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description).merge(spammable_params) - end end diff --git a/app/helpers/container_expiration_policies_helper.rb b/app/helpers/container_expiration_policies_helper.rb index cc6d717ce35..52f68ac53f0 100644 --- a/app/helpers/container_expiration_policies_helper.rb +++ b/app/helpers/container_expiration_policies_helper.rb @@ -24,4 +24,9 @@ module ContainerExpirationPoliciesHelper end end end + + def container_expiration_policies_historic_entry_enabled?(project) + Gitlab::CurrentSettings.container_expiration_policies_enable_historic_entries || + Feature.enabled?(:container_expiration_policies_historic_entry, project) + end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 21c3ab3eebe..42f326d40dc 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -526,7 +526,6 @@ module Ci .concat(job_jwt_variables) .concat(scoped_variables) .concat(job_variables) - .concat(environment_changed_page_variables) .concat(persisted_environment_variables) .to_runner_variables end @@ -563,15 +562,6 @@ module Ci end end - def environment_changed_page_variables - Gitlab::Ci::Variables::Collection.new.tap do |variables| - break variables unless environment_status && Feature.enabled?(:modifed_path_ci_variables, project) - - variables.append(key: 'CI_MERGE_REQUEST_CHANGED_PAGE_PATHS', value: environment_status.changed_paths.join(',')) - variables.append(key: 'CI_MERGE_REQUEST_CHANGED_PAGE_URLS', value: environment_status.changed_urls.join(',')) - end - end - def deploy_token_variables Gitlab::Ci::Variables::Collection.new.tap do |variables| break variables unless gitlab_deploy_token diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb index 46e41c22139..55ea4e2fe18 100644 --- a/app/models/environment_status.rb +++ b/app/models/environment_status.rb @@ -72,14 +72,6 @@ class EnvironmentStatus .merge_request_diff_files.where(deleted_file: false) end - def changed_paths - changes.map { |change| change[:path] } - end - - def changed_urls - changes.map { |change| change[:external_url] } - end - def has_route_map? project.route_map_for(sha).present? end diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb index 2957205a81c..497471699b2 100644 --- a/app/serializers/discussion_entity.rb +++ b/app/serializers/discussion_entity.rb @@ -69,9 +69,6 @@ class DiscussionEntity < Grape::Entity end def display_merge_ref_discussions?(discussion) - return unless discussion.diff_discussion? - return if discussion.legacy_diff_discussion? - - Feature.enabled?(:merge_ref_head_comments, discussion.project, default_enabled: true) + discussion.diff_discussion? && !discussion.legacy_diff_discussion? end end diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb index 12c04772ef4..b41a5fa317e 100644 --- a/app/services/merge_requests/mergeability_check_service.rb +++ b/app/services/merge_requests/mergeability_check_service.rb @@ -125,8 +125,6 @@ module MergeRequests end def update_diff_discussion_positions! - return if Feature.disabled?(:merge_ref_head_comments, merge_request.target_project, default_enabled: true) - Discussions::CaptureDiffNotePositionsService.new(merge_request).execute end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index e26f662a697..48f44affb23 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -70,7 +70,7 @@ module Notes Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note)) end - if Feature.enabled?(:merge_ref_head_comments, project, default_enabled: true) && note.for_merge_request? && note.diff_note? && note.start_of_discussion? + if note.for_merge_request? && note.diff_note? && note.start_of_discussion? Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion) end end diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index dd9fd34f284..2111b66d26e 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -14,7 +14,7 @@ .top-area = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set -= render 'shared/issuable/search_bar', type: :merge_requests += render 'shared/issuable/search_bar', type: :merge_requests, disable_target_branch: true - if current_user && @no_filters_set = render 'shared/dashboard/no_filter_selected' diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 768acac96c0..a257f2e9433 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,14 +1,14 @@ = form_tag project_compare_index_path(@project), method: :post, class: 'form-inline js-requires-input js-signature-container', data: { 'signatures-path' => signatures_namespace_project_compare_index_path } do - if params[:to] && params[:from] .compare-switch-container - = link_to sprite_icon('substitute'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions' + = link_to sprite_icon('substitute'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn gl-button btn-white', title: 'Swap revisions' .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .input-group.inline-input-group %span.input-group-prepend .input-group-text = s_("CompareBranches|Source") = hidden_field_tag :to, params[:to] - = button_tag type: 'button', title: params[:to], class: "btn form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do + = button_tag type: 'button', title: params[:to], class: "btn gl-button form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do .dropdown-toggle-text.str-truncated.monospace.float-left= params[:to] || _("Select branch/tag") = sprite_icon('chevron-down', css_class: 'float-right') = render 'shared/ref_dropdown' @@ -19,12 +19,12 @@ .input-group-text = s_("CompareBranches|Target") = hidden_field_tag :from, params[:from] - = button_tag type: 'button', title: params[:from], class: "btn form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do + = button_tag type: 'button', title: params[:from], class: "btn gl-button form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do .dropdown-toggle-text.str-truncated.monospace.float-left= params[:from] || _("Select branch/tag") = sprite_icon('chevron-down', css_class: 'float-right') = render 'shared/ref_dropdown' - = button_tag s_("CompareBranches|Compare"), class: "btn btn-success commits-compare-btn" + = button_tag s_("CompareBranches|Compare"), class: "btn gl-button btn-success commits-compare-btn" - if @merge_request.present? = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'gl-ml-3 btn' - elsif create_mr_button? diff --git a/app/views/projects/registry/settings/_index.haml b/app/views/projects/registry/settings/_index.haml index b53fac83830..c6fae2cc7a1 100644 --- a/app/views/projects/registry/settings/_index.haml +++ b/app/views/projects/registry/settings/_index.haml @@ -5,4 +5,4 @@ older_than_options: older_than_options.to_json, is_admin: current_user&.admin.to_s, admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), - enable_historic_entries: Gitlab::CurrentSettings.try(:container_expiration_policies_enable_historic_entries).to_s} } + enable_historic_entries: container_expiration_policies_historic_entry_enabled?(@project).to_s} } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index cd7d792738d..d654bbe0700 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -1,6 +1,7 @@ - type = local_assigns.fetch(:type) - board = local_assigns.fetch(:board, nil) - show_sorting_dropdown = local_assigns.fetch(:show_sorting_dropdown, true) +- disable_target_branch = local_assigns.fetch(:disable_target_branch, false) - placeholder = local_assigns[:placeholder] || _('Search or filter results...') - is_not_boards_modal_or_productivity_analytics = type != :boards_modal && type != :productivity_analytics - block_css_class = is_not_boards_modal_or_productivity_analytics ? 'row-content-block second-block' : '' @@ -154,11 +155,12 @@ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } %button.btn.btn-link{ type: 'button' } = _('No') - #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu - %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } - %li.filter-dropdown-item - %button.btn.btn-link.js-data-value.monospace - {{title}} + - unless disable_target_branch + #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + %button.btn.btn-link.js-data-value.monospace + {{title}} = render_if_exists 'shared/issuable/filter_weight', type: type diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb index e66bad3962f..9071e4b8a1b 100644 --- a/app/workers/git_garbage_collect_worker.rb +++ b/app/workers/git_garbage_collect_worker.rb @@ -100,7 +100,7 @@ class GitGarbageCollectWorker # rubocop:disable Scalability/IdempotentWorker end def flush_ref_caches(project) - project.repository.after_create_branch + project.repository.expire_branches_cache project.repository.branch_names project.repository.has_visible_content? end diff --git a/changelogs/unreleased/-231207-projects-compare.yml b/changelogs/unreleased/-231207-projects-compare.yml new file mode 100644 index 00000000000..37ebf5e7e4f --- /dev/null +++ b/changelogs/unreleased/-231207-projects-compare.yml @@ -0,0 +1,5 @@ +--- +title: Apply GitLab UI button styles to buttons in app/views/projects/compare directory +merge_request: 44342 +author: Lakshit +type: other diff --git a/changelogs/unreleased/18969-allow-optional-caching-in-failed-builds.yml b/changelogs/unreleased/18969-allow-optional-caching-in-failed-builds.yml new file mode 100644 index 00000000000..7b55ecc75d8 --- /dev/null +++ b/changelogs/unreleased/18969-allow-optional-caching-in-failed-builds.yml @@ -0,0 +1,5 @@ +--- +title: Add cache:when keyword for ci yml config +merge_request: 41822 +author: +type: added diff --git a/changelogs/unreleased/244050-feature-flag-to-allow-old-projects-to-have-a-cleanup-policy.yml b/changelogs/unreleased/244050-feature-flag-to-allow-old-projects-to-have-a-cleanup-policy.yml new file mode 100644 index 00000000000..1a66f602e54 --- /dev/null +++ b/changelogs/unreleased/244050-feature-flag-to-allow-old-projects-to-have-a-cleanup-policy.yml @@ -0,0 +1,5 @@ +--- +title: Add feature flag for a phased rollout of cleanup policies +merge_request: 44444 +author: +type: added diff --git a/changelogs/unreleased/mb_rails_save_bang_fix3.yml b/changelogs/unreleased/mb_rails_save_bang_fix3.yml new file mode 100644 index 00000000000..af25c188ead --- /dev/null +++ b/changelogs/unreleased/mb_rails_save_bang_fix3.yml @@ -0,0 +1,5 @@ +--- +title: Fix Rails/SaveBang offenses in spec/support/* +merge_request: 44884 +author: matthewbried +type: other diff --git a/changelogs/unreleased/ph-207481-targetBranchFilterDashboard.yml b/changelogs/unreleased/ph-207481-targetBranchFilterDashboard.yml new file mode 100644 index 00000000000..e1eed671357 --- /dev/null +++ b/changelogs/unreleased/ph-207481-targetBranchFilterDashboard.yml @@ -0,0 +1,5 @@ +--- +title: Disable target branch filter option on merge requests dashboard +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/ph-218300-fixedSystemHeaderOnDiffs.yml b/changelogs/unreleased/ph-218300-fixedSystemHeaderOnDiffs.yml new file mode 100644 index 00000000000..01c7d14e600 --- /dev/null +++ b/changelogs/unreleased/ph-218300-fixedSystemHeaderOnDiffs.yml @@ -0,0 +1,5 @@ +--- +title: Fixed merge request tabs overlapping with system header +merge_request: +author: +type: fixed diff --git a/config/environments/production.rb b/config/environments/production.rb index 393a274606e..d9b3ee354b0 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -12,7 +12,7 @@ Rails.application.configure do config.public_file_server.enabled = false # Compress JavaScripts and CSS. - config.assets.js_compressor = :uglifier + config.assets.js_compressor = :terser # config.assets.css_compressor = :sass # Don't fallback to assets pipeline if a precompiled asset is missed diff --git a/config/feature_flags/development/additional_snowplow_tracking.yml b/config/feature_flags/development/additional_snowplow_tracking.yml index 8cff8389dbb..3e2b542b1a8 100644 --- a/config/feature_flags/development/additional_snowplow_tracking.yml +++ b/config/feature_flags/development/additional_snowplow_tracking.yml @@ -1,6 +1,6 @@ name: additional_snowplow_tracking introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/12088 rollout_issue_url: -group: group::product_analytics +group: group::product analytics type: development default_enabled: false diff --git a/config/feature_flags/development/ci_child_of_child_pipeline.yml b/config/feature_flags/development/ci_child_of_child_pipeline.yml index 02122076434..7a90334619a 100644 --- a/config/feature_flags/development/ci_child_of_child_pipeline.yml +++ b/config/feature_flags/development/ci_child_of_child_pipeline.yml @@ -2,6 +2,6 @@ name: ci_child_of_child_pipeline introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41102 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/243747 -group: 'group::continuous integration' +group: group::continuous integration type: development default_enabled: true diff --git a/config/feature_flags/development/ci_lint_vue.yml b/config/feature_flags/development/ci_lint_vue.yml index 832f543ba3d..a72e97909be 100644 --- a/config/feature_flags/development/ci_lint_vue.yml +++ b/config/feature_flags/development/ci_lint_vue.yml @@ -2,6 +2,6 @@ name: ci_lint_vue introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42401 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/249661 -group: group::continuous intergration +group: group::continuous integration type: development default_enabled: false
\ No newline at end of file diff --git a/config/feature_flags/development/container_expiration_policies_historic_entry.yml b/config/feature_flags/development/container_expiration_policies_historic_entry.yml new file mode 100644 index 00000000000..0525f77eacf --- /dev/null +++ b/config/feature_flags/development/container_expiration_policies_historic_entry.yml @@ -0,0 +1,7 @@ +--- +name: container_expiration_policies_historic_entry +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44444 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/262639 +type: development +group: group::package +default_enabled: false diff --git a/config/feature_flags/development/deploy_boards_dedupe_instances.yml b/config/feature_flags/development/deploy_boards_dedupe_instances.yml index 04cbd6f6602..d407e11babd 100644 --- a/config/feature_flags/development/deploy_boards_dedupe_instances.yml +++ b/config/feature_flags/development/deploy_boards_dedupe_instances.yml @@ -3,5 +3,5 @@ name: deploy_boards_dedupe_instances introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40768 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258214 type: development -group: group::progressive-delivery +group: group::progressive delivery default_enabled: false diff --git a/config/feature_flags/development/drop_license_management_artifact.yml b/config/feature_flags/development/drop_license_management_artifact.yml index cb13486b7f5..34e10fa7ae6 100644 --- a/config/feature_flags/development/drop_license_management_artifact.yml +++ b/config/feature_flags/development/drop_license_management_artifact.yml @@ -2,6 +2,6 @@ name: drop_license_management_artifact introduced_by_url: rollout_issue_url: -group: composition_analysis +group: group::composition analysis type: development default_enabled: true diff --git a/config/feature_flags/development/ingress_modsecurity.yml b/config/feature_flags/development/ingress_modsecurity.yml index a8c8c3b26da..7ed1d089476 100644 --- a/config/feature_flags/development/ingress_modsecurity.yml +++ b/config/feature_flags/development/ingress_modsecurity.yml @@ -2,6 +2,6 @@ name: ingress_modsecurity introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20194 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258554 -group: "group::container security" +group: group::container security type: development default_enabled: false diff --git a/config/feature_flags/development/junit_pipeline_screenshots_view.yml b/config/feature_flags/development/junit_pipeline_screenshots_view.yml index a148c4b70f3..273e0ed450e 100644 --- a/config/feature_flags/development/junit_pipeline_screenshots_view.yml +++ b/config/feature_flags/development/junit_pipeline_screenshots_view.yml @@ -2,6 +2,6 @@ name: junit_pipeline_screenshots_view introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/202114 rollout_issue_url: -group: 'group::verify testing' +group: group::verify testing type: development default_enabled: false diff --git a/config/feature_flags/development/merge_ref_head_comments.yml b/config/feature_flags/development/merge_ref_head_comments.yml deleted file mode 100644 index 6f391860e29..00000000000 --- a/config/feature_flags/development/merge_ref_head_comments.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: merge_ref_head_comments -introduced_by_url: -rollout_issue_url: -group: -type: development -default_enabled: true diff --git a/config/feature_flags/development/modifed_path_ci_variables.yml b/config/feature_flags/development/modifed_path_ci_variables.yml deleted file mode 100644 index a72a5ae56e1..00000000000 --- a/config/feature_flags/development/modifed_path_ci_variables.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: modifed_path_ci_variables -introduced_by_url: -rollout_issue_url: -group: -type: development -default_enabled: false diff --git a/config/feature_flags/development/product_analytics.yml b/config/feature_flags/development/product_analytics.yml index cc1859e149c..02840f3212b 100644 --- a/config/feature_flags/development/product_analytics.yml +++ b/config/feature_flags/development/product_analytics.yml @@ -2,6 +2,6 @@ name: product_analytics introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36443 rollout_issue_url: -group: group::product_analytics +group: group::product analytics type: development default_enabled: false diff --git a/config/feature_flags/development/project_finder_similarity_sort.yml b/config/feature_flags/development/project_finder_similarity_sort.yml index c0460d44b6c..2d29bed82c4 100644 --- a/config/feature_flags/development/project_finder_similarity_sort.yml +++ b/config/feature_flags/development/project_finder_similarity_sort.yml @@ -3,5 +3,5 @@ name: project_finder_similarity_sort introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43136 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/263249 type: development -group: group::threat_insights +group: group::threat insights default_enabled: false diff --git a/config/feature_flags/development/push_rules_supersede_code_owners.yml b/config/feature_flags/development/push_rules_supersede_code_owners.yml new file mode 100644 index 00000000000..d185d19522d --- /dev/null +++ b/config/feature_flags/development/push_rules_supersede_code_owners.yml @@ -0,0 +1,7 @@ +--- +name: push_rules_supersede_code_owners +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44126 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/262019 +type: development +group: group::source code +default_enabled: false diff --git a/config/feature_flags/development/rebalance_issues.yml b/config/feature_flags/development/rebalance_issues.yml index 4c14824a35d..df04da8c8d3 100644 --- a/config/feature_flags/development/rebalance_issues.yml +++ b/config/feature_flags/development/rebalance_issues.yml @@ -2,6 +2,6 @@ name: rebalance_issues introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40124 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/239344 -group: 'group::project management' +group: group::project management type: development default_enabled: false diff --git a/config/feature_flags/development/save_raw_usage_data.yml b/config/feature_flags/development/save_raw_usage_data.yml index f71393414ae..b3c65c12e2d 100644 --- a/config/feature_flags/development/save_raw_usage_data.yml +++ b/config/feature_flags/development/save_raw_usage_data.yml @@ -2,6 +2,6 @@ name: save_raw_usage_data introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38457 rollout_issue_url: -group: group::product_analytics +group: group::product analytics type: development default_enabled: false diff --git a/config/feature_flags/development/track_issue_activity_actions.yml b/config/feature_flags/development/track_issue_activity_actions.yml index 034b697ab52..97deb11e2cf 100644 --- a/config/feature_flags/development/track_issue_activity_actions.yml +++ b/config/feature_flags/development/track_issue_activity_actions.yml @@ -2,6 +2,6 @@ name: track_issue_activity_actions introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40904 rollout_issue_url: -group: group::project_management +group: group::project management type: development default_enabled: false
\ No newline at end of file diff --git a/config/feature_flags/development/usage_data_api.yml b/config/feature_flags/development/usage_data_api.yml index 139e39807dc..83a08fa3c43 100644 --- a/config/feature_flags/development/usage_data_api.yml +++ b/config/feature_flags/development/usage_data_api.yml @@ -2,6 +2,6 @@ name: usage_data_api introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41301 rollout_issue_url: -group: group::product_analytics +group: group::product analytics type: development default_enabled: false diff --git a/config/feature_flags/development/usage_data_i_source_code_code_intelligence.yml b/config/feature_flags/development/usage_data_i_source_code_code_intelligence.yml index 9ea05b0c7df..15ce7194264 100644 --- a/config/feature_flags/development/usage_data_i_source_code_code_intelligence.yml +++ b/config/feature_flags/development/usage_data_i_source_code_code_intelligence.yml @@ -2,6 +2,6 @@ name: usage_data_i_source_code_code_intelligence introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41881 rollout_issue_url: -group: group::source_code +group: group::source code type: development default_enabled: true diff --git a/config/initializers/sprockets.rb b/config/initializers/sprockets.rb new file mode 100644 index 00000000000..a20b7dc75e9 --- /dev/null +++ b/config/initializers/sprockets.rb @@ -0,0 +1 @@ +Sprockets.register_compressor 'application/javascript', :terser, Terser::Compressor diff --git a/config/redis.cache.yml.example b/config/redis.cache.yml.example index b20f1dd2122..fb92c205ce1 100644 --- a/config/redis.cache.yml.example +++ b/config/redis.cache.yml.example @@ -26,7 +26,7 @@ production: # http://redis.io/topics/sentinel # # You must specify a list of a few sentinels that will handle client connection - # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html + # please read here for more information: https://docs.gitlab.com/ee/administration/redis/index.html ## # url: redis://master:6380 # sentinels: diff --git a/config/redis.queues.yml.example b/config/redis.queues.yml.example index 46ab39729c4..dd6c10e0e06 100644 --- a/config/redis.queues.yml.example +++ b/config/redis.queues.yml.example @@ -26,7 +26,7 @@ production: # http://redis.io/topics/sentinel # # You must specify a list of a few sentinels that will handle client connection - # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html + # please read here for more information: https://docs.gitlab.com/ee/administration/redis/index.html ## # url: redis://master:6381 # sentinels: diff --git a/config/redis.shared_state.yml.example b/config/redis.shared_state.yml.example index 05fed947f52..98f6f330bc7 100644 --- a/config/redis.shared_state.yml.example +++ b/config/redis.shared_state.yml.example @@ -26,7 +26,7 @@ production: # http://redis.io/topics/sentinel # # You must specify a list of a few sentinels that will handle client connection - # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html + # please read here for more information: https://docs.gitlab.com/ee/administration/redis/index.html ## # url: redis://master:6382 # sentinels: diff --git a/config/resque.yml.example b/config/resque.yml.example index 932c1553dfb..0f629a5229c 100644 --- a/config/resque.yml.example +++ b/config/resque.yml.example @@ -22,7 +22,7 @@ production: # http://redis.io/topics/sentinel # # You must specify a list of a few sentinels that will handle client connection - # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html + # please read here for more information: https://docs.gitlab.com/ee/administration/redis/index.html ## # url: redis://master:6379 # sentinels: diff --git a/config/routes/project.rb b/config/routes/project.rb index 1072a037823..5a30f1026f8 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -368,7 +368,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do resource :jira, only: [:show], controller: :jira end - resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do + resources :snippets, except: [:create, :update, :destroy], concerns: :awardable, constraints: { id: /\d+/ } do member do get :raw post :mark_as_spam diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb index 7bb82da4910..9e0c42fa07d 100644 --- a/config/routes/snippets.rb +++ b/config/routes/snippets.rb @@ -1,4 +1,4 @@ -resources :snippets, concerns: :awardable do +resources :snippets, except: [:create, :update, :destroy], concerns: :awardable, constraints: { id: /\d+/ } do member do get :raw post :mark_as_spam diff --git a/db/fixtures/development/29_instance_statistics.rb b/db/fixtures/development/29_instance_statistics.rb index e4ef0f26be0..02afdc61339 100644 --- a/db/fixtures/development/29_instance_statistics.rb +++ b/db/fixtures/development/29_instance_statistics.rb @@ -3,17 +3,31 @@ require './spec/support/sidekiq_middleware' Gitlab::Seeder.quiet do + chance_for_decrement = 0.1 # 10% chance that we'll generate smaller count than the previous count + max_increase = 10000 + max_decrease = 1000 + model_class = Analytics::InstanceStatistics::Measurement - recorded_at = Date.today - # Insert random counts for the last 60 days - measurements = 60.times.flat_map do - recorded_at = (recorded_at - 1.day).end_of_day - 5.minutes + measurements = model_class.identifiers.flat_map do |_, id| + recorded_at = 60.days.ago + current_count = rand(1_000_000) + + # Insert random counts for the last 60 days + Array.new(60) do + recorded_at = (recorded_at + 1.day).end_of_day - 5.minutes + + # Normally our counts should slowly increase as the gitlab instance grows. + # Small chance (10%) to have a slight decrease (simulating cleanups, bulk delete) + if rand < chance_for_decrement + current_count -= rand(max_decrease) + else + current_count += rand(max_increase) + end - model_class.identifiers.map do |_, id| { recorded_at: recorded_at, - count: rand(1_000_000), + count: current_count, identifier: id } end diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 6a6e7bde1b2..ca36f729c25 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -104,12 +104,12 @@ The following table lists available parameters for jobs: | [`script`](#script) | Shell script that is executed by a runner. | | [`after_script`](#before_script-and-after_script) | Override a set of commands that are executed after job. | | [`allow_failure`](#allow_failure) | Allow job to fail. Failed job does not contribute to commit status. | -| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:exclude`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`. | +| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:exclude`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, and `artifacts:reports`. | | [`before_script`](#before_script-and-after_script) | Override a set of commands that are executed before job. | -| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. | +| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, `cache:when`, and `cache:policy`. | | [`coverage`](#coverage) | Code coverage settings for a given job. | | [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. | -| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in` and `environment:action`. | +| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in`, and `environment:action`. | | [`except`](#onlyexcept-basic) | Limit when jobs are not created. Also available: [`except:refs`, `except:kubernetes`, `except:variables`, and `except:changes`](#onlyexcept-advanced). | | [`extends`](#extends) | Configuration entries that this job inherits from. | | [`image`](#image) | Use Docker images. Also available: `image:name` and `image:entrypoint`. | @@ -2914,6 +2914,28 @@ rspec: - binaries/ ``` +#### `cache:when` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18969) in GitLab 13.5 and GitLab Runner v13.5.0. + +`cache:when` defines when to save the cache, based on the status of the job. You can +set `cache:when` to: + +- `on_success` - save the cache only when the job succeeds. This is the default. +- `on_failure` - save the cache only when the job fails. +- `always` - save the cache regardless of the job status. + +For example, to store a cache whether or not the job fails or succeeds: + +```yaml +rspec: + script: rspec + cache: + paths: + - rspec/ + when: 'always' +``` + #### `cache:policy` > Introduced in GitLab 9.4. @@ -3236,7 +3258,7 @@ failure. 1. `on_failure` - upload artifacts only when the job fails. 1. `always` - upload artifacts regardless of the job status. -To upload artifacts only when job fails: +For example, to upload artifacts only when a job fails: ```yaml job: diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index ef23b6c4ed2..f909866d44e 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -76,6 +76,10 @@ How we use SVG for our [Icons and Illustrations](icons.md). General information about frontend [dependencies](dependencies.md) and how we manage them. +## Keyboard Shortcuts + +How we implement [keyboard shortcuts](keyboard_shortcuts.md) that can be customized and disabled. + ## Frontend FAQ Read the [frontend's FAQ](frontend_faq.md) for common small pieces of helpful information. diff --git a/doc/development/fe_guide/keyboard_shortcuts.md b/doc/development/fe_guide/keyboard_shortcuts.md new file mode 100644 index 00000000000..da9b3702a8d --- /dev/null +++ b/doc/development/fe_guide/keyboard_shortcuts.md @@ -0,0 +1,98 @@ +# Implementing keyboard shortcuts + +We use [Mousetrap](https://craig.is/killing/mice) to implement keyboard +shortcuts in GitLab. + +Mousetrap provides an API that allows keyboard shortcut strings (like +`mod+shift+p` or `p b`) to be bound to a JavaScript handler: + +```javascript +// Don't do this; see note below +Mousetrap.bind('p b', togglePerformanceBar) +``` + +However, associating a hard-coded key sequence to a handler (as shown above) +prevents these keyboard shortcuts from being customized or disabled by users. + +To allow keyboard shortcuts to be customized, commands are defined in +`~/behaviors/shortcuts/keybindings.js`. The `keysFor` method is responsible for +returning the correct key sequence for the provided command: + +```javascript +import { keysFor, TOGGLE_PERFORMANCE_BAR } from '~/behaviors/shortcuts/keybindings' + +Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), togglePerformanceBar); +``` + +## Shortcut customization + +`keybindings.js` stores keyboard shortcut customizations as a JSON string in +`localStorage`. When `keybindings.js` is first imported, it fetches any +customizations from `localStorage` and merges these customizations into the +default set of keybindings. There is no UI to edit these customizations. + +## Adding new shortcuts + +Because keyboard shortcuts can be customized or disabled by end users, +developers are encouraged to build _lots_ of keyboard shortcuts into GitLab. +Shortcuts that are less likely to be used should be +[disabled](#disabling-shortcuts) by default. + +To add a new shortcut, define and export a new command string in +`keybindings.js`: + +```javascript +export const MAKE_COFFEE = 'foodAndBeverage.makeCoffee'; +``` + +Next, add a new command definition under the appropriate group in the +`keybindingGroups` array: + +```javascript +{ + description: s__('KeyboardShortcuts|Make coffee'), + command: MAKE_COFFEE, + defaultKeys: ['mod+shift+c'], + customKeys: customizations[MAKE_COFFEE], +} +``` + +Finally, in the application code, import the `keysFor` function and the new +command and bind the shortcut to the handler using Mousetrap: + +```javascript +import { keysFor, MAKE_COFFEE } from '~/behaviors/shortcuts/keybindings' + +Mousetrap.bind(keysFor(MAKE_COFFEE), makeCoffee); +``` + +See the existing the command definitions in `keybindings.js` for more examples. + +## Disabling shortcuts + +A shortcut can be disabled, also known as _unassigned_, by assigning the +shortcut to an empty array `[]`. For example, to introduce a new shortcut that +is disabled by default, a command can be defined like this: + +```javascript +export const MAKE_MOCHA = 'foodAndBeverage.makeMocha'; + +{ + description: s__('KeyboardShortcuts|Make a mocha'), + command: MAKE_MOCHA, + defaultKeys: [], + customKeys: customizations[MAKE_MOCHA], +} +``` + +## Make cross-platform shortcuts + +It's difficult to make shortcuts that work well in all platforms and browsers. +This is one of the reasons that being able to customize and disable shortcuts is +so important. + +One important way to make keyboard shortcuts more portable is to use the `mod` +shortcut string, which resolves to `command` on Mac and `ctrl` otherwise. + +See [Mousetrap's documentation](https://craig.is/killing/mice#api.bind.combo) +for more information. diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 1105f419c8b..8cb20ed4829 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1419,26 +1419,20 @@ Example: ```markdown | header 1 | header 2 | header 3 | -| --- | ------ |---------:| +| --- | ------ |----------| | cell 1 | cell 2 | cell 3 | | cell 4 | cell 5 is longer | cell 6 is much longer than the others, but that's ok. It eventually wraps the text when the cell is too large for the display size. | -| cell 7 | | cell <br> 9 | -| cell 10 | <ul><li> - [ ] Task One </li></ul> | <ul><li> - [ ] Task Two </li><li> - [ ] Task Three </li></ul> | +| cell 7 | | cell 9 | ``` | header 1 | header 2 | header 3 | -| --- | ------ |---------:| +| --- | ------ |----------| | cell 1 | cell 2 | cell 3 | | cell 4 | cell 5 is longer | cell 6 is much longer than the others, but that's ok. It eventually wraps the text when the cell is too large for the display size. | -| cell 7 | | cell <br> 9 | -| cell 10 | <ul><li> - [ ] Task One </li></ul> | <ul><li> - [ ] Task Two </li><li> - [ ] Task Three </li></ul> | +| cell 7 | | cell 9 | Additionally, you can choose the alignment of text within columns by adding colons (`:`) -to the sides of the "dash" lines in the second row. This affects every cell in the column. - -NOTE: **Note:** -[Within GitLab itself](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/user/markdown.md#tables), -the headers are always left-aligned in Chrome and Firefox, and centered in Safari. +to the sides of the "dash" lines in the second row. This affects every cell in the column: ```markdown | Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | @@ -1452,6 +1446,34 @@ the headers are always left-aligned in Chrome and Firefox, and centered in Safar | Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | | Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | +[Within GitLab itself](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/user/markdown.md#tables), +the headers are always left-aligned in Chrome and Firefox, and centered in Safari. + +You can use HTML formatting to adjust the rendering of tables. For example, you can +use `<br>` tags to force a cell to have multiple lines: + +```markdown +| Name | Details | +|------|---------| +| Item1 | This is on one line | +| Item2 | This item has:<br>- Multiple items<br>- That we want listed separately | +``` + +| Name | Details | +|------|---------| +| Item1 | This is on one line | +| Item2 | This item has:<br>- Multiple items<br>- That we want listed separately | + +You can use HTML formatting within GitLab itself to add [task lists](#task-lists) with checkboxes, +but they do not render properly on `docs.gitlab.com`: + +```markdown +| header 1 | header 2 | +|----------|----------| +| cell 1 | cell 2 | +| cell 3 | <ul><li> - [ ] Task one </li><li> - [ ] Task two </li></ul> | +``` + #### Copy from spreadsheet and paste in Markdown [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27205) in GitLab 12.7. diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md index c003eec784b..33235eef7b8 100644 --- a/doc/user/packages/container_registry/index.md +++ b/doc/user/packages/container_registry/index.md @@ -469,6 +469,20 @@ Cleanup policies can be run on all projects, with these exceptions: There are performance risks with enabling it for all projects, especially if you are using an [external registry](./index.md#use-with-external-container-registries). +- For self-managed GitLab instances, you can enable or disable the cleanup policy for a specific + project. + + To enable it: + + ```ruby + Feature.enable(:container_expiration_policies_historic_entry, Project.find(<project id>)) + ``` + + To disable it: + + ```ruby + Feature.disable(:container_expiration_policies_historic_entry, Project.find(<project id>)) + ``` ### How the cleanup policy works diff --git a/lib/api/entities/job_request/cache.rb b/lib/api/entities/job_request/cache.rb index a75affbaf84..cd533d7e5b3 100644 --- a/lib/api/entities/job_request/cache.rb +++ b/lib/api/entities/job_request/cache.rb @@ -4,7 +4,7 @@ module API module Entities module JobRequest class Cache < Grape::Entity - expose :key, :untracked, :paths, :policy + expose :key, :untracked, :paths, :policy, :when end end end diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb index a304d9b724f..6b036182706 100644 --- a/lib/gitlab/ci/config/entry/cache.rb +++ b/lib/gitlab/ci/config/entry/cache.rb @@ -9,14 +9,28 @@ module Gitlab # class Cache < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Configurable + include ::Gitlab::Config::Entry::Validatable include ::Gitlab::Config::Entry::Attributable - ALLOWED_KEYS = %i[key untracked paths policy].freeze + ALLOWED_KEYS = %i[key untracked paths when policy].freeze + ALLOWED_POLICY = %w[pull-push push pull].freeze DEFAULT_POLICY = 'pull-push' + ALLOWED_WHEN = %w[on_success on_failure always].freeze + DEFAULT_WHEN = 'on_success' validations do - validates :config, allowed_keys: ALLOWED_KEYS - validates :policy, inclusion: { in: %w[pull-push push pull], message: 'should be pull-push, push, or pull' }, allow_blank: true + validates :config, type: Hash, allowed_keys: ALLOWED_KEYS + validates :policy, + inclusion: { in: ALLOWED_POLICY, message: 'should be pull-push, push, or pull' }, + allow_blank: true + + with_options allow_nil: true do + validates :when, + inclusion: { + in: ALLOWED_WHEN, + message: 'should be on_success, on_failure or always' + } + end end entry :key, Entry::Key, @@ -28,13 +42,15 @@ module Gitlab entry :paths, Entry::Paths, description: 'Specify which paths should be cached across builds.' - attributes :policy + attributes :policy, :when def value result = super result[:key] = key_value result[:policy] = policy || DEFAULT_POLICY + # Use self.when to avoid conflict with reserved word + result[:when] = self.when || DEFAULT_WHEN result end diff --git a/lib/gitlab/ci/config/entry/needs.rb b/lib/gitlab/ci/config/entry/needs.rb index d7ba8624882..66cd57b8cf3 100644 --- a/lib/gitlab/ci/config/entry/needs.rb +++ b/lib/gitlab/ci/config/entry/needs.rb @@ -7,7 +7,7 @@ module Gitlab ## # Entry that represents a set of needs dependencies. # - class Needs < ::Gitlab::Config::Entry::Node + class Needs < ::Gitlab::Config::Entry::ComposableArray include ::Gitlab::Config::Entry::Validatable validations do @@ -29,27 +29,16 @@ module Gitlab end end - def compose!(deps = nil) - super(deps) do - [@config].flatten.each_with_index do |need, index| - @entries[index] = ::Gitlab::Config::Entry::Factory.new(Entry::Need) - .value(need) - .with(key: "need", parent: self, description: "need definition.") # rubocop:disable CodeReuse/ActiveRecord - .create! - end - - @entries.each_value do |entry| - entry.compose!(deps) - end - end - end - def value - values = @entries.values.select(&:type) + values = @entries.select(&:type) values.group_by(&:type).transform_values do |values| values.map(&:value) end end + + def composable_class + Entry::Need + end end end end diff --git a/lib/gitlab/ci/config/entry/ports.rb b/lib/gitlab/ci/config/entry/ports.rb index 01ffcc7dd87..d26b31deca8 100644 --- a/lib/gitlab/ci/config/entry/ports.rb +++ b/lib/gitlab/ci/config/entry/ports.rb @@ -7,7 +7,7 @@ module Gitlab ## # Entry that represents a configuration of the ports of a Docker service. # - class Ports < ::Gitlab::Config::Entry::Node + class Ports < ::Gitlab::Config::Entry::ComposableArray include ::Gitlab::Config::Entry::Validatable validations do @@ -16,28 +16,8 @@ module Gitlab validates :config, port_unique: true end - def compose!(deps = nil) - super do - @entries = [] - @config.each do |config| - @entries << ::Gitlab::Config::Entry::Factory.new(Entry::Port) - .value(config || {}) - .with(key: "port", parent: self, description: "port definition.") # rubocop:disable CodeReuse/ActiveRecord - .create! - end - - @entries.each do |entry| - entry.compose!(deps) - end - end - end - - def value - @entries.map(&:value) - end - - def descendants - @entries + def composable_class + Entry::Port end end end diff --git a/lib/gitlab/ci/config/entry/rules.rb b/lib/gitlab/ci/config/entry/rules.rb index 2fbc3d9e367..bf74f995e80 100644 --- a/lib/gitlab/ci/config/entry/rules.rb +++ b/lib/gitlab/ci/config/entry/rules.rb @@ -4,7 +4,7 @@ module Gitlab module Ci class Config module Entry - class Rules < ::Gitlab::Config::Entry::Node + class Rules < ::Gitlab::Config::Entry::ComposableArray include ::Gitlab::Config::Entry::Validatable validations do @@ -12,24 +12,13 @@ module Gitlab validates :config, type: Array end - def compose!(deps = nil) - super(deps) do - @config.each_with_index do |rule, index| - @entries[index] = ::Gitlab::Config::Entry::Factory.new(Entry::Rules::Rule) - .value(rule) - .with(key: "rule", parent: self, description: "rule definition.") # rubocop:disable CodeReuse/ActiveRecord - .create! - end - - @entries.each_value do |entry| - entry.compose!(deps) - end - end - end - def value @config end + + def composable_class + Entry::Rules::Rule + end end end end diff --git a/lib/gitlab/ci/config/entry/services.rb b/lib/gitlab/ci/config/entry/services.rb index 83baa83711f..44e2903a300 100644 --- a/lib/gitlab/ci/config/entry/services.rb +++ b/lib/gitlab/ci/config/entry/services.rb @@ -7,7 +7,7 @@ module Gitlab ## # Entry that represents a configuration of Docker services. # - class Services < ::Gitlab::Config::Entry::Node + class Services < ::Gitlab::Config::Entry::ComposableArray include ::Gitlab::Config::Entry::Validatable validations do @@ -15,28 +15,8 @@ module Gitlab validates :config, services_with_ports_alias_unique: true, if: ->(record) { record.opt(:with_image_ports) } end - def compose!(deps = nil) - super do - @entries = [] - @config.each do |config| - @entries << ::Gitlab::Config::Entry::Factory.new(Entry::Service) - .value(config || {}) - .with(key: "service", parent: self, description: "service definition.") # rubocop:disable CodeReuse/ActiveRecord - .create! - end - - @entries.each do |entry| - entry.compose!(deps) - end - end - end - - def value - @entries.map(&:value) - end - - def descendants - @entries + def composable_class + Entry::Service end end end diff --git a/lib/gitlab/ci/pipeline/seed/build/cache.rb b/lib/gitlab/ci/pipeline/seed/build/cache.rb index a4127ea0be2..8d6fe13c3b9 100644 --- a/lib/gitlab/ci/pipeline/seed/build/cache.rb +++ b/lib/gitlab/ci/pipeline/seed/build/cache.rb @@ -13,6 +13,7 @@ module Gitlab @paths = local_cache.delete(:paths) @policy = local_cache.delete(:policy) @untracked = local_cache.delete(:untracked) + @when = local_cache.delete(:when) raise ArgumentError, "unknown cache keys: #{local_cache.keys}" if local_cache.any? end @@ -24,7 +25,8 @@ module Gitlab key: key_string, paths: @paths, policy: @policy, - untracked: @untracked + untracked: @untracked, + when: @when }.compact.presence }.compact } diff --git a/lib/gitlab/config/entry/composable_array.rb b/lib/gitlab/config/entry/composable_array.rb new file mode 100644 index 00000000000..e7ad259e826 --- /dev/null +++ b/lib/gitlab/config/entry/composable_array.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Gitlab + module Config + module Entry + ## + # Entry that represents a composable array definition + # + class ComposableArray < ::Gitlab::Config::Entry::Node + include ::Gitlab::Config::Entry::Validatable + include Gitlab::Utils::StrongMemoize + + # TODO: Refactor `Validatable` code so that validations can apply to a child class + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/263231 + validations do + validates :config, type: Array + end + + def compose!(deps = nil) + super do + @entries = Array(@entries) + + # TODO: Isolate handling for a hash via: `[@config].flatten` to the `Needs` entry + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/264376 + [@config].flatten.each_with_index do |value, index| + raise ArgumentError, 'Missing Composable class' unless composable_class + + composable_class_name = composable_class.name.demodulize.underscore + + @entries << ::Gitlab::Config::Entry::Factory.new(composable_class) + .value(value) + .with(key: composable_class_name, parent: self, description: "#{composable_class_name} definition") # rubocop:disable CodeReuse/ActiveRecord + .create! + end + + @entries.each do |entry| + entry.compose!(deps) + end + end + end + + def value + @entries.map(&:value) + end + + def descendants + @entries + end + + def composable_class + strong_memoize(:composable_class) do + opt(:composable_class) + end + end + end + end + end +end diff --git a/lib/gitlab/config/entry/composable_hash.rb b/lib/gitlab/config/entry/composable_hash.rb index 74070915940..9531b7e56fd 100644 --- a/lib/gitlab/config/entry/composable_hash.rb +++ b/lib/gitlab/config/entry/composable_hash.rb @@ -10,7 +10,7 @@ module Gitlab class ComposableHash < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Validatable - # TODO: Refactor Validatable so these validations will not apply to a child class + # TODO: Refactor `Validatable` code so that validations can apply to a child class # See: https://gitlab.com/gitlab-org/gitlab/-/issues/263231 validations do validates :config, type: Hash diff --git a/lib/system_check/app/redis_version_check.rb b/lib/system_check/app/redis_version_check.rb index 697ced3590b..a205861b9a9 100644 --- a/lib/system_check/app/redis_version_check.rb +++ b/lib/system_check/app/redis_version_check.rb @@ -37,7 +37,7 @@ module SystemCheck @custom_error_message ) for_more_information( - 'doc/administration/high_availability/redis.md#provide-your-own-redis-instance' + 'doc/administration/redis/index.html#redis-replication-and-failover-using-the-non-bundled-redis' ) fix_and_rerun end diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake index 1e28d15f75e..e2c92054d62 100644 --- a/lib/tasks/gettext.rake +++ b/lib/tasks/gettext.rake @@ -38,7 +38,7 @@ namespace :gettext do Rake::Task['gettext:find'].invoke # leave only the required changes. - unless system(*%w(git checkout -- locale/*/gitlab.po)) + unless system(*%w(git -c core.hooksPath=/dev/null checkout -- locale/*/gitlab.po)) raise 'failed to cleanup generated locale/*/gitlab.po files' end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8d38319762e..3a790d87539 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14773,6 +14773,12 @@ msgstr "" msgid "KeyboardKey|Ctrl+" msgstr "" +msgid "KeyboardShortcuts|Global Shortcuts" +msgstr "" + +msgid "KeyboardShortcuts|Toggle the Performance Bar" +msgstr "" + msgid "Keys" msgstr "" diff --git a/qa/spec/fixtures/banana_sample.gif b/qa/qa/fixtures/designs/banana_sample.gif Binary files differindex 1322ac92d14..1322ac92d14 100644 --- a/qa/spec/fixtures/banana_sample.gif +++ b/qa/qa/fixtures/designs/banana_sample.gif diff --git a/qa/spec/fixtures/tanuki.jpg b/qa/qa/fixtures/designs/tanuki.jpg Binary files differindex f0df472663e..f0df472663e 100644 --- a/qa/spec/fixtures/tanuki.jpg +++ b/qa/qa/fixtures/designs/tanuki.jpg diff --git a/qa/qa/fixtures/designs/update/tanuki.jpg b/qa/qa/fixtures/designs/update/tanuki.jpg Binary files differnew file mode 100644 index 00000000000..162beda6c7b --- /dev/null +++ b/qa/qa/fixtures/designs/update/tanuki.jpg diff --git a/qa/spec/fixtures/values.png b/qa/qa/fixtures/designs/values.png Binary files differindex 9ecb6e7b778..9ecb6e7b778 100644 --- a/qa/spec/fixtures/values.png +++ b/qa/qa/fixtures/designs/values.png diff --git a/qa/qa/page/component/design_management.rb b/qa/qa/page/component/design_management.rb index 44d6b02ccd8..fafbda58b07 100644 --- a/qa/qa/page/component/design_management.rb +++ b/qa/qa/page/component/design_management.rb @@ -30,6 +30,7 @@ module QA view 'app/assets/javascripts/design_management/components/list/item.vue' do element :design_file_name element :design_image + element :design_status_icon end view 'app/assets/javascripts/design_management/pages/index.vue' do @@ -79,6 +80,11 @@ module QA raise ElementNotFound, %Q(Attempted to attach design "#{filename}" but it did not appear) unless found end + def update_design(filename) + filepath = ::File.join('qa', 'fixtures', 'designs', 'update', filename) + add_design(filepath) + end + def click_design(filename) click_element(:design_file_name, text: filename) end @@ -101,6 +107,14 @@ module QA def has_design?(filename) has_element?(:design_file_name, text: filename) end + + def has_created_icon? + has_element?(:design_status_icon, status: 'file-addition-solid') + end + + def has_modified_icon? + has_element?(:design_status_icon, status: 'file-modified-solid') + end end end end diff --git a/qa/qa/resource/design.rb b/qa/qa/resource/design.rb index 10d4f32d49d..182985f2d9f 100644 --- a/qa/qa/resource/design.rb +++ b/qa/qa/resource/design.rb @@ -3,18 +3,15 @@ module QA module Resource class Design < Base + attr_reader :id + attr_accessor :filename + attribute :issue do Issue.fabricate_via_api! end - attribute :filepath do - ::File.absolute_path(::File.join('spec', 'fixtures', @filename)) - end - - attribute :id - attribute :filename - def initialize + @update = false @filename = 'banana_sample.gif' end @@ -26,6 +23,12 @@ module QA issue.add_design(filepath) end end + + private + + def filepath + ::File.absolute_path(::File.join('qa', 'fixtures', 'designs', @filename)) + end end end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index a10329d5936..863c394a9f9 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -41,7 +41,7 @@ module QA context 'when using attachments in comments', :object_storage do let(:gif_file_name) { 'banana_sample.gif' } let(:file_to_attach) do - File.absolute_path(File.join('spec', 'fixtures', gif_file_name)) + File.absolute_path(File.join('qa', 'fixtures', 'designs', gif_file_name)) end before do diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb index ff2b4fa5364..051e8fcecbe 100644 --- a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb @@ -5,7 +5,7 @@ module QA context 'Design Management' do let(:issue) { Resource::Issue.fabricate_via_api! } let(:design_filename) { 'banana_sample.gif' } - let(:design) { File.absolute_path(File.join('spec', 'fixtures', design_filename)) } + let(:design) { File.absolute_path(File.join('qa', 'fixtures', 'designs', design_filename)) } let(:annotation) { "This design is great!" } before do diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb new file mode 100644 index 00000000000..135063b6644 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + context 'Design Management' do + let(:design) do + Resource::Design.fabricate! do |design| + design.filename = 'tanuki.jpg' + end + end + + before do + Flow::Login.sign_in + end + + it 'user adds a design and modifies it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/273' do + design.issue.visit! + + Page::Project::Issue::Show.perform do |issue| + expect(issue).to have_created_icon + end + + Page::Project::Issue::Show.perform do |issue| + issue.update_design(design.filename) + expect(issue).to have_modified_icon + end + end + end + end +end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index d0e412dfdb8..6b394fab14c 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -82,215 +82,6 @@ RSpec.describe Projects::SnippetsController do end end - describe 'POST #create' do - def create_snippet(project, snippet_params = {}, additional_params = {}) - sign_in(user) - - project.add_developer(user) - - post :create, params: { - namespace_id: project.namespace.to_param, - project_id: project, - project_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params) - }.merge(additional_params) - - Snippet.last - end - - it 'creates the snippet correctly' do - snippet = create_snippet(project, visibility_level: Snippet::PRIVATE) - - expect(snippet.title).to eq('Title') - expect(snippet.content).to eq('Content') - expect(snippet.description).to eq('Description') - end - - context 'when the snippet is spam' do - before do - allow_next_instance_of(Spam::AkismetService) do |instance| - allow(instance).to receive(:spam?).and_return(true) - end - end - - context 'when the snippet is private' do - it 'creates the snippet' do - expect { create_snippet(project, visibility_level: Snippet::PRIVATE) } - .to change { Snippet.count }.by(1) - end - end - - context 'when the snippet is public' do - it 'rejects the snippet' do - expect { create_snippet(project, visibility_level: Snippet::PUBLIC) } - .not_to change { Snippet.count } - expect(response).to render_template(:new) - end - - it 'creates a spam log' do - expect { create_snippet(project, visibility_level: Snippet::PUBLIC) } - .to log_spam(title: 'Title', user_id: user.id, noteable_type: 'ProjectSnippet') - end - - it 'renders :new with reCAPTCHA disabled' do - stub_application_setting(recaptcha_enabled: false) - - create_snippet(project, visibility_level: Snippet::PUBLIC) - - expect(response).to render_template(:new) - end - - context 'reCAPTCHA enabled' do - before do - stub_application_setting(recaptcha_enabled: true) - end - - it 'renders :verify with reCAPTCHA enabled' do - create_snippet(project, visibility_level: Snippet::PUBLIC) - - expect(response).to render_template(:verify) - end - - it 'renders snippet page when reCAPTCHA verified' do - spammy_title = 'Whatever' - - spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title) - create_snippet(project, - { visibility_level: Snippet::PUBLIC }, - { spam_log_id: spam_logs.last.id, - recaptcha_verification: true }) - - expect(response).to redirect_to(project_snippet_path(project, Snippet.last)) - end - end - end - end - end - - describe 'PUT #update' do - let(:visibility_level) { Snippet::PUBLIC } - let(:snippet) { create :project_snippet, author: user, project: project, visibility_level: visibility_level } - - def update_snippet(snippet_params = {}, additional_params = {}) - sign_in(user) - - project.add_developer(user) - - put :update, params: { - namespace_id: project.namespace.to_param, - project_id: project, - id: snippet, - project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params) - }.merge(additional_params) - - snippet.reload - end - - context 'when the snippet is spam' do - before do - allow_next_instance_of(Spam::AkismetService) do |instance| - allow(instance).to receive(:spam?).and_return(true) - end - end - - context 'when the snippet is private' do - let(:visibility_level) { Snippet::PRIVATE } - - it 'updates the snippet' do - expect { update_snippet(title: 'Foo') } - .to change { snippet.reload.title }.to('Foo') - end - end - - context 'when the snippet is public' do - it 'rejects the snippet' do - expect { update_snippet(title: 'Foo') } - .not_to change { snippet.reload.title } - end - - it 'creates a spam log' do - expect { update_snippet(title: 'Foo') } - .to log_spam(title: 'Foo', user_id: user.id, noteable_type: 'ProjectSnippet') - end - - it 'renders :edit with reCAPTCHA disabled' do - stub_application_setting(recaptcha_enabled: false) - - update_snippet(title: 'Foo') - - expect(response).to render_template(:edit) - end - - context 'reCAPTCHA enabled' do - before do - stub_application_setting(recaptcha_enabled: true) - end - - it 'renders :verify with reCAPTCHA enabled' do - update_snippet(title: 'Foo') - - expect(response).to render_template(:verify) - end - - it 'renders snippet page when reCAPTCHA verified' do - spammy_title = 'Whatever' - - spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title) - snippet = update_snippet({ title: spammy_title }, - { spam_log_id: spam_logs.last.id, - recaptcha_verification: true }) - - expect(response).to redirect_to(project_snippet_path(project, snippet)) - end - end - end - - context 'when the private snippet is made public' do - let(:visibility_level) { Snippet::PRIVATE } - - it 'rejects the snippet' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) } - .not_to change { snippet.reload.title } - end - - it 'creates a spam log' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) } - .to log_spam(title: 'Foo', user_id: user.id, noteable_type: 'ProjectSnippet') - end - - it 'renders :edit with reCAPTCHA disabled' do - stub_application_setting(recaptcha_enabled: false) - - update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) - - expect(response).to render_template(:edit) - end - - context 'reCAPTCHA enabled' do - before do - stub_application_setting(recaptcha_enabled: true) - end - - it 'renders :verify' do - update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) - - expect(response).to render_template(:verify) - end - - it 'renders snippet page' do - spammy_title = 'Whatever' - - spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title) - snippet = update_snippet({ title: spammy_title, visibility_level: Snippet::PUBLIC }, - { spam_log_id: spam_logs.last.id, - recaptcha_verification: true }) - - expect(response).to redirect_to(project_snippet_path(project, snippet)) - end - end - end - end - end - describe 'POST #mark_as_spam' do let_it_be(:snippet) { create(:project_snippet, :private, project: project, author: user) } @@ -329,12 +120,6 @@ RSpec.describe Projects::SnippetsController do expect(assigns(:snippet)).to eq(project_snippet) expect(response).to have_gitlab_http_status(:ok) end - - it 'renders the blob from the repository' do - subject - - expect(assigns(:blob)).to eq(project_snippet.blobs.first) - end end %w[show raw].each do |action| @@ -395,6 +180,16 @@ RSpec.describe Projects::SnippetsController do end end + describe 'GET #show as JSON' do + it 'renders the blob from the repository' do + project_snippet = create(:project_snippet, :public, :repository, project: project, author: user) + + get :show, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param }, format: :json + + expect(assigns(:blob)).to eq(project_snippet.blobs.first) + end + end + describe "GET #show for embeddable content" do let(:project_snippet) { create(:project_snippet, :repository, snippet_permission, project: project, author: user) } let(:extra_params) { {} } @@ -533,62 +328,4 @@ RSpec.describe Projects::SnippetsController do it_behaves_like 'content disposition headers' end end - - describe 'DELETE #destroy' do - let_it_be(:snippet) { create(:project_snippet, :private, project: project, author: user) } - - let(:params) do - { - namespace_id: project.namespace.to_param, - project_id: project, - id: snippet.to_param - } - end - - subject { delete :destroy, params: params } - - context 'when current user has ability to destroy the snippet' do - before do - sign_in(user) - end - - it 'removes the snippet' do - subject - - expect { snippet.reload }.to raise_error(ActiveRecord::RecordNotFound) - end - - context 'when snippet is succesfuly destroyed' do - it 'redirects to the project snippets page' do - subject - - expect(response).to redirect_to(project_snippets_path(project)) - end - end - - context 'when snippet is not destroyed' do - before do - allow(snippet).to receive(:destroy).and_return(false) - controller.instance_variable_set(:@snippet, snippet) - end - - it 'renders the snippet page with errors' do - subject - - expect(flash[:alert]).to eq('Failed to remove snippet.') - expect(response).to redirect_to(project_snippet_path(project, snippet)) - end - end - end - - context 'when current_user does not have ability to destroy the snippet' do - it 'responds with status 404' do - sign_in(other_user) - - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 6517922d92a..05d725ee8b6 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -86,12 +86,6 @@ RSpec.describe SnippetsController do expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(:ok) end - - it 'renders the blob from the repository' do - subject - - expect(assigns(:blob)).to eq(personal_snippet.blobs.first) - end end context 'when the personal snippet is private' do @@ -200,7 +194,7 @@ RSpec.describe SnippetsController do end it 'responds with status 404' do - get :show, params: { id: 'doesntexist' } + get :show, params: { id: non_existing_record_id } expect(response).to have_gitlab_http_status(:not_found) end @@ -208,234 +202,20 @@ RSpec.describe SnippetsController do context 'when not signed in' do it 'responds with status 404' do - get :show, params: { id: 'doesntexist' } + get :show, params: { id: non_existing_record_id } expect(response).to redirect_to(new_user_session_path) end end end - end - - describe 'POST #create' do - def create_snippet(snippet_params = {}, additional_params = {}) - sign_in(user) - - post :create, params: { - personal_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params) - }.merge(additional_params) - - Snippet.last - end - - it 'creates the snippet correctly' do - snippet = create_snippet(visibility_level: Snippet::PRIVATE) - - expect(snippet.title).to eq('Title') - expect(snippet.content).to eq('Content') - expect(snippet.description).to eq('Description') - end - - context 'when user is not allowed to create a personal snippet' do - let(:user) { create(:user, :external) } - - it 'responds with status 404' do - aggregate_failures do - expect do - create_snippet(visibility_level: Snippet::PUBLIC) - end.not_to change { Snippet.count } - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - context 'when the controller receives the files param' do - let(:files) { %w(foo bar) } - - it 'passes the files param to the snippet create service' do - expect(Snippets::CreateService).to receive(:new).with(nil, user, hash_including(files: files)).and_call_original - - create_snippet({ title: nil }, { files: files }) - end - end - - context 'when the snippet is spam' do - before do - allow_next_instance_of(Spam::AkismetService) do |instance| - allow(instance).to receive(:spam?).and_return(true) - end - end - - context 'when the snippet is private' do - it 'creates the snippet' do - expect { create_snippet(visibility_level: Snippet::PRIVATE) } - .to change { Snippet.count }.by(1) - end - end - - context 'when the snippet is public' do - it 'rejects the snippet' do - expect { create_snippet(visibility_level: Snippet::PUBLIC) } - .not_to change { Snippet.count } - end - - it 'creates a spam log' do - expect { create_snippet(visibility_level: Snippet::PUBLIC) } - .to log_spam(title: 'Title', user: user, noteable_type: 'PersonalSnippet') - end - - it 'renders :new with reCAPTCHA disabled' do - stub_application_setting(recaptcha_enabled: false) - - create_snippet(visibility_level: Snippet::PUBLIC) - - expect(response).to render_template(:new) - end - - context 'reCAPTCHA enabled' do - before do - stub_application_setting(recaptcha_enabled: true) - end - - it 'renders :verify' do - create_snippet(visibility_level: Snippet::PUBLIC) - - expect(response).to render_template(:verify) - end - - it 'renders snippet page' do - spammy_title = 'Whatever' - - spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title) - snippet = create_snippet({ title: spammy_title }, - { spam_log_id: spam_logs.last.id, - recaptcha_verification: true }) - - expect(response).to redirect_to(snippet_path(snippet)) - end - end - end - end - end - - describe 'PUT #update' do - let(:project) { create :project } - let(:visibility_level) { Snippet::PUBLIC } - let(:snippet) { create :personal_snippet, author: user, project: project, visibility_level: visibility_level } - def update_snippet(snippet_params = {}, additional_params = {}) - sign_in(user) - - put :update, params: { - id: snippet.id, - personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params) - }.merge(additional_params) - - snippet.reload - end - - context 'when the snippet is spam' do - before do - allow_next_instance_of(Spam::AkismetService) do |instance| - allow(instance).to receive(:spam?).and_return(true) - end - end - - context 'when the snippet is private' do - let(:visibility_level) { Snippet::PRIVATE } - - it 'updates the snippet' do - expect { update_snippet(title: 'Foo') } - .to change { snippet.reload.title }.to('Foo') - end - end - - context 'when a private snippet is made public' do - let(:visibility_level) { Snippet::PRIVATE } - - it 'rejects the snippet' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) } - .not_to change { snippet.reload.title } - end - - it 'creates a spam log' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) } - .to log_spam(title: 'Foo', user: user, noteable_type: 'PersonalSnippet') - end - - it 'renders :edit with reCAPTCHA disabled' do - stub_application_setting(recaptcha_enabled: false) - - update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) - - expect(response).to render_template(:edit) - end - - context 'reCAPTCHA enabled' do - before do - stub_application_setting(recaptcha_enabled: true) - end - - it 'renders :verify' do - update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) - - expect(response).to render_template(:verify) - end - - it 'renders snippet page when reCAPTCHA verified' do - spammy_title = 'Whatever' - - spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title) - snippet = update_snippet({ title: spammy_title, visibility_level: Snippet::PUBLIC }, - { spam_log_id: spam_logs.last.id, - recaptcha_verification: true }) - - expect(response).to redirect_to(snippet_path(snippet)) - end - end - end - - context 'when the snippet is public' do - it 'rejects the snippet' do - expect { update_snippet(title: 'Foo') } - .not_to change { snippet.reload.title } - end - - it 'creates a spam log' do - expect {update_snippet(title: 'Foo') } - .to log_spam(title: 'Foo', user: user, noteable_type: 'PersonalSnippet') - end - - it 'renders :edit with reCAPTCHA disabled' do - stub_application_setting(recaptcha_enabled: false) - - update_snippet(title: 'Foo') - - expect(response).to render_template(:edit) - end - - context 'recaptcha enabled' do - before do - stub_application_setting(recaptcha_enabled: true) - end - - it 'renders :verify' do - update_snippet(title: 'Foo') - - expect(response).to render_template(:verify) - end - - it 'renders snippet page when reCAPTCHA verified' do - spammy_title = 'Whatever' + context 'when requesting JSON' do + it 'renders the blob from the repository' do + personal_snippet = create(:personal_snippet, :public, :repository, author: user) - spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title) - snippet = update_snippet({ title: spammy_title }, - { spam_log_id: spam_logs.last.id, - recaptcha_verification: true }) + get :show, params: { id: personal_snippet.to_param }, format: :json - expect(response).to redirect_to(snippet_path(snippet)) - end - end + expect(assigns(:blob)).to eq(personal_snippet.blobs.first) end end end @@ -632,7 +412,7 @@ RSpec.describe SnippetsController do end it 'responds with status 404' do - get :raw, params: { id: 'doesntexist' } + get :raw, params: { id: non_existing_record_id } expect(response).to have_gitlab_http_status(:not_found) end @@ -640,7 +420,7 @@ RSpec.describe SnippetsController do context 'when not signed in' do it 'redirects to the sign in path' do - get :raw, params: { id: 'doesntexist' } + get :raw, params: { id: non_existing_record_id } expect(response).to redirect_to(new_user_session_path) end @@ -688,56 +468,4 @@ RSpec.describe SnippetsController do expect(json_response.keys).to match_array(%w(body references)) end end - - describe 'DELETE #destroy' do - let!(:snippet) { create :personal_snippet, author: user } - - context 'when current user has ability to destroy the snippet' do - before do - sign_in(user) - end - - it 'removes the snippet' do - delete :destroy, params: { id: snippet.to_param } - - expect { snippet.reload }.to raise_error(ActiveRecord::RecordNotFound) - end - - context 'when snippet is succesfuly destroyed' do - it 'redirects to the project snippets page' do - delete :destroy, params: { id: snippet.to_param } - - expect(response).to redirect_to(dashboard_snippets_path) - end - end - - context 'when snippet is not destroyed' do - before do - allow(snippet).to receive(:destroy).and_return(false) - controller.instance_variable_set(:@snippet, snippet) - end - - it 'renders the snippet page with errors' do - delete :destroy, params: { id: snippet.to_param } - - expect(flash[:alert]).to eq('Failed to remove snippet.') - expect(response).to redirect_to(snippet_path(snippet)) - end - end - end - - context 'when current_user does not have ability to destroy the snippet' do - let(:another_user) { create(:user) } - - before do - sign_in(another_user) - end - - it 'responds with status 404' do - delete :destroy, params: { id: snippet.to_param } - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index b3815b53c2b..73920b76025 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -384,7 +384,8 @@ FactoryBot.define do key: 'cache_key', untracked: false, paths: ['vendor/*'], - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' } } end diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 5331b5559d8..952a78ec79a 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -19,6 +19,12 @@ RSpec.describe 'Dashboard Merge Requests' do sign_in(current_user) end + it 'disables target branch filter' do + visit merge_requests_dashboard_path + + expect(page).not_to have_selector('#js-dropdown-target-branch', visible: false) + end + context 'new merge request dropdown' do let(:project_with_disabled_merge_requests) { create(:project, :merge_requests_disabled) } diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 8e2f97fd6a0..4e1b53ffc87 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -3,27 +3,35 @@ require 'spec_helper' RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration policy', :js do - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace, container_registry_enabled: container_registry_enabled) } + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) } + let(:container_registry_enabled) { true } + let(:container_registry_enabled_on_project) { true } + + subject { visit project_settings_ci_cd_path(project) } before do + project.update!(container_registry_enabled: container_registry_enabled_on_project) + sign_in(user) - stub_container_registry_config(enabled: true) + stub_container_registry_config(enabled: container_registry_enabled) stub_feature_flags(new_variables_ui: false) end context 'as owner' do - before do - visit project_settings_ci_cd_path(project) - end - it 'shows available section' do + subject + settings_block = find('#js-registry-policies') expect(settings_block).to have_text 'Cleanup policy for tags' end it 'saves cleanup policy submit the form' do + subject + within '#js-registry-policies' do within '.card-body' do select('7 days until tags are automatically removed', from: 'Expiration interval:') @@ -40,6 +48,8 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p end it 'does not save cleanup policy submit form with invalid regex' do + subject + within '#js-registry-policies' do within '.card-body' do fill_in('Tags with names matching this regex pattern will expire:', with: '*-production') @@ -53,25 +63,53 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p end end - context 'when registry is disabled' do - before do - stub_container_registry_config(enabled: false) - visit project_settings_ci_cd_path(project) + context 'with a project without expiration policy' do + where(:application_setting, :feature_flag, :result) do + true | true | :available_section + true | false | :available_section + false | true | :available_section + false | false | :disabled_message end - it 'does not exists' do - expect(page).not_to have_selector('#js-registry-policies') + with_them do + before do + project.container_expiration_policy.destroy! + stub_feature_flags(container_expiration_policies_historic_entry: false) + stub_application_setting(container_expiration_policies_enable_historic_entries: application_setting) + stub_feature_flags(container_expiration_policies_historic_entry: project) if feature_flag + end + + it 'displays the expected result' do + subject + + within '#js-registry-policies' do + case result + when :available_section + expect(find('.card-header')).to have_content('Tag expiration policy') + when :disabled_message + expect(find('.gl-alert-title')).to have_content('Cleanup policy for tags is disabled') + end + end + end end end - context 'when container registry is disabled on project' do + context 'when registry is disabled' do let(:container_registry_enabled) { false } - before do - visit project_settings_ci_cd_path(project) + it 'does not exists' do + subject + + expect(page).not_to have_selector('#js-registry-policies') end + end + + context 'when container registry is disabled on project' do + let(:container_registry_enabled_on_project) { false } it 'does not exists' do + subject + expect(page).not_to have_selector('#js-registry-policies') end end diff --git a/spec/frontend/behaviors/shortcuts/keybindings_spec.js b/spec/frontend/behaviors/shortcuts/keybindings_spec.js new file mode 100644 index 00000000000..23fea79f828 --- /dev/null +++ b/spec/frontend/behaviors/shortcuts/keybindings_spec.js @@ -0,0 +1,66 @@ +import { useLocalStorageSpy } from 'helpers/local_storage_helper'; + +describe('~/behaviors/shortcuts/keybindings.js', () => { + let keysFor; + let TOGGLE_PERFORMANCE_BAR; + let LOCAL_STORAGE_KEY; + + beforeAll(() => { + useLocalStorageSpy(); + }); + + const setupCustomizations = async customizationsAsString => { + localStorage.clear(); + + if (customizationsAsString) { + localStorage.setItem(LOCAL_STORAGE_KEY, customizationsAsString); + } + + jest.resetModules(); + ({ keysFor, TOGGLE_PERFORMANCE_BAR, LOCAL_STORAGE_KEY } = await import( + '~/behaviors/shortcuts/keybindings' + )); + }; + + describe('when a command has not been customized', () => { + beforeEach(async () => { + await setupCustomizations('{}'); + }); + + it('returns the default keybinding for the command', () => { + expect(keysFor(TOGGLE_PERFORMANCE_BAR)).toEqual(['p b']); + }); + }); + + describe('when a command has been customized', () => { + const customization = ['p b a r']; + + beforeEach(async () => { + await setupCustomizations(JSON.stringify({ [TOGGLE_PERFORMANCE_BAR]: customization })); + }); + + it('returns the default keybinding for the command', () => { + expect(keysFor(TOGGLE_PERFORMANCE_BAR)).toEqual(customization); + }); + }); + + describe("when the localStorage entry isn't valid JSON", () => { + beforeEach(async () => { + await setupCustomizations('{'); + }); + + it('returns the default keybinding for the command', () => { + expect(keysFor(TOGGLE_PERFORMANCE_BAR)).toEqual(['p b']); + }); + }); + + describe(`when localStorage doesn't contain the ${LOCAL_STORAGE_KEY} key`, () => { + beforeEach(async () => { + await setupCustomizations(); + }); + + it('returns the default keybinding for the command', () => { + expect(keysFor(TOGGLE_PERFORMANCE_BAR)).toEqual(['p b']); + }); + }); +}); diff --git a/spec/frontend/diffs/components/inline_diff_table_row_spec.js b/spec/frontend/diffs/components/inline_diff_table_row_spec.js index 951b3f6258b..c65a39b9083 100644 --- a/spec/frontend/diffs/components/inline_diff_table_row_spec.js +++ b/spec/frontend/diffs/components/inline_diff_table_row_spec.js @@ -1,5 +1,4 @@ import { shallowMount } from '@vue/test-utils'; -import { TEST_HOST } from 'helpers/test_constants'; import { createStore } from '~/mr_notes/stores'; import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue'; import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue'; @@ -28,13 +27,6 @@ describe('InlineDiffTableRow', () => { }); }; - const setWindowLocation = value => { - Object.defineProperty(window, 'location', { - writable: true, - value, - }); - }; - beforeEach(() => { store = createStore(); store.state.notes.userData = TEST_USER; @@ -122,22 +114,15 @@ describe('InlineDiffTableRow', () => { const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButton' }); it.each` - userData | query | mergeRefHeadComments | expectation - ${TEST_USER} | ${'diff_head=false'} | ${false} | ${true} - ${TEST_USER} | ${'diff_head=true'} | ${true} | ${true} - ${TEST_USER} | ${'diff_head=true'} | ${false} | ${false} - ${null} | ${''} | ${true} | ${false} - `( - 'exists is $expectation - with userData ($userData) query ($query)', - ({ userData, query, mergeRefHeadComments, expectation }) => { - store.state.notes.userData = userData; - gon.features = { mergeRefHeadComments }; - setWindowLocation({ href: `${TEST_HOST}?${query}` }); - createComponent({}, store); - - expect(findNoteButton().exists()).toBe(expectation); - }, - ); + userData | expectation + ${TEST_USER} | ${true} + ${null} | ${false} + `('exists is $expectation - with userData ($userData)', ({ userData, expectation }) => { + store.state.notes.userData = userData; + createComponent({}, store); + + expect(findNoteButton().exists()).toBe(expectation); + }); it.each` isHover | line | expectation diff --git a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js index 13c4ce06f18..13031bd8b66 100644 --- a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js +++ b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js @@ -1,7 +1,6 @@ import Vue from 'vue'; import { shallowMount } from '@vue/test-utils'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import { TEST_HOST } from 'helpers/test_constants'; import { createStore } from '~/mr_notes/stores'; import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue'; import diffFileMockData from '../mock_data/diff_file'; @@ -186,13 +185,6 @@ describe('ParallelDiffTableRow', () => { }); }; - const setWindowLocation = value => { - Object.defineProperty(window, 'location', { - writable: true, - value, - }); - }; - beforeEach(() => { // eslint-disable-next-line prefer-destructuring thisLine = diffFileMockData.parallel_diff_lines[2]; @@ -228,19 +220,15 @@ describe('ParallelDiffTableRow', () => { const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButtonLeft' }); it.each` - hover | line | userData | query | mergeRefHeadComments | expectation - ${true} | ${{}} | ${TEST_USER} | ${'diff_head=false'} | ${false} | ${true} - ${true} | ${{ line: { left: null } }} | ${TEST_USER} | ${'diff_head=false'} | ${false} | ${false} - ${true} | ${{}} | ${TEST_USER} | ${'diff_head=true'} | ${true} | ${true} - ${true} | ${{}} | ${TEST_USER} | ${'diff_head=true'} | ${false} | ${false} - ${true} | ${{}} | ${null} | ${''} | ${true} | ${false} - ${false} | ${{}} | ${TEST_USER} | ${'diff_head=false'} | ${false} | ${false} + hover | line | userData | expectation + ${true} | ${{}} | ${TEST_USER} | ${true} + ${true} | ${{ line: { left: null } }} | ${TEST_USER} | ${false} + ${true} | ${{}} | ${null} | ${false} + ${false} | ${{}} | ${TEST_USER} | ${false} `( - 'exists is $expectation - with userData ($userData) query ($query)', - async ({ hover, line, userData, query, mergeRefHeadComments, expectation }) => { + 'exists is $expectation - with userData ($userData)', + async ({ hover, line, userData, expectation }) => { store.state.notes.userData = userData; - gon.features = { mergeRefHeadComments }; - setWindowLocation({ href: `${TEST_HOST}?${query}` }); createComponent(line, store); if (hover) await wrapper.find('.line_holder').trigger('mouseover'); diff --git a/spec/frontend/issuable_show/components/issuable_header_spec.js b/spec/frontend/issuable_show/components/issuable_header_spec.js new file mode 100644 index 00000000000..fad8ec8a891 --- /dev/null +++ b/spec/frontend/issuable_show/components/issuable_header_spec.js @@ -0,0 +1,132 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlIcon, GlAvatarLabeled } from '@gitlab/ui'; + +import IssuableHeader from '~/issuable_show/components/issuable_header.vue'; + +import { mockIssuableShowProps, mockIssuable } from '../mock_data'; + +const issuableHeaderProps = { + ...mockIssuable, + ...mockIssuableShowProps, +}; + +const createComponent = (propsData = issuableHeaderProps) => + shallowMount(IssuableHeader, { + propsData, + slots: { + 'status-badge': 'Open', + 'header-actions': ` + <button class="js-close">Close issuable</button> + <a class="js-new" href="/gitlab-org/gitlab-shell/-/issues/new">New issuable</a> + `, + }, + }); + +describe('IssuableHeader', () => { + let wrapper; + const findByTestId = testId => wrapper.find(`[data-testid="${testId}"]`); + + beforeEach(() => { + wrapper = createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('computed', () => { + describe('authorId', () => { + it('returns numeric ID from GraphQL ID of `author` prop', () => { + expect(wrapper.vm.authorId).toBe(1); + }); + }); + }); + + describe('handleRightSidebarToggleClick', () => { + beforeEach(() => { + setFixtures('<button class="js-toggle-right-sidebar-button">Collapse sidebar</button>'); + }); + + it('dispatches `click` event on sidebar toggle button', () => { + wrapper.vm.toggleSidebarButtonEl = document.querySelector('.js-toggle-right-sidebar-button'); + jest.spyOn(wrapper.vm.toggleSidebarButtonEl, 'dispatchEvent').mockImplementation(jest.fn); + + wrapper.vm.handleRightSidebarToggleClick(); + + expect(wrapper.vm.toggleSidebarButtonEl.dispatchEvent).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'click', + }), + ); + }); + }); + + describe('template', () => { + it('renders issuable status icon and text', () => { + const statusBoxEl = findByTestId('status'); + + expect(statusBoxEl.exists()).toBe(true); + expect(statusBoxEl.find(GlIcon).props('name')).toBe(mockIssuableShowProps.statusIcon); + expect(statusBoxEl.text()).toContain('Open'); + }); + + it('renders blocked icon when issuable is blocked', async () => { + wrapper.setProps({ + blocked: true, + }); + + await wrapper.vm.$nextTick(); + + const blockedEl = findByTestId('blocked'); + + expect(blockedEl.exists()).toBe(true); + expect(blockedEl.find(GlIcon).props('name')).toBe('lock'); + }); + + it('renders confidential icon when issuable is confidential', async () => { + wrapper.setProps({ + confidential: true, + }); + + await wrapper.vm.$nextTick(); + + const confidentialEl = findByTestId('confidential'); + + expect(confidentialEl.exists()).toBe(true); + expect(confidentialEl.find(GlIcon).props('name')).toBe('eye-slash'); + }); + + it('renders issuable author avatar', () => { + const { username, name, webUrl, avatarUrl } = mockIssuable.author; + const avatarElAttrs = { + 'data-user-id': '1', + 'data-username': username, + 'data-name': name, + href: webUrl, + target: '_blank', + }; + const avatarEl = findByTestId('avatar'); + expect(avatarEl.exists()).toBe(true); + expect(avatarEl.attributes()).toMatchObject(avatarElAttrs); + expect(avatarEl.find(GlAvatarLabeled).attributes()).toMatchObject({ + size: '24', + src: avatarUrl, + label: name, + }); + }); + + it('renders sidebar toggle button', () => { + const toggleButtonEl = findByTestId('sidebar-toggle'); + + expect(toggleButtonEl.exists()).toBe(true); + expect(toggleButtonEl.props('icon')).toBe('chevron-double-lg-left'); + }); + + it('renders header actions', () => { + const actionsEl = findByTestId('header-actions'); + + expect(actionsEl.find('button.js-close').exists()).toBe(true); + expect(actionsEl.find('a.js-new').exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/issuable_show/mock_data.js b/spec/frontend/issuable_show/mock_data.js new file mode 100644 index 00000000000..0a4c0880856 --- /dev/null +++ b/spec/frontend/issuable_show/mock_data.js @@ -0,0 +1,33 @@ +import { mockIssuable as issuable } from '../issuable_list/mock_data'; + +export const mockIssuable = { + ...issuable, + id: 'gid://gitlab/Issue/30', + title: 'Sample title', + titleHtml: 'Sample title', + description: '# Summary', + descriptionHtml: + '<h1 data-sourcepos="1:1-1:25" dir="auto">
<a id="user-content-magnoque-it-lurida-deus" class="anchor" href="#magnoque-it-lurida-deus" aria-hidden="true"></a>Summary</h1>', + state: 'opened', + blocked: false, + confidential: false, + currentUserTodos: { + nodes: [ + { + id: 'gid://gitlab/Todo/489', + state: 'done', + }, + ], + }, +}; + +export const mockIssuableShowProps = { + issuable: mockIssuable, + descriptionHelpPath: '/help/user/markdown', + descriptionPreviewPath: '/gitlab-org/gitlab-shell/preview_markdown', + editFormVisible: false, + enableAutocomplete: true, + enableEdit: true, + statusBadgeClass: 'status-box-open', + statusIcon: 'issue-open-m', +}; diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js index a7973d66b50..d168de5bf8b 100644 --- a/spec/frontend/lib/utils/datetime_utility_spec.js +++ b/spec/frontend/lib/utils/datetime_utility_spec.js @@ -69,6 +69,34 @@ describe('Date time utils', () => { }); }); + describe('formatDateAsMonth', () => { + it('should format dash cased date properly', () => { + const formattedMonth = datetimeUtility.formatDateAsMonth(new Date('2020-06-28')); + + expect(formattedMonth).toBe('Jun'); + }); + + it('should format return the non-abbreviated month', () => { + const formattedMonth = datetimeUtility.formatDateAsMonth(new Date('2020-07-28'), { + abbreviated: false, + }); + + expect(formattedMonth).toBe('July'); + }); + + it('should format date with slashes properly', () => { + const formattedMonth = datetimeUtility.formatDateAsMonth(new Date('07/23/2016')); + + expect(formattedMonth).toBe('Jul'); + }); + + it('should format ISO date properly', () => { + const formattedMonth = datetimeUtility.formatDateAsMonth('2016-07-23T00:00:00.559Z'); + + expect(formattedMonth).toBe('Jul'); + }); + }); + describe('formatDate', () => { it('should format date properly', () => { const formattedDate = datetimeUtility.formatDate(new Date('07/23/2016')); diff --git a/spec/helpers/container_expiration_policies_helper_spec.rb b/spec/helpers/container_expiration_policies_helper_spec.rb index b2a03f8d90f..7ad3804e3a9 100644 --- a/spec/helpers/container_expiration_policies_helper_spec.rb +++ b/spec/helpers/container_expiration_policies_helper_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe ContainerExpirationPoliciesHelper do + using RSpec::Parameterized::TableSyntax + describe '#keep_n_options' do it 'returns keep_n options formatted for dropdown usage' do expected_result = [ @@ -44,4 +46,27 @@ RSpec.describe ContainerExpirationPoliciesHelper do expect(helper.older_than_options).to eq(expected_result) end end + + describe '#container_expiration_policies_historic_entry_enabled?' do + let_it_be(:project) { build_stubbed(:project) } + + subject { helper.container_expiration_policies_historic_entry_enabled?(project) } + + where(:application_setting, :feature_flag, :expected_result) do + true | true | true + true | false | true + false | true | true + false | false | false + end + + with_them do + before do + stub_feature_flags(container_expiration_policies_historic_entry: false) + stub_application_setting(container_expiration_policies_enable_historic_entries: application_setting) + stub_feature_flags(container_expiration_policies_historic_entry: project) if feature_flag + end + + it { is_expected.to eq(expected_result) } + end + end end diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 3501812b76e..80427eaa6ee 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -13,18 +13,23 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do context 'when entry config value is correct' do let(:policy) { nil } let(:key) { 'some key' } + let(:when_config) { nil } let(:config) do - { key: key, + { + key: key, untracked: true, - paths: ['some/path/'], - policy: policy } + paths: ['some/path/'] + }.tap do |config| + config[:policy] = policy if policy + config[:when] = when_config if when_config + end end describe '#value' do shared_examples 'hash key value' do it 'returns hash value' do - expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push') + expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success') end end @@ -49,6 +54,48 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do expect(entry.value).to match(a_hash_including(key: nil)) end end + + context 'with `policy`' do + using RSpec::Parameterized::TableSyntax + + where(:policy, :result) do + 'pull-push' | 'pull-push' + 'push' | 'push' + 'pull' | 'pull' + 'unknown' | 'unknown' # invalid + end + + with_them do + it { expect(entry.value).to include(policy: result) } + end + end + + context 'without `policy`' do + it 'assigns policy to default' do + expect(entry.value).to include(policy: 'pull-push') + end + end + + context 'with `when`' do + using RSpec::Parameterized::TableSyntax + + where(:when_config, :result) do + 'on_success' | 'on_success' + 'on_failure' | 'on_failure' + 'always' | 'always' + 'unknown' | 'unknown' # invalid + end + + with_them do + it { expect(entry.value).to include(when: result) } + end + end + + context 'without `when`' do + it 'assigns when to default' do + expect(entry.value).to include(when: 'on_success') + end + end end describe '#valid?' do @@ -61,28 +108,41 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do end end - context 'policy is pull-push' do - let(:policy) { 'pull-push' } + context 'with `policy`' do + using RSpec::Parameterized::TableSyntax - it { is_expected.to be_valid } - it { expect(entry.value).to include(policy: 'pull-push') } - end - - context 'policy is push' do - let(:policy) { 'push' } + where(:policy, :valid) do + 'pull-push' | true + 'push' | true + 'pull' | true + 'unknown' | false + end - it { is_expected.to be_valid } - it { expect(entry.value).to include(policy: 'push') } + with_them do + it 'returns expected validity' do + expect(entry.valid?).to eq(valid) + end + end end - context 'policy is pull' do - let(:policy) { 'pull' } + context 'with `when`' do + using RSpec::Parameterized::TableSyntax - it { is_expected.to be_valid } - it { expect(entry.value).to include(policy: 'pull') } + where(:when_config, :valid) do + 'on_success' | true + 'on_failure' | true + 'always' | true + 'unknown' | false + end + + with_them do + it 'returns expected validity' do + expect(entry.valid?).to eq(valid) + end + end end - context 'when key is missing' do + context 'with key missing' do let(:config) do { untracked: true, paths: ['some/path/'] } @@ -110,13 +170,21 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do end context 'when policy is unknown' do - let(:config) { { policy: "unknown" } } + let(:config) { { policy: 'unknown' } } it 'reports error' do is_expected.to include('cache policy should be pull-push, push, or pull') end end + context 'when `when` is unknown' do + let(:config) { { when: 'unknown' } } + + it 'reports error' do + is_expected.to include('cache when should be on_success, on_failure or always') + end + end + context 'when descendants are invalid' do context 'with invalid keys' do let(:config) { { key: 1 } } diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index ab760b107f8..e0e8bc93770 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -537,7 +537,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it 'overrides default config' do expect(entry[:image].value).to eq(name: 'some_image') - expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push') + expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success') end end @@ -552,7 +552,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it 'uses config from default entry' do expect(entry[:image].value).to eq 'specified' - expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push') + expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success') end end diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 252bda6461d..79716df6b60 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -127,7 +127,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'ruby:2.7' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }, variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], @@ -141,7 +141,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'ruby:2.7' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }, variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], @@ -156,7 +156,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" }, image: { name: "ruby:2.7" }, services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }], - cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push" }, + cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }, only: { refs: %w(branches tags) }, variables: { 'VAR' => 'job' }, after_script: [], @@ -203,7 +203,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'ruby:2.7' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }, variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], @@ -215,7 +215,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'ruby:2.7' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }, variables: { 'VAR' => 'job' }, ignore: false, after_script: ['make clean'], @@ -261,7 +261,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do describe '#cache_value' do it 'returns correct cache definition' do - expect(root.cache_value).to eq(key: 'a', policy: 'pull-push') + expect(root.cache_value).to eq(key: 'a', policy: 'pull-push', when: 'on_success') end end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb index 74c014b6408..570706bfaac 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb @@ -224,7 +224,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do key: 'a-key', paths: ['vendor/ruby'], untracked: true, - policy: 'push' + policy: 'push', + when: 'on_success' } end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 31ccbdcd3c8..03579d0936c 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1361,7 +1361,8 @@ module Gitlab paths: ["logs/", "binaries/"], untracked: true, key: 'key', - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1383,7 +1384,8 @@ module Gitlab paths: ["logs/", "binaries/"], untracked: true, key: { files: ['file'] }, - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1402,7 +1404,8 @@ module Gitlab paths: ['logs/', 'binaries/'], untracked: true, key: 'key', - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1425,7 +1428,8 @@ module Gitlab paths: ['logs/', 'binaries/'], untracked: true, key: { files: ['file'] }, - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1448,7 +1452,8 @@ module Gitlab paths: ['logs/', 'binaries/'], untracked: true, key: { files: ['file'], prefix: 'prefix' }, - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1468,7 +1473,8 @@ module Gitlab paths: ["test/"], untracked: false, key: 'local', - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end end diff --git a/spec/lib/gitlab/config/entry/composable_array_spec.rb b/spec/lib/gitlab/config/entry/composable_array_spec.rb new file mode 100644 index 00000000000..77766cb3b0a --- /dev/null +++ b/spec/lib/gitlab/config/entry/composable_array_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Config::Entry::ComposableArray, :aggregate_failures do + let(:valid_config) do + [ + { + DATABASE_SECRET: 'passw0rd' + }, + { + API_TOKEN: 'passw0rd2' + } + ] + end + + let(:config) { valid_config } + let(:entry) { described_class.new(config) } + + before do + allow(entry).to receive(:composable_class).and_return(Gitlab::Config::Entry::Node) + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + + context 'is invalid' do + let(:config) { { hello: :world } } + + it { expect(entry).not_to be_valid } + end + end + + describe '#compose!' do + before do + entry.compose! + end + + it 'composes child entry with configured value' do + expect(entry.value).to eq(config) + end + + it 'composes child entries with configured values' do + expect(entry[0]).to be_a(Gitlab::Config::Entry::Node) + expect(entry[0].description).to eq('node definition') + expect(entry[0].key).to eq('node') + expect(entry[0].metadata).to eq({}) + expect(entry[0].parent.class).to eq(Gitlab::Config::Entry::ComposableArray) + expect(entry[0].value).to eq(DATABASE_SECRET: 'passw0rd') + expect(entry[1]).to be_a(Gitlab::Config::Entry::Node) + expect(entry[1].description).to eq('node definition') + expect(entry[1].key).to eq('node') + expect(entry[1].metadata).to eq({}) + expect(entry[1].parent.class).to eq(Gitlab::Config::Entry::ComposableArray) + expect(entry[1].value).to eq(API_TOKEN: 'passw0rd2') + end + + describe '#descendants' do + it 'creates descendant nodes' do + expect(entry.descendants.first).to be_a(Gitlab::Config::Entry::Node) + expect(entry.descendants.first.value).to eq(DATABASE_SECRET: 'passw0rd') + expect(entry.descendants.second).to be_a(Gitlab::Config::Entry::Node) + expect(entry.descendants.second.value).to eq(API_TOKEN: 'passw0rd2') + end + end + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index ab22a203d06..f1d51324bbf 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2554,94 +2554,6 @@ RSpec.describe Ci::Build do end end - describe 'CHANGED_PAGES variables' do - let(:route_map_yaml) do - <<~ROUTEMAP - - source: 'bar/branch-test.txt' - public: '/bar/branches' - - source: 'with space/README.md' - public: '/README' - ROUTEMAP - end - - before do - allow_any_instance_of(Project) - .to receive(:route_map_for).with(/.+/) - .and_return(Gitlab::RouteMap.new(route_map_yaml)) - end - - context 'with a deployment environment and a merge request' do - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:environment) { create(:environment, project: merge_request.project, name: "foo-#{project.default_branch}") } - let(:build) { create(:ci_build, pipeline: pipeline, environment: environment.name) } - - let(:full_urls) do - [ - File.join(environment.external_url, '/bar/branches'), - File.join(environment.external_url, '/README') - ] - end - - it 'populates CI_MERGE_REQUEST_CHANGED_PAGES_* variables' do - expect(subject).to include( - { - key: 'CI_MERGE_REQUEST_CHANGED_PAGE_PATHS', - value: '/bar/branches,/README', - public: true, - masked: false - }, - { - key: 'CI_MERGE_REQUEST_CHANGED_PAGE_URLS', - value: full_urls.join(','), - public: true, - masked: false - } - ) - end - - context 'with a deployment environment and no merge request' do - let(:environment) { create(:environment, project: project, name: "foo-#{project.default_branch}") } - let(:build) { create(:ci_build, pipeline: pipeline, environment: environment.name) } - - it 'does not append CHANGED_PAGES variables' do - ci_variables = subject.select { |var| var[:key] =~ /MERGE_REQUEST_CHANGED_PAGES/ } - - expect(ci_variables).to be_empty - end - end - - context 'with no deployment environment and a present merge request' do - let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project, target_project: project) } - let(:build) { create(:ci_build, pipeline: merge_request.all_pipelines.take) } - - it 'does not append CHANGED_PAGES variables' do - ci_variables = subject.select { |var| var[:key] =~ /MERGE_REQUEST_CHANGED_PAGES/ } - - expect(ci_variables).to be_empty - end - end - - context 'with no deployment environment and no merge request' do - it 'does not append CHANGED_PAGES variables' do - ci_variables = subject.select { |var| var[:key] =~ /MERGE_REQUEST_CHANGED_PAGES/ } - - expect(ci_variables).to be_empty - end - end - end - - context 'with the :modified_path_ci_variables feature flag disabled' do - before do - stub_feature_flags(modified_path_ci_variables: false) - end - - it 'does not set CI_MERGE_REQUEST_CHANGED_PAGES_* variables' do - expect(subject.find { |var| var[:key] == 'CI_MERGE_REQUEST_CHANGED_PAGE_PATHS' }).to be_nil - expect(subject.find { |var| var[:key] == 'CI_MERGE_REQUEST_CHANGED_PAGE_URLS' }).to be_nil - end - end - end - context 'when build has user' do let(:user_variables) do [ diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index a6954fb5d56..09a73a4cdcb 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -66,18 +66,6 @@ RSpec.describe EnvironmentStatus do end end - describe '#changed_paths' do - subject { environment_status.changed_urls } - - it { is_expected.to contain_exactly("#{environment.external_url}/ruby-style-guide.html", "#{environment.external_url}/html/page.html") } - end - - describe '#changed_urls' do - subject { environment_status.changed_paths } - - it { is_expected.to contain_exactly('ruby-style-guide.html', 'html/page.html') } - end - describe '.for_merge_request' do let(:admin) { create(:admin) } let!(:pipeline) { create(:ci_pipeline, sha: sha, merge_requests_as_head_pipeline: [merge_request]) } diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb index 4fa95f8ebb2..2dc92417892 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -194,7 +194,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do [{ 'key' => 'cache_key', 'untracked' => false, 'paths' => ['vendor/*'], - 'policy' => 'pull-push' }] + 'policy' => 'pull-push', + 'when' => 'on_success' }] end let(:expected_features) { { 'trace_sections' => true } } diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index b04bae3e224..a683dc28f4f 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -306,12 +306,9 @@ RSpec.describe 'project routing' do end # raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw # project_snippets GET /:project_id/snippets(.:format) snippets#index - # POST /:project_id/snippets(.:format) snippets#create # new_project_snippet GET /:project_id/snippets/new(.:format) snippets#new # edit_project_snippet GET /:project_id/snippets/:id/edit(.:format) snippets#edit # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show - # PUT /:project_id/snippets/:id(.:format) snippets#update - # DELETE /:project_id/snippets/:id(.:format) snippets#destroy describe SnippetsController, 'routing' do it 'to #raw' do expect(get('/gitlab/gitlabhq/-/snippets/1/raw')).to route_to('projects/snippets#raw', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') @@ -321,10 +318,6 @@ RSpec.describe 'project routing' do expect(get('/gitlab/gitlabhq/-/snippets')).to route_to('projects/snippets#index', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it 'to #create' do - expect(post('/gitlab/gitlabhq/-/snippets')).to route_to('projects/snippets#create', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - it 'to #new' do expect(get('/gitlab/gitlabhq/-/snippets/new')).to route_to('projects/snippets#new', namespace_id: 'gitlab', project_id: 'gitlabhq') end @@ -337,14 +330,6 @@ RSpec.describe 'project routing' do expect(get('/gitlab/gitlabhq/-/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it 'to #update' do - expect(put('/gitlab/gitlabhq/-/snippets/1')).to route_to('projects/snippets#update', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it 'to #destroy' do - expect(delete('/gitlab/gitlabhq/-/snippets/1')).to route_to('projects/snippets#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - it 'to #show from unscope routing' do expect(get('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 6150a8b26cc..6b7a0d018f1 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -61,12 +61,9 @@ RSpec.describe "Mounted Apps", "routing" do end # snippets GET /snippets(.:format) snippets#index -# POST /snippets(.:format) snippets#create # new_snippet GET /snippets/new(.:format) snippets#new # edit_snippet GET /snippets/:id/edit(.:format) snippets#edit # snippet GET /snippets/:id(.:format) snippets#show -# PUT /snippets/:id(.:format) snippets#update -# DELETE /snippets/:id(.:format) snippets#destroy RSpec.describe SnippetsController, "routing" do it "to #raw" do expect(get("/-/snippets/1/raw")).to route_to('snippets#raw', id: '1') @@ -76,10 +73,6 @@ RSpec.describe SnippetsController, "routing" do expect(get("/-/snippets")).to route_to('snippets#index') end - it "to #create" do - expect(post("/-/snippets")).to route_to('snippets#create') - end - it "to #new" do expect(get("/-/snippets/new")).to route_to('snippets#new') end @@ -92,14 +85,6 @@ RSpec.describe SnippetsController, "routing" do expect(get("/-/snippets/1")).to route_to('snippets#show', id: '1') end - it "to #update" do - expect(put("/-/snippets/1")).to route_to('snippets#update', id: '1') - end - - it "to #destroy" do - expect(delete("/-/snippets/1")).to route_to('snippets#destroy', id: '1') - end - it 'to #show from unscoped routing' do expect(get("/snippets/1")).to route_to('snippets#show', id: '1') end diff --git a/spec/serializers/discussion_entity_spec.rb b/spec/serializers/discussion_entity_spec.rb index 306a4fa43a9..e1734d5290f 100644 --- a/spec/serializers/discussion_entity_spec.rb +++ b/spec/serializers/discussion_entity_spec.rb @@ -79,13 +79,5 @@ RSpec.describe DiscussionEntity do :active ) end - - context 'diff_head_compare feature is disabled' do - it 'does not expose positions and line_codes attributes' do - stub_feature_flags(merge_ref_head_comments: false) - - expect(subject.keys).not_to include(:positions, :line_codes) - end - end end end diff --git a/spec/services/ci/create_pipeline_service/cache_spec.rb b/spec/services/ci/create_pipeline_service/cache_spec.rb index 614e46f1b1a..1438c2e4aa0 100644 --- a/spec/services/ci/create_pipeline_service/cache_spec.rb +++ b/spec/services/ci/create_pipeline_service/cache_spec.rb @@ -36,7 +36,8 @@ RSpec.describe Ci::CreatePipelineService do 'key' => 'a-key', 'paths' => ['logs/', 'binaries/'], 'policy' => 'pull-push', - 'untracked' => true + 'untracked' => true, + 'when' => 'on_success' } expect(pipeline).to be_persisted @@ -67,7 +68,8 @@ RSpec.describe Ci::CreatePipelineService do expected = { 'key' => /[a-f0-9]{40}/, 'paths' => ['logs/'], - 'policy' => 'pull-push' + 'policy' => 'pull-push', + 'when' => 'on_success' } expect(pipeline).to be_persisted @@ -82,7 +84,8 @@ RSpec.describe Ci::CreatePipelineService do expected = { 'key' => /default/, 'paths' => ['logs/'], - 'policy' => 'pull-push' + 'policy' => 'pull-push', + 'when' => 'on_success' } expect(pipeline).to be_persisted @@ -114,7 +117,8 @@ RSpec.describe Ci::CreatePipelineService do expected = { 'key' => /\$ENV_VAR-[a-f0-9]{40}/, 'paths' => ['logs/'], - 'policy' => 'pull-push' + 'policy' => 'pull-push', + 'when' => 'on_success' } expect(pipeline).to be_persisted @@ -129,7 +133,8 @@ RSpec.describe Ci::CreatePipelineService do expected = { 'key' => /\$ENV_VAR-default/, 'paths' => ['logs/'], - 'policy' => 'pull-push' + 'policy' => 'pull-push', + 'when' => 'on_success' } expect(pipeline).to be_persisted diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb index fa70ad8c559..86e49fe601c 100644 --- a/spec/services/merge_requests/create_from_issue_service_spec.rb +++ b/spec/services/merge_requests/create_from_issue_service_spec.rb @@ -154,7 +154,7 @@ RSpec.describe MergeRequests::CreateFromIssueService do result = service.execute - expect(result[:merge_request].label_ids).to eq(label_ids) + expect(result[:merge_request].label_ids).to match_array(label_ids) end it "inherits milestones" do diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb index 543da46f883..bffd4c4d34d 100644 --- a/spec/services/merge_requests/mergeability_check_service_spec.rb +++ b/spec/services/merge_requests/mergeability_check_service_spec.rb @@ -41,16 +41,6 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar subject end - context 'when merge_ref_head_comments is disabled' do - it 'does not update diff discussion positions' do - stub_feature_flags(merge_ref_head_comments: false) - - expect(Discussions::CaptureDiffNotePositionsService).not_to receive(:new) - - subject - end - end - it 'updates the merge ref' do expect { subject }.to change(merge_request, :merge_ref_head).from(nil) end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 4da9f4115a1..1e5536a2d0b 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -163,14 +163,6 @@ RSpec.describe Notes::CreateService do expect(note.note_diff_file).to be_present expect(note.diff_note_positions).to be_present end - - it 'does not create diff positions merge_ref_head_comments is disabled' do - stub_feature_flags(merge_ref_head_comments: false) - - expect(Discussions::CaptureDiffNotePositionService).not_to receive(:new) - - described_class.new(project_with_repo, user, new_opts).execute - end end context 'when DiffNote is a reply' do diff --git a/spec/support/migrations_helpers/cluster_helpers.rb b/spec/support/migrations_helpers/cluster_helpers.rb index b54af15c29e..03104e22bcf 100644 --- a/spec/support/migrations_helpers/cluster_helpers.rb +++ b/spec/support/migrations_helpers/cluster_helpers.rb @@ -4,7 +4,7 @@ module MigrationHelpers module ClusterHelpers # Creates a list of cluster projects. def create_cluster_project_list(quantity) - group = namespaces_table.create(name: 'gitlab-org', path: 'gitlab-org') + group = namespaces_table.create!(name: 'gitlab-org', path: 'gitlab-org') quantity.times do |id| create_cluster_project(group, id) @@ -25,14 +25,14 @@ module MigrationHelpers namespace_id: group.id ) - cluster = clusters_table.create( + cluster = clusters_table.create!( name: 'test-cluster', cluster_type: 3, provider_type: :gcp, platform_type: :kubernetes ) - cluster_projects_table.create(project_id: project.id, cluster_id: cluster.id) + cluster_projects_table.create!(project_id: project.id, cluster_id: cluster.id) provider_gcp_table.create!( gcp_project_id: "test-gcp-project-#{id}", @@ -43,7 +43,7 @@ module MigrationHelpers zone: 'us-central1-a' ) - platform_kubernetes_table.create( + platform_kubernetes_table.create!( cluster_id: cluster.id, api_url: 'https://kubernetes.example.com', encrypted_token: 'a' * 40, @@ -58,7 +58,7 @@ module MigrationHelpers project = projects_table.find(cluster_project.project_id) namespace = "#{project.path}-#{project.id}" - cluster_kubernetes_namespaces_table.create( + cluster_kubernetes_namespaces_table.create!( cluster_project_id: cluster_project.id, cluster_id: cluster.id, project_id: cluster_project.project_id, diff --git a/spec/support/migrations_helpers/namespaces_helper.rb b/spec/support/migrations_helpers/namespaces_helper.rb index 4ca01c87568..c62ef6a4620 100644 --- a/spec/support/migrations_helpers/namespaces_helper.rb +++ b/spec/support/migrations_helpers/namespaces_helper.rb @@ -3,7 +3,7 @@ module MigrationHelpers module NamespacesHelpers def create_namespace(name, visibility, options = {}) - table(:namespaces).create({ + table(:namespaces).create!({ name: name, path: name, type: 'Group', diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb index b4d7722f03d..298e03162c4 100644 --- a/spec/support/shared_contexts/email_shared_context.rb +++ b/spec/support/shared_contexts/email_shared_context.rb @@ -21,7 +21,7 @@ end RSpec.shared_examples :reply_processing_shared_examples do context "when the user could not be found" do before do - user.destroy + user.destroy! end it "raises a UserNotFoundError" do diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb index 58ee48a98f1..2b6edb4c07d 100644 --- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb @@ -18,8 +18,8 @@ RSpec.shared_context 'GroupProjectsFinder context' do let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) } before do - shared_project_1.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) - shared_project_2.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) - shared_project_3.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) + shared_project_1.project_group_links.create!(group_access: Gitlab::Access::MAINTAINER, group: group) + shared_project_2.project_group_links.create!(group_access: Gitlab::Access::MAINTAINER, group: group) + shared_project_3.project_group_links.create!(group_access: Gitlab::Access::MAINTAINER, group: group) end end diff --git a/spec/support/shared_contexts/mailers/notify_shared_context.rb b/spec/support/shared_contexts/mailers/notify_shared_context.rb index de8c0d5d2b4..4b7d028410a 100644 --- a/spec/support/shared_contexts/mailers/notify_shared_context.rb +++ b/spec/support/shared_contexts/mailers/notify_shared_context.rb @@ -11,7 +11,7 @@ RSpec.shared_context 'gitlab email notification' do let(:new_user_address) { 'newguy@example.com' } before do - email = recipient.emails.create(email: "notifications@example.com") + email = recipient.emails.create!(email: "notifications@example.com") recipient.update_attribute(:notification_email, email.email) stub_incoming_email_setting(enabled: true, address: "reply+%{key}@#{Gitlab.config.gitlab.host}") end diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 018971e288c..fc9115a5ea1 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -56,7 +56,7 @@ RSpec.describe GitGarbageCollectWorker do it "flushes ref caches when the task if 'gc'" do expect(subject).to receive(:renew_lease).with(lease_key, lease_uuid).and_call_original - expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original + expect_any_instance_of(Repository).to receive(:expire_branches_cache).and_call_original expect_any_instance_of(Repository).to receive(:branch_names).and_call_original expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original @@ -77,7 +77,7 @@ RSpec.describe GitGarbageCollectWorker do end it 'returns silently' do - expect_any_instance_of(Repository).not_to receive(:after_create_branch).and_call_original + expect_any_instance_of(Repository).not_to receive(:expire_branches_cache).and_call_original expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original @@ -102,7 +102,7 @@ RSpec.describe GitGarbageCollectWorker do it "flushes ref caches when the task if 'gc'" do expect(subject).to receive(:get_lease_uuid).with("git_gc:#{task}:#{project.id}").and_return(false) - expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original + expect_any_instance_of(Repository).to receive(:expire_branches_cache).and_call_original expect_any_instance_of(Repository).to receive(:branch_names).and_call_original expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original @@ -170,7 +170,7 @@ RSpec.describe GitGarbageCollectWorker do it 'returns silently' do expect(subject).not_to receive(:command) - expect_any_instance_of(Repository).not_to receive(:after_create_branch).and_call_original + expect_any_instance_of(Repository).not_to receive(:expire_branches_cache).and_call_original expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original |