Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2b1e7f7dac
commit
f3b9e205bb
|
@ -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
|
||||
|
|
|
@ -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) ..."
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 }
|
|
@ -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?
|
|
@ -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) }
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Configure CSP for displaying Youtube videos in the Static Site Editor
|
||||
merge_request: 45767
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use CodeQuality 0.85.18 in the CI template
|
||||
merge_request: 46253
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add environment variables to override backup/restore DB settings
|
||||
merge_request: 45855
|
||||
author:
|
||||
type: added
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
name: oj_json
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
type: development
|
||||
default_enabled: true
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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/).
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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+.
|
||||
### 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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
See the [PostgreSQL documentation](https://www.postgresql.org/docs/12/libpq-envars.html)
|
||||
for more details on what these parameters do.
|
||||
|
||||
## Additional notes
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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: '<img onerror=alert(1)>')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 a string" do
|
||||
expect(subject.parse('"foo"', legacy_mode: false)).to eq("foo")
|
||||
end
|
||||
|
||||
it "parses an object" do
|
||||
expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
|
||||
end
|
||||
it "parses a true bool" do
|
||||
expect(subject.parse("true", legacy_mode: false)).to be(true)
|
||||
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 true bool" do
|
||||
expect(subject.parse("true", legacy_mode: true)).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
|
||||
|
||||
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
|
||||
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 "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" }]', 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 "raises an error on a string" do
|
||||
expect { subject.parse('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
|
||||
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 true bool" do
|
||||
expect { subject.parse("true", 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 "parses a string" do
|
||||
expect(subject.parse!('"foo"', legacy_mode: true)).to eq("foo")
|
||||
end
|
||||
|
||||
it "parses a true bool" do
|
||||
expect(subject.parse!("true", legacy_mode: true)).to be(true)
|
||||
end
|
||||
|
||||
it "parses a false bool" do
|
||||
expect(subject.parse!("false", legacy_mode: true)).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
|
||||
|
||||
describe ".dump" do
|
||||
it "dumps an object" do
|
||||
expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}')
|
||||
context "feature flag is disabled" do
|
||||
before do
|
||||
stub_feature_flags(json_wrapper_legacy_mode: false)
|
||||
end
|
||||
|
||||
it "dumps an array" do
|
||||
expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]')
|
||||
it "parses an object" 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 "parses an array" do
|
||||
expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
|
||||
end
|
||||
|
||||
it "dumps a true bool" do
|
||||
expect(subject.dump(true)).to eq("true")
|
||||
it "parses a string" do
|
||||
expect(subject.parse('"foo"', legacy_mode: true)).to eq("foo")
|
||||
end
|
||||
|
||||
it "dumps a false bool" do
|
||||
expect(subject.dump(false)).to eq("false")
|
||||
it "parses a true bool" do
|
||||
expect(subject.parse("true", legacy_mode: true)).to be(true)
|
||||
end
|
||||
|
||||
it "parses a false bool" do
|
||||
expect(subject.parse("false", legacy_mode: true)).to be(false)
|
||||
end
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
describe ".generate" do
|
||||
let(:obj) do
|
||||
{ test: true, "foo.bar" => "baz", is_json: 1, some: [1, 2, 3] }
|
||||
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 "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)
|
||||
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 "raises an error on a string" do
|
||||
expect { subject.parse!('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
|
||||
end
|
||||
|
||||
json = subject.generate(obj, opts)
|
||||
it "raises an error on a true bool" do
|
||||
expect { subject.parse!("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
|
||||
end
|
||||
|
||||
expected_string = <<~STR.chomp
|
||||
{
|
||||
"test" : true,
|
||||
"foo.bar" : "baz",
|
||||
"is_json" : 1,
|
||||
"some" : [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
STR
|
||||
|
||||
expect(json).to eq(expected_string)
|
||||
it "raises an error on a false bool" do
|
||||
expect { subject.parse!("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".pretty_generate" do
|
||||
let(:obj) do
|
||||
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 true bool" do
|
||||
expect(subject.parse!("true", legacy_mode: true)).to be(true)
|
||||
end
|
||||
|
||||
it "parses a false bool" do
|
||||
expect(subject.parse!("false", legacy_mode: true)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".dump" do
|
||||
it "dumps an object" do
|
||||
expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}')
|
||||
end
|
||||
|
||||
it "dumps an array" do
|
||||
expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]')
|
||||
end
|
||||
|
||||
it "dumps a string" do
|
||||
expect(subject.dump("foo")).to eq('"foo"')
|
||||
end
|
||||
|
||||
it "dumps a true bool" do
|
||||
expect(subject.dump(true)).to eq("true")
|
||||
end
|
||||
|
||||
it "dumps a false bool" do
|
||||
expect(subject.dump(false)).to eq("false")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".generate" do
|
||||
let(:obj) do
|
||||
{ test: true, "foo.bar" => "baz", is_json: 1, some: [1, 2, 3] }
|
||||
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 "allows you to customise the output" do
|
||||
opts = {
|
||||
indent: " ",
|
||||
space: " ",
|
||||
space_before: " ",
|
||||
object_nl: "\n",
|
||||
array_nl: "\n"
|
||||
}
|
||||
|
||||
json = subject.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: {}
|
||||
"test" : true,
|
||||
"foo.bar" : "baz",
|
||||
"is_json" : 1,
|
||||
"some" : [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
end
|
||||
STR
|
||||
|
||||
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
|
||||
|
||||
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 "the feature table is missing" do
|
||||
before do
|
||||
allow(Feature::FlipperFeature).to receive(:table_exists?).and_return(false)
|
||||
end
|
||||
|
||||
it "skips legacy mode handling" do
|
||||
expect(Feature).not_to receive(:enabled?).with(:json_wrapper_legacy_mode, default_enabled: true)
|
||||
|
||||
subject.send(:handle_legacy_mode!, {})
|
||||
end
|
||||
|
||||
it "skips oj feature detection" do
|
||||
expect(Feature).not_to receive(:enabled?).with(:oj_json, default_enabled: true)
|
||||
|
||||
subject.send(:enable_oj?)
|
||||
end
|
||||
end
|
||||
|
||||
context "the database is missing" do
|
||||
before do
|
||||
allow(Feature::FlipperFeature).to receive(:table_exists?).and_raise(PG::ConnectionBad)
|
||||
end
|
||||
|
||||
it "still parses json" do
|
||||
expect(subject.parse("{}")).to eq({})
|
||||
end
|
||||
|
||||
it "still generates json" do
|
||||
expect(subject.dump({})).to eq("{}")
|
||||
end
|
||||
expect(json).to eq(expected_string)
|
||||
end
|
||||
end
|
||||
|
||||
context "oj gem" do
|
||||
before do
|
||||
stub_feature_flags(oj_json: true)
|
||||
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_behaves_like "json"
|
||||
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": [
|
||||
|
||||
describe "#enable_oj?" do
|
||||
it "returns true" do
|
||||
expect(subject.enable_oj?).to be(true)
|
||||
end
|
||||
],
|
||||
"multi_line_empty_obj": {
|
||||
}
|
||||
}
|
||||
STR
|
||||
|
||||
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 "json gem" do
|
||||
context "the feature table is missing" do
|
||||
before do
|
||||
stub_feature_flags(oj_json: false)
|
||||
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 false" do
|
||||
expect(subject.enable_oj?).to be(false)
|
||||
end
|
||||
subject.send(:handle_legacy_mode!, {})
|
||||
end
|
||||
end
|
||||
|
||||
context "the database is missing" do
|
||||
before do
|
||||
allow(Feature::FlipperFeature).to receive(:table_exists?).and_raise(PG::ConnectionBad)
|
||||
end
|
||||
|
||||
it "still parses json" do
|
||||
expect(subject.parse("{}")).to eq({})
|
||||
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 "uses Gitlab::Json" do
|
||||
expect(Gitlab::Json).to receive(:dump).with(obj)
|
||||
|
||||
it "generates JSON" do
|
||||
expect(subject).to eq(result)
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue