summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-12 15:08:32 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-12 15:08:32 +0000
commitb17f0b91a66f2101a54dd1efed0c4973f04b1daf (patch)
tree55d2a3dc5581b90bd1304c5d53d4d5b5da0076c9
parentb47e7cd6b2049c1fb87e7c4ffd1de898cda52364 (diff)
downloadgitlab-ce-b17f0b91a66f2101a54dd1efed0c4973f04b1daf.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml6
-rw-r--r--.rubocop_todo.yml5
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock13
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/keybindings.js96
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js3
-rw-r--r--app/assets/javascripts/design_management/components/list/item.vue8
-rw-r--r--app/assets/javascripts/diffs/components/diff_row_utils.js18
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue6
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue6
-rw-r--r--app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js26
-rw-r--r--app/assets/javascripts/issuable_show/components/issuable_header.vue120
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js15
-rw-r--r--app/assets/javascripts/pages/dashboard/merge_requests/index.js2
-rw-r--r--app/assets/stylesheets/pages/diff.scss16
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss16
-rw-r--r--app/controllers/concerns/snippets_actions.rb40
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects/snippets_controller.rb21
-rw-r--r--app/controllers/snippets_controller.rb23
-rw-r--r--app/helpers/container_expiration_policies_helper.rb5
-rw-r--r--app/models/ci/build.rb10
-rw-r--r--app/models/environment_status.rb8
-rw-r--r--app/serializers/discussion_entity.rb5
-rw-r--r--app/services/merge_requests/mergeability_check_service.rb2
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/projects/compare/_form.html.haml8
-rw-r--r--app/views/projects/registry/settings/_index.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml12
-rw-r--r--app/workers/git_garbage_collect_worker.rb2
-rw-r--r--changelogs/unreleased/-231207-projects-compare.yml5
-rw-r--r--changelogs/unreleased/18969-allow-optional-caching-in-failed-builds.yml5
-rw-r--r--changelogs/unreleased/244050-feature-flag-to-allow-old-projects-to-have-a-cleanup-policy.yml5
-rw-r--r--changelogs/unreleased/mb_rails_save_bang_fix3.yml5
-rw-r--r--changelogs/unreleased/ph-207481-targetBranchFilterDashboard.yml5
-rw-r--r--changelogs/unreleased/ph-218300-fixedSystemHeaderOnDiffs.yml5
-rw-r--r--config/environments/production.rb2
-rw-r--r--config/feature_flags/development/additional_snowplow_tracking.yml2
-rw-r--r--config/feature_flags/development/ci_child_of_child_pipeline.yml2
-rw-r--r--config/feature_flags/development/ci_lint_vue.yml2
-rw-r--r--config/feature_flags/development/container_expiration_policies_historic_entry.yml7
-rw-r--r--config/feature_flags/development/deploy_boards_dedupe_instances.yml2
-rw-r--r--config/feature_flags/development/drop_license_management_artifact.yml2
-rw-r--r--config/feature_flags/development/ingress_modsecurity.yml2
-rw-r--r--config/feature_flags/development/junit_pipeline_screenshots_view.yml2
-rw-r--r--config/feature_flags/development/merge_ref_head_comments.yml7
-rw-r--r--config/feature_flags/development/modifed_path_ci_variables.yml7
-rw-r--r--config/feature_flags/development/product_analytics.yml2
-rw-r--r--config/feature_flags/development/project_finder_similarity_sort.yml2
-rw-r--r--config/feature_flags/development/push_rules_supersede_code_owners.yml7
-rw-r--r--config/feature_flags/development/rebalance_issues.yml2
-rw-r--r--config/feature_flags/development/save_raw_usage_data.yml2
-rw-r--r--config/feature_flags/development/track_issue_activity_actions.yml2
-rw-r--r--config/feature_flags/development/usage_data_api.yml2
-rw-r--r--config/feature_flags/development/usage_data_i_source_code_code_intelligence.yml2
-rw-r--r--config/initializers/sprockets.rb1
-rw-r--r--config/redis.cache.yml.example2
-rw-r--r--config/redis.queues.yml.example2
-rw-r--r--config/redis.shared_state.yml.example2
-rw-r--r--config/resque.yml.example2
-rw-r--r--config/routes/project.rb2
-rw-r--r--config/routes/snippets.rb2
-rw-r--r--db/fixtures/development/29_instance_statistics.rb26
-rw-r--r--doc/ci/yaml/README.md30
-rw-r--r--doc/development/fe_guide/index.md4
-rw-r--r--doc/development/fe_guide/keyboard_shortcuts.md98
-rw-r--r--doc/user/markdown.md44
-rw-r--r--doc/user/packages/container_registry/index.md14
-rw-r--r--lib/api/entities/job_request/cache.rb2
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb24
-rw-r--r--lib/gitlab/ci/config/entry/needs.rb23
-rw-r--r--lib/gitlab/ci/config/entry/ports.rb26
-rw-r--r--lib/gitlab/ci/config/entry/rules.rb21
-rw-r--r--lib/gitlab/ci/config/entry/services.rb26
-rw-r--r--lib/gitlab/ci/pipeline/seed/build/cache.rb4
-rw-r--r--lib/gitlab/config/entry/composable_array.rb58
-rw-r--r--lib/gitlab/config/entry/composable_hash.rb2
-rw-r--r--lib/system_check/app/redis_version_check.rb2
-rw-r--r--lib/tasks/gettext.rake2
-rw-r--r--locale/gitlab.pot6
-rw-r--r--qa/qa/fixtures/designs/banana_sample.gif (renamed from qa/spec/fixtures/banana_sample.gif)bin71759 -> 71759 bytes
-rw-r--r--qa/qa/fixtures/designs/tanuki.jpg (renamed from qa/spec/fixtures/tanuki.jpg)bin84616 -> 84616 bytes
-rw-r--r--qa/qa/fixtures/designs/update/tanuki.jpgbin0 -> 83907 bytes
-rw-r--r--qa/qa/fixtures/designs/values.png (renamed from qa/spec/fixtures/values.png)bin122101 -> 122101 bytes
-rw-r--r--qa/qa/page/component/design_management.rb14
-rw-r--r--qa/qa/resource/design.rb17
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb30
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb283
-rw-r--r--spec/controllers/snippets_controller_spec.rb290
-rw-r--r--spec/factories/ci/builds.rb3
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb6
-rw-r--r--spec/features/projects/settings/registry_settings_spec.rb70
-rw-r--r--spec/frontend/behaviors/shortcuts/keybindings_spec.js66
-rw-r--r--spec/frontend/diffs/components/inline_diff_table_row_spec.js33
-rw-r--r--spec/frontend/diffs/components/parallel_diff_table_row_spec.js26
-rw-r--r--spec/frontend/issuable_show/components/issuable_header_spec.js132
-rw-r--r--spec/frontend/issuable_show/mock_data.js33
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js28
-rw-r--r--spec/helpers/container_expiration_policies_helper_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb108
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb18
-rw-r--r--spec/lib/gitlab/config/entry/composable_array_spec.rb69
-rw-r--r--spec/models/ci/build_spec.rb88
-rw-r--r--spec/models/environment_status_spec.rb12
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb3
-rw-r--r--spec/routing/project_routing_spec.rb15
-rw-r--r--spec/routing/routing_spec.rb15
-rw-r--r--spec/serializers/discussion_entity_spec.rb8
-rw-r--r--spec/services/ci/create_pipeline_service/cache_spec.rb15
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb2
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb10
-rw-r--r--spec/services/notes/create_service_spec.rb8
-rw-r--r--spec/support/migrations_helpers/cluster_helpers.rb10
-rw-r--r--spec/support/migrations_helpers/namespaces_helper.rb2
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb6
-rw-r--r--spec/support/shared_contexts/mailers/notify_shared_context.rb2
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb8
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.
diff --git a/Gemfile b/Gemfile
index faaa8290d1d..d426ad42403 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
&nbsp;
- = 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
index 1322ac92d14..1322ac92d14 100644
--- a/qa/spec/fixtures/banana_sample.gif
+++ b/qa/qa/fixtures/designs/banana_sample.gif
Binary files differ
diff --git a/qa/spec/fixtures/tanuki.jpg b/qa/qa/fixtures/designs/tanuki.jpg
index f0df472663e..f0df472663e 100644
--- a/qa/spec/fixtures/tanuki.jpg
+++ b/qa/qa/fixtures/designs/tanuki.jpg
Binary files differ
diff --git a/qa/qa/fixtures/designs/update/tanuki.jpg b/qa/qa/fixtures/designs/update/tanuki.jpg
new file mode 100644
index 00000000000..162beda6c7b
--- /dev/null
+++ b/qa/qa/fixtures/designs/update/tanuki.jpg
Binary files differ
diff --git a/qa/spec/fixtures/values.png b/qa/qa/fixtures/designs/values.png
index 9ecb6e7b778..9ecb6e7b778 100644
--- a/qa/spec/fixtures/values.png
+++ b/qa/qa/fixtures/designs/values.png
Binary files differ
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">&#x000A;<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