Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-07-25 18:09:40 +00:00
parent 0636ab91ee
commit 2b5399ae01
102 changed files with 715 additions and 27256 deletions

View File

@ -226,7 +226,7 @@
{"name":"gitlab-styles","version":"12.0.1","platform":"ruby","checksum":"d8a302b0ab0e1f18e2d11501760f1b85c5e70b5e5ca628828a0786c7984ed133"},
{"name":"gitlab_chronic_duration","version":"0.12.0","platform":"ruby","checksum":"0d766944d415b5c831f176871ee8625783fc0c5bfbef2d79a3a616f207ffc16d"},
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
{"name":"gitlab_quality-test_tooling","version":"1.32.0","platform":"ruby","checksum":"72b7bb243d3e1f8006bef4bcf3585480a774f72bb9dd97bfcc52db4b32fd30fb"},
{"name":"gitlab_quality-test_tooling","version":"1.32.1","platform":"ruby","checksum":"6918e2048d25124583643d18d9d5cec7958976d98166a0affb5ee0386ce8be24"},
{"name":"globalid","version":"1.1.0","platform":"ruby","checksum":"b337e1746f0c8cb0a6c918234b03a1ddeb4966206ce288fbb57779f59b2d154f"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-apis-androidpublisher_v3","version":"0.34.0","platform":"ruby","checksum":"d7e1d7dd92f79c498fe2082222a1740d788e022e660c135564b3fd299cab5425"},

View File

@ -746,7 +746,7 @@ GEM
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
gitlab_quality-test_tooling (1.32.0)
gitlab_quality-test_tooling (1.32.1)
activesupport (>= 7.0, < 7.2)
amatch (~> 0.4.1)
gitlab (~> 4.19)

View File

@ -131,7 +131,7 @@ export default {
</div>
</div>
</div>
<div data-testid="issuable-create-actions" class="footer-block gl-display-flex gl-mt-6">
<div data-testid="issuable-create-actions" class="footer-block gl-mt-6 gl-flex">
<slot
name="actions"
:issuable-title="issuableTitle"

View File

@ -47,8 +47,8 @@ export default {
</script>
<template>
<gl-form-group class="row" label-class="gl-display-none">
<label class="col-12 gl-display-flex gl-align-center">
<gl-form-group class="row" label-class="gl-hidden">
<label class="col-12 gl-align-center gl-flex">
{{ $options.i18n.fieldLabel }}
</label>
<div class="col-12">

View File

@ -34,7 +34,7 @@ export default {
class="issues-bulk-update right-sidebar"
aria-live="polite"
>
<div class="gl-display-flex gl-justify-content-space-between gl-p-4 gl-border-b">
<div class="gl-border-b gl-flex gl-justify-between gl-p-4">
<slot name="bulk-edit-actions"></slot>
</div>
<slot name="sidebar-items"></slot>

View File

@ -249,7 +249,7 @@ export default {
<template>
<li
:id="`issuable_${issuableId}`"
class="issue gl-display-flex! gl-px-5!"
class="issue !gl-flex !gl-px-5"
:class="{ closed: issuable.closedAt, 'gl-bg-blue-50': isActive }"
:data-labels="labelIdsString"
:data-qa-issue-id="issuableId"
@ -292,7 +292,7 @@ export default {
<work-item-prefetch :work-item-iid="issuableIid" data-testid="issuable-prefetch-trigger">
<template #default="{ prefetchWorkItem, clearPrefetching }">
<gl-link
class="issue-title-text gl-font-base"
class="issue-title-text gl-text-base"
dir="auto"
:href="issuableLinkHref"
data-testid="issuable-title-link"
@ -309,7 +309,7 @@ export default {
</template>
<template v-else>
<gl-link
class="issue-title-text gl-font-base"
class="issue-title-text gl-text-base"
dir="auto"
:href="issuableLinkHref"
data-testid="issuable-title-link"
@ -323,7 +323,7 @@ export default {
<slot v-if="hasSlotContents('title-icons')" name="title-icons"></slot>
<span
v-if="taskStatus"
class="task-status gl-hidden sm:!gl-inline-block gl-ml-2 gl-font-sm"
class="task-status gl-ml-2 gl-hidden gl-text-sm sm:!gl-inline-block"
data-testid="task-status"
>
{{ taskStatus }}
@ -360,7 +360,7 @@ export default {
:data-avatar-url="author.avatarUrl"
:href="author.webPath"
data-testid="issuable-author"
class="author-link js-user-link gl-font-sm gl-text-gray-500!"
class="author-link js-user-link gl-text-sm !gl-text-gray-500"
>
<span class="author">{{ author.name }}</span>
</gl-link>
@ -384,7 +384,7 @@ export default {
v-if="labels.length"
role="group"
:aria-label="__('Labels')"
class="gl-mt-1 gl-mb-0 gl-display-flex gl-flex-wrap gl-gap-2"
class="gl-mb-0 gl-mt-1 gl-flex gl-flex-wrap gl-gap-2"
>
<gl-label
v-for="(label, index) in labels"
@ -401,30 +401,30 @@ export default {
<div class="issuable-meta">
<ul v-if="showIssuableMeta" class="controls gl-gap-3">
<!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
<li v-if="$slots.status" data-testid="issuable-status" class="gl-mr-0!">
<li v-if="$slots.status" data-testid="issuable-status" class="!gl-mr-0">
<gl-badge v-if="isNotOpen" :variant="statusBadgeVariant">
<slot name="status"></slot>
</gl-badge>
<slot v-else name="status"></slot>
</li>
<slot name="pipeline-status"></slot>
<li v-if="assignees.length" class="gl-mr-0!">
<li v-if="assignees.length" class="!gl-mr-0">
<issuable-assignees
:assignees="assignees"
:icon-size="16"
:max-visible="4"
class="gl-align-items-center gl-display-flex"
class="gl-flex gl-items-center"
/>
</li>
<li
v-if="showDiscussions && notesCount"
class="gl-hidden sm:gl-block gl-mr-0!"
class="!gl-mr-0 gl-hidden sm:gl-block"
data-testid="issuable-comments"
>
<div
v-gl-tooltip.top
:title="__('Comments')"
class="!gl-text-inherit gl-display-flex gl-align-items-center"
class="gl-flex gl-items-center !gl-text-inherit"
>
<gl-icon name="comments" class="gl-mr-2" />
{{ notesCount }}
@ -434,7 +434,7 @@ export default {
</ul>
<div
v-gl-tooltip.bottom
class="gl-text-gray-500 gl-hidden sm:gl-inline-block"
class="gl-hidden gl-text-gray-500 sm:gl-inline-block"
:title="tooltipTitle(timestamp)"
data-testid="issuable-timestamp"
>

View File

@ -330,7 +330,7 @@ export default {
:checkbox-checked="allIssuablesChecked"
:show-friendly-text="showFilteredSearchFriendlyText"
terms-as-tokens
class="gl-grow gl-border-t-0 row-content-block max-md:-gl-ml-5 max-md:gl-w-screen max-md:gl-border-t max-md:-gl-mt-1 sm:gl-flex"
class="row-content-block gl-grow gl-border-t-0 max-md:gl-border-t max-md:-gl-ml-5 max-md:-gl-mt-1 max-md:gl-w-screen sm:gl-flex"
data-testid="issuable-search-container"
@checked-input="handleAllIssuablesCheckedInput"
@onFilter="$emit('filter', $event)"
@ -355,7 +355,7 @@ export default {
</issuable-bulk-edit-sidebar>
<slot name="list-body"></slot>
<ul v-if="issuablesLoading" class="content-list">
<li v-for="n in skeletonItemCount" :key="n" class="issue gl-px-5! gl-py-5!">
<li v-for="n in skeletonItemCount" :key="n" class="issue !gl-px-5 !gl-py-5">
<gl-skeleton-loader />
</li>
</ul>
@ -415,9 +415,7 @@ export default {
<slot v-else-if="!error" name="empty-state"></slot>
</template>
<div
class="gl-display-flex gl-justify-content-space-between gl-md-justify-content-center! gl-mt-6 gl-relative"
>
<div class="gl-relative gl-mt-6 gl-flex gl-justify-between md:!gl-justify-center">
<gl-keyset-pagination
v-if="showPaginationControls && useKeysetPagination"
:has-next-page="hasNextPage"
@ -445,7 +443,7 @@ export default {
>
<page-size-selector
:value="defaultPageSize"
class="gl-right-0 gl-relative md:gl-absolute"
class="gl-relative gl-right-0 md:gl-absolute"
@input="handlePageSizeChange"
/>
</local-storage-sync>

View File

@ -46,7 +46,7 @@ export default {
<template>
<div class="top-area">
<gl-tabs
class="gl-display-flex gl-flex-grow-1 gl-p-0 gl-m-0 mobile-separator issuable-state-filters"
class="mobile-separator issuable-state-filters gl-m-0 gl-flex gl-grow gl-p-0"
nav-class="gl-border-b-0"
>
<gl-tab

View File

@ -186,12 +186,12 @@ export default {
:task-list-update-path="taskListUpdatePath"
/>
<slot name="secondary-content"></slot>
<small v-if="isUpdated" class="edited-text gl-font-sm! gl-text-secondary">
<small v-if="isUpdated" class="edited-text !gl-text-sm gl-text-secondary">
{{ __('Edited') }}
<time-ago-tooltip :time="issuable.updatedAt" tooltip-placement="bottom" />
<span v-if="updatedBy">
{{ __('by') }}
<gl-link :href="updatedBy.webUrl" class="author-link gl-font-sm! gl-text-secondary">
<gl-link :href="updatedBy.webUrl" class="author-link !gl-text-sm gl-text-secondary">
<span>{{ updatedBy.name }}</span>
</gl-link>
</span>

View File

@ -42,7 +42,7 @@ export default {
ref="textarea"
:value="issuable.description"
:data-update-url="taskListUpdatePath"
class="gl-display-none js-task-list-field"
class="js-task-list-field gl-hidden"
dir="auto"
>
</textarea>

View File

@ -140,7 +140,7 @@ export default {
:label="__('Description')"
:label-sr-only="!showFieldTitle"
label-for="issuable-description"
class="col-12 gl-px-0 common-note-form"
class="col-12 common-note-form gl-px-0"
>
<markdown-editor
v-model="description"
@ -152,10 +152,7 @@ export default {
@keydown="handleKeydown($event, 'description')"
/>
</gl-form-group>
<div
data-testid="actions"
class="col-12 gl-mt-3 gl-mb-3 gl-px-0 clearfix gl-display-flex gl-gap-3"
>
<div data-testid="actions" class="col-12 clearfix gl-mb-3 gl-mt-3 gl-flex gl-gap-3 gl-px-0">
<slot
name="edit-form-actions"
:issuable-title="title"

View File

@ -162,7 +162,7 @@ export default {
</script>
<template>
<div class="detail-page-header gl-flex-direction-column gl-md-flex-direction-row">
<div class="detail-page-header gl-flex-col md:gl-flex-row">
<div class="detail-page-header-body gl-flex-wrap gl-gap-x-2">
<gl-badge :variant="badgeVariant" :icon="statusIcon" data-testid="issue-state-badge">
<slot name="status-badge">{{ badgeText }}</slot>
@ -189,7 +189,7 @@ export default {
{{ serviceDeskReplyTo }}
</template>
<template #author>
<gl-link class="gl-font-bold js-user-link" :href="author.webUrl" :data-user-id="authorId">
<gl-link class="js-user-link gl-font-bold" :href="author.webUrl" :data-user-id="authorId">
<span :class="[{ 'gl-hidden': !isAuthorExternal }, 'sm:gl-inline']">
{{ author.name }}
</span>
@ -218,12 +218,12 @@ export default {
>
<gl-button
icon="chevron-double-lg-left"
class="gl-ml-auto gl-block sm:!gl-hidden js-sidebar-toggle"
class="js-sidebar-toggle gl-ml-auto gl-block sm:!gl-hidden"
:aria-label="__('Expand sidebar')"
@click="handleRightSidebarToggleClick"
/>
</div>
<div class="detail-page-header-actions gl-align-self-center gl-display-flex gl-gap-3">
<div class="detail-page-header-actions gl-flex gl-gap-3 gl-self-center">
<slot name="header-actions"></slot>
</div>
</div>

View File

@ -69,7 +69,7 @@ export default {
<div class="title-container">
<h1
v-safe-html="issuable.titleHtml || issuable.title"
class="title gl-font-size-h-display"
class="title gl-text-size-h-display"
dir="auto"
data-testid="issuable-title"
></h1>
@ -87,11 +87,11 @@ export default {
<transition name="issuable-header-slide">
<div
v-if="stickyTitleVisible"
class="issue-sticky-header gl-fixed gl-z-3 gl-bg-default gl-border-b gl-py-3"
class="issue-sticky-header gl-border-b gl-fixed gl-z-3 gl-bg-default gl-py-3"
data-testid="header"
>
<div class="issue-sticky-header-text gl-flex gl-items-baseline gl-mx-auto gl-gap-3">
<gl-badge class="gl-whitespace-nowrap gl-self-center" :variant="badgeVariant">
<div class="issue-sticky-header-text gl-mx-auto gl-flex gl-items-baseline gl-gap-3">
<gl-badge class="gl-self-center gl-whitespace-nowrap" :variant="badgeVariant">
<gl-icon v-if="statusIcon" class="sm:gl-hidden" :name="statusIcon" />
<span class="gl-sr-only sm:gl-not-sr-only">
<slot name="status-badge"></slot>
@ -104,7 +104,7 @@ export default {
:workspace-type="workspaceType"
/>
<p
class="gl-font-bold gl-overflow-hidden gl-whitespace-nowrap gl-text-overflow-ellipsis gl-my-0"
class="gl-my-0 gl-overflow-hidden gl-text-ellipsis gl-whitespace-nowrap gl-font-bold"
:title="issuable.title"
>
{{ issuable.title }}

View File

@ -292,10 +292,6 @@ module ApplicationHelper
"#{request.path}?#{options.compact.to_param}"
end
def stylesheet_link_tag_defer(path)
universal_stylesheet_link_tag(path, media: "all", crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil)
end
def sign_in_with_redirect?
current_page?(new_user_session_path) && session[:user_return_to].present?
end
@ -419,17 +415,13 @@ module ApplicationHelper
}
end
def add_page_specific_style(path, defer: true)
def add_page_specific_style(path)
@already_added_styles ||= Set.new
return if @already_added_styles.include?(path)
@already_added_styles.add(path)
content_for :page_specific_styles do
if defer
stylesheet_link_tag_defer path
else
universal_stylesheet_link_tag path
end
universal_stylesheet_link_tag path
end
end

View File

@ -0,0 +1,107 @@
# frozen_string_literal: true
module Ci
module Taggable
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
included do
after_save :save_tags
# rubocop:disable Cop/ActiveRecordDependent -- existing
has_many :tag_taggings, -> { includes(:tag).where(context: :tags) }, # rubocop:disable Rails/InverseOf -- existing
as: :taggable,
class_name: 'Ci::Tagging',
dependent: :destroy,
after_add: :dirtify_tag_list,
after_remove: :dirtify_tag_list
has_many :tags,
class_name: 'Ci::Tag',
through: :tag_taggings,
source: :tag,
after_add: :dirtify_tag_list,
after_remove: :dirtify_tag_list
has_many :taggings, as: :taggable, dependent: :destroy, class_name: '::Ci::Tagging'
has_many :base_tags, through: :taggings, source: :tag, class_name: '::Ci::Tag'
# rubocop:enable Cop/ActiveRecordDependent -- existing
attribute :tag_list, Gitlab::Database::Type::TagListType.new
scope :tagged_with, ->(tags) do
Gitlab::Ci::Tags::Parser
.new(tags)
.parse
.map { |tag| with_tag(tag) }
.reduce(:and)
end
scope :with_tag, ->(name) do
where_exists(
Tagging
.merge(unscoped.scoped_tagging)
.where(context: :tags)
.where(tag_id: Tag.where(name: name))
)
end
scope :scoped_tagging, -> do
where(arel_table[primary_key].eq(Tagging.arel_table[:taggable_id]))
.where(Tagging.arel_table[:taggable_type].eq(base_class.name))
end
end
def tag_list
Gitlab::Ci::Tags::TagList.new(context_tags.map(&:name))
end
strong_memoize_attr :tag_list
def tag_list=(new_tags)
parsed_new_list = Gitlab::Ci::Tags::Parser.new(new_tags).parse
write_attribute('tag_list', parsed_new_list)
instance_variable_set(:@tag_list, parsed_new_list)
end
def reload(*args)
clear_memoization(:tag_list)
super(*args)
end
private
def dirtify_tag_list(_tag)
attribute_will_change!(:tag_list)
clear_memoization(:tag_list)
end
def context_tags
base_tags.where(taggings: { context: :tags, tagger_id: nil })
end
def tag_list_cache_set?
strong_memoized?(:tag_list) && tag_list.any?
end
def save_tags
return unless tag_list_cache_set?
tags = find_or_create_tags_from_list(tag_list.uniq)
current_tags = context_tags
old_tags = current_tags - tags
new_tags = tags - current_tags
taggings.by_context(:tags).where(tag_id: old_tags).delete_all if old_tags.present?
new_tags.each do |tag|
taggings.create!(tag_id: tag.id, context: 'tags', taggable: self)
end
true
end
def find_or_create_tags_from_list(tags)
Ci::Tag.find_or_create_all_with_like_by_name(tags)
end
end
end

View File

@ -12,6 +12,8 @@ class RedirectRoute < MainClusterwide::ApplicationRecord
presence: true,
uniqueness: { case_sensitive: false }
scope :for_source_type, ->(source_type) { where(source_type: source_type) }
scope :by_paths, ->(paths) { where(path: [paths]) }
scope :matching_path_and_descendants, ->(path) do
wheres = 'LOWER(redirect_routes.path) = LOWER(?) OR LOWER(redirect_routes.path) LIKE LOWER(?)'

View File

@ -1,4 +1,8 @@
- add_to_breadcrumbs _("Groups"), admin_groups_path
- add_to_breadcrumbs @group.name, admin_group_path(@group)
- breadcrumb_title _("Edit")
- page_title _("Edit"), @group.name, _("Groups")
%h1.page-title.gl-font-size-h-display= _('Edit group: %{group_name}') % { group_name: @group.name }
%hr
= render 'form', visibility_level: @group.visibility_level

View File

@ -1,4 +1,8 @@
- add_to_breadcrumbs _("Projects"), admin_projects_path
- add_to_breadcrumbs @project.full_name, admin_project_path(@project)
- breadcrumb_title _("Edit")
- page_title _("Edit"), @project.name, _("Projects")
%h1.page-title.gl-font-size-h-display= _('Edit project: %{project_name}') % { project_name: @project.name }
%hr
= render 'form'

View File

@ -1,4 +1,8 @@
- add_to_breadcrumbs _("Topics"), admin_topics_path
- add_to_breadcrumbs @topic.name, edit_admin_topic_path(@topic) # There is no show view, so we reuse edit here
- breadcrumb_title _("Edit")
- page_title _("Edit"), @topic.name, _("Topics")
%h1.page-title.gl-font-size-h-display= _('Edit topic: %{topic_name}') % { topic_name: @topic.name }
%hr
= render 'form', url: admin_topic_path(@topic)

View File

@ -1,4 +1,8 @@
- add_to_breadcrumbs _("Users"), admin_users_path
- add_to_breadcrumbs @user.name, admin_user_path(@user)
- breadcrumb_title _("Edit")
- page_title _("Edit"), @user.name, _("Users")
%h1.page-title.gl-font-size-h-display
= _("Edit user: %{user_name}") % { user_name: @user.name }
= render 'form'

View File

@ -2,5 +2,5 @@
= webpack_bundle_tag 'jira_connect_app'
- add_page_specific_style 'page_bundles/jira_connect', defer: false
- add_page_specific_style 'page_bundles/jira_connect'
- add_page_specific_style 'tailwind'

View File

@ -2,7 +2,7 @@
- addition_color = local_assigns.fetch(:addition, nil)
- if deletion_color.present? || request.path == profile_preferences_path
= stylesheet_link_tag_defer "highlight/diff_custom_colors_deletion"
= universal_stylesheet_link_tag "highlight/diff_custom_colors_deletion"
- if deletion_color.present?
- deletion_color_rgb = hex_color_to_rgb_array(deletion_color).join(',')
:css
@ -11,7 +11,7 @@
}
- if addition_color.present? || request.path == profile_preferences_path
= stylesheet_link_tag_defer "highlight/diff_custom_colors_addition"
= universal_stylesheet_link_tag "highlight/diff_custom_colors_addition"
- if addition_color.present?
- addition_color_rgb = hex_color_to_rgb_array(addition_color).join(',')
:css

View File

@ -31,9 +31,9 @@
- if user_application_dark_mode?
%meta{ name: 'color-scheme', content: 'dark light' }
= stylesheet_link_tag_defer "application_dark"
= universal_stylesheet_link_tag "application_dark"
= yield :page_specific_styles
= stylesheet_link_tag_defer "application_utilities_dark"
= universal_stylesheet_link_tag "application_utilities_dark"
- elsif user_application_system_mode?
%meta{ name: 'color-scheme', content: 'light dark' }
= universal_stylesheet_link_tag "application", media: "(prefers-color-scheme: light)"
@ -42,16 +42,16 @@
= universal_stylesheet_link_tag "application_utilities", media: "(prefers-color-scheme: light)"
= universal_stylesheet_link_tag "application_utilities_dark", media: "(prefers-color-scheme: dark)"
- else
= stylesheet_link_tag_defer "application"
= universal_stylesheet_link_tag "application"
= yield :page_specific_styles
= stylesheet_link_tag_defer 'application_utilities'
= stylesheet_link_tag_defer 'tailwind'
= universal_stylesheet_link_tag 'application_utilities'
= universal_stylesheet_link_tag 'tailwind'
= universal_stylesheet_link_tag "disable_animations", media: "all" if Rails.env.test? || Gitlab.config.gitlab['disable_animations']
= universal_stylesheet_link_tag "test_environment", media: "all" if Rails.env.test?
= stylesheet_link_tag_defer "fonts"
= universal_stylesheet_link_tag "fonts"
= stylesheet_link_tag_defer "highlight/themes/#{user_color_scheme}"
= universal_stylesheet_link_tag "highlight/themes/#{user_color_scheme}"
= universal_stylesheet_link_tag 'performance_bar' if performance_bar_enabled?

View File

@ -6,6 +6,7 @@
- else
= universal_stylesheet_link_tag "application_dark"
= universal_stylesheet_link_tag "application_utilities_dark"
= universal_stylesheet_link_tag 'tailwind'
%body
.gl-mt-6{ class: (params[:lookbook][:display][:layout] == "fluid" ? "container-fluid" : "container") }

View File

@ -1,8 +0,0 @@
---
name: group_level_vulnerability_report_grouping
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137778
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432778
milestone: '16.7'
type: development
group: group::threat insights
default_enabled: true

View File

@ -665,7 +665,14 @@ class FixSequenceOwnersForCiBuilds < Gitlab::Database::Migration[2.2]
sequences = find_sequences_owned_by_ci_builds.to_a.pluck('seq_name') - ['ci_builds_id_seq']
sequences.each do |seq_name|
owner = SEQUENCES.fetch(seq_name)
owner = SEQUENCES[seq_name]
# Ignore unknown sequences: it's possible that a sequence was
# assigned the wrong owner due to a bug in `sequences_owned_by`
# (fixed by https://gitlab.com/gitlab-org/gitlab/-/merge_requests/160528),
# and so it didn't get dropped automatically when its associated
# table was dropped: https://gitlab.com/gitlab-org/gitlab/-/issues/474293
next unless owner
with_lock_retries do
execute(<<~SQL.squish)

View File

@ -234,6 +234,7 @@ configuration option in `gitlab.yml`. These metrics are served from the
| `sidekiq_mem_total_bytes` | Gauge | 15.3 | Number of bytes allocated for both objects consuming an object slot and objects that required a malloc'| |
| `sidekiq_concurrency_limit_queue_jobs` | Gauge | 17.3 | Number of Sidekiq jobs waiting in the concurrency limit queue| `worker` |
| `sidekiq_concurrency_limit_max_concurrent_jobs` | Gauge | 17.3 | Max number of concurrent running Sidekiq jobs | `worker` |
| `sidekiq_concurrency_limit_deferred_jobs_total` | Counter | 17.3 | Total number of deferred Sidekiq jobs | `worker` |
| `geo_db_replication_lag_seconds` | Gauge | 10.2 | Database replication lag (seconds) | `url` |
| `geo_repositories` | Gauge | 10.2 | Total number of repositories available on primary | `url` |
| `geo_lfs_objects` | Gauge | 10.2 | Number of LFS objects on primary | `url` |

View File

@ -1264,15 +1264,15 @@ Parameters:
| `room` | string | yes | Unique identifier for the target room (in the format `!qPKKM111FFKKsfoCVy:matrix.org`). |
| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
| `push_events` | boolean | yes | Enable notifications for push events. |
| `issues_events` | boolean | yes | Enable notifications for issue events. |
| `confidential_issues_events` | boolean | yes | Enable notifications for confidential issue events. |
| `merge_requests_events` | boolean | yes | Enable notifications for merge request events. |
| `tag_push_events` | boolean | yes | Enable notifications for tag push events. |
| `note_events` | boolean | yes | Enable notifications for note events. |
| `confidential_note_events` | boolean | yes | Enable notifications for confidential note events. |
| `pipeline_events` | boolean | yes | Enable notifications for pipeline events. |
| `wiki_page_events` | boolean | yes | Enable notifications for wiki page events. |
| `push_events` | boolean | no | Enable notifications for push events. |
| `issues_events` | boolean | no | Enable notifications for issue events. |
| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
| `note_events` | boolean | no | Enable notifications for note events. |
| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
### Disable Matrix notifications

View File

@ -80,6 +80,7 @@ PUT /projects/:id/external_status_checks/:check_id
| `check_id` | integer | yes | ID of an external status check service |
| `name` | string | no | Display name of external status check service |
| `external_url` | string | no | URL of external status check service |
| `shared_secret` | string | no | HMAC secret for external status check |
| `protected_branch_ids` | `array<Integer>` | no | IDs of protected branches to scope the rule by |
## Delete external status check service

View File

@ -32,7 +32,7 @@ For CI jobs where `FF_TIMESTAMPS` is enabled,
the size of generated artifacts is bigger by approximately 10 percent.
NOTE:
If `FF_TIMESTMPS` is disabled or not included in `.gitlab-ci.yml`,
If `FF_TIMESTAMPS` is disabled or not included in `.gitlab-ci.yml`,
the CI log output does not include the timestamp for each log line.
## Example configuration and output

View File

@ -127,7 +127,7 @@ myFancyToggleToOpenChat.addEventListener('click', () => {
#### Initiating GitLab Duo Chat with a pre-defined prompt
In some scenarios, you may want to direct users towards a specific topic or
query when they open GitLab Duo Chat. We have a utility function that will
query when they open GitLab Duo Chat. We have a utility function that will
open DuoChat drawer and send a command in a queue for DuoChat to execute on.
This should trigger the loading state and the streaming with the given prompt.
@ -375,7 +375,7 @@ The GraphQL Subscription for Chat behaves slightly different because it's user-c
We therefore need to broadcast messages to multiple clients to keep them in sync. The `aiAction` mutation with the `chat` action behaves the following:
1. All complete Chat messages (including messages from the user) are broadcasted with the `userId`, `aiAction: "chat"` as identifier.
1. Chunks from streamed Chat messages and currently used tools are broadcasted with the `userId`, `resourceId`, and the `clientSubscriptionId` from the mutation as identifier.
1. Chunks from streamed Chat messages are broadcasted with the `clientSubscriptionId` from the mutation as identifier.
Examples of GraphQL Subscriptions in a Vue component:
@ -417,8 +417,8 @@ Examples of GraphQL Subscriptions in a Vue component:
query: aiResponseSubscription,
variables() {
return {
aiAction: 'CHAT',
userId, // for example "gid://gitlab/User/1"
resourceId, // can be either a resourceId (on Issue, Epic, etc. items), or userId
clientSubscriptionId // randomly generated identifier for every message
htmlResponse: false, // important to bypass HTML processing on every chunk
};
@ -433,8 +433,7 @@ Examples of GraphQL Subscriptions in a Vue component:
},
```
Note that we still broadcast chat messages and currently used tools using the `userId` and `resourceId` as identifier.
However, this is deprecated and should no longer be used. We want to remove `resourceId` on the subscription as part of [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/420296).
Please keep in mind that the clientSubscriptionId must be unique for every request. Reusing a clientSubscriptionId will cause several unwanted side effects in the subscription responses.
### Duo Chat GraphQL queries

View File

@ -372,12 +372,18 @@ With the `concurrency_limit` property, you can limit the worker's concurrency. I
a separate `LIST` and re-enqueued when it falls under the limit. `ConcurrencyLimit::ResumeWorker` is a cron
worker that checks if any throttled jobs should be re-enqueued.
The first job that crosses the defined concurency limit initiates the throttling process for all other jobs of this class.
The first job that crosses the defined concurrency limit initiates the throttling process for all other jobs of this class.
Until this happens, jobs are scheduled and executed as usual.
When the throttling starts, newly scheduled and executed jobs will be added to the end of the `LIST` to ensure that
the execution order is preserved. As soon as the `LIST` is empty again, the throttling process ends.
Prometheus metrics are exposed to monitor workers using concurrency limit middleware:
- `sidekiq_concurrency_limit_deferred_jobs_total`
- `sidekiq_concurrency_limit_queue_jobs`
- `sidekiq_concurrency_limit_max_concurrent_jobs`
WARNING:
If there is a sustained workload over the limit, the `LIST` is going to grow until the limit is disabled or
the workload drops under the limit.

View File

@ -360,3 +360,4 @@ To merge changes from a local branch to a feature branch, follow this workflow.
- [Add file from the UI](../user/project/repository/index.md#add-a-file-from-the-ui)
- [Add file from the Web IDE](../user/project/repository/web_editor.md#upload-a-file)
- [Sign commits](../user/project/repository/signed_commits/gpg.md)

View File

@ -61,3 +61,8 @@ To update your branch with the latest changes in the default branch, either:
- Run `git pull <remote-name> <default-branch-name>`. Use this command when you want your changes to appear in Git logs
in chronological order with the changes from the default branch, or if you're sharing your branch with others. If
you're unsure of the correct value for `<remote-name>`, run: `git remote`.
## Related topics
- [Branches](../../user/project/repository/branches/index.md)
- [Tags](../../user/project/repository/tags/index.md)

View File

@ -72,8 +72,44 @@ sudo apt-get update && sudo apt-get install git
## Configure Git
To start using Git from your computer, you must enter your credentials
to identify yourself as the author of your work. The full name and
email address should match the ones you use in GitLab.
to identify yourself as the author of your work.
You can configure your Git identity locally or globally:
- Locally: Use for the current project only.
- Globally: Use for all current and future projects.
::Tabs
:::TabTitle Local setup
Configure your Git identity locally to use it for the current project only.
The full name and email address should match the ones you use in GitLab.
1. In your terminal, add your full name. For example:
```shell
git config --local user.name "Alex Smith"
```
1. Add your email address. For example:
```shell
git config --local user.email "your_email_address@example.com"
```
1. To check the configuration, run:
```shell
git config --local --list
```
:::TabTitle Global setup
Configure your Git identity globally to use it for all current and future projects on your machine.
The full name and email address should match the ones you use in GitLab.
1. In your terminal, add your full name. For example:
@ -93,9 +129,13 @@ email address should match the ones you use in GitLab.
git config --global --list
```
The `--global` option tells Git to always use this information for anything you do on your system.
If you omit `--global` or use `--local`, the configuration applies only to the current
repository.
::EndTabs
To check your configured Git settings, run:
```shell
git config user.name && git config user.email
```
After you set your name and email address, you should add an SSH key.
See [Use SSH keys to communicate with GitLab](../../../user/ssh.md).

View File

@ -15,110 +15,14 @@ GitLab is built on top of (and with) Git, and provides you a Git-based, fully-in
platform for software development. GitLab adds many powerful
[features](https://about.gitlab.com/features/) on top of Git to enhance your workflow.
These resources can help you to get the best from using Git with GitLab.
## Learn about Git
New to Git? These resources can help you understand basic Git concepts before
you dive in:
- [Git concepts](get_started.md)
- <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
GitLab workflow video tutorial: [GitLab source code management walkthrough](https://www.youtube.com/watch?v=wTQ3aXJswtM)
- <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
Git basics video tutorial: [Git-ing started with Git](https://www.youtube.com/watch?v=Ce5nz5n41z4)
- PDF download: [GitLab Git Cheat Sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf)
The official Git documentation also offers information on
[Git basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics).
## Begin using Git
After you learn how Git works, you're ready to try it out. These resources are
appropriate for when you're ready to start learning Git by doing:
- [How to install Git](how_to_install_git/index.md)
- Tutorial: [Make your first Git commit](../../tutorials/make_first_git_commit/index.md)
- Tutorial: [Update Git commit messages](../../tutorials/update_commit_messages/index.md)
- The [GitLab CLI](https://gitlab.com/gitlab-org/cli/)
A typical Git user encounters these concepts soon after starting to use Git:
- [`git add`](../../gitlab-basics/add-file.md) to start tracking files with Git.
- [Tags](../../user/project/repository/tags/index.md) and
[branches](../../user/project/repository/branches/index.md).
- [How to undo changes](undo.md), including `git reset`.
- View a chronological list of changes to a file with
[Git history](../../user/project/repository/files/git_history.md).
- View a line-by-line editing history of a file with
[`git blame`](../../user/project/repository/files/git_blame.md).
- [Sign commits](../../user/project/repository/signed_commits/gpg.md)
for increased accountability and trust.
## Learn more complex commands
When you're comfortable with basic Git commands, you're ready to dive into the
more complex features of Git. These commands aren't required when creating
straightforward changes. When you begin managing multiple branches or need more complex
change management, you're ready for these features:
- To stop tracking changes to a file, because you don't want to commit them,
[unstage the changes](undo.md).
- [Stash your changes](../../gitlab-basics/add-file.md) when your current work isn't ready to create a commit locally,
but you need to switch branches to work on something else.
- If you create many small commits locally, you can use
[squash and merge](../../user/project/merge_requests/squash_and_merge.md)
to combine them into fewer commits before pushing them.
- [Cherry-pick](../../user/project/merge_requests/cherry_pick_changes.md) the contents
of a commit from one branch to another.
- [Revert an existing commit](../../user/project/merge_requests/revert_changes.md#revert-a-commit)
if it contains changes you no longer want.
## Learn branching and workflow strategies
When you're comfortable with the creation and handling of individual branches,
you're ready to learn about Git workflows and branching strategies:
- [Feature branch workflow](../../gitlab-basics/feature_branch_workflow.md)
- [Introduction to Git rebase, force-push, and merge conflicts](git_rebase.md)
- [GitLab Flow](https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/)
- [GitLab Flow best practices](https://about.gitlab.com/topics/version-control/what-are-gitlab-flow-best-practices/)
- From the official Git documentation:
- [Git Branching - Branches in a Nutshell](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)
- [Git Branching - Branching Workflows](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows)
## Learn advanced topics in Git management
Git and GitLab, combined together, provide advanced features for repository management:
- Enforce commit policies and run tasks with [Git server hooks](../../administration/server_hooks.md).
- Define which file types to treat as binary, and set the languages to use for
syntax highlighting with [the `.gitattributes` file](../../user/project/git_attributes.md).
- To keep a Git repository as a subdirectory in another repository,
[use Git submodules with GitLab CI](../../ci/git_submodules.md).
- When working with extremely large repositories, you can use a [partial clone](partial_clone.md)
of a repository instead of a complete clone.
- GitLab APIs for [`.gitignore` files](../../api/templates/gitignores.md),
[commits](../../api/commits.md), [tags](../../api/tags.md),
and [repositories](../../api/repositories.md).
### Git Large File Storage (LFS)
Many Git projects must manage large binary assets, such as videos and images.
Implementing [Git Large File Storage](https://git-lfs.com) can help manage these assets while keeping
your repository small:
- [User documentation](lfs/index.md) for Git LFS at GitLab
- [Administrator documentation](../../administration/lfs/index.md) for Git LFS at GitLab
- Blog post: [Getting Started with Git LFS](https://about.gitlab.com/blog/2017/01/30/getting-started-with-git-lfs-tutorial/)
- [Migrate an existing Git repository](lfs/index.md#migrate-an-existing-repository-to-git-lfs) to Git LFS
- [Stop tracking a file](lfs/index.md#stop-tracking-a-file-with-git-lfs) with Git LFS
- Blog post: [Towards a production-quality open source Git LFS server](https://about.gitlab.com/blog/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
## Related topics
- Official [Git documentation](https://git-scm.com), including
[Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
- [Git troubleshooting](troubleshooting_git.md) techniques
- Blog post: [Git Tips & Tricks](https://about.gitlab.com/blog/2016/12/08/git-tips-and-tricks/)
- Blog post: [Eight Tips to help you work better with Git](https://about.gitlab.com/blog/2015/02/19/8-tips-to-help-you-work-better-with-git/)
- [Get started](get_started.md)
- [Install Git](how_to_install_git/index.md)
- [Tutorial: Create your first commit](../../tutorials/make_first_git_commit/index.md)
- [Clone a repository to your local machine](clone.md)
- [Create a branch for your changes](branch.md)
- [Add files and make changes](../../gitlab-basics/add-file.md)
- [Undo changes](undo.md)
- [Tutorial: Update Git commit messages](../../tutorials/update_commit_messages/index.md)
- [Common Git commands](../../gitlab-basics/start-using-git.md)
- [Rebase and force-push](git_rebase.md)
- [Troubleshooting](troubleshooting_git.md)

View File

@ -573,16 +573,10 @@ git checkout -- <file>
git rm <filename> --cache
```
<!-- ## Troubleshooting
## Related topics
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
one might have when setting this up, or when something is changed, or on upgrading, it's
important to describe those, too. Think of things that may go wrong and include them here.
This is important to minimize requests for support, and to avoid doc comments with
questions that you know someone might ask.
Each scenario can be a third-level heading, for example `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
<!-- Identifiers, in alphabetical order -->
- [`git blame`](../../user/project/repository/files/git_blame.md)
- [Cherry-pick](../../user/project/merge_requests/cherry_pick_changes.md)
- [Git history](../../user/project/repository/files/git_history.md)
- [Revert an existing commit](../../user/project/merge_requests/revert_changes.md#revert-a-commit)
- [Squash and merge](../../user/project/merge_requests/squash_and_merge.md)

View File

@ -17,3 +17,5 @@ the most out of GitLab.
| [Take advantage of Git rebase](https://about.gitlab.com/blog/2022/10/06/take-advantage-of-git-rebase/) | Learn how to use the `rebase` command in your workflow. | |
| [Update Git commit messages](update_commit_messages/index.md) | Learn how to update commit messages and push the changes to GitLab. | |
| [Git cheat sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) | Download a PDF of common Git commands. | |
| [Git-ing started with Git](https://www.youtube.com/watch?v=Ce5nz5n41z4) | Git basics video tutorial. | |
| [GitLab source code management walkthrough](https://www.youtube.com/watch?v=wTQ3aXJswtM) | GitLab workflow video tutorial. | |

View File

@ -274,3 +274,8 @@ Nice work.
- Get a complete introduction to Git in the <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Git for GitLab](https://www.youtube.com/watch?v=4lxvVj7wlZw) beginner's course (1h 33m).
- Find other tutorials about Git and GitLab on the [tutorials page](../index.md).
- PDF download: [GitLab Git Cheat Sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf).
- Blog post: [Git Tips & Tricks](https://about.gitlab.com/blog/2016/12/08/git-tips-and-tricks/).
- Blog post: [Eight Tips to help you work better with Git](https://about.gitlab.com/blog/2015/02/19/8-tips-to-help-you-work-better-with-git/).
- Official [Git documentation](https://git-scm.com), including
[Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab).

View File

@ -170,6 +170,7 @@ To filter the list of vulnerabilities:
> - Project-level grouping of vulnerabilities [generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/422509) in GitLab 16.6. Feature flag `vulnerability_report_grouping` removed.
> - Group-level grouping of vulnerabilities [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137778) in GitLab 16.7 with a flag named [`group_level_vulnerability_report_grouping`](https://gitlab.com/gitlab-org/gitlab/-/issues/432778). Disabled by default.
> - Group-level grouping of vulnerabilities [enabled on GitLab.com, self-managed, and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/157949) in GitLab 17.2.
> - Group-level grouping of vulnerabilities [generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/472669) in GitLab 17.3. Feature flag `group_level_vulnerability_report_grouping` removed.
You can group vulnerabilities on the vulnerability report page to more efficiently triage them.
@ -193,9 +194,9 @@ To group vulnerabilities:
1. On the left sidebar, select **Search or go to** and find your project or group.
1. Select **Secure > Vulnerability report**.
1. From the **Group By** dropdown list, select an attribute.
1. From the **Group By** dropdown list, select a group.
Vulnerabilities are grouped according to the attribute you selected. Each group is collapsed, with
Vulnerabilities are grouped according to the group you selected. Each group is collapsed, with
the total number of vulnerabilities per group displayed beside their name. To see the
vulnerabilities in each group, select the group's name.

View File

@ -73,7 +73,7 @@ DETAILS:
FLAG:
The availability of this feature is controlled by a feature flag.
For more information, see the history.
This feature is available for testing, but not ready for production use.
This feature is currently in development and not ready for production use.
Prerequisites:

View File

@ -518,10 +518,14 @@ Users with the Minimal Access role do not:
Owners must explicitly add these users to the specific subgroups and
projects.
You can use the Minimal Access role to give the same member more than one role in a group:
You can use the Minimal Access role with [SAML SSO for GitLab.com groups](group/saml_sso/index.md)
to control access to groups and projects in the group hierarchy. You can set the default role to
Minimal Access for members automatically added to the root group through SSO.
1. Add the member to the root group with a Minimal Access role.
1. Invite the member as a direct member with a specific role in any subgroup or project in that group.
1. On the left sidebar, select **Search or go to** and find your group.
1. Select **Settings > SAML SSO**.
1. From the **Default membership role** dropdown list, select **Minimal Access**.
1. Select **Save changes**.
### Minimal access users receive 404 errors

View File

@ -13,9 +13,6 @@ DETAILS:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/) in GitLab 17.3.
You can configure GitLab to send notifications to a Matrix room.
To set up the Matrix integration, you must:
1. [Set up the Matrix integration in GitLab](#set-up-the-matrix-integration-in-gitlab).
## Set up the Matrix integration in GitLab

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Gitlab
module Database
module Type
class TagListType < ActiveModel::Type::Value
end
end
end
end

View File

@ -112,7 +112,14 @@ module Gitlab
path_segments.pop
end while path_segments.length >= 2
Project.where_full_path_in(project_paths).first
project = Project.where_full_path_in(project_paths).first
return project if project
# It's possible that the project was transferred and has a redirect
redirect = RedirectRoute.for_source_type(Project).by_paths(project_paths).first
return redirect.source if redirect
nil
end
# can_read_project? checks if the request's credentials have read access to the project

View File

@ -33,6 +33,8 @@ module Gitlab
with_redis do |redis|
redis.rpush(redis_key, serialize(args, context))
end
deferred_job_counter.increment({ worker: worker_name })
end
def queue_size
@ -96,6 +98,11 @@ module Gitlab
def remove_processed_jobs(redis, limit:)
redis.ltrim(redis_key, limit, -1)
end
def deferred_job_counter
@deferred_job_count ||= ::Gitlab::Metrics.counter(:sidekiq_concurrency_limit_deferred_jobs_total,
'Count of jobs deferred by the concurrency limit middleware.')
end
end
end
end

View File

@ -53848,6 +53848,9 @@ msgstr ""
msgid "There is no data available. Please change your selection."
msgstr ""
msgid "There is no job log to troubleshoot."
msgstr ""
msgid "There is no table data available."
msgstr ""

View File

@ -295,7 +295,7 @@
"swagger-cli": "^4.0.4",
"tailwindcss": "^3.4.1",
"timezone-mock": "^1.0.8",
"vite": "^5.3.4",
"vite": "^5.3.5",
"vite-plugin-ruby": "^5.0.0",
"vue-loader-vue3": "npm:vue-loader@17.4.2",
"vue-test-utils-compat": "0.0.14",

View File

@ -2,4 +2,4 @@
# frozen_string_literal: true
require_relative '../lib/glfm/update_example_snapshots'
Glfm::UpdateExampleSnapshots.new.process(skip_static_and_wysiwyg: ENV['SKIP_STATIC_AND_WYSIWYG'] == 'true')
Glfm::UpdateExampleSnapshots.new.process

View File

@ -1,40 +0,0 @@
import fs from 'fs';
import jsYaml from 'js-yaml';
import { renderHtmlAndJsonForAllExamples } from 'jest/content_editor/render_html_and_json_for_all_examples';
/* eslint-disable no-undef */
jest.mock('~/emoji');
// The purpose of this file is to deserialize markdown examples
// to WYSIWYG HTML and to prosemirror documents in JSON form, using
// the logic implemented as part of the Content Editor.
//
// It reads an input YAML file containing all the markdown examples,
// and outputs a YAML files containing the rendered HTML and JSON
// corresponding each markdown example.
//
// The input and output file paths are provides as command line arguments.
//
// Although it is implemented as a Jest test, it is not a unit test. We use
// Jest because that is the simplest environment in which to execute the
// relevant Content Editor logic.
//
// This script should be invoked via jest with the a command similar to the following:
// yarn jest --testMatch '**/render_wysiwyg_html_and_json.js' ./scripts/lib/glfm/render_wysiwyg_html_and_json.js
it('serializes html to prosemirror json', async () => {
jest.setTimeout(20000);
const inputMarkdownTempfilePath = process.env.INPUT_MARKDOWN_YML_PATH;
expect(inputMarkdownTempfilePath).not.toBeUndefined();
const outputWysiwygHtmlAndJsonTempfilePath =
process.env.OUTPUT_WYSIWYG_HTML_AND_JSON_TEMPFILE_PATH;
expect(outputWysiwygHtmlAndJsonTempfilePath).not.toBeUndefined();
/* eslint-enable no-undef */
const markdownExamples = jsYaml.safeLoad(fs.readFileSync(inputMarkdownTempfilePath), {});
const htmlAndJsonExamples = await renderHtmlAndJsonForAllExamples(markdownExamples);
const htmlAndJsonExamplesYamlString = jsYaml.safeDump(htmlAndJsonExamples, {});
fs.writeFileSync(outputWysiwygHtmlAndJsonTempfilePath, htmlAndJsonExamplesYamlString);
});

View File

@ -24,10 +24,9 @@ module Glfm
include Shared
include ParseExamples
# skip_static_and_wysiwyg can be used to skip the backend/frontend html and prosemirror JSON
# generation which depends on external calls. This allows for faster processing in unit tests
# which do not require it.
def process(skip_static_and_wysiwyg: false)
# skip_static can be used to skip the backend html generation which depends on external
# calls. This allows for faster processing in unit tests which do not require it.
def process(skip_static: false)
output('Updating example snapshots...')
output("Reading #{ES_SNAPSHOT_SPEC_MD_PATH}...")
@ -41,7 +40,7 @@ module Glfm
reject_disabled_examples(all_examples)
write_snapshot_example_files(all_examples, skip_static_and_wysiwyg: skip_static_and_wysiwyg)
write_snapshot_example_files(all_examples, skip_static: skip_static)
end
private
@ -112,7 +111,7 @@ module Glfm
all_examples.reject! { |example| example[:disabled] }
end
def write_snapshot_example_files(all_examples, skip_static_and_wysiwyg:)
def write_snapshot_example_files(all_examples, skip_static: false)
output("Reading #{GLFM_EXAMPLE_STATUS_YML_PATH}...")
glfm_examples_statuses = YAML.safe_load(File.open(GLFM_EXAMPLE_STATUS_YML_PATH), symbolize_names: true) || {}
validate_glfm_example_status_yml(glfm_examples_statuses)
@ -123,8 +122,8 @@ module Glfm
write_markdown_yml(all_examples)
if skip_static_and_wysiwyg
output("Skipping static/WYSIWYG HTML and prosemirror JSON generation...")
if skip_static
output("Skipping static HTML generation...")
return
end
@ -134,11 +133,8 @@ module Glfm
# it straightforward to pass arguments via the command line.
ENV['INPUT_MARKDOWN_YML_PATH'], ENV['INPUT_METADATA_YML_PATH'] = copy_tempfiles_for_subprocesses
static_html_hash = generate_static_html
wysiwyg_html_and_json_hash = generate_wysiwyg_html_and_json
write_html_yml(all_examples, static_html_hash, wysiwyg_html_and_json_hash, glfm_examples_statuses)
write_prosemirror_json_yml(all_examples, wysiwyg_html_and_json_hash, glfm_examples_statuses)
write_html_yml(all_examples, static_html_hash, glfm_examples_statuses)
end
def validate_glfm_example_status_yml(glfm_examples_statuses)
@ -281,23 +277,7 @@ module Glfm
YAML.safe_load(File.open(static_html_tempfile_path), symbolize_names: true)
end
def generate_wysiwyg_html_and_json
output("Generating WYSIWYG HTML and prosemirror JSON from markdown examples...")
# Dir::Tmpname.create requires a block, but we are using the non-block form to get the path
# via the return value, so we pass an empty block to avoid an error.
wysiwyg_html_and_json_tempfile_path = Dir::Tmpname.create(WYSIWYG_HTML_AND_JSON_TEMPFILE_BASENAME) {}
ENV['OUTPUT_WYSIWYG_HTML_AND_JSON_TEMPFILE_PATH'] = wysiwyg_html_and_json_tempfile_path
cmd = "yarn jest:scripts #{__dir__}/render_wysiwyg_html_and_json.js"
run_external_cmd(cmd)
output("Reading generated WYSIWYG HTML and prosemirror JSON from tempfile " \
"#{wysiwyg_html_and_json_tempfile_path}...")
YAML.safe_load(File.open(wysiwyg_html_and_json_tempfile_path), symbolize_names: true)
end
def write_html_yml(all_examples, static_html_hash, wysiwyg_html_and_json_hash, glfm_examples_statuses)
def write_html_yml(all_examples, static_html_hash, glfm_examples_statuses)
generate_and_write_for_all_examples(
all_examples, ES_HTML_YML_PATH, glfm_examples_statuses: glfm_examples_statuses
) do |example, hash, existing_hash|
@ -310,37 +290,13 @@ module Glfm
static_html_hash[name]
end
wysiwyg = if example_statuses[:skip_update_example_snapshot_html_wysiwyg]
existing_hash.dig(name, :wysiwyg)
else
wysiwyg_html_and_json_hash.dig(name, :html)
end
hash[name] = {
'canonical' => example.fetch(:html),
'static' => static,
'wysiwyg' => wysiwyg
'static' => static
}.compact # Do not assign nil values
end
end
def write_prosemirror_json_yml(all_examples, wysiwyg_html_and_json_hash, glfm_examples_statuses)
generate_and_write_for_all_examples(
all_examples, ES_PROSEMIRROR_JSON_YML_PATH, glfm_examples_statuses: glfm_examples_statuses
) do |example, hash, existing_hash|
name = example.fetch(:name).to_sym
json = if glfm_examples_statuses.dig(name, :skip_update_example_snapshot_prosemirror_json)
existing_hash[name]
else
wysiwyg_html_and_json_hash.dig(name, :json)
end
# Do not assign nil values
hash[name] = json if json
end
end
def generate_and_write_for_all_examples(
all_examples, output_file_path, glfm_examples_statuses: {}, literal_scalars: true
)

View File

@ -10,13 +10,6 @@ RSpec.describe 'admin deploy keys', :js, feature_category: :system_access do
let!(:deploy_key) { create(:deploy_key, public: true) }
let!(:another_deploy_key) { create(:another_deploy_key, public: true) }
def page_breadcrumbs
all('[data-testid=breadcrumb-links] a').map do |a|
# We use `.dom_attribute` because Selenium transforms `.attribute('href')` to include the full URL.
{ text: a.text, href: a.native.dom_attribute('href') }
end
end
before do
sign_in(admin)
enable_admin_mode!(admin)

View File

@ -172,6 +172,17 @@ RSpec.describe 'Admin Groups', feature_category: :groups_and_projects do
end
describe 'group edit' do
it 'shows all breadcrumbs', :js do
visit admin_group_edit_path(group)
expect(page_breadcrumbs).to eq([
{ text: 'Admin area', href: admin_root_path },
{ text: 'Groups', href: admin_groups_path },
{ text: group.name, href: admin_group_path(group) },
{ text: 'Edit', href: admin_group_edit_path(group) }
])
end
it 'shows the visibility level radio populated with the group visibility_level value' do
group = create(:group, :private)

View File

@ -176,6 +176,18 @@ RSpec.describe "Admin::Projects", feature_category: :groups_and_projects do
end
describe 'project edit' do
it 'shows all breadcrumbs', :js do
project_params = { id: project.to_param, namespace_id: project.namespace.to_param }
visit edit_admin_namespace_project_path(project_params)
expect(page_breadcrumbs).to eq([
{ text: 'Admin area', href: admin_root_path },
{ text: 'Projects', href: admin_projects_path },
{ text: project.full_name, href: admin_namespace_project_path(project_params) },
{ text: 'Edit', href: edit_admin_namespace_project_path(project_params) }
])
end
it 'updates project details' do
project = create(:project, :private, name: 'Garfield', description: 'Funny Cat')

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Admin Topics', feature_category: :groups_and_projects do
let_it_be(:topic) { create :topic }
let_it_be(:admin) { create(:admin) }
before do
sign_in(admin)
enable_admin_mode!(admin)
end
describe 'topic edit' do
it 'shows all breadcrumbs', :js do
visit edit_admin_topic_path(topic)
expect(page_breadcrumbs).to eq([
{ text: 'Admin area', href: admin_root_path },
{ text: 'Topics', href: admin_topics_path },
{ text: topic.name, href: edit_admin_topic_path(topic) },
{ text: 'Edit', href: edit_admin_topic_path(topic) }
])
end
end
end

View File

@ -565,6 +565,15 @@ RSpec.describe 'Admin::Users', feature_category: :user_management do
visit edit_admin_user_path(user)
end
it 'shows all breadcrumbs', :js do
expect(page_breadcrumbs).to eq([
{ text: 'Admin area', href: admin_root_path },
{ text: 'Users', href: admin_users_path },
{ text: user.name, href: admin_user_path(user) },
{ text: 'Edit', href: edit_admin_user_path(user) }
])
end
describe 'Update user' do
before do
fill_in 'user_name', with: 'Big Bang'

View File

@ -1,94 +0,0 @@
// See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#markdown-snapshot-testing
// for documentation on this spec.
//
// NOTE: Unlike the backend markdown_snapshot_spec.rb which has a CE and EE version, there is only
// one version of this spec. This is because the frontend markdown rendering does not require EE-only
// backend features.
import jsYaml from 'js-yaml';
import { pick } from 'lodash';
import glfmExampleStatusYml from '../../../glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.yml';
import markdownYml from '../../../glfm_specification/output_example_snapshots/markdown.yml';
import htmlYml from '../../../glfm_specification/output_example_snapshots/html.yml';
import prosemirrorJsonYml from '../../../glfm_specification/output_example_snapshots/prosemirror_json.yml';
import {
IMPLEMENTATION_ERROR_MSG,
renderHtmlAndJsonForAllExamples,
} from './render_html_and_json_for_all_examples';
jest.mock('~/emoji');
const filterExamples = (examples) => {
const focusedMarkdownExamples = process.env.FOCUSED_MARKDOWN_EXAMPLES?.split(',') || [];
if (!focusedMarkdownExamples.length) {
return examples;
}
return pick(examples, focusedMarkdownExamples);
};
const loadExamples = (yaml) => {
const examples = jsYaml.safeLoad(yaml, {});
return filterExamples(examples);
};
describe('markdown example snapshots in ContentEditor', () => {
let actualHtmlAndJsonExamples;
let skipRunningSnapshotWysiwygHtmlTests;
let skipRunningSnapshotProsemirrorJsonTests;
const exampleStatuses = loadExamples(glfmExampleStatusYml);
const markdownExamples = loadExamples(markdownYml);
const expectedHtmlExamples = loadExamples(htmlYml);
const expectedProseMirrorJsonExamples = loadExamples(prosemirrorJsonYml);
const exampleNames = Object.keys(markdownExamples);
beforeAll(() => {
return renderHtmlAndJsonForAllExamples(markdownExamples).then((examples) => {
actualHtmlAndJsonExamples = examples;
});
});
describe.each(exampleNames)('%s', (name) => {
const exampleNamePrefix = 'verifies conversion of GLFM to';
skipRunningSnapshotWysiwygHtmlTests =
exampleStatuses[name]?.skip_running_snapshot_wysiwyg_html_tests;
skipRunningSnapshotProsemirrorJsonTests =
exampleStatuses[name]?.skip_running_snapshot_prosemirror_json_tests;
const markdown = markdownExamples[name];
if (skipRunningSnapshotWysiwygHtmlTests) {
it.todo(`${exampleNamePrefix} HTML: ${skipRunningSnapshotWysiwygHtmlTests}`);
} else {
it(`${exampleNamePrefix} HTML`, () => {
const expectedHtml = expectedHtmlExamples[name].wysiwyg;
const { html: actualHtml } = actualHtmlAndJsonExamples[name];
expect(actualHtml).toMatchExpectedForMarkdown(
'HTML',
name,
markdown,
IMPLEMENTATION_ERROR_MSG,
expectedHtml,
);
});
}
if (skipRunningSnapshotProsemirrorJsonTests) {
it.todo(`${exampleNamePrefix} ProseMirror JSON: ${skipRunningSnapshotProsemirrorJsonTests}`);
} else {
it(`${exampleNamePrefix} ProseMirror JSON`, () => {
const expectedJson = expectedProseMirrorJsonExamples[name];
const { json: actualJson } = actualHtmlAndJsonExamples[name];
expect(actualJson).toMatchExpectedForMarkdown(
'JSON',
name,
markdown,
IMPLEMENTATION_ERROR_MSG,
expectedJson,
);
});
}
});
});

View File

@ -1,46 +0,0 @@
import { DOMSerializer } from '@tiptap/pm/model';
import createMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTiptapEditor } from 'jest/content_editor/test_utils';
const tiptapEditor = createTiptapEditor();
export const IMPLEMENTATION_ERROR_MSG = 'Error - check implementation';
async function renderMarkdownToHTMLAndJSON(markdown, schema, deserializer) {
let prosemirrorDocument;
try {
const { document } = await deserializer.deserialize({ schema, markdown });
prosemirrorDocument = document;
} catch (e) {
const errorMsg = `${IMPLEMENTATION_ERROR_MSG}:\n${e.message}`;
return {
html: errorMsg,
json: errorMsg,
};
}
const documentFragment = DOMSerializer.fromSchema(schema).serializeFragment(
prosemirrorDocument.content,
);
const htmlString = Array.from(documentFragment.children)
.map((el) => el.outerHTML)
.join('\n');
const json = prosemirrorDocument.toJSON();
const jsonString = JSON.stringify(json, null, 2);
return { html: htmlString, json: jsonString };
}
export function renderHtmlAndJsonForAllExamples(markdownExamples) {
const { schema } = tiptapEditor;
const deserializer = createMarkdownDeserializer();
const exampleNames = Object.keys(markdownExamples);
return exampleNames.reduce(async (promisedExamples, exampleName) => {
const markdown = markdownExamples[exampleName];
const htmlAndJson = await renderMarkdownToHTMLAndJSON(markdown, schema, deserializer);
const examples = await promisedExamples;
examples[exampleName] = htmlAndJson;
return examples;
}, Promise.resolve({}));
}

View File

@ -46,7 +46,7 @@ describe('IssuableDescription', () => {
});
it('renders hidden textarea element when issuable.description is present and enableTaskList prop is true', () => {
const textareaEl = wrapper.find('textarea.gl-display-none.js-task-list-field');
const textareaEl = wrapper.find('textarea.gl-hidden.js-task-list-field');
expect(textareaEl.exists()).toBe(true);
expect(textareaEl.attributes('data-update-url')).toBe(`${mockIssuable.webUrl}.json`);

View File

@ -71,7 +71,7 @@ describe('IssuableTitle', () => {
expect(titleEl.exists()).toBe(true);
expect(titleEl.html()).toBe(
'<h1 dir="auto" data-testid="issuable-title" class="title gl-font-size-h-display"><b>Sample</b> title</h1>',
'<h1 dir="auto" data-testid="issuable-title" class="title gl-text-size-h-display"><b>Sample</b> title</h1>',
);
wrapperWithTitle.destroy();

View File

@ -13,7 +13,7 @@ RSpec.describe Mutations::Achievements::Award, feature_category: :user_profile d
describe '#resolve' do
subject(:resolve_mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve(
described_class.new(object: nil, context: query_context, field: nil).resolve(
achievement_id: achievement&.to_global_id, user_id: recipient&.to_global_id
)
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Mutations::Achievements::Create, feature_category: :user_profile do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:current_user) { create(:user) }
let(:group) { create(:group) }
let(:valid_params) do
@ -14,7 +14,7 @@ RSpec.describe Mutations::Achievements::Create, feature_category: :user_profile
describe '#resolve' do
subject(:resolve_mutation) do
described_class.new(object: nil, context: { current_user: user }, field: nil).resolve(
described_class.new(object: nil, context: query_context, field: nil).resolve(
**valid_params,
namespace_id: group.to_global_id
)
@ -22,7 +22,7 @@ RSpec.describe Mutations::Achievements::Create, feature_category: :user_profile
context 'when the user does not have permission' do
before do
group.add_developer(user)
group.add_developer(current_user)
end
it 'raises an error' do
@ -33,7 +33,7 @@ RSpec.describe Mutations::Achievements::Create, feature_category: :user_profile
context 'when the user has permission' do
before do
group.add_maintainer(user)
group.add_maintainer(current_user)
end
context 'when the params are invalid' do

View File

@ -14,7 +14,7 @@ RSpec.describe Mutations::Achievements::Delete, feature_category: :user_profile
describe '#resolve' do
subject(:resolve_mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve(
described_class.new(object: nil, context: query_context, field: nil).resolve(
achievement_id: achievement&.to_global_id
)
end

View File

@ -13,7 +13,7 @@ RSpec.describe Mutations::Achievements::DeleteUserAchievement, feature_category:
describe '#resolve' do
subject(:resolve_mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve(
described_class.new(object: nil, context: query_context, field: nil).resolve(
user_achievement_id: user_achievement&.to_global_id
)
end

View File

@ -14,7 +14,7 @@ RSpec.describe Mutations::Achievements::Revoke, feature_category: :user_profile
describe '#resolve' do
subject(:resolve_mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve(
described_class.new(object: nil, context: query_context, field: nil).resolve(
user_achievement_id: user_achievement&.to_global_id
)
end

View File

@ -15,7 +15,7 @@ RSpec.describe Mutations::Achievements::Update, feature_category: :user_profile
describe '#resolve' do
subject(:resolve_mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve(
described_class.new(object: nil, context: query_context, field: nil).resolve(
achievement_id: achievement&.to_global_id, name: name
)
end

View File

@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::Alerts::SetAssignees do
RSpec.describe Mutations::AlertManagement::Alerts::SetAssignees, feature_category: :api do
include GraphqlHelpers
let_it_be(:starting_assignee) { create(:user) }
let_it_be(:unassigned_user) { create(:user) }
let_it_be(:alert) { create(:alert_management_alert, assignees: [starting_assignee]) }
@ -170,7 +172,7 @@ RSpec.describe Mutations::AlertManagement::Alerts::SetAssignees do
end
end
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,18 +2,19 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::Alerts::Todo::Create do
subject(:mutation) { described_class.new(object: project, context: { current_user: current_user }, field: nil) }
RSpec.describe Mutations::AlertManagement::Alerts::Todo::Create, feature_category: :api do
include GraphqlHelpers
let_it_be(:alert) { create(:alert_management_alert) }
let_it_be(:project) { alert.project }
let(:current_user) { project.first_owner }
let(:args) { { project_path: project.full_path, iid: alert.iid } }
specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) }
subject(:mutation) { described_class.new(object: project, context: query_context, field: nil) }
describe '#resolve' do
subject(:resolve) { mutation.resolve(args) }

View File

@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::CreateAlertIssue do
RSpec.describe Mutations::AlertManagement::CreateAlertIssue, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered') }
@ -74,7 +76,7 @@ RSpec.describe Mutations::AlertManagement::CreateAlertIssue do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,10 +2,11 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::HttpIntegration::Create do
RSpec.describe Mutations::AlertManagement::HttpIntegration::Create, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:args) { { project_path: project.full_path, active: true, name: 'HTTP Integration' } }
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
@ -52,7 +53,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::Create do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::HttpIntegration::Destroy do
RSpec.describe Mutations::AlertManagement::HttpIntegration::Destroy, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
@ -53,7 +55,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::Destroy do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::HttpIntegration::ResetToken do
RSpec.describe Mutations::AlertManagement::HttpIntegration::ResetToken, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
@ -53,7 +55,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::ResetToken do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::HttpIntegration::Update do
RSpec.describe Mutations::AlertManagement::HttpIntegration::Update, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
@ -53,7 +55,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::Update do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,7 +2,8 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create do
RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
@ -76,7 +77,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken do
RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:prometheus_integration, project: project) }
@ -53,7 +55,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Update do
RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Update, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:integration) { create(:prometheus_integration, project: project) }
@ -53,7 +55,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Update do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do
RSpec.describe Mutations::AlertManagement::UpdateAlertStatus, feature_category: :api do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:alert) { create(:alert_management_alert, :triggered) }
let_it_be(:project) { alert.project }
@ -80,7 +82,7 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do
private
def mutation_for(project, user)
described_class.new(object: project, context: { current_user: user }, field: nil)
def mutation_for(project, _user)
described_class.new(object: project, context: query_context, field: nil)
end
end

View File

@ -2,12 +2,14 @@
require 'spec_helper'
RSpec.describe Mutations::Boards::Update do
RSpec.describe Mutations::Boards::Update, feature_category: :api do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:current_user) { create(:user) }
let_it_be(:board) { create(:board, project: project) }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutation) { described_class.new(object: nil, context: query_context, field: nil) }
let(:mutated_board) { subject[:board] }
let(:mutation_params) do
@ -31,7 +33,7 @@ RSpec.describe Mutations::Boards::Update do
context 'when user can update board' do
before do
board.resource_parent.add_reporter(user)
board.resource_parent.add_reporter(current_user)
end
it 'updates board with correct values' do

View File

@ -2,20 +2,13 @@
require 'spec_helper'
RSpec.describe Mutations::Branches::Create do
RSpec.describe Mutations::Branches::Create, feature_category: :api do
include GraphqlHelpers
subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
let_it_be(:current_user) { create(:user) }
let(:context) do
GraphQL::Query::Context.new(
query: query_double(schema: nil),
values: { current_user: user }
)
end
subject(:mutation) { described_class.new(object: nil, context: query_context, field: nil) }
describe '#resolve' do
subject { mutation.resolve(project_path: project.full_path, name: branch, ref: ref) }
@ -30,9 +23,9 @@ RSpec.describe Mutations::Branches::Create do
context 'when the user can create a branch' do
before do
project.add_developer(user)
project.add_developer(current_user)
allow_next_instance_of(::Branches::CreateService, project, user) do |create_service|
allow_next_instance_of(::Branches::CreateService, project, current_user) do |create_service|
allow(create_service).to receive(:execute).with(branch, ref) { service_result }
end
end

View File

@ -3,8 +3,10 @@
require 'spec_helper'
RSpec.describe Mutations::Ci::JobTokenScope::AddGroupOrProject, feature_category: :continuous_integration do
include GraphqlHelpers
let(:mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil)
described_class.new(object: nil, context: query_context, field: nil)
end
describe '#resolve' do

View File

@ -2,8 +2,10 @@
require 'spec_helper'
RSpec.describe Mutations::Ci::JobTokenScope::AddProject, feature_category: :continuous_integration do
include GraphqlHelpers
let(:mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil)
described_class.new(object: nil, context: query_context, field: nil)
end
describe '#resolve' do

View File

@ -3,8 +3,10 @@
require 'spec_helper'
RSpec.describe Mutations::Ci::JobTokenScope::RemoveGroup, feature_category: :continuous_integration do
include GraphqlHelpers
let(:mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil)
described_class.new(object: nil, context: query_context, field: nil)
end
describe '#resolve' do

View File

@ -2,8 +2,10 @@
require 'spec_helper'
RSpec.describe Mutations::Ci::JobTokenScope::RemoveProject, feature_category: :continuous_integration do
include GraphqlHelpers
let(:mutation) do
described_class.new(object: nil, context: { current_user: current_user }, field: nil)
described_class.new(object: nil, context: query_context, field: nil)
end
describe '#resolve' do

View File

@ -2,26 +2,27 @@
require 'spec_helper'
RSpec.describe Mutations::DesignManagement::Delete do
RSpec.describe Mutations::DesignManagement::Delete, feature_category: :api do
include DesignManagementTestHelpers
include GraphqlHelpers
let(:issue) { create(:issue) }
let(:current_designs) { issue.designs.current }
let(:user) { issue.author }
let(:current_user) { issue.author }
let(:project) { issue.project }
let(:design_a) { create(:design, :with_file, issue: issue) }
let(:design_b) { create(:design, :with_file, issue: issue) }
let(:design_c) { create(:design, :with_file, issue: issue) }
let(:filenames) { [design_a, design_b, design_c].map(&:filename) }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutation) { described_class.new(object: nil, context: context, field: nil) }
before do
stub_const('Errors', Gitlab::Graphql::Errors, transfer_nested_constants: true)
end
def run_mutation
mutation = described_class.new(object: nil, context: { current_user: user }, field: nil)
mutation = described_class.new(object: nil, context: query_context, field: nil)
mutation.resolve(project_path: project.full_path, iid: issue.iid, filenames: filenames)
end
@ -54,7 +55,7 @@ RSpec.describe Mutations::DesignManagement::Delete do
end
context "when the user is not allowed to delete designs" do
let(:user) { create(:user) }
let(:current_user) { create(:user) }
it_behaves_like "resource not available"
end

View File

@ -2,16 +2,17 @@
require 'spec_helper'
RSpec.describe Mutations::DesignManagement::Move do
RSpec.describe Mutations::DesignManagement::Move, feature_category: :api do
include DesignManagementTestHelpers
include GraphqlHelpers
let_it_be(:issue) { create(:issue) }
let_it_be(:designs) { create_list(:design, 3, issue: issue) }
let_it_be(:developer) { create(:user, developer_of: issue.project) }
let(:user) { developer }
let(:current_user) { developer }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutation) { described_class.new(object: nil, context: query_context, field: nil) }
let(:current_design) { designs.first }
let(:previous_design) { designs.second }

View File

@ -1,20 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::DesignManagement::Upload do
RSpec.describe Mutations::DesignManagement::Upload, feature_category: :api do
include DesignManagementTestHelpers
include ConcurrentHelpers
include GraphqlHelpers
let(:issue) { create(:issue) }
let(:user) { issue.author }
let(:current_user) { issue.author }
let(:project) { issue.project }
subject(:mutation) do
described_class.new(object: nil, context: { current_user: user }, field: nil)
described_class.new(object: nil, context: query_context, field: nil)
end
def run_mutation(files_to_upload = files, project_path = project.full_path, iid = issue.iid)
mutation = described_class.new(object: nil, context: { current_user: user }, field: nil)
mutation = described_class.new(object: nil, context: query_context, field: nil)
Gitlab::ExclusiveLease.skipping_transaction_check do
mutation.resolve(project_path: project_path, iid: iid, files: files_to_upload)
end
@ -87,8 +88,8 @@ RSpec.describe Mutations::DesignManagement::Upload do
describe 'running requests in parallel on different issues' do
it 'does not cause errors' do
creates_designs do
issues = create_list(:issue, files.size, author: user)
issues.each { |i| i.project.add_developer(user) }
issues = create_list(:issue, files.size, author: current_user)
issues.each { |i| i.project.add_developer(current_user) }
blocks = files.zip(issues).map do |(f, i)|
-> { run_mutation([f], i.project.full_path, i.iid) }
end
@ -110,7 +111,7 @@ RSpec.describe Mutations::DesignManagement::Upload do
end
context "when the user is not allowed to upload designs" do
let(:user) { create(:user) }
let(:current_user) { create(:user) }
it_behaves_like "resource not available"
end

View File

@ -869,12 +869,6 @@ RSpec.describe ApplicationHelper do
end
end
describe 'stylesheet_link_tag_defer' do
it 'uses media="all" in stylesheet' do
expect(helper.stylesheet_link_tag_defer('test')).to eq('<link rel="stylesheet" href="/stylesheets/test.css" media="all" />')
end
end
describe 'sign_in_with_redirect?' do
context 'when on the sign-in page that redirects afterwards' do
before do

View File

@ -71,6 +71,15 @@ RSpec.describe Gitlab::Middleware::Go, feature_category: :source_code_management
end
end
end
context 'when the project accessed by a redirect' do
let!(:redirect_route) { create(:redirect_route, source: project, path: 'redirect/project') }
let(:path) { redirect_route.path }
it 'returns the full project path' do
expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
end
end
end
context 'with a nested project path' do

View File

@ -45,6 +45,15 @@ RSpec.describe Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitServ
add_to_queue!
end
it 'reports prometheus metrics' do
deferred_job_count_double = instance_double(Prometheus::Client::Counter)
expect(Gitlab::Metrics).to receive(:counter).with(:sidekiq_concurrency_limit_deferred_jobs_total, anything)
.and_return(deferred_job_count_double)
expect(deferred_job_count_double).to receive(:increment).with({ worker: worker_class_name })
add_to_queue!
end
end
describe '.has_jobs_in_queue?' do

View File

@ -48,6 +48,18 @@ RSpec.describe FixSequenceOwnersForCiBuilds, :migration, feature_category: :cont
ApplicationRecord.connection.execute('DROP SEQUENCE unrelated_seq;')
end
it 'ignores unknown sequences', :aggregate_failures do
ApplicationRecord.connection.execute('DROP SEQUENCE IF EXISTS unknown_seq;')
ApplicationRecord.connection.execute('CREATE SEQUENCE unknown_seq;')
ApplicationRecord.connection.execute(<<-SQL)
ALTER SEQUENCE unknown_seq OWNED BY ci_builds.id;
SQL
expect { migration.up }.not_to change { sequence_owner('unknown_seq') }
ApplicationRecord.connection.execute('DROP SEQUENCE unknown_seq;')
end
end
def sequence_owner(sequence_name)

View File

@ -0,0 +1,115 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::Taggable, feature_category: :continuous_integration do
let_it_be(:taggable_model) do
Class.new(Ci::ApplicationRecord) do |_model|
connection.create_table :_test_gitlab_ci_taggings, force: true do |t|
t.string :name
end
self.table_name = '_test_gitlab_ci_taggings'
def self.name
'TestCiTaggings'
end
include Ci::Taggable
end
end
let(:taggable_record) { taggable_model.new(name: 'tags') }
it { expect(taggable_record).to have_many(:taggings).class_name('Ci::Tagging') }
it { expect(taggable_record).to have_many(:tag_taggings).class_name('Ci::Tagging') }
it { expect(taggable_record).to have_many(:tags).class_name('Ci::Tag').through(:tag_taggings) }
it { expect(taggable_record).to have_many(:base_tags).class_name('Ci::Tag').through(:taggings) }
it { expect(taggable_record.tag_list).to be_empty }
it { expect(taggable_record.tag_list).to be_a(Gitlab::Ci::Tags::TagList) }
it 'sets the tag list' do
taggable_record.tag_list = 'ruby, docker, postgres'
expect(taggable_record.tag_list).to match_array(%w[ruby docker postgres])
expect(taggable_record.tag_list).to be_a(Gitlab::Ci::Tags::TagList)
end
it 'persists the tag list' do
taggable_record.tag_list = 'ruby, docker, postgres'
expect { taggable_record.save! }.to change { Ci::Tag.count }.by(3)
end
it 'loads the tag list' do
taggable_record.tag_list = 'ruby, docker, postgres'
taggable_record.save!
fresh_record = taggable_model.find(taggable_record.id)
expect(fresh_record.tag_list).to match_array(%w[ruby docker postgres])
expect(fresh_record.tags).to be_all(Ci::Tag)
end
it 'removes unwanted tags from the list' do
taggable_record.tag_list = 'ruby, docker, postgres'
taggable_record.save!
taggable_record.tag_list = 'ruby, docker'
expect { taggable_record.save! }.to change { Ci::Tagging.count }.by(-1)
expect(taggable_record.reload.tag_list).to match_array(%w[ruby docker])
end
it 'updates the tag list' do
taggable_record.tag_list = 'ruby, docker, postgres'
taggable_record.save!
taggable_record.tag_list = 'ruby, docker, elasticsearch, golang'
expect { taggable_record.save! }
.to change { Ci::Tag.count }.by(2)
.and change { Ci::Tagging.count }.by(1)
expect(taggable_record.reload.tag_list).to match_array(%w[ruby docker elasticsearch golang])
end
describe '.tagged_with' do
let_it_be(:tags) { taggable_model.create!(name: 'tags', tag_list: 'ruby, docker, postgres') }
let_it_be(:other_tags) { taggable_model.create!(name: 'other tags', tag_list: 'ruby, golang') }
it { expect(taggable_model.tagged_with('ruby')).to match_array([tags, other_tags]) }
it { expect(taggable_model.tagged_with('ruby, docker')).to match_array([tags]) }
it { expect(taggable_model.tagged_with(%w[ruby docker])).to match_array([tags]) }
it { expect(taggable_model.tagged_with(%w[ruby docker golang])).to be_empty }
end
describe '#reload' do
before do
taggable_record.tag_list = 'ruby, docker, postgres'
taggable_record.save!
end
it { expect { taggable_record.reload }.to change { taggable_record.tag_list.object_id } }
end
describe '#dirtify_tag_list' do
before do
taggable_record.tag_list = 'ruby, docker, postgres'
taggable_record.save!
end
it 'resets the tag list after a tag is added' do
expect { taggable_record.tags << create(:ci_tag) }
.to change { Ci::Tagging.count }.by(1)
.and change { taggable_record.tag_list.size }.by(1)
.and change { taggable_record.tag_list_changed? }.to(true)
end
it 'resets the tag list after a tag is removed' do
expect { taggable_record.tags.destroy(Ci::Tag.find_by_name('docker')) }
.to change { Ci::Tagging.count }.by(-1)
.and change { taggable_record.tag_list.size }.by(-1)
.and change { taggable_record.tag_list_changed? }.to(true)
end
end
end

View File

@ -16,6 +16,46 @@ RSpec.describe RedirectRoute do
it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
end
describe '.for_source_type' do
subject { described_class.for_source_type(source_type) }
context 'when Project' do
let(:source_type) { Project }
it { is_expected.to be_empty }
end
context 'when Namespace' do
let(:source_type) { Namespace }
it { is_expected.to match_array(redirect_route) }
end
end
describe '.by_paths' do
subject { described_class.by_paths(paths) }
let!(:redirect2) { group.redirect_routes.create!(path: 'gitlabb/test') }
context 'when no matches' do
let(:paths) { ['unknown'] }
it { is_expected.to be_empty }
end
context 'when some matches' do
let(:paths) { %w[unknown gitlabb] }
it { is_expected.to match_array([redirect_route]) }
end
context 'when multiple matches' do
let(:paths) { ['unknown', 'gitlabb', 'gitlabb/test'] }
it { is_expected.to match_array([redirect_route, redirect2]) }
end
end
describe '.matching_path_and_descendants' do
let!(:redirect2) { group.redirect_routes.create!(path: 'gitlabb/test') }
let!(:redirect3) { group.redirect_routes.create!(path: 'gitlabb/test/foo') }

View File

@ -19,7 +19,7 @@ require_relative '../../../../scripts/lib/glfm/update_example_snapshots'
# However, only the `with full processing of static and WYSIWYG HTML` context is used
# to test these slow sub-processes, and it only contains two examples.
#
# All other tests currently in the file pass the `skip_static_and_wysiwyg: true`
# All other tests currently in the file pass the `skip_static: true`
# flag to `#process`, which skips the slow sub-processes. All of these other tests
# should run in sub-second time when the Spring pre-loader is used. This allows
# logic which is not directly related to the slow sub-processes to be TDD'd with a
@ -46,9 +46,6 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
let(:es_html_yml_path) { described_class::ES_HTML_YML_PATH }
let(:es_html_yml_io_existing) { StringIO.new(es_html_yml_io_existing_contents) }
let(:es_html_yml_io) { StringIO.new }
let(:es_prosemirror_json_yml_path) { described_class::ES_PROSEMIRROR_JSON_YML_PATH }
let(:es_prosemirror_json_yml_io_existing) { StringIO.new(es_prosemirror_json_yml_io_existing_contents) }
let(:es_prosemirror_json_yml_io) { StringIO.new }
# Internal tempfiles
let(:static_html_tempfile_path) { Tempfile.new.path }
@ -297,59 +294,16 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
This entry is no longer exists in the snapshot_spec.md, so it will be deleted.
static: |-
This entry is no longer exists in the snapshot_spec.md, so it will be deleted.
wysiwyg: |-
This entry is no longer exists in the snapshot_spec.md, so it will be deleted.
02_01_00__inlines__strong__001:
canonical: |
This entry is existing, but not skipped, so it will be overwritten.
static: |-
This entry is existing, but not skipped, so it will be overwritten.
wysiwyg: |-
This entry is existing, but not skipped, so it will be overwritten.
05_02_00__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001:
canonical: |
<p><strong>This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved</strong></p>
static: |-
<p>This is the manually modified static HTML which will be preserved</p>
wysiwyg: |-
<p dir="auto">This is the manually modified WYSIWYG HTML which will be preserved</p>
YAML
end
let(:es_prosemirror_json_yml_io_existing_contents) do
<<~YAML
---
01_00_00__obsolete_entry_to_be_deleted__001: |-
{
"obsolete": "This entry is no longer exists in the snapshot_spec.md, and is not skipped, so it will be deleted."
}
02_01_00__inlines__strong__001: |-
{
"existing": "This entry is existing, but not skipped, so it will be overwritten."
}
02_03_00__inlines__strikethrough_extension__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "~~Hi~~ Hello, world!"
}
]
}
]
}
04_01_00__second_gitlab_specific_section_with_examples__strong_but_with_html__001: |-
{
"existing": "This entry is manually modified and preserved because skip_update_example_snapshot_prosemirror_json will be truthy"
}
05_02_00__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001: |-
{
"existing": "This entry is manually modified and preserved because skip_update_example_snapshots will be truthy"
}
YAML
end
@ -378,15 +332,12 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
allow(File).to receive(:open).with(es_markdown_yml_path) { es_markdown_yml_io }
allow(File).to receive(:open).with(es_html_yml_path, 'w') { es_html_yml_io }
allow(File).to receive(:open).with(es_html_yml_path) { es_html_yml_io_existing }
allow(File).to receive(:open).with(es_prosemirror_json_yml_path, 'w') { es_prosemirror_json_yml_io }
allow(File).to receive(:open).with(es_prosemirror_json_yml_path) { es_prosemirror_json_yml_io_existing }
# Allow normal opening of Tempfile files created during script execution.
tempfile_basenames = [
described_class::MARKDOWN_TEMPFILE_BASENAME[0],
described_class::METADATA_TEMPFILE_BASENAME[0],
described_class::STATIC_HTML_TEMPFILE_BASENAME[0],
described_class::WYSIWYG_HTML_AND_JSON_TEMPFILE_BASENAME[0]
described_class::STATIC_HTML_TEMPFILE_BASENAME[0]
].join('|')
# NOTE: This approach with a single regex seems to be the only way this can work. If you
# attempt to have multiple `allow...and_call_original` with `any_args`, the mocked
@ -407,13 +358,13 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
end
it 'still writes the example to examples_index.yml' do
subject.process(skip_static_and_wysiwyg: true)
subject.process(skip_static: true)
expect(es_examples_index_yml_contents).to match(expected_unskipped_example)
end
it 'still writes the example to markdown.yml' do
subject.process(skip_static_and_wysiwyg: true)
subject.process(skip_static: true)
expect(es_markdown_yml_contents).to match(expected_unskipped_example)
end
@ -485,7 +436,7 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
end
it 'writes the correct content' do
subject.process(skip_static_and_wysiwyg: true)
subject.process(skip_static: true)
expect(es_examples_index_yml_contents).to eq(expected_examples_index_yml_contents)
end
@ -528,62 +479,13 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
end
it 'writes the correct content' do
subject.process(skip_static_and_wysiwyg: true)
subject.process(skip_static: true)
expect(es_markdown_yml_contents).to eq(expected_markdown_yml_contents)
end
end
describe 'error handling when manually-curated input specification config files contain invalid example names:' do
let(:err_msg) do
/#{config_file}.*01_00_00__invalid__001.*does not have.*entry in.*#{described_class::ES_EXAMPLES_INDEX_YML_PATH}/m
end
let(:invalid_example_name_file_contents) do
<<~YAML
---
01_00_00__invalid__001:
a: 1
YAML
end
context 'for glfm_example_status.yml' do
let(:config_file) { described_class::GLFM_EXAMPLE_STATUS_YML_PATH }
let(:glfm_example_status_yml_contents) { invalid_example_name_file_contents }
it 'raises error' do
expect { subject.process(skip_static_and_wysiwyg: true) }.to raise_error(err_msg)
end
end
context 'for glfm_example_metadata.yml' do
let(:config_file) { described_class::GLFM_EXAMPLE_METADATA_YML_PATH }
let(:glfm_example_metadata_yml_contents) { invalid_example_name_file_contents }
it 'raises error' do
expect { subject.process(skip_static_and_wysiwyg: true) }.to raise_error(err_msg)
end
end
context 'for glfm_example_normalizations.yml' do
let(:config_file) { described_class::GLFM_EXAMPLE_NORMALIZATIONS_YML_PATH }
let(:glfm_example_normalizations_yml_contents) { invalid_example_name_file_contents }
it 'raises error' do
expect { subject.process(skip_static_and_wysiwyg: true) }.to raise_error(err_msg)
end
end
end
context 'with full processing of static and WYSIWYG HTML' do
before(:all) do # rubocop: disable RSpec/BeforeAll
# NOTE: It is a necessary to do a `yarn install` in order to ensure that
# `scripts/lib/glfm/render_wysiwyg_html_and_json.js` can be invoked successfully
# on the CI job (which will not be set up for frontend specs since this is
# an RSpec spec), or if the current yarn dependencies are not installed locally.
described_class.new.run_external_cmd('yarn install --frozen-lockfile')
end
context 'with full processing of static HTML' do
describe 'manually-curated input specification config files' do
let(:glfm_example_status_yml_contents) { '' }
let(:glfm_example_metadata_yml_contents) { '' }
@ -594,9 +496,8 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
end
end
describe 'writing html.yml and prosemirror_json.yml' do
describe 'writing html.yml' do
let(:es_html_yml_contents) { reread_io(es_html_yml_io) }
let(:es_prosemirror_json_yml_contents) { reread_io(es_prosemirror_json_yml_io) }
# NOTE: This example_status.yml is crafted in conjunction with expected_html_yml_contents
# to test the behavior of the `skip_update_*` flags
@ -630,34 +531,24 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
<p><strong>bold</strong></p>
static: |-
<p data-sourcepos="1:1-1:8" dir="auto"><strong data-sourcepos="1:1-1:8">bold</strong></p>
wysiwyg: |-
<p dir="auto"><strong>bold</strong></p>
02_01_00__inlines__strong__002:
canonical: |
<p><strong>bold with more text</strong></p>
static: |-
<p data-sourcepos="1:1-1:23" dir="auto"><strong data-sourcepos="1:1-1:23">bold with more text</strong></p>
wysiwyg: |-
<p dir="auto"><strong>bold with more text</strong></p>
02_03_00__inlines__strikethrough_extension__001:
canonical: |
<p><del>Hi</del> Hello, world!</p>
static: |-
<p data-sourcepos="1:1-1:20" dir="auto"><del data-sourcepos="1:1-1:6">Hi</del> Hello, world!</p>
wysiwyg: |-
<p dir="auto"><s>Hi</s> Hello, world!</p>
03_01_00__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001:
canonical: |
<p><strong>bold</strong></p>
wysiwyg: |-
<p dir="auto"><strong>bold</strong></p>
03_02_01__first_gitlab_specific_section_with_examples__h2_which_contains_an_h3__example_in_an_h3__001:
canonical: |
<p>Example in an H3</p>
static: |-
<p data-sourcepos="1:1-1:16" dir="auto">Example in an H3</p>
wysiwyg: |-
<p dir="auto">Example in an H3</p>
04_01_00__second_gitlab_specific_section_with_examples__strong_but_with_html__001:
canonical: |
<p><strong>
@ -672,263 +563,34 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
<p><strong>This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved</strong></p>
static: |-
<p>This is the manually modified static HTML which will be preserved</p>
wysiwyg: |-
<p dir="auto">This is the manually modified WYSIWYG HTML which will be preserved</p>
06_01_00__api_request_overrides__group_upload_link__001:
canonical: |
<p><a href="groups-test-file">groups-test-file</a></p>
static: |-
<p data-sourcepos="1:1-1:45" dir="auto"><a data-sourcepos="1:1-1:45" href="/-/group/66666/uploads/groups-test-file" data-canonical-src="/uploads/groups-test-file" data-link="true" class="gfm">groups-test-file</a></p>
wysiwyg: |-
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="/uploads/groups-test-file">groups-test-file</a></p>
06_02_00__api_request_overrides__project_repo_link__001:
canonical: |
<p><a href="projects-test-file">projects-test-file</a></p>
static: |-
<p data-sourcepos="1:1-1:40" dir="auto"><a data-sourcepos="1:1-1:40" href="/glfm_group/glfm_project/-/blob/master/projects-test-file" class="gfm">projects-test-file</a></p>
wysiwyg: |-
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="projects-test-file">projects-test-file</a></p>
06_03_00__api_request_overrides__project_snippet_ref__001:
canonical: |
<p>This project snippet ID reference IS filtered: <a href="/glfm_group/glfm_project/-/snippets/88888">$88888</a>
static: |-
<p data-sourcepos="1:1-1:53" dir="auto">This project snippet ID reference IS filtered: <a href="/glfm_group/glfm_project/-/snippets/88888" data-reference-type="snippet" data-original="$88888" data-link="false" data-link-reference="false" data-snippet="88888" data-project="77777" data-container="body" data-placement="top" title="glfm_project_snippet" class="gfm gfm-snippet has-tooltip">$88888</a></p>
wysiwyg: |-
<p dir="auto">This project snippet ID reference IS filtered: $88888</p>
06_04_00__api_request_overrides__personal_snippet_ref__001:
canonical: |
<p>This personal snippet ID reference is NOT filtered: $99999</p>
static: |-
<p data-sourcepos="1:1-1:58" dir="auto">This personal snippet ID reference is NOT filtered: $99999</p>
wysiwyg: |-
<p dir="auto">This personal snippet ID reference is NOT filtered: $99999</p>
06_05_00__api_request_overrides__project_wiki_link__001:
canonical: |
<p><a href="project-wikis-test-file">project-wikis-test-file</a></p>
static: |-
<p data-sourcepos="1:1-1:50" dir="auto"><a data-sourcepos="1:1-1:50" href="/glfm_group/glfm_project/-/wikis/project-wikis-test-file" data-canonical-src="project-wikis-test-file">project-wikis-test-file</a></p>
wysiwyg: |-
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="project-wikis-test-file">project-wikis-test-file</a></p>
YAML
end
let(:expected_prosemirror_json_contents) do
<<~YAML
---
02_01_00__inlines__strong__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "bold"
}
],
"text": "bold"
}
]
}
]
}
02_03_00__inlines__strikethrough_extension__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "strike",
"attrs": {
"htmlTag": null
}
}
],
"text": "Hi"
},
{
"type": "text",
"text": " Hello, world!"
}
]
}
]
}
03_01_00__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "bold"
}
],
"text": "bold"
}
]
}
]
}
03_02_01__first_gitlab_specific_section_with_examples__h2_which_contains_an_h3__example_in_an_h3__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Example in an H3"
}
]
}
]
}
04_01_00__second_gitlab_specific_section_with_examples__strong_but_with_html__001: |-
{
"existing": "This entry is manually modified and preserved because skip_update_example_snapshot_prosemirror_json will be truthy"
}
05_02_00__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001: |-
{
"existing": "This entry is manually modified and preserved because skip_update_example_snapshots will be truthy"
}
06_01_00__api_request_overrides__group_upload_link__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "link",
"attrs": {
"uploading": false,
"href": "/uploads/groups-test-file",
"title": null,
"isGollumLink": false,
"isWikiPage": false,
"canonicalSrc": "/uploads/groups-test-file",
"isReference": false
}
}
],
"text": "groups-test-file"
}
]
}
]
}
06_02_00__api_request_overrides__project_repo_link__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "link",
"attrs": {
"uploading": false,
"href": "projects-test-file",
"title": null,
"isGollumLink": false,
"isWikiPage": false,
"canonicalSrc": "projects-test-file",
"isReference": false
}
}
],
"text": "projects-test-file"
}
]
}
]
}
06_03_00__api_request_overrides__project_snippet_ref__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This project snippet ID reference IS filtered: $88888"
}
]
}
]
}
06_04_00__api_request_overrides__personal_snippet_ref__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This personal snippet ID reference is NOT filtered: $99999"
}
]
}
]
}
06_05_00__api_request_overrides__project_wiki_link__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "link",
"attrs": {
"uploading": false,
"href": "project-wikis-test-file",
"title": null,
"isGollumLink": false,
"isWikiPage": false,
"canonicalSrc": "project-wikis-test-file",
"isReference": false
}
}
],
"text": "project-wikis-test-file"
}
]
}
]
}
YAML
end
# NOTE: Both `html.yml` and `prosemirror_json.yml` generation are tested in a single example, to
# avoid slower tests, because generating the static HTML is slow due to the need to invoke
# the rails environment. We could have separate sections, but this would require an extra flag
# to the `process` method to independently skip static vs. WYSIWYG, which is not worth the effort.
it 'writes the correct content', :unlimited_max_formatted_output_length do
# expectation that skipping message is only output once per example
expect(subject).to receive(:output).once.with(/reason.*skipping this example because it is very bad/i)
@ -936,7 +598,6 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', :uses_fast_spec_helper_
subject.process
expect(es_html_yml_contents).to eq(expected_html_yml_contents)
expect(es_prosemirror_json_yml_contents).to eq(expected_prosemirror_json_contents)
end
end
end

View File

@ -17,5 +17,12 @@ module Features
def within_testid(testid, context: page, **kwargs, &block)
context.within("[data-testid='#{testid}']", **kwargs, &block)
end
def page_breadcrumbs
all('[data-testid=breadcrumb-links] a').map do |a|
# We use `.dom_attribute` because Selenium transforms `.attribute('href')` to include the full URL.
{ text: a.text, href: a.native.dom_attribute('href') }
end
end
end
end

View File

@ -167,6 +167,13 @@ module GraphqlHelpers
end
end
end
# create a valid query context object
def query_context(user: current_user)
query = GraphQL::Query.new(empty_schema, document: nil, context: {}, variables: {})
GraphQL::Query::Context.new(query: query, values: { current_user: user })
end
# rubocop:enable Metrics/ParameterLists
# Pros:

View File

@ -189,7 +189,7 @@ RSpec.shared_examples 'work items rolled up dates' do
context 'when removing all children' do
it 'rolled up child dates' do
# https://gitlab.com/gitlab-org/gitlab/-/issues/473408
allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(107)
allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(108)
add_new_child(title: 'child issue 1', start_date: '2020-11-01', due_date: '2020-12-02')
add_new_child(title: 'child issue 2', start_date: '2020-12-01', due_date: '2021-01-02')

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
RSpec.shared_examples 'update board list mutation' do
include GraphqlHelpers
describe '#resolve' do
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
let(:mutation) { described_class.new(object: nil, context: query_context, field: nil) }
let(:list_update_params) { { position: 1, collapsed: true } }
subject { mutation.resolve(list: list, **list_update_params) }

View File

@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.shared_examples 'board lists create mutation' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:current_user) { create(:user) }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutation) { described_class.new(object: nil, context: query_context, field: nil) }
let(:list_create_params) { {} }
subject { mutation.resolve(board_id: board.to_global_id, **list_create_params) }
@ -27,7 +27,7 @@ RSpec.shared_examples 'board lists create mutation' do
describe '#resolve' do
context 'with proper permissions' do
before_all do
group.add_reporter(user)
group.add_reporter(current_user)
end
describe 'backlog list' do
@ -76,7 +76,7 @@ RSpec.shared_examples 'board lists create mutation' do
context 'without proper permissions' do
before_all do
group.add_guest(user)
group.add_guest(current_user)
end
it 'raises an error' do

Some files were not shown because too many files have changed in this diff Show More