summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml2
-rw-r--r--.gitpod.yml9
-rw-r--r--app/assets/javascripts/awards_handler.js37
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js3
-rw-r--r--app/controllers/projects/static_site_editor_controller.rb7
-rw-r--r--app/helpers/issuables_helper.rb7
-rw-r--r--app/models/snippet.rb6
-rw-r--r--app/views/projects/incidents/show.html.haml7
-rw-r--r--app/views/projects/issuable/_show.html.haml10
-rw-r--r--app/views/projects/issues/show.html.haml101
-rw-r--r--app/views/shared/issue_type/_details_content.html.haml31
-rw-r--r--app/views/shared/issue_type/_details_header.html.haml52
-rw-r--r--app/views/shared/issue_type/_emoji_block.html.haml9
-rw-r--r--app/views/shared/issue_type/_sentry_stack_trace.html.haml4
-rw-r--r--changelogs/unreleased/270106_enable_csp_for_sse.yml5
-rw-r--r--changelogs/unreleased/mo-bump-codequality.yml5
-rw-r--r--changelogs/unreleased/sh-pgbouncer-bpass-config.yml5
-rw-r--r--config/feature_flags/development/oj_json.yml7
-rw-r--r--doc/README.md2
-rw-r--r--doc/api/protected_environments.md17
-rw-r--r--doc/raketasks/backup_restore.md64
-rw-r--r--doc/user/application_security/coverage_fuzzing/index.md1
-rw-r--r--doc/user/packages/index.md48
-rw-r--r--lib/backup/database.rb9
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml2
-rw-r--r--lib/gitlab/json.rb36
-rw-r--r--lib/gitlab/repository_size_checker.rb2
-rw-r--r--lib/gitlab/repository_url_builder.rb3
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/features/incidents/user_views_incident_spec.rb80
-rw-r--r--spec/features/issues/user_interacts_with_awards_spec.rb2
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb6
-rw-r--r--spec/features/static_site_editor_spec.rb40
-rw-r--r--spec/frontend/awards_handler_spec.js23
-rw-r--r--spec/helpers/issuables_helper_spec.rb18
-rw-r--r--spec/lib/backup/database_spec.rb21
-rw-r--r--spec/lib/gitlab/json_spec.rb518
-rw-r--r--spec/models/personal_snippet_spec.rb3
-rw-r--r--spec/models/project_snippet_spec.rb3
39 files changed, 665 insertions, 546 deletions
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index 168f60f0f65..279800cb230 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -15,7 +15,7 @@ code_quality:
stage: test
needs: []
variables:
- CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10-gitlab.1"
+ CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.18"
script:
- |
if ! docker info &>/dev/null; then
diff --git a/.gitpod.yml b/.gitpod.yml
index d83e5ff3e61..2c6aa2a962d 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -14,6 +14,7 @@ tasks:
set -e
cd /workspace/gitlab-development-kit
[[ ! -L /workspace/gitlab-development-kit/gitlab ]] && ln -fs /workspace/gitlab /workspace/gitlab-development-kit/gitlab
+ mv /workspace/gitlab-development-kit/secrets.yml /workspace/gitlab-development-kit/gitlab/config
# make webpack static, prevents that GitLab tries to connect to localhost webpack from browser outside the workspace
echo "webpack:" >> gdk.yml
echo " static: true" >> gdk.yml
@@ -48,14 +49,6 @@ tasks:
if [ "$GITLAB_RUN_DB_MIGRATIONS" == true ]; then
make gitlab-db-migrate
fi
- # Fix DB key
- if [ "$GITLAB_FIX_DB_KEY" = true ]; then
- echo "$(date) – Fixing DB key" | tee -a /workspace/startup.log
- cd gitlab
- # see https://gitlab.com/gitlab-org/gitlab-foss/-/issues/56403#note_132515069
- printf 'ApplicationSetting.last.update_column(:runners_registration_token_encrypted, nil)\nexit\n' | bundle exec rails c
- cd -
- fi
# Waiting for GitLab ...
gp await-port 3000
printf "Waiting for GitLab at $(gp url 3000) ..."
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 7055cd42978..17e6255700a 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -5,11 +5,11 @@ import { uniq } from 'lodash';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Cookies from 'js-cookie';
import { __ } from './locale';
-import { updateTooltipTitle } from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
import { deprecatedCreateFlash as flash } from './flash';
import axios from './lib/utils/axios_utils';
import * as Emoji from '~/emoji';
+import { dispose, fixTitle } from '~/tooltips';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
@@ -374,7 +374,7 @@ export class AwardsHandler {
counter.text(counterNumber - 1);
this.removeYouFromUserList($emojiButton);
} else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
- $emojiButton.tooltip('dispose');
+ dispose($emojiButton);
counter.text('0');
this.removeYouFromUserList($emojiButton);
if ($emojiButton.parents('.note').length) {
@@ -387,7 +387,8 @@ export class AwardsHandler {
}
removeEmoji($emojiButton) {
- $emojiButton.tooltip('dispose');
+ dispose($emojiButton);
+
$emojiButton.remove();
const $votesBlock = this.getVotesBlock();
if ($votesBlock.find('.js-emoji-btn').length === 0) {
@@ -415,13 +416,17 @@ export class AwardsHandler {
const originalTitle = this.getAwardTooltip(awardBlock);
const authors = originalTitle.split(FROM_SENTENCE_REGEX);
authors.splice(authors.indexOf('You'), 1);
- return awardBlock
+
+ awardBlock
.closest('.js-emoji-btn')
.removeData('title')
.removeAttr('data-title')
.removeAttr('data-original-title')
- .attr('title', this.toSentence(authors))
- .tooltip('_fixTitle');
+ .attr('title', this.toSentence(authors));
+
+ fixTitle(awardBlock);
+
+ return awardBlock;
}
addYouToUserList(votesBlock, emoji) {
@@ -432,7 +437,12 @@ export class AwardsHandler {
users = origTitle.trim().split(FROM_SENTENCE_REGEX);
}
users.unshift('You');
- return awardBlock.attr('title', this.toSentence(users)).tooltip('_fixTitle');
+
+ awardBlock.attr('title', this.toSentence(users));
+
+ fixTitle(awardBlock);
+
+ return awardBlock;
}
createAwardButtonForVotesBlock(votesBlock, emojiName) {
@@ -448,7 +458,7 @@ export class AwardsHandler {
.find('.emoji-icon')
.data('name', emojiName);
this.animateEmoji($emojiButton);
- $('.award-control').tooltip();
+
votesBlock.removeClass('current');
}
@@ -487,17 +497,6 @@ export class AwardsHandler {
return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
}
- userAuthored($emojiButton) {
- const oldTitle = this.getAwardTooltip($emojiButton);
- const newTitle = 'You cannot vote on your own issue, MR and note';
- updateTooltipTitle($emojiButton, newTitle).tooltip('show');
- // Restore tooltip back to award list
- return setTimeout(() => {
- $emojiButton.tooltip('hide');
- updateTooltipTitle($emojiButton, oldTitle);
- }, 2800);
- }
-
scrollToAwards() {
const options = {
scrollTop: $('.awards').offset().top - 110,
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index fe1ac00fd1d..7845f2968db 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -61,9 +61,6 @@ export const rstrip = val => {
return val;
};
-export const updateTooltipTitle = ($tooltipEl, newTitle) =>
- $tooltipEl.attr('title', newTitle).tooltip('_fixTitle');
-
export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventName = 'input') => {
const field = $(fieldSelector);
const closestSubmit = field.closest('form').find(buttonSelector);
diff --git a/app/controllers/projects/static_site_editor_controller.rb b/app/controllers/projects/static_site_editor_controller.rb
index 58cda84ebe7..9fb5976e11a 100644
--- a/app/controllers/projects/static_site_editor_controller.rb
+++ b/app/controllers/projects/static_site_editor_controller.rb
@@ -6,6 +6,13 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController
layout 'fullscreen'
+ content_security_policy do |policy|
+ next if policy.directives.blank?
+
+ frame_src_values = Array.wrap(policy.directives['frame-src']) | ['https://www.youtube.com']
+ policy.frame_src(*frame_src_values)
+ end
+
prepend_before_action :authenticate_user!, only: [:show]
before_action :assign_ref_and_path, only: [:show]
before_action :authorize_edit_tree!, only: [:show]
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 6a74b7010b9..577d8d5cb2a 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -377,7 +377,12 @@ module IssuablesHelper
end
def issuable_display_type(issuable)
- issuable.model_name.human.downcase
+ case issuable
+ when Issue
+ issuable.issue_type.downcase
+ when MergeRequest
+ issuable.model_name.human.downcase
+ end
end
def has_filter_bar_param?
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index d71853e11cf..dc370b46bda 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -293,9 +293,7 @@ class Snippet < ApplicationRecord
@storage ||= Storage::Hashed.new(self, prefix: Storage::Hashed::SNIPPET_REPOSITORY_PATH_PREFIX)
end
- # This is the full_path used to identify the
- # the snippet repository. It will be used mostly
- # for logging purposes.
+ # This is the full_path used to identify the the snippet repository.
override :full_path
def full_path
return unless persisted?
@@ -303,7 +301,7 @@ class Snippet < ApplicationRecord
@full_path ||= begin
components = []
components << project.full_path if project_id?
- components << '@snippets'
+ components << 'snippets'
components << self.id
components.join('/')
end
diff --git a/app/views/projects/incidents/show.html.haml b/app/views/projects/incidents/show.html.haml
index b0ddc85df5d..4d4607e8e36 100644
--- a/app/views/projects/incidents/show.html.haml
+++ b/app/views/projects/incidents/show.html.haml
@@ -1 +1,6 @@
-= render template: 'projects/issues/show'
+- @content_class = "limit-container-width" unless fluid_layout
+- add_to_breadcrumbs _("Incidents"), project_incidents_path(@project)
+- breadcrumb_title @issue.to_reference
+- page_title "#{@issue.title} (#{@issue.to_reference})", _("Incidents")
+
+= render 'projects/issuable/show', issuable: @issue
diff --git a/app/views/projects/issuable/_show.html.haml b/app/views/projects/issuable/_show.html.haml
new file mode 100644
index 00000000000..48920c4e342
--- /dev/null
+++ b/app/views/projects/issuable/_show.html.haml
@@ -0,0 +1,10 @@
+- page_description issuable.description_html
+- page_card_attributes issuable.card_attributes
+- if issuable.relocation_target
+ - page_canonical_link issuable.relocation_target.present(current_user: current_user).web_url
+
+= render_if_exists "projects/issues/alert_blocked", issue: issuable, current_user: current_user
+= render "projects/issues/alert_moved_from_service_desk", issue: issuable
+
+= render 'shared/issue_type/details_header', issuable: issuable
+= render 'shared/issue_type/details_content', issuable: issuable
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 4778221335e..c3949a83e3f 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,103 +1,6 @@
- @content_class = "limit-container-width" unless fluid_layout
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference
-- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
-- page_description @issue.description_html
-- page_card_attributes @issue.card_attributes
-- if @issue.relocation_target
- - page_canonical_link @issue.relocation_target.present(current_user: current_user).web_url
-- if @issue.sentry_issue.present?
- - add_page_specific_style 'page_bundles/error_tracking_details'
+- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
-- can_update_issue = can?(current_user, :update_issue, @issue)
-- can_reopen_issue = can?(current_user, :reopen_issue, @issue)
-- can_report_spam = @issue.submittable_as_spam_by?(current_user)
-- can_create_issue = show_new_issue_link?(@project)
-- related_branches_path = related_branches_project_issue_path(@project, @issue)
-
-= render_if_exists "projects/issues/alert_blocked", issue: @issue, current_user: current_user
-= render "projects/issues/alert_moved_from_service_desk", issue: @issue
-
-.detail-page-header
- .detail-page-header-body
- .issuable-status-box.status-box.status-box-issue-closed{ class: issue_status_visibility(@issue, status_box: :closed) }
- = sprite_icon('mobile-issue-close', css_class: 'd-block d-sm-none')
- .d-none.d-sm-block
- = issue_closed_text(@issue, current_user)
- .issuable-status-box.status-box.status-box-open{ class: issue_status_visibility(@issue, status_box: :open) }
- = sprite_icon('issue-open-m', css_class: 'd-block d-sm-none')
- %span.d-none.d-sm-block Open
-
- .issuable-meta
- #js-issuable-header-warnings
- = issuable_meta(@issue, @project, "Issue")
-
- %a.btn.btn-default.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
- = sprite_icon('chevron-double-lg-left')
-
- .detail-page-header-actions.js-issuable-actions.js-issuable-buttons{ data: { "action": "close-reopen" } }
- .clearfix.issue-btn-group.dropdown
- %button.btn.btn-default.float-left.d-md-none{ type: "button", data: { toggle: "dropdown" } }
- Options
- = icon('caret-down')
- .dropdown-menu.dropdown-menu-right.d-lg-none
- %ul
- - unless current_user == @issue.author
- %li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- - if can_update_issue
- %li= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close js-btn-issue-action #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- - if can_reopen_issue
- %li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen js-btn-issue-action #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- - if can_report_spam
- %li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
- - if can_create_issue
- - if can_update_issue || can_report_spam
- %li.divider
- %li= link_to 'New issue', new_project_issue_path(@project), id: 'new_issue_link'
-
- = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue, can_reopen: can_reopen_issue, warn_before_close: defined?(@issue.blocked?) && @issue.blocked?
-
- - if can_report_spam
- = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'd-none d-md-block gl-button btn btn-grouped btn-spam', title: 'Submit as spam'
- - if can_create_issue
- = link_to new_project_issue_path(@project), class: 'd-none d-md-block gl-button btn btn-grouped btn-success btn-inverted', title: 'New issue', id: 'new_issue_link' do
- New issue
-
-.issue-details.issuable-details
- .detail-page-description.content-block
- #js-issuable-app{ data: { initial: issuable_initial_data(@issue).to_json} }
- .title-container
- %h2.title= markdown_field(@issue, :title)
- - if @issue.description.present?
- .description
- .md= markdown_field(@issue, :description)
-
- = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
-
- - if @issue.sentry_issue.present?
- #js-sentry-error-stack-trace{ data: error_details_data(@project, @issue.sentry_issue.sentry_issue_identifier) }
-
- = render 'projects/issues/design_management'
-
- = render_if_exists 'projects/issues/related_issues'
-
- #js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }
-
- - if can?(current_user, :download_code, @project)
- - add_page_startup_api_call related_branches_path
- #related-branches{ data: { url: related_branches_path } }
- -# This element is filled in using JavaScript.
-
- .content-block.emoji-block.emoji-block-sticky
- .row.gl-m-0.gl-justify-content-space-between
- .js-noteable-awards
- = render 'award_emoji/awards_block', awardable: @issue, inline: true
- .new-branch-col
- = render_if_exists "projects/issues/timeline_toggle", issue: @issue
- #js-vue-sort-issue-discussions
- #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
- = render 'new_branch' if show_new_branch_button?
-
- = render 'projects/issues/discussion'
-
-= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees
+= render 'projects/issuable/show', issuable: @issue
diff --git a/app/views/shared/issue_type/_details_content.html.haml b/app/views/shared/issue_type/_details_content.html.haml
new file mode 100644
index 00000000000..7c1ec332ba4
--- /dev/null
+++ b/app/views/shared/issue_type/_details_content.html.haml
@@ -0,0 +1,31 @@
+- related_branches_path = related_branches_project_issue_path(@project, issuable)
+
+.issue-details.issuable-details
+ .detail-page-description.content-block
+ #js-issuable-app{ data: { initial: issuable_initial_data(issuable).to_json} }
+ .title-container
+ %h2.title= markdown_field(issuable, :title)
+ - if issuable.description.present?
+ .description
+ .md= markdown_field(issuable, :description)
+
+ = edited_time_ago_with_tooltip(issuable, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
+
+ = render 'shared/issue_type/sentry_stack_trace', issuable: issuable
+
+ = render 'projects/issues/design_management'
+
+ = render_if_exists 'projects/issues/related_issues'
+
+ #js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: issuable.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }
+
+ - if can?(current_user, :download_code, @project)
+ - add_page_startup_api_call related_branches_path
+ #related-branches{ data: { url: related_branches_path } }
+ -# This element is filled in using JavaScript.
+
+ = render 'shared/issue_type/emoji_block', issuable: issuable
+
+ = render 'projects/issues/discussion'
+
+= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees
diff --git a/app/views/shared/issue_type/_details_header.html.haml b/app/views/shared/issue_type/_details_header.html.haml
new file mode 100644
index 00000000000..79ad45e0c6a
--- /dev/null
+++ b/app/views/shared/issue_type/_details_header.html.haml
@@ -0,0 +1,52 @@
+- can_update_issue = can?(current_user, :update_issue, issuable)
+- can_reopen_issue = can?(current_user, :reopen_issue, issuable)
+- can_report_spam = issuable.submittable_as_spam_by?(current_user)
+- can_create_issue = show_new_issue_link?(@project)
+- display_issuable_type = issuable_display_type(issuable)
+- new_issuable_params = ({ issuable_template: 'incident', issue: { issue_type: 'incident' } } if issuable.incident?)
+
+.detail-page-header
+ .detail-page-header-body
+ .issuable-status-box.status-box.status-box-issue-closed{ class: issue_status_visibility(issuable, status_box: :closed) }
+ = sprite_icon('mobile-issue-close', css_class: 'gl-display-block gl-display-sm-none!')
+ .gl-display-none.gl-display-sm-block!
+ = issue_closed_text(issuable, current_user)
+ .issuable-status-box.status-box.status-box-open{ class: issue_status_visibility(issuable, status_box: :open) }
+ = sprite_icon('issue-open-m', css_class: 'gl-display-block gl-display-sm-none!')
+ %span.gl-display-none.gl-display-sm-block!
+ = _('Open')
+
+ .issuable-meta
+ #js-issuable-header-warnings
+ = issuable_meta(issuable, @project, display_issuable_type)
+
+ %a.btn.gl-button.btn-default.float-right.gl-display-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
+ = sprite_icon('chevron-double-lg-left')
+
+ .detail-page-header-actions.js-issuable-actions.js-issuable-buttons{ data: { "action": "close-reopen" } }
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.gl-button.btn-default.float-left.d-md-none.d-lg-none.d-xl-none{ type: "button", data: { toggle: "dropdown" } }
+ = _('Options')
+ = icon('caret-down')
+ .dropdown-menu.dropdown-menu-right.d-lg-none.d-xl-none
+ %ul
+ - unless current_user == issuable.author
+ %li= link_to _('Report abuse'), new_abuse_report_path(user_id: issuable.author.id, ref_url: issue_url(issuable))
+ - if can_update_issue
+ %li= link_to _('Close %{display_issuable_type}') % { display_issuable_type: display_issuable_type }, issue_path(issuable, issue: { state_event: :close }, format: 'json'), class: "btn-close js-btn-issue-action #{issue_button_visibility(issuable, true)}", title: _('Close %{display_issuable_type}') % { display_issuable_type: display_issuable_type }
+ - if can_reopen_issue
+ %li= link_to _('Reopen %{display_issuable_type}') % { display_issuable_type: display_issuable_type }, issue_path(issuable, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen js-btn-issue-action #{issue_button_visibility(issuable, false)}", title: _('Reopen %{display_issuable_type}') % { display_issuable_type: display_issuable_type }
+ - if can_report_spam
+ %li= link_to _('Submit as spam'), mark_as_spam_project_issue_path(@project, issuable), method: :post, class: 'btn-spam', title: 'Submit as spam'
+ - if can_create_issue
+ - if can_update_issue || can_report_spam
+ %li.divider
+ %li= link_to _('New %{display_issuable_type}') % { display_issuable_type: display_issuable_type }, new_project_issue_path(@project, new_issuable_params), id: 'new_%{display_issuable_type}_link' % { display_issuable_type: display_issuable_type }
+
+ = render 'shared/issuable/close_reopen_button', issuable: issuable, can_update: can_update_issue, can_reopen: can_reopen_issue, warn_before_close: defined?(issuable.blocked?) && issuable.blocked?
+
+ - if can_report_spam
+ = link_to _('Submit as spam'), mark_as_spam_project_issue_path(@project, issuable), method: :post, class: 'gl-display-none gl-display-sm-none gl-display-md-block gl-button btn btn-grouped btn-spam', title: 'Submit as spam'
+ - if can_create_issue
+ = link_to new_project_issue_path(@project, new_issuable_params), class: 'gl-display-none gl-display-sm-none gl-display-md-block gl-button btn btn-grouped btn-success btn-inverted', title: _('New %{display_issuable_type}') % { display_issuable_type: display_issuable_type }, id: 'new_%{display_issuable_type}_link' % { display_issuable_type: display_issuable_type } do
+ = _('New %{display_issuable_type}') % { display_issuable_type: display_issuable_type }
diff --git a/app/views/shared/issue_type/_emoji_block.html.haml b/app/views/shared/issue_type/_emoji_block.html.haml
new file mode 100644
index 00000000000..42d149b2ab3
--- /dev/null
+++ b/app/views/shared/issue_type/_emoji_block.html.haml
@@ -0,0 +1,9 @@
+.content-block.emoji-block.emoji-block-sticky
+ .row.gl-m-0.gl-justify-content-space-between
+ .js-noteable-awards
+ = render 'award_emoji/awards_block', awardable: issuable, inline: true
+ .new-branch-col
+ = render_if_exists "projects/issues/timeline_toggle", issuable: issuable
+ #js-vue-sort-issue-discussions
+ #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(issuable), notes_filters: UserPreference.notes_filters.to_json } }
+ = render 'new_branch' if show_new_branch_button?
diff --git a/app/views/shared/issue_type/_sentry_stack_trace.html.haml b/app/views/shared/issue_type/_sentry_stack_trace.html.haml
new file mode 100644
index 00000000000..40b29a74b53
--- /dev/null
+++ b/app/views/shared/issue_type/_sentry_stack_trace.html.haml
@@ -0,0 +1,4 @@
+- return unless issuable.sentry_issue.present?
+- add_page_specific_style 'page_bundles/error_tracking_details'
+
+#js-sentry-error-stack-trace{ data: error_details_data(@project, issuable.sentry_issue.sentry_issue_identifier) }
diff --git a/changelogs/unreleased/270106_enable_csp_for_sse.yml b/changelogs/unreleased/270106_enable_csp_for_sse.yml
new file mode 100644
index 00000000000..e31fe2e3738
--- /dev/null
+++ b/changelogs/unreleased/270106_enable_csp_for_sse.yml
@@ -0,0 +1,5 @@
+---
+title: Configure CSP for displaying Youtube videos in the Static Site Editor
+merge_request: 45767
+author:
+type: fixed
diff --git a/changelogs/unreleased/mo-bump-codequality.yml b/changelogs/unreleased/mo-bump-codequality.yml
new file mode 100644
index 00000000000..6956b22a0e9
--- /dev/null
+++ b/changelogs/unreleased/mo-bump-codequality.yml
@@ -0,0 +1,5 @@
+---
+title: Use CodeQuality 0.85.18 in the CI template
+merge_request: 46253
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-pgbouncer-bpass-config.yml b/changelogs/unreleased/sh-pgbouncer-bpass-config.yml
new file mode 100644
index 00000000000..5ca15b33163
--- /dev/null
+++ b/changelogs/unreleased/sh-pgbouncer-bpass-config.yml
@@ -0,0 +1,5 @@
+---
+title: Add environment variables to override backup/restore DB settings
+merge_request: 45855
+author:
+type: added
diff --git a/config/feature_flags/development/oj_json.yml b/config/feature_flags/development/oj_json.yml
deleted file mode 100644
index b7e112ae544..00000000000
--- a/config/feature_flags/development/oj_json.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: oj_json
-introduced_by_url:
-rollout_issue_url:
-group:
-type: development
-default_enabled: true
diff --git a/doc/README.md b/doc/README.md
index 09638bb4ce8..ddec638505a 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -3,7 +3,7 @@ comments: false
description: 'Learn how to use and administer GitLab, the most scalable Git-based fully integrated platform for software development.'
---
-<div class="display-none">
+<div class="d-none">
<em>Visit <a href="https://docs.gitlab.com/ee/">docs.gitlab.com</a> for optimized
navigation, discoverability, and readability.</em>
</div>
diff --git a/doc/api/protected_environments.md b/doc/api/protected_environments.md
index 56b399cec9b..c42d6776341 100644
--- a/doc/api/protected_environments.md
+++ b/doc/api/protected_environments.md
@@ -96,7 +96,7 @@ POST /projects/:id/protected_environments
```
```shell
-curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/protected_environments?name=staging&deploy_access_levels%5B%5D%5Buser_id%5D=1"
+curl --header 'Content-Type: application/json' --request POST --data '{"name": "production", "deploy_access_levels": [{"group_id": 9899826}]}' --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/22034114/protected_environments"
```
| Attribute | Type | Required | Description |
@@ -105,21 +105,22 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
| `name` | string | yes | The name of the environment. |
| `deploy_access_levels` | array | yes | Array of access levels allowed to deploy, with each described by a hash. |
-Elements in the `deploy_access_levels` array should take the
-form `{user_id: integer}`, `{group_id: integer}` or `{access_level: integer}`.
+Elements in the `deploy_access_levels` array should be one of `user_id`, `group_id` or
+`access_level`, and take the form `{user_id: integer}`, `{group_id: integer}` or
+`{access_level: integer}`.
Each user must have access to the project and each group must [have this project shared](../user/project/members/share_project_with_groups.md).
Example response:
```json
{
- "name":"staging",
+ "name":"production",
"deploy_access_levels":[
{
- "access_level":null,
- "access_level_description":"Administrator",
- "user_id":1,
- "group_id":null
+ "access_level":40,
+ "access_level_description":"protected-access-group",
+ "user_id":null,
+ "group_id":9899826
}
]
}
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 066a38d68de..8a4cc0c8ff2 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -940,9 +940,7 @@ message. Install the [correct GitLab version](https://packages.gitlab.com/gitlab
and then try again.
NOTE: **Note:**
-There is a known issue with restore not working with `pgbouncer`. The [workaround is to bypass
-`pgbouncer` and connect directly to the primary database node](../administration/postgresql/pgbouncer.md#procedure-for-bypassing-pgbouncer).
-[Read more about backup and restore with `pgbouncer`](#backup-and-restore-for-installations-using-pgbouncer).
+There is a known issue with restore not working with `pgbouncer`. [Read more about backup and restore with `pgbouncer`](#backup-and-restore-for-installations-using-pgbouncer).
### Restore for Docker image and GitLab Helm chart installations
@@ -1039,26 +1037,60 @@ practical use.
## Backup and restore for installations using PgBouncer
-PgBouncer can cause the following errors when performing backups and restores:
+Do NOT backup or restore GitLab through a PgBouncer connection. These
+tasks must [bypass PgBouncer and connect directly to the PostgreSQL primary database node](#bypassing-pgbouncer),
+or they will cause a GitLab outage.
+
+When the GitLab backup or restore task is used with PgBouncer, the
+following error message is shown:
```ruby
ActiveRecord::StatementInvalid: PG::UndefinedTable
```
-There is a [known issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3470) for restore not working
-with `pgbouncer`.
+This happens because the task uses `pg_dump`, which [sets a null search
+path and explicitly includes the schema in every SQL query](https://gitlab.com/gitlab-org/gitlab/-/issues/23211)
+to address [CVE-2018-1058](https://www.postgresql.org/about/news/postgresql-103-968-9512-9417-and-9322-released-1834/).
+
+Since connections are reused with PgBouncer in transaction pooling mode,
+PostgreSQL fails to search the default `public` schema. As a result,
+this clearing of the search path causes tables and columns to appear
+missing.
+
+### Bypassing PgBouncer
+
+There are two ways to fix this:
+
+1. [Use environment variables to override the database settings](#environment-variable-overrides) for the backup task.
+1. Reconfigure a node to [connect directly to the PostgreSQL primary database node](../administration/postgresql/pgbouncer.md#procedure-for-bypassing-pgbouncer).
+
+#### Environment variable overrides
-To workaround this issue, the GitLab server will need to bypass `pgbouncer` and
-[connect directly to the primary database node](../administration/postgresql/pgbouncer.md#procedure-for-bypassing-pgbouncer)
-to perform the database restore.
+By default, GitLab uses the database configuration stored in a
+configuration file (`database.yml`). However, you can override the database settings
+for the backup and restore task by setting environment
+variables that are prefixed with `GITLAB_BACKUP_`:
+
+- `GITLAB_BACKUP_PGHOST`
+- `GITLAB_BACKUP_PGUSER`
+- `GITLAB_BACKUP_PGPORT`
+- `GITLAB_BACKUP_PGPASSWORD`
+- `GITLAB_BACKUP_PGSSLMODE`
+- `GITLAB_BACKUP_PGSSLKEY`
+- `GITLAB_BACKUP_PGSSLCERT`
+- `GITLAB_BACKUP_PGSSLROOTCERT`
+- `GITLAB_BACKUP_PGSSLCRL`
+- `GITLAB_BACKUP_PGSSLCOMPRESSION`
+
+For example, to override the database host and port to use 192.168.1.10
+and port 5432 with the Omnibus package:
+
+```shell
+sudo GITLAB_BACKUP_PGHOST=192.168.1.10 GITLAB_BACKUP_PGPORT=5432 /opt/gitlab/bin/gitlab-backup create
+```
-There is also a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/23211)
-with PostgreSQL 9 and running a database backup through PgBouncer that can cause
-an outage to GitLab. If you're still on PostgreSQL 9 and upgrading PostgreSQL isn't
-an option, workarounds include having a dedicated application node just for backups,
-configured to connect directly the primary database node as noted above. You're
-advised to upgrade your PostgreSQL version though, GitLab 11.11 shipped with PostgreSQL
-10.7, and that is the recommended version for GitLab 12+.
+See the [PostgreSQL documentation](https://www.postgresql.org/docs/12/libpq-envars.html)
+for more details on what these parameters do.
## Additional notes
diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md
index 9508407ccae..0d619659bd1 100644
--- a/doc/user/application_security/coverage_fuzzing/index.md
+++ b/doc/user/application_security/coverage_fuzzing/index.md
@@ -30,6 +30,7 @@ Docker image with the fuzz engine to run your app.
| Swift | [libfuzzer](https://github.com/apple/swift/blob/master/docs/libFuzzerIntegration.md) | [swift-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/swift-fuzzing-example) |
| Rust | [cargo-fuzz (libFuzzer support)](https://github.com/rust-fuzz/cargo-fuzz) | [rust-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/rust-fuzzing-example) |
| Java | [JQF](https://github.com/rohanpadhye/JQF) | [java-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/java-fuzzing-example) |
+| Java | [javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz) (recommended) | [javafuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/javafuzz-fuzzing-example) |
## Configuration
diff --git a/doc/user/packages/index.md b/doc/user/packages/index.md
index 53ba3d01b3e..8e999de8f4c 100644
--- a/doc/user/packages/index.md
+++ b/doc/user/packages/index.md
@@ -30,31 +30,33 @@ The Package Registry supports the following formats:
You can also use the [API](../../api/packages.md) to administer the Package Registry.
-The GitLab [Container Registry](container_registry/index.md) is a secure and private registry for container images.
-It's built on open source software and completely integrated within GitLab.
-Use GitLab CI/CD to create and publish images. Use the GitLab [API](../../api/container_registry.md) to
-manage the registry across groups and projects.
+## Accepting contributions
-The [Dependency Proxy](dependency_proxy/index.md) is a local proxy for frequently-used upstream images and packages.
-
-## Suggested contributions
-
-Consider contributing to GitLab. This [development documentation](../../development/packages.md) will
+The below table lists formats that are not supported, but are accepting Community contributions for. Consider contributing to GitLab. This [development documentation](../../development/packages.md) will
guide you through the process. Or check out how other members of the community
are adding support for [PHP](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17417) or [Terraform](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18834).
-| Format | Use case |
+| Format | Status |
| ------ | ------ |
-| [Cargo](https://gitlab.com/gitlab-org/gitlab/-/issues/33060) | Cargo is the Rust package manager. Build, publish and share Rust packages |
-| [Chef](https://gitlab.com/gitlab-org/gitlab/-/issues/36889) | Configuration management with Chef using all the benefits of a repository manager. |
-| [CocoaPods](https://gitlab.com/gitlab-org/gitlab/-/issues/36890) | Speed up development with Xcode and CocoaPods. |
-| [Conda](https://gitlab.com/gitlab-org/gitlab/-/issues/36891) | Secure and private local Conda repositories. |
-| [CRAN](https://gitlab.com/gitlab-org/gitlab/-/issues/36892) | Deploy and resolve CRAN packages for the R language. |
-| [Debian](https://gitlab.com/gitlab-org/gitlab/-/issues/5835) | Host and provision Debian packages. |
-| [Opkg](https://gitlab.com/gitlab-org/gitlab/-/issues/36894) | Optimize your work with OpenWrt using Opkg repositories. |
-| [P2](https://gitlab.com/gitlab-org/gitlab/-/issues/36895) | Host all your Eclipse plugins in your own GitLab P2 repository. |
-| [Puppet](https://gitlab.com/gitlab-org/gitlab/-/issues/36897) | Configuration management meets repository management with Puppet repositories. |
-| [RPM](https://gitlab.com/gitlab-org/gitlab/-/issues/5932) | Distribute RPMs directly from GitLab. |
-| [RubyGems](https://gitlab.com/gitlab-org/gitlab/-/issues/803) | Use GitLab to host your own gems. |
-| [SBT](https://gitlab.com/gitlab-org/gitlab/-/issues/36898) | Resolve dependencies from and deploy build output to SBT repositories when running SBT builds. |
-| [Vagrant](https://gitlab.com/gitlab-org/gitlab/-/issues/36899) | Securely host your Vagrant boxes in local repositories. |
+| Chef | [#36889](https://gitlab.com/gitlab-org/gitlab/-/issues/36889) |
+| CocoaPods | [#36890](https://gitlab.com/gitlab-org/gitlab/-/issues/36890) |
+| CocoaPods | [#36891](https://gitlab.com/gitlab-org/gitlab/-/issues/36891) |
+| Conda | [#36891](https://gitlab.com/gitlab-org/gitlab/-/issues/36891) |
+| CRAN | [#36892](https://gitlab.com/gitlab-org/gitlab/-/issues/36892) |
+| Debian | [WIP: Merge Request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44746) |
+| Opkg | [#36894](https://gitlab.com/gitlab-org/gitlab/-/issues/36894) |
+| P2 | [#36895](https://gitlab.com/gitlab-org/gitlab/-/issues/36895) |
+| Puppet | [#36897](https://gitlab.com/gitlab-org/gitlab/-/issues/36897) |
+| RPM | [#5932](https://gitlab.com/gitlab-org/gitlab/-/issues/5932) |
+| RubyGems | [#803](https://gitlab.com/gitlab-org/gitlab/-/issues/803) |
+| SBT | [#36898](https://gitlab.com/gitlab-org/gitlab/-/issues/36898) |
+| Terraform | [WIP: Merge Request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18834) |
+| Vagrant | [#36899](https://gitlab.com/gitlab-org/gitlab/-/issues/36899) |
+
+## Container Registry
+
+The GitLab [Container Registry](container_registry/index.md) is a secure and private registry for container images. It's built on open source software and completely integrated within GitLab. Use GitLab CI/CD to create and publish images. Use the GitLab [API](../../api/container_registry.md) to manage the registry across groups and projects.
+
+## Dependency Proxy
+
+The [Dependency Proxy](dependency_proxy/index.md) is a local proxy for frequently-used upstream images and packages.
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 851445f703d..0429d9496d6 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -140,7 +140,14 @@ module Backup
'sslcrl' => 'PGSSLCRL',
'sslcompression' => 'PGSSLCOMPRESSION'
}
- args.each { |opt, arg| ENV[arg] = config[opt].to_s if config[opt] }
+ args.each do |opt, arg|
+ # This enables the use of different PostgreSQL settings in
+ # case PgBouncer is used. PgBouncer clears the search path,
+ # which wreaks havoc on Rails if connections are reused.
+ override = "GITLAB_BACKUP_#{arg}"
+ val = ENV[override].presence || config[opt].to_s.presence
+ ENV[arg] = val if val
+ end
end
def report_success(success)
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
index ec33020205b..054ec03cdf7 100644
--- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
@@ -7,7 +7,7 @@ code_quality:
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
- CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10-gitlab.1"
+ CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.18"
needs: []
script:
- export SOURCE_CODE=$PWD
diff --git a/lib/gitlab/json.rb b/lib/gitlab/json.rb
index 29cfec443e8..8565f664cd4 100644
--- a/lib/gitlab/json.rb
+++ b/lib/gitlab/json.rb
@@ -67,15 +67,6 @@ module Gitlab
::JSON.pretty_generate(object, opts)
end
- # Feature detection for using Oj instead of the `json` gem.
- #
- # @return [Boolean]
- def enable_oj?
- return false unless feature_table_exists?
-
- Feature.enabled?(:oj_json, default_enabled: true)
- end
-
private
# Convert JSON string into Ruby through toggleable adapters.
@@ -91,11 +82,7 @@ module Gitlab
def adapter_load(string, *args, **opts)
opts = standardize_opts(opts)
- if enable_oj?
- Oj.load(string, opts)
- else
- ::JSON.parse(string, opts)
- end
+ Oj.load(string, opts)
rescue Oj::ParseError, Encoding::UndefinedConversionError => ex
raise parser_error.new(ex)
end
@@ -120,11 +107,7 @@ module Gitlab
#
# @return [String]
def adapter_dump(object, *args, **opts)
- if enable_oj?
- Oj.dump(object, opts)
- else
- ::JSON.dump(object, *args)
- end
+ Oj.dump(object, opts)
end
# Generates JSON for an object but with fewer options, using toggleable adapters.
@@ -135,11 +118,7 @@ module Gitlab
def adapter_generate(object, opts = {})
opts = standardize_opts(opts)
- if enable_oj?
- Oj.generate(object, opts)
- else
- ::JSON.generate(object, opts)
- end
+ Oj.generate(object, opts)
end
# Take a JSON standard options hash and standardize it to work across adapters
@@ -149,11 +128,8 @@ module Gitlab
# @return [Hash]
def standardize_opts(opts)
opts ||= {}
-
- if enable_oj?
- opts[:mode] = :rails
- opts[:symbol_keys] = opts[:symbolize_keys] || opts[:symbolize_names]
- end
+ opts[:mode] = :rails
+ opts[:symbol_keys] = opts[:symbolize_keys] || opts[:symbolize_names]
opts
end
@@ -213,7 +189,7 @@ module Gitlab
# @param object [Object]
# @return [String]
def self.call(object, env = nil)
- if Gitlab::Json.enable_oj? && Feature.enabled?(:grape_gitlab_json, default_enabled: true)
+ if Feature.enabled?(:grape_gitlab_json, default_enabled: true)
Gitlab::Json.dump(object)
else
Grape::Formatter::Json.call(object, env)
diff --git a/lib/gitlab/repository_size_checker.rb b/lib/gitlab/repository_size_checker.rb
index 03d9f961dd9..dbfec77cb18 100644
--- a/lib/gitlab/repository_size_checker.rb
+++ b/lib/gitlab/repository_size_checker.rb
@@ -32,7 +32,7 @@ module Gitlab
def changes_will_exceed_size_limit?(change_size)
return false unless enabled?
- change_size > limit || exceeded_size(change_size) > 0
+ above_size_limit? || exceeded_size(change_size) > 0
end
# @param change_size [int] in bytes
diff --git a/lib/gitlab/repository_url_builder.rb b/lib/gitlab/repository_url_builder.rb
index 2b88af1f77c..a2d0d50d20b 100644
--- a/lib/gitlab/repository_url_builder.rb
+++ b/lib/gitlab/repository_url_builder.rb
@@ -4,9 +4,6 @@ module Gitlab
module RepositoryUrlBuilder
class << self
def build(path, protocol: :ssh)
- # TODO: See https://gitlab.com/gitlab-org/gitlab/-/issues/213021
- path = path.sub('@snippets', 'snippets')
-
case protocol
when :ssh
ssh_url(path)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6def372b3e7..efb09f2f871 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17716,6 +17716,9 @@ msgstr ""
msgid "New"
msgstr ""
+msgid "New %{display_issuable_type}"
+msgstr ""
+
msgid "New Application"
msgstr ""
@@ -25457,6 +25460,9 @@ msgstr ""
msgid "Submit a review"
msgstr ""
+msgid "Submit as spam"
+msgstr ""
+
msgid "Submit changes"
msgstr ""
diff --git a/spec/features/incidents/user_views_incident_spec.rb b/spec/features/incidents/user_views_incident_spec.rb
new file mode 100644
index 00000000000..9615abebfde
--- /dev/null
+++ b/spec/features/incidents/user_views_incident_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "User views incident" do
+ let_it_be(:project) { create(:project_empty_repo, :public) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:incident) { create(:incident, project: project, description: "# Description header\n\n**Lorem** _ipsum_ dolor sit [amet](https://example.com)", author: user) }
+ let_it_be(:note) { create(:note, noteable: incident, project: project, author: user) }
+
+ before_all do
+ project.add_developer(user)
+ end
+
+ before do
+ sign_in(user)
+
+ visit(project_issues_incident_path(project, incident))
+ end
+
+ it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") }
+
+ it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet'
+
+ it 'shows the merge request and incident actions', :aggregate_failures do
+ expect(page).to have_link('New incident')
+ expect(page).to have_button('Create merge request')
+ expect(page).to have_link('Close incident')
+ end
+
+ context 'when the project is archived' do
+ before do
+ project.update!(archived: true)
+ visit(project_issues_incident_path(project, incident))
+ end
+
+ it 'hides the merge request and incident actions', :aggregate_failures do
+ expect(page).not_to have_link('New incident')
+ expect(page).not_to have_button('Create merge request')
+ expect(page).not_to have_link('Close incident')
+ end
+ end
+
+ describe 'user status' do
+ subject { visit(project_issues_incident_path(project, incident)) }
+
+ context 'when showing status of the author of the incident' do
+ it_behaves_like 'showing user status' do
+ let(:user_with_status) { user }
+ end
+ end
+
+ context 'when showing status of a user who commented on an incident', :js do
+ it_behaves_like 'showing user status' do
+ let(:user_with_status) { user }
+ end
+ end
+
+ context 'when status message has an emoji', :js do
+ let_it_be(:message) { 'My status with an emoji' }
+ let_it_be(:message_emoji) { 'basketball' }
+ let_it_be(:status) { create(:user_status, user: user, emoji: 'smirk', message: "#{message} :#{message_emoji}:") }
+
+ it 'correctly renders the emoji' do
+ wait_for_requests
+
+ tooltip_span = page.first(".user-status-emoji[title^='#{message}']")
+ tooltip_span.hover
+
+ wait_for_requests
+
+ tooltip = page.find('.tooltip .tooltip-inner')
+
+ page.within(tooltip) do
+ expect(page).to have_emoji(message_emoji)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb
index 7db72f2cd05..bb9bce9eccf 100644
--- a/spec/features/issues/user_interacts_with_awards_spec.rb
+++ b/spec/features/issues/user_interacts_with_awards_spec.rb
@@ -68,7 +68,7 @@ RSpec.describe 'User interacts with awards' do
page.within('.awards') do
expect(page).to have_selector('.js-emoji-btn')
expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
- expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+ expect(page).to have_css(".js-emoji-btn.active[title='You']")
expect do
page.find('.js-emoji-btn.active').click
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 5cbfacf4e48..c2d5b0acda6 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe 'User uses header search field', :js do
wait_for_all_requests
end
- it 'shows the category search dropdown' do
+ it 'shows the category search dropdown', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/250285' do
expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
end
@@ -44,7 +44,7 @@ RSpec.describe 'User uses header search field', :js do
page.find('#search').click
end
- it 'shows category search dropdown' do
+ it 'shows category search dropdown', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/250285' do
expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
@@ -104,7 +104,7 @@ RSpec.describe 'User uses header search field', :js do
let(:scope_name) { 'All GitLab' }
end
- it 'displays search options' do
+ it 'displays search options', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/251076' do
fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test'))
diff --git a/spec/features/static_site_editor_spec.rb b/spec/features/static_site_editor_spec.rb
index 03085917d67..a47579582e2 100644
--- a/spec/features/static_site_editor_spec.rb
+++ b/spec/features/static_site_editor_spec.rb
@@ -73,4 +73,44 @@ RSpec.describe 'Static Site Editor' do
expect(node['data-static-site-generator']).to eq('middleman')
end
end
+
+ describe 'Static Site Editor Content Security Policy' do
+ subject { response_headers['Content-Security-Policy'] }
+
+ context 'when no global CSP config exists' do
+ before do
+ expect_next_instance_of(Projects::StaticSiteEditorController) do |controller|
+ expect(controller).to receive(:current_content_security_policy)
+ .and_return(ActionDispatch::ContentSecurityPolicy.new)
+ end
+ end
+
+ it 'does not add CSP directives' do
+ visit sse_path
+
+ is_expected.to be_blank
+ end
+ end
+
+ context 'when a global CSP config exists' do
+ let_it_be(:cdn_url) { 'https://some-cdn.test' }
+ let_it_be(:youtube_url) { 'https://www.youtube.com' }
+
+ before do
+ csp = ActionDispatch::ContentSecurityPolicy.new do |p|
+ p.frame_src :self, cdn_url
+ end
+
+ expect_next_instance_of(Projects::StaticSiteEditorController) do |controller|
+ expect(controller).to receive(:current_content_security_policy).and_return(csp)
+ end
+ end
+
+ it 'appends youtube to the CSP frame-src policy' do
+ visit sse_path
+
+ is_expected.to eql("frame-src 'self' #{cdn_url} #{youtube_url}")
+ end
+ end
+ end
end
diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index 7fd6a9e7b87..c6a9c911ccf 100644
--- a/spec/frontend/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -169,29 +169,6 @@ describe('AwardsHandler', () => {
});
});
- describe('::userAuthored', () => {
- it('should update tooltip to user authored title', () => {
- const $votesBlock = $('.js-awards-block').eq(0);
- const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
- $thumbsUpEmoji.attr('data-title', 'sam');
- awardsHandler.userAuthored($thumbsUpEmoji);
-
- expect($thumbsUpEmoji.data('originalTitle')).toBe(
- 'You cannot vote on your own issue, MR and note',
- );
- });
-
- it('should restore tooltip back to initial vote list', () => {
- const $votesBlock = $('.js-awards-block').eq(0);
- const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
- $thumbsUpEmoji.attr('data-title', 'sam');
- awardsHandler.userAuthored($thumbsUpEmoji);
- jest.advanceTimersByTime(2801);
-
- expect($thumbsUpEmoji.data('originalTitle')).toBe('sam');
- });
- });
-
describe('::getAwardUrl', () => {
it('returns the url for request', () => {
expect(awardsHandler.getAwardUrl()).toBe('http://test.host/-/snippets/1/toggle_award_emoji');
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 7a243573b6d..0e3752f220e 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -345,6 +345,24 @@ RSpec.describe IssuablesHelper do
end
end
+ describe '#issuable_display_type' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:issuable_type, :issuable_display_type) do
+ :issue | 'issue'
+ :incident | 'incident'
+ :merge_request | 'merge request'
+ end
+
+ with_them do
+ let(:issuable) { build_stubbed(issuable_type) }
+
+ subject { helper.issuable_display_type(issuable) }
+
+ it { is_expected.to eq(issuable_display_type) }
+ end
+ end
+
describe '#sidebar_milestone_tooltip_label' do
it 'escapes HTML in the milestone title' do
milestone = build(:milestone, title: '&lt;img onerror=alert(1)&gt;')
diff --git a/spec/lib/backup/database_spec.rb b/spec/lib/backup/database_spec.rb
index fccd6db0018..2bce4cab679 100644
--- a/spec/lib/backup/database_spec.rb
+++ b/spec/lib/backup/database_spec.rb
@@ -48,5 +48,26 @@ RSpec.describe Backup::Database do
expect(output).to include(visible_error)
end
end
+
+ context 'with PostgreSQL settings defined in the environment' do
+ let(:cmd) { %W[#{Gem.ruby} -e] + ["$stderr.puts ENV.to_h.select { |k, _| k.start_with?('PG') }"] }
+ let(:config) { YAML.load_file(File.join(Rails.root, 'config', 'database.yml'))['test'] }
+
+ before do
+ stub_const 'ENV', ENV.to_h.merge({
+ 'GITLAB_BACKUP_PGHOST' => 'test.example.com',
+ 'PGPASSWORD' => 'donotchange'
+ })
+ end
+
+ it 'overrides default config values' do
+ subject.restore
+
+ expect(output).to include(%("PGHOST"=>"test.example.com"))
+ expect(output).to include(%("PGPASSWORD"=>"donotchange"))
+ expect(output).to include(%("PGPORT"=>"#{config['port']}")) if config['port']
+ expect(output).to include(%("PGUSER"=>"#{config['username']}")) if config['username']
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb
index 0402296a3a8..59ec94f2855 100644
--- a/spec/lib/gitlab/json_spec.rb
+++ b/spec/lib/gitlab/json_spec.rb
@@ -7,342 +7,306 @@ RSpec.describe Gitlab::Json do
stub_feature_flags(json_wrapper_legacy_mode: true)
end
- shared_examples "json" do
- describe ".parse" do
- context "legacy_mode is disabled by default" do
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
- end
-
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
- end
-
- it "parses a string" do
- expect(subject.parse('"foo"', legacy_mode: false)).to eq("foo")
- end
-
- it "parses a true bool" do
- expect(subject.parse("true", legacy_mode: false)).to be(true)
- end
-
- it "parses a false bool" do
- expect(subject.parse("false", legacy_mode: false)).to be(false)
- end
+ describe ".parse" do
+ context "legacy_mode is disabled by default" do
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
end
- context "legacy_mode is enabled" do
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
- end
-
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
-
- it "raises an error on a string" do
- expect { subject.parse('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
- end
-
- it "raises an error on a true bool" do
- expect { subject.parse("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
- end
-
- it "raises an error on a false bool" do
- expect { subject.parse("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
- end
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
end
- context "feature flag is disabled" do
- before do
- stub_feature_flags(json_wrapper_legacy_mode: false)
- end
-
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
- end
-
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
-
- it "parses a string" do
- expect(subject.parse('"foo"', legacy_mode: true)).to eq("foo")
- end
+ it "parses a string" do
+ expect(subject.parse('"foo"', legacy_mode: false)).to eq("foo")
+ end
- it "parses a true bool" do
- expect(subject.parse("true", legacy_mode: true)).to be(true)
- end
+ it "parses a true bool" do
+ expect(subject.parse("true", legacy_mode: false)).to be(true)
+ end
- it "parses a false bool" do
- expect(subject.parse("false", legacy_mode: true)).to be(false)
- end
+ it "parses a false bool" do
+ expect(subject.parse("false", legacy_mode: false)).to be(false)
end
end
- describe ".parse!" do
- context "legacy_mode is disabled by default" do
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
- end
+ context "legacy_mode is enabled" do
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
- end
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
- it "parses a string" do
- expect(subject.parse!('"foo"', legacy_mode: false)).to eq("foo")
- end
+ it "raises an error on a string" do
+ expect { subject.parse('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
- it "parses a true bool" do
- expect(subject.parse!("true", legacy_mode: false)).to be(true)
- end
+ it "raises an error on a true bool" do
+ expect { subject.parse("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
- it "parses a false bool" do
- expect(subject.parse!("false", legacy_mode: false)).to be(false)
- end
+ it "raises an error on a false bool" do
+ expect { subject.parse("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
end
+ end
- context "legacy_mode is enabled" do
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
- end
+ context "feature flag is disabled" do
+ before do
+ stub_feature_flags(json_wrapper_legacy_mode: false)
+ end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
- it "raises an error on a string" do
- expect { subject.parse!('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
- end
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
- it "raises an error on a true bool" do
- expect { subject.parse!("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
- end
+ it "parses a string" do
+ expect(subject.parse('"foo"', legacy_mode: true)).to eq("foo")
+ end
- it "raises an error on a false bool" do
- expect { subject.parse!("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
- end
+ it "parses a true bool" do
+ expect(subject.parse("true", legacy_mode: true)).to be(true)
end
- context "feature flag is disabled" do
- before do
- stub_feature_flags(json_wrapper_legacy_mode: false)
- end
+ it "parses a false bool" do
+ expect(subject.parse("false", legacy_mode: true)).to be(false)
+ end
+ end
+ end
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
- end
+ describe ".parse!" do
+ context "legacy_mode is disabled by default" do
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
+ end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
+ end
- it "parses a string" do
- expect(subject.parse!('"foo"', legacy_mode: true)).to eq("foo")
- end
+ it "parses a string" do
+ expect(subject.parse!('"foo"', legacy_mode: false)).to eq("foo")
+ end
- it "parses a true bool" do
- expect(subject.parse!("true", legacy_mode: true)).to be(true)
- end
+ it "parses a true bool" do
+ expect(subject.parse!("true", legacy_mode: false)).to be(true)
+ end
- it "parses a false bool" do
- expect(subject.parse!("false", legacy_mode: true)).to be(false)
- end
+ it "parses a false bool" do
+ expect(subject.parse!("false", legacy_mode: false)).to be(false)
end
end
- describe ".dump" do
- it "dumps an object" do
- expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}')
+ context "legacy_mode is enabled" do
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
end
- it "dumps an array" do
- expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]')
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
end
- it "dumps a string" do
- expect(subject.dump("foo")).to eq('"foo"')
+ it "raises an error on a string" do
+ expect { subject.parse!('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
end
- it "dumps a true bool" do
- expect(subject.dump(true)).to eq("true")
+ it "raises an error on a true bool" do
+ expect { subject.parse!("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
end
- it "dumps a false bool" do
- expect(subject.dump(false)).to eq("false")
+ it "raises an error on a false bool" do
+ expect { subject.parse!("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
end
end
- describe ".generate" do
- let(:obj) do
- { test: true, "foo.bar" => "baz", is_json: 1, some: [1, 2, 3] }
+ context "feature flag is disabled" do
+ before do
+ stub_feature_flags(json_wrapper_legacy_mode: false)
end
- it "generates JSON" do
- expected_string = <<~STR.chomp
- {"test":true,"foo.bar":"baz","is_json":1,"some":[1,2,3]}
- STR
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
- expect(subject.generate(obj)).to eq(expected_string)
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
end
- it "allows you to customise the output" do
- opts = {
- indent: " ",
- space: " ",
- space_before: " ",
- object_nl: "\n",
- array_nl: "\n"
- }
+ it "parses a string" do
+ expect(subject.parse!('"foo"', legacy_mode: true)).to eq("foo")
+ end
- json = subject.generate(obj, opts)
-
- expected_string = <<~STR.chomp
- {
- "test" : true,
- "foo.bar" : "baz",
- "is_json" : 1,
- "some" : [
- 1,
- 2,
- 3
- ]
- }
- STR
+ it "parses a true bool" do
+ expect(subject.parse!("true", legacy_mode: true)).to be(true)
+ end
- expect(json).to eq(expected_string)
+ it "parses a false bool" do
+ expect(subject.parse!("false", legacy_mode: true)).to be(false)
end
end
+ end
- describe ".pretty_generate" do
- let(:obj) do
- {
- test: true,
- "foo.bar" => "baz",
- is_json: 1,
- some: [1, 2, 3],
- more: { test: true },
- multi_line_empty_array: [],
- multi_line_empty_obj: {}
- }
- end
+ describe ".dump" do
+ it "dumps an object" do
+ expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}')
+ end
- it "generates pretty JSON" do
- expected_string = <<~STR.chomp
- {
- "test": true,
- "foo.bar": "baz",
- "is_json": 1,
- "some": [
- 1,
- 2,
- 3
- ],
- "more": {
- "test": true
- },
- "multi_line_empty_array": [
-
- ],
- "multi_line_empty_obj": {
- }
- }
- STR
+ it "dumps an array" do
+ expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]')
+ end
- expect(subject.pretty_generate(obj)).to eq(expected_string)
- end
+ it "dumps a string" do
+ expect(subject.dump("foo")).to eq('"foo"')
+ end
- it "allows you to customise the output" do
- opts = {
- space_before: " "
- }
+ it "dumps a true bool" do
+ expect(subject.dump(true)).to eq("true")
+ end
- json = subject.pretty_generate(obj, opts)
-
- expected_string = <<~STR.chomp
- {
- "test" : true,
- "foo.bar" : "baz",
- "is_json" : 1,
- "some" : [
- 1,
- 2,
- 3
- ],
- "more" : {
- "test" : true
- },
- "multi_line_empty_array" : [
-
- ],
- "multi_line_empty_obj" : {
- }
- }
- STR
+ it "dumps a false bool" do
+ expect(subject.dump(false)).to eq("false")
+ end
+ end
- expect(json).to eq(expected_string)
- end
+ describe ".generate" do
+ let(:obj) do
+ { test: true, "foo.bar" => "baz", is_json: 1, some: [1, 2, 3] }
end
- context "the feature table is missing" do
- before do
- allow(Feature::FlipperFeature).to receive(:table_exists?).and_return(false)
- end
+ it "generates JSON" do
+ expected_string = <<~STR.chomp
+ {"test":true,"foo.bar":"baz","is_json":1,"some":[1,2,3]}
+ STR
+
+ expect(subject.generate(obj)).to eq(expected_string)
+ end
- it "skips legacy mode handling" do
- expect(Feature).not_to receive(:enabled?).with(:json_wrapper_legacy_mode, default_enabled: true)
+ it "allows you to customise the output" do
+ opts = {
+ indent: " ",
+ space: " ",
+ space_before: " ",
+ object_nl: "\n",
+ array_nl: "\n"
+ }
- subject.send(:handle_legacy_mode!, {})
- end
+ json = subject.generate(obj, opts)
- it "skips oj feature detection" do
- expect(Feature).not_to receive(:enabled?).with(:oj_json, default_enabled: true)
+ expected_string = <<~STR.chomp
+ {
+ "test" : true,
+ "foo.bar" : "baz",
+ "is_json" : 1,
+ "some" : [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ STR
- subject.send(:enable_oj?)
- end
+ expect(json).to eq(expected_string)
end
+ end
- context "the database is missing" do
- before do
- allow(Feature::FlipperFeature).to receive(:table_exists?).and_raise(PG::ConnectionBad)
- end
+ describe ".pretty_generate" do
+ let(:obj) do
+ {
+ test: true,
+ "foo.bar" => "baz",
+ is_json: 1,
+ some: [1, 2, 3],
+ more: { test: true },
+ multi_line_empty_array: [],
+ multi_line_empty_obj: {}
+ }
+ end
- it "still parses json" do
- expect(subject.parse("{}")).to eq({})
- end
+ it "generates pretty JSON" do
+ expected_string = <<~STR.chomp
+ {
+ "test": true,
+ "foo.bar": "baz",
+ "is_json": 1,
+ "some": [
+ 1,
+ 2,
+ 3
+ ],
+ "more": {
+ "test": true
+ },
+ "multi_line_empty_array": [
+
+ ],
+ "multi_line_empty_obj": {
+ }
+ }
+ STR
- it "still generates json" do
- expect(subject.dump({})).to eq("{}")
- end
+ expect(subject.pretty_generate(obj)).to eq(expected_string)
+ end
+
+ it "allows you to customise the output" do
+ opts = {
+ space_before: " "
+ }
+
+ json = subject.pretty_generate(obj, opts)
+
+ expected_string = <<~STR.chomp
+ {
+ "test" : true,
+ "foo.bar" : "baz",
+ "is_json" : 1,
+ "some" : [
+ 1,
+ 2,
+ 3
+ ],
+ "more" : {
+ "test" : true
+ },
+ "multi_line_empty_array" : [
+
+ ],
+ "multi_line_empty_obj" : {
+ }
+ }
+ STR
+
+ expect(json).to eq(expected_string)
end
end
- context "oj gem" do
+ context "the feature table is missing" do
before do
- stub_feature_flags(oj_json: true)
+ allow(Feature::FlipperFeature).to receive(:table_exists?).and_return(false)
end
- it_behaves_like "json"
+ it "skips legacy mode handling" do
+ expect(Feature).not_to receive(:enabled?).with(:json_wrapper_legacy_mode, default_enabled: true)
- describe "#enable_oj?" do
- it "returns true" do
- expect(subject.enable_oj?).to be(true)
- end
+ subject.send(:handle_legacy_mode!, {})
end
end
- context "json gem" do
+ context "the database is missing" do
before do
- stub_feature_flags(oj_json: false)
+ allow(Feature::FlipperFeature).to receive(:table_exists?).and_raise(PG::ConnectionBad)
end
- it_behaves_like "json"
+ it "still parses json" do
+ expect(subject.parse("{}")).to eq({})
+ end
- describe "#enable_oj?" do
- it "returns false" do
- expect(subject.enable_oj?).to be(false)
- end
+ it "still generates json" do
+ expect(subject.dump({})).to eq("{}")
end
end
@@ -353,47 +317,25 @@ RSpec.describe Gitlab::Json do
let(:env) { {} }
let(:result) { "{\"test\":true}" }
- context "oj is enabled" do
+ context "grape_gitlab_json flag is enabled" do
before do
- stub_feature_flags(oj_json: true)
+ stub_feature_flags(grape_gitlab_json: true)
end
- context "grape_gitlab_json flag is enabled" do
- before do
- stub_feature_flags(grape_gitlab_json: true)
- end
-
- it "generates JSON" do
- expect(subject).to eq(result)
- end
-
- it "uses Gitlab::Json" do
- expect(Gitlab::Json).to receive(:dump).with(obj)
-
- subject
- end
+ it "generates JSON" do
+ expect(subject).to eq(result)
end
- context "grape_gitlab_json flag is disabled" do
- before do
- stub_feature_flags(grape_gitlab_json: false)
- end
-
- it "generates JSON" do
- expect(subject).to eq(result)
- end
+ it "uses Gitlab::Json" do
+ expect(Gitlab::Json).to receive(:dump).with(obj)
- it "uses Grape::Formatter::Json" do
- expect(Grape::Formatter::Json).to receive(:call).with(obj, env)
-
- subject
- end
+ subject
end
end
- context "oj is disabled" do
+ context "grape_gitlab_json flag is disabled" do
before do
- stub_feature_flags(oj_json: false)
+ stub_feature_flags(grape_gitlab_json: false)
end
it "generates JSON" do
diff --git a/spec/models/personal_snippet_spec.rb b/spec/models/personal_snippet_spec.rb
index 234f6e4b4b5..212605445ff 100644
--- a/spec/models/personal_snippet_spec.rb
+++ b/spec/models/personal_snippet_spec.rb
@@ -20,9 +20,8 @@ RSpec.describe PersonalSnippet do
it_behaves_like 'model with repository' do
let_it_be(:container) { create(:personal_snippet, :repository) }
let(:stubbed_container) { build_stubbed(:personal_snippet) }
- let(:expected_full_path) { "@snippets/#{container.id}" }
+ let(:expected_full_path) { "snippets/#{container.id}" }
let(:expected_web_url_path) { "-/snippets/#{container.id}" }
- let(:expected_repo_url_path) { "snippets/#{container.id}" }
end
describe '#parent_user' do
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index 3bcbf6b9e1b..3d1c87771f3 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -36,8 +36,7 @@ RSpec.describe ProjectSnippet do
it_behaves_like 'model with repository' do
let_it_be(:container) { create(:project_snippet, :repository) }
let(:stubbed_container) { build_stubbed(:project_snippet) }
- let(:expected_full_path) { "#{container.project.full_path}/@snippets/#{container.id}" }
+ let(:expected_full_path) { "#{container.project.full_path}/snippets/#{container.id}" }
let(:expected_web_url_path) { "#{container.project.full_path}/-/snippets/#{container.id}" }
- let(:expected_repo_url_path) { "#{container.project.full_path}/snippets/#{container.id}" }
end
end