diff options
82 files changed, 1078 insertions, 432 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 64feb4c7d55..cb3cdd83103 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -4,16 +4,17 @@ * @gitlab-org/maintainers/rails-backend @gitlab-org/maintainers/frontend @gitlab-org/maintainers/database @gl-quality/qe-maintainers @gitlab-org/delivery @gitlab-org/maintainers/cicd-templates @kwiebers @nolith @jacobvosmaer-gitlab -CODEOWNERS @clefelhocz1 @timzallmann @cdu1 @whaber @dsatcher @sgoldstein @jeromezng @stanhu -docs/CODEOWNERS @clefelhocz1 @timzallmann @cdu1 @whaber @dsatcher @sgoldstein @jeromezng @stanhu -.gitlab/CODEOWNERS @clefelhocz1 @timzallmann @cdu1 @whaber @dsatcher @sgoldstein @jeromezng @stanhu +CODEOWNERS @clefelhocz1 @timzallmann @cdu1 @whaber @dsatcher @sgoldstein @jeromezng @stanhu @susantacker @dianalogan @kpaizee @sselhorn +docs/CODEOWNERS @clefelhocz1 @timzallmann @cdu1 @whaber @dsatcher @sgoldstein @jeromezng @stanhu @susantacker @dianalogan @kpaizee @sselhorn +.gitlab/CODEOWNERS @clefelhocz1 @timzallmann @cdu1 @whaber @dsatcher @sgoldstein @jeromezng @stanhu @susantacker @dianalogan @kpaizee @sselhorn ## Allows release tooling to update the Gitaly Version GITALY_SERVER_VERSION @project_278964_bot6 @gitlab-org/maintainers/rails-backend @gitlab-org/delivery -## Excludes documentation markdown files and deprecation/removal announcements from required approval +## Excludes documentation files and deprecation/removal announcements from required approval /doc/*.md /doc/**/*.md +/doc/**/*.png /data/deprecations/ /data/removals/ diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 10fd06d1b0d..392a6981cb7 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -325ef97d3218f1680399bcc78eb86034d6209a29 +c3718f5f9a3285ad8d99b4e3383edc13b1546fda diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js index d8626289f2c..54d69d83188 100644 --- a/app/assets/javascripts/content_editor/extensions/sourcemap.js +++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js @@ -27,6 +27,7 @@ import Table from './table'; import TableCell from './table_cell'; import TableHeader from './table_header'; import TableRow from './table_row'; +import TableOfContents from './table_of_contents'; import Video from './video'; export default Extension.create({ @@ -61,6 +62,7 @@ export default Extension.create({ TableCell.name, TableHeader.name, TableRow.name, + TableOfContents.name, Video.name, ...HTMLNodes.map((htmlNode) => htmlNode.name), ], diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index 5fc7204212b..ba0cad6c91c 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -206,10 +206,10 @@ const defaultSerializerConfig = { }, overwriteSourcePreservationStrategy: true, }), - [TableOfContents.name]: (state, node) => { + [TableOfContents.name]: preserveUnchanged((state, node) => { state.write('[[_TOC_]]'); state.closeBlock(node); - }, + }), [Table.name]: preserveUnchanged(renderTable), [TableCell.name]: renderTableCell, [TableHeader.name]: renderTableCell, diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js index 9113ad5997e..ca290efca11 100644 --- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js @@ -237,6 +237,11 @@ const factorySpecs = { language: hastNode.properties.language, }), }, + + tableOfContents: { + type: 'block', + selector: 'tableofcontents', + }, }; const SANITIZE_ALLOWLIST = ['level', 'identifier', 'numeric', 'language', 'url', 'isReference']; @@ -294,6 +299,7 @@ export default () => { 'yaml', 'toml', 'json', + 'tableOfContents', ], }); diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index 6ab4eb58977..42988e96692 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -1,5 +1,5 @@ <script> -import { capitalize, escape, isEmpty } from 'lodash'; +import { escape, isEmpty } from 'lodash'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { reportToSentry } from '../../utils'; import MainGraphWrapper from '../graph_shared/main_graph_wrapper.vue'; @@ -92,9 +92,6 @@ export default { columnSpacingClass() { return this.isStageView ? 'gl-px-6' : 'gl-px-9'; }, - formattedTitle() { - return capitalize(escape(this.name)); - }, hasAction() { return !isEmpty(this.action); }, @@ -141,8 +138,8 @@ export default { class="gl-display-flex gl-justify-content-space-between gl-relative" :class="$options.titleClasses" > - <span :title="formattedTitle" class="gl-text-truncate gl-pr-3 gl-w-85p"> - {{ formattedTitle }} + <span :title="name" class="gl-text-truncate gl-pr-3 gl-w-85p"> + {{ name }} </span> <action-component v-if="hasAction && canUpdatePipeline" diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue index e693075e19a..600832b7633 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue @@ -1,5 +1,4 @@ <script> -import { capitalize, escape } from 'lodash'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; export default { @@ -12,17 +11,12 @@ export default { required: true, }, }, - computed: { - formattedTitle() { - return capitalize(escape(this.stageName)); - }, - }, }; </script> <template> <tooltip-on-truncate :title="stageName" truncate-target="child" placement="top"> <div class="gl-py-2 gl-text-truncate gl-font-weight-bold gl-w-20"> - {{ formattedTitle }} + {{ stageName }} </div> </tooltip-on-truncate> </template> diff --git a/app/assets/javascripts/runner/components/runner_projects.vue b/app/assets/javascripts/runner/components/runner_projects.vue index 2c1d2fc2b10..84008e8eee8 100644 --- a/app/assets/javascripts/runner/components/runner_projects.vue +++ b/app/assets/javascripts/runner/components/runner_projects.vue @@ -1,11 +1,13 @@ <script> -import { GlSkeletonLoader } from '@gitlab/ui'; +import { GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui'; import { sprintf, formatNumber } from '~/locale'; import { createAlert } from '~/flash'; import runnerProjectsQuery from '../graphql/show/runner_projects.query.graphql'; import { I18N_ASSIGNED_PROJECTS, - I18N_NONE, + I18N_CLEAR_FILTER_PROJECTS, + I18N_FILTER_PROJECTS, + I18N_NO_PROJECTS_FOUND, I18N_FETCH_ERROR, RUNNER_DETAILS_PROJECTS_PAGE_SIZE, } from '../constants'; @@ -14,9 +16,12 @@ import { captureException } from '../sentry_utils'; import RunnerAssignedItem from './runner_assigned_item.vue'; import RunnerPagination from './runner_pagination.vue'; +const SHORT_SEARCH_LENGTH = 3; + export default { name: 'RunnerProjects', components: { + GlSearchBoxByType, GlSkeletonLoader, RunnerAssignedItem, RunnerPagination, @@ -35,6 +40,7 @@ export default { pageInfo: {}, count: 0, }, + search: '', pagination: {}, }; }, @@ -61,9 +67,10 @@ export default { }, computed: { variables() { - const { id } = this.runner; + const { search, runner } = this; return { - id, + id: runner.id, + search: search.length >= SHORT_SEARCH_LENGTH ? search : '', ...getPaginationVariables(this.pagination, RUNNER_DETAILS_PROJECTS_PAGE_SIZE), }; }, @@ -80,22 +87,38 @@ export default { isOwner(projectId) { return projectId === this.projects.ownerProjectId; }, + onSearchInput(search) { + this.search = search; + this.pagination = {}; + }, onPaginationInput(value) { this.pagination = value; }, }, - I18N_NONE, + RUNNER_DETAILS_PROJECTS_PAGE_SIZE, + I18N_CLEAR_FILTER_PROJECTS, + I18N_FILTER_PROJECTS, + I18N_NO_PROJECTS_FOUND, }; </script> <template> <div class="gl-border-t-gray-100 gl-border-t-1 gl-border-t-solid"> - <h3 class="gl-font-lg gl-mt-5 gl-mb-0"> + <h3 class="gl-font-lg gl-mt-5"> {{ heading }} </h3> + <gl-search-box-by-type + :is-loading="loading" + :clear-button-title="$options.I18N_CLEAR_FILTER_PROJECTS" + :placeholder="$options.I18N_FILTER_PROJECTS" + debounce="500" + class="gl-w-28" + :value="search" + @input="onSearchInput" + /> - <div v-if="loading" class="gl-py-5"> - <gl-skeleton-loader /> + <div v-if="!projects.items.length && loading" class="gl-py-5"> + <gl-skeleton-loader v-for="i in $options.RUNNER_DETAILS_PROJECTS_PAGE_SIZE" :key="i" /> </div> <template v-else-if="projects.items.length"> <runner-assigned-item @@ -110,7 +133,7 @@ export default { :is-owner="isOwner(project.id)" /> </template> - <span v-else class="gl-text-gray-500">{{ $options.I18N_NONE }}</span> + <div v-else class="gl-py-5 gl-text-gray-500">{{ $options.I18N_NO_PROJECTS_FOUND }}</div> <runner-pagination :disabled="loading" diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js index 957ca489ffa..3009577599f 100644 --- a/app/assets/javascripts/runner/constants.js +++ b/app/assets/javascripts/runner/constants.js @@ -96,8 +96,11 @@ export const I18N_CREATED_AT_LABEL = s__('Runners|Created %{timeAgo}'); export const I18N_DETAILS = s__('Runners|Details'); export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})'); +export const I18N_FILTER_PROJECTS = s__('Runners|Filter projects'); +export const I18N_CLEAR_FILTER_PROJECTS = __('Clear'); export const I18N_NONE = __('None'); export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.'); +export const I18N_NO_PROJECTS_FOUND = __('No projects found'); // Styles diff --git a/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql index acc4a641565..e42648b3079 100644 --- a/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql +++ b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql @@ -2,6 +2,7 @@ query getRunnerProjects( $id: CiRunnerID! + $search: String $first: Int $last: Int $before: String @@ -13,7 +14,7 @@ query getRunnerProjects( id } projectCount - projects(first: $first, last: $last, before: $before, after: $after) { + projects(search: $search, first: $first, last: $last, before: $before, after: $after) { nodes { id avatarUrl diff --git a/app/controllers/admin/cohorts_controller.rb b/app/controllers/admin/cohorts_controller.rb index 468a1077694..ce3d769f35e 100644 --- a/app/controllers/admin/cohorts_controller.rb +++ b/app/controllers/admin/cohorts_controller.rb @@ -1,15 +1,20 @@ # frozen_string_literal: true class Admin::CohortsController < Admin::ApplicationController - include RedisTracking + include ProductAnalyticsTracking feature_category :devops_reports urgency :low + track_custom_event :index, + name: 'i_analytics_cohorts', + action: 'perform_analytics_usage_action', + label: 'redis_hll_counters.analytics.analytics_total_unique_counts_monthly', + destinations: %i[redis_hll snowplow] + def index @cohorts = load_cohorts - track_cohorts_visit end private @@ -22,7 +27,11 @@ class Admin::CohortsController < Admin::ApplicationController CohortsSerializer.new.represent(cohorts_results) end - def track_cohorts_visit - track_unique_redis_hll_event('i_analytics_cohorts') if trackable_html_request? + def tracking_namespace_source + nil + end + + def tracking_project_source + nil end end diff --git a/app/controllers/concerns/product_analytics_tracking.rb b/app/controllers/concerns/product_analytics_tracking.rb index 9d4a26c8d25..8e936782e5a 100644 --- a/app/controllers/concerns/product_analytics_tracking.rb +++ b/app/controllers/concerns/product_analytics_tracking.rb @@ -72,7 +72,11 @@ module ProductAnalyticsTracking p_analytics_valuestream: :route_hll_to_snowplow_phase2, p_analytics_insights: :route_hll_to_snowplow_phase2, p_analytics_issues: :route_hll_to_snowplow_phase2, - p_analytics_repo: :route_hll_to_snowplow_phase2 + p_analytics_repo: :route_hll_to_snowplow_phase2, + g_analytics_insights: :route_hll_to_snowplow_phase2, + g_analytics_issues: :route_hll_to_snowplow_phase2, + g_analytics_productivity: :route_hll_to_snowplow_phase2, + i_analytics_cohorts: :route_hll_to_snowplow_phase2 } Feature.enabled?(events_to_ff[event.to_sym], tracking_namespace_source) diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb index 1fa85064c57..1bf7deec542 100644 --- a/app/mailers/abuse_report_mailer.rb +++ b/app/mailers/abuse_report_mailer.rb @@ -10,7 +10,7 @@ class AbuseReportMailer < ApplicationMailer @abuse_report = AbuseReport.find(abuse_report_id) - mail( + mail_with_locale( to: Gitlab::CurrentSettings.abuse_notification_email, subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse" ) diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 94ed83a7d4a..bb8d20b8301 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -34,4 +34,23 @@ class ApplicationMailer < ActionMailer::Base address.display_name = Gitlab.config.gitlab.email_display_name address end + + def mail_with_locale(headers = {}, &block) + locale = recipient_locale headers + + Gitlab::I18n.with_locale(locale) do + mail(headers, &block) + end + end + + def recipient_locale(headers = {}) + to = Array(headers[:to]) + locale = I18n.locale + locale = preferred_language_by_email(to.first) if to.one? + locale + end + + def preferred_language_by_email(email) + User.find_by_any_email(email)&.preferred_language || I18n.locale + end end diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb index 25721658285..f681aa67a77 100644 --- a/app/mailers/email_rejection_mailer.rb +++ b/app/mailers/email_rejection_mailer.rb @@ -22,6 +22,6 @@ class EmailRejectionMailer < ApplicationMailer headers['Reply-To'] = @original_message.to.first if can_retry - mail(headers) + mail_with_locale(headers) end end diff --git a/app/mailers/emails/admin_notification.rb b/app/mailers/emails/admin_notification.rb index 3766b4447d1..5c5497d8eb5 100644 --- a/app/mailers/emails/admin_notification.rb +++ b/app/mailers/emails/admin_notification.rb @@ -7,13 +7,13 @@ module Emails email = user.notification_email_or_default @unsubscribe_url = unsubscribe_url(email: Base64.urlsafe_encode64(email)) @body = body - mail to: email, subject: subject + mail_with_locale to: email, subject: subject end def send_unsubscribed_notification(user_id) user = User.find(user_id) email = user.notification_email_or_default - mail to: email, subject: "Unsubscribed from GitLab administrator notifications" + mail_with_locale to: email, subject: "Unsubscribed from GitLab administrator notifications" end end end diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb index 07812a01202..3c9bf41c208 100644 --- a/app/mailers/emails/groups.rb +++ b/app/mailers/emails/groups.rb @@ -13,7 +13,7 @@ module Emails def group_email(current_user, group, subj, errors: nil) @group = group @errors = errors - mail(to: current_user.notification_email_for(@group), subject: subject(subj)) + mail_with_locale(to: current_user.notification_email_for(@group), subject: subject(subj)) end end end diff --git a/app/mailers/emails/in_product_marketing.rb b/app/mailers/emails/in_product_marketing.rb index 1b46d4841b0..972c1da065a 100644 --- a/app/mailers/emails/in_product_marketing.rb +++ b/app/mailers/emails/in_product_marketing.rb @@ -31,7 +31,7 @@ module Emails def mail_to(to:, subject:) custom_headers = Gitlab.com? ? CUSTOM_HEADERS : {} - mail(to: to, subject: subject, **custom_headers) do |format| + mail_with_locale(to: to, subject: subject, **custom_headers) do |format| format.html do @message.format = :html diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index c885e41671c..33c955f94ee 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -61,7 +61,7 @@ module Emails Gitlab::Tracking.event(self.class.name, 'invite_email_sent', label: 'invite_email', property: member_id.to_s) - mail(to: member.invite_email, subject: invite_email_subject, **invite_email_headers) do |format| + mail_with_locale(to: member.invite_email, subject: invite_email_subject, **invite_email_headers) do |format| format.html { render layout: 'unknown_user_mailer' } format.text { render layout: 'unknown_user_mailer' } end diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb index 6c3dcf8746b..a6e9da18689 100644 --- a/app/mailers/emails/pages_domains.rb +++ b/app/mailers/emails/pages_domains.rb @@ -6,7 +6,7 @@ module Emails @domain = domain @project = domain.project - mail( + mail_with_locale( to: recipient.notification_email_for(@project.group), subject: subject("GitLab Pages domain '#{domain.domain}' has been enabled") ) @@ -16,7 +16,7 @@ module Emails @domain = domain @project = domain.project - mail( + mail_with_locale( to: recipient.notification_email_for(@project.group), subject: subject("GitLab Pages domain '#{domain.domain}' has been disabled") ) @@ -26,7 +26,7 @@ module Emails @domain = domain @project = domain.project - mail( + mail_with_locale( to: recipient.notification_email_for(@project.group), subject: subject("Verification succeeded for GitLab Pages domain '#{domain.domain}'") ) @@ -36,7 +36,7 @@ module Emails @domain = domain @project = domain.project - mail( + mail_with_locale( to: recipient.notification_email_for(@project.group), subject: subject("ACTION REQUIRED: Verification failed for GitLab Pages domain '#{domain.domain}'") ) @@ -47,7 +47,7 @@ module Emails @project = domain.project subject_text = _("ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'") % { domain: domain.domain } - mail( + mail_with_locale( to: recipient.notification_email_for(@project.group), subject: subject(subject_text) ) diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index 81f082b9680..8fe471a48f2 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -6,7 +6,7 @@ module Emails @current_user = @user = User.find(user_id) @target_url = user_url(@user) @token = token - mail(to: @user.notification_email_or_default, subject: subject("Account was created for you")) + mail_with_locale(to: @user.notification_email_or_default, subject: subject("Account was created for you")) end def instance_access_request_email(user, recipient) @@ -42,7 +42,7 @@ module Emails @current_user = @user = @key.user @target_url = user_url(@user) - mail(to: @user.notification_email_or_default, subject: subject("SSH key was added to your account")) + mail_with_locale(to: @user.notification_email_or_default, subject: subject("SSH key was added to your account")) end # rubocop: enable CodeReuse/ActiveRecord @@ -54,7 +54,7 @@ module Emails @current_user = @user = @gpg_key.user @target_url = user_url(@user) - mail(to: @user.notification_email_or_default, subject: subject("GPG key was added to your account")) + mail_with_locale(to: @user.notification_email_or_default, subject: subject("GPG key was added to your account")) end # rubocop: enable CodeReuse/ActiveRecord @@ -66,7 +66,7 @@ module Emails @token_name = token_name Gitlab::I18n.with_locale(@user.preferred_language) do - mail(to: @user.notification_email_or_default, subject: subject(_("A new personal access token has been created"))) + mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("A new personal access token has been created"))) end end @@ -79,7 +79,7 @@ module Emails @days_to_expire = PersonalAccessToken::DAYS_TO_EXPIRE Gitlab::I18n.with_locale(@user.preferred_language) do - mail(to: @user.notification_email_or_default, subject: subject(_("Your personal access tokens will expire in %{days_to_expire} days or less") % { days_to_expire: @days_to_expire })) + mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("Your personal access tokens will expire in %{days_to_expire} days or less") % { days_to_expire: @days_to_expire })) end end @@ -90,7 +90,7 @@ module Emails @target_url = profile_personal_access_tokens_url Gitlab::I18n.with_locale(@user.preferred_language) do - mail(to: @user.notification_email_or_default, subject: subject(_("Your personal access token has expired"))) + mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("Your personal access token has expired"))) end end @@ -102,7 +102,7 @@ module Emails @target_url = profile_keys_url Gitlab::I18n.with_locale(@user.preferred_language) do - mail(to: @user.notification_email_or_default, subject: subject(_("Your SSH key has expired"))) + mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("Your SSH key has expired"))) end end @@ -114,7 +114,7 @@ module Emails @target_url = profile_keys_url Gitlab::I18n.with_locale(@user.preferred_language) do - mail(to: @user.notification_email_or_default, subject: subject(_("Your SSH key is expiring soon."))) + mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("Your SSH key is expiring soon."))) end end @@ -137,7 +137,7 @@ module Emails @user = user Gitlab::I18n.with_locale(@user.preferred_language) do - mail(to: @user.notification_email_or_default, subject: subject(_("Two-factor authentication disabled"))) + mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("Two-factor authentication disabled"))) end end @@ -148,7 +148,7 @@ module Emails @email = email Gitlab::I18n.with_locale(@user.preferred_language) do - mail(to: @user.notification_email_or_default, subject: subject(_("New email address added"))) + mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("New email address added"))) end end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 5b8471abb0f..4bb624c27e9 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -7,28 +7,29 @@ module Emails @project = Project.find project_id @target_url = project_url(@project) @old_path_with_namespace = old_path_with_namespace - mail(to: @user.notification_email_for(@project.group), - subject: subject("Project was moved")) + mail_with_locale(to: @user.notification_email_for(@project.group), + subject: subject("Project was moved")) end def project_was_exported_email(current_user, project) @project = project - mail(to: current_user.notification_email_for(project.group), - subject: subject("Project was exported")) + mail_with_locale(to: current_user.notification_email_for(project.group), + subject: subject("Project was exported")) end def project_was_not_exported_email(current_user, project, errors) @project = project @errors = errors - mail(to: current_user.notification_email_for(@project.group), - subject: subject("Project export error")) + mail_with_locale(to: current_user.notification_email_for(@project.group), + subject: subject("Project export error")) end def repository_cleanup_success_email(project, user) @project = project @user = user - mail(to: user.notification_email_for(project.group), subject: subject("Project cleanup has completed")) + mail_with_locale(to: user.notification_email_for(project.group), + subject: subject("Project cleanup has completed")) end def repository_cleanup_failure_email(project, user, error) @@ -36,7 +37,7 @@ module Emails @user = user @error = error - mail(to: user.notification_email_for(project.group), subject: subject("Project cleanup failure")) + mail_with_locale(to: user.notification_email_for(project.group), subject: subject("Project cleanup failure")) end def repository_push_email(project_id, opts = {}) @@ -51,9 +52,9 @@ module Emails add_project_headers headers['X-GitLab-Author'] = @message.author_username - mail(from: sender(@message.author_id, send_from_user_email: @message.send_from_committer_email?), - reply_to: @message.reply_to, - subject: @message.subject) + mail_with_locale(from: sender(@message.author_id, send_from_user_email: @message.send_from_committer_email?), + reply_to: @message.reply_to, + subject: @message.subject) end def prometheus_alert_fired_email(project, user, alert) @@ -65,7 +66,7 @@ module Emails add_alert_headers subject_text = "Alert: #{@alert.email_title}" - mail(to: user.notification_email_for(@project.group), subject: subject(subject_text)) + mail_with_locale(to: user.notification_email_for(@project.group), subject: subject(subject_text)) end def inactive_project_deletion_warning_email(project, user, deletion_date) diff --git a/app/mailers/emails/releases.rb b/app/mailers/emails/releases.rb index 4875abafe8d..8fe93f59662 100644 --- a/app/mailers/emails/releases.rb +++ b/app/mailers/emails/releases.rb @@ -11,7 +11,7 @@ module Emails ) @recipient = User.find(user_id) - mail( + mail_with_locale( to: @recipient.notification_email_for(@project.group), subject: subject(release_email_subject) ) diff --git a/app/mailers/emails/remote_mirrors.rb b/app/mailers/emails/remote_mirrors.rb index 9cde53918b9..791ab7103b4 100644 --- a/app/mailers/emails/remote_mirrors.rb +++ b/app/mailers/emails/remote_mirrors.rb @@ -7,7 +7,7 @@ module Emails @project = @remote_mirror.project user = User.find(recipient_id) - mail(to: user.notification_email_for(@project.group), subject: subject('Remote mirror update failed')) + mail_with_locale(to: user.notification_email_for(@project.group), subject: subject('Remote mirror update failed')) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index ed7681e595f..5a3fc70832c 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -38,11 +38,11 @@ class Notify < ApplicationMailer helper InProductMarketingHelper def test_email(recipient_email, subject, body) - mail(to: recipient_email, - subject: subject, - body: body.html_safe, - content_type: 'text/html' - ) + mail_with_locale(to: recipient_email, + subject: subject, + body: body.html_safe, + content_type: 'text/html' + ) end # Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com", @@ -139,7 +139,7 @@ class Notify < ApplicationMailer @reply_by_email = true end - mail(headers) + mail_with_locale(headers) end # `model` is used on EE code @@ -225,7 +225,7 @@ class Notify < ApplicationMailer end def email_with_layout(to:, subject:, layout: 'mailer') - mail(to: to, subject: subject) do |format| + mail_with_locale(to: to, subject: subject) do |format| format.html { render layout: layout } format.text { render layout: layout } end diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb index b8f990f26c8..17c36c19955 100644 --- a/app/mailers/repository_check_mailer.rb +++ b/app/mailers/repository_check_mailer.rb @@ -14,7 +14,7 @@ class RepositoryCheckMailer < ApplicationMailer "#{failed_count} projects failed their last repository check" end - mail( + mail_with_locale( to: User.admins.active.pluck(:email), subject: "GitLab Admin | #{@message}" ) diff --git a/app/models/concerns/integrations/has_web_hook.rb b/app/models/concerns/integrations/has_web_hook.rb index e6ca6cc7938..e9fcb0d151d 100644 --- a/app/models/concerns/integrations/has_web_hook.rb +++ b/app/models/concerns/integrations/has_web_hook.rb @@ -5,7 +5,6 @@ module Integrations extend ActiveSupport::Concern included do - after_save :update_web_hook!, if: :activated? has_one :service_hook, inverse_of: :integration, foreign_key: :service_id end diff --git a/app/models/integrations/buildkite.rb b/app/models/integrations/buildkite.rb index 7a48e71b934..c19e06f055e 100644 --- a/app/models/integrations/buildkite.rb +++ b/app/models/integrations/buildkite.rb @@ -8,6 +8,8 @@ module Integrations include ReactivelyCached extend Gitlab::Utils::Override + after_save :ensure_ssl_verification + ENDPOINT = "https://buildkite.com" field :project_url, @@ -48,6 +50,12 @@ module Integrations self.properties = properties.except('enable_ssl_verification') # Remove unused key end + def ensure_ssl_verification + return unless service_hook + + update_web_hook! + end + override :hook_url def hook_url "#{buildkite_endpoint('webhook')}/deliver/#{webhook_token}" diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb index 887980430f4..32a7d205f46 100644 --- a/app/presenters/ci/pipeline_presenter.rb +++ b/app/presenters/ci/pipeline_presenter.rb @@ -49,7 +49,7 @@ module Ci { merge_train: s_('Pipeline|Merge train pipeline'), merged_result: s_('Pipeline|Merged result pipeline'), - detached: s_('Pipeline|Detached merge request pipeline') + detached: s_('Pipeline|Merge request pipeline') }.freeze end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index f51923b7035..5244f2acd66 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -92,15 +92,16 @@ module MergeRequests raise_error(GENERIC_ERROR_MESSAGE) end - merge_request.update!(merge_commit_sha: commit_id) + data_to_update = { merge_commit_sha: commit_id } + data_to_update[:squash_commit_sha] = source if merge_request.squash_on_merge? + + merge_request.update!(**data_to_update) ensure merge_request.update_and_mark_in_progress_merge_commit_sha(nil) end def try_merge - repository.merge(current_user, source, merge_request, commit_message).tap do - merge_request.update_column(:squash_commit_sha, source) if merge_request.squash_on_merge? - end + repository.merge(current_user, source, merge_request, commit_message) rescue Gitlab::Git::PreReceiveError => e raise MergeError, "Something went wrong during merge pre-receive hook. #{e.message}".strip diff --git a/app/views/admin/application_settings/_floc.html.haml b/app/views/admin/application_settings/_floc.html.haml index 9bd2fd0a895..e56ba635890 100644 --- a/app/views/admin/application_settings/_floc.html.haml +++ b/app/views/admin/application_settings/_floc.html.haml @@ -3,12 +3,13 @@ %section.settings.no-animate#js-floc-settings{ class: ('expanded' if expanded) } .settings-header %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = s_('FloC|Federated Learning of Cohorts') + = s_('FloC|Federated Learning of Cohorts (FLoC)') = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do = expanded ? _('Collapse') : _('Expand') %p - = s_('FloC|Configure whether you want to participate in FloC.').html_safe - = link_to sprite_icon('question-o'), 'https://github.com/WICG/floc', target: '_blank', rel: 'noopener noreferrer', class: 'has-tooltip', title: _('More information') + - floc_link_url = help_page_path('user/admin_area/settings/floc.md') + - floc_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: floc_link_url } + = html_escape(s_('FloC|Configure whether you want to participate in FLoC. %{floc_link_start}What is FLoC?%{floc_link_end}')) % { floc_link_start: floc_link_start, floc_link_end: '</a>'.html_safe } .settings-content = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-floc-settings'), html: { class: 'fieldset-form', id: 'floc-settings' } do |f| @@ -17,5 +18,5 @@ %fieldset .form-group = f.gitlab_ui_checkbox_component :floc_enabled, - s_('FloC|Enable FloC (Federated Learning of Cohorts)') + s_('FloC|Participate in FLoC') = f.submit _('Save changes'), class: 'gl-button btn btn-confirm' diff --git a/app/views/notify/push_to_merge_request_email.html.haml b/app/views/notify/push_to_merge_request_email.html.haml index 5197a1bdd08..fc32ceab765 100644 --- a/app/views/notify/push_to_merge_request_email.html.haml +++ b/app/views/notify/push_to_merge_request_email.html.haml @@ -1,7 +1,7 @@ %h3 - = sanitize_name(@updated_by_user.name) - pushed new commits to merge request - = merge_request_reference_link(@merge_request) + - updated_by_user_name = sanitize_name(@updated_by_user.name) + - mr_link = sanitize(merge_request_reference_link(@merge_request)) + = s_('Notify|%{updated_by_user_name} pushed new commits to merge request %{mr_link}').html_safe % {updated_by_user_name: updated_by_user_name, mr_link: mr_link} - if @total_existing_commits_count > 0 %ul @@ -13,8 +13,8 @@ = link_to(project_compare_url(@merge_request.target_project, from: @existing_commits.first[:short_id], to: @existing_commits.last[:short_id])) do #{@existing_commits.first[:short_id]}...#{@existing_commits.last[:short_id]} = precede ' - ' do - - commits_text = "#{@total_existing_commits_count} commit".pluralize(@total_existing_commits_count) - #{commits_text} from branch `#{@merge_request.target_branch}` + - commits_text = n_("#%d commit", "#%d commits", @total_existing_commits_count) % @total_existing_commits_count + = s_('Notify|%{commits_text} from branch `%{target_branch}`') % {commits_text: commits_text, target_branch: @merge_request.target_branch} - if @total_new_commits_count > 0 %ul @@ -24,4 +24,5 @@ = precede ' - ' do #{commit[:title]} - if @total_stripped_new_commits_count > 0 - %li And #{@total_stripped_new_commits_count} more + %li + = s_('Notify|And %{total_stripped_new_commits_count} more') % {total_stripped_new_commits_count: @total_stripped_new_commits_count} diff --git a/app/views/shared/issuable/_sidebar_reviewers.html.haml b/app/views/shared/issuable/_sidebar_reviewers.html.haml index cd976b88304..3f78f29ea24 100644 --- a/app/views/shared/issuable/_sidebar_reviewers.html.haml +++ b/app/views/shared/issuable/_sidebar_reviewers.html.haml @@ -2,7 +2,7 @@ #js-vue-sidebar-reviewers{ data: { field: issuable_type, signed_in: signed_in } } .title.hide-collapsed - = _('Reviewer') + = _('Reviewers') = gl_loading_icon(inline: true) .selectbox.hide-collapsed diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml index 51a5c9dd38f..a5170b199e8 100644 --- a/app/views/shared/projects/_search_form.html.haml +++ b/app/views/shared/projects/_search_form.html.haml @@ -1,5 +1,5 @@ - form_field_classes = local_assigns[:admin_view] || !Feature.enabled?(:project_list_filter_bar) ? 'input-short js-projects-list-filter' : '' -- placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : 'Filter by name...' +- placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : _('Filter by name') = form_tag filter_projects_path, method: :get, class: 'project-filter-form', data: { qa_selector: 'project_filter_form_container' }, id: 'project-filter-form' do |f| = search_field_tag :name, params[:name], diff --git a/config/feature_flags/undefined/gitaly_simplify_find_local_branches_response.yml b/config/feature_flags/undefined/gitaly_simplify_find_local_branches_response.yml new file mode 100644 index 00000000000..c82f8ee26b7 --- /dev/null +++ b/config/feature_flags/undefined/gitaly_simplify_find_local_branches_response.yml @@ -0,0 +1,8 @@ +--- +name: gitaly_simplify_find_local_branches_response +introduced_by_url: https://gitlab.com/gitlab-org/gitaly/-/merge_requests/4850 +rollout_issue_url: https://gitlab.com/gitlab-org/gitaly/-/issues/4452 +milestone: '15.4' +type: undefined +group: group::gitaly +default_enabled: false diff --git a/doc/development/cicd/templates.md b/doc/development/cicd/templates.md index 4ea7a9d960c..d0c56fb18bc 100644 --- a/doc/development/cicd/templates.md +++ b/doc/development/cicd/templates.md @@ -465,7 +465,10 @@ To add a metric definition for a new template: - `name:` and `performance_indicator_type:`: Delete (not needed). - `introduced_by_url:`: The URL of the MR adding the template. - `data_source:`: Set to `redis_hll`. - - All other fields that have no values: Set to empty strings (`''`). + - `description`: Add a short description of what this metric counts, for example: `Count of pipelines using the latest Auto Deploy template` + - `product_*`: Set to [section, stage, group, and feature category](https://about.gitlab.com/handbook/product/categories/#devops-stages) + as per the [metrics dictionary guide](../service_ping/metrics_dictionary.md#metrics-definition-and-validation). + If you are unsure what to use for these keywords, you can ask for help in the merge request. - Add the following to the end of each file: ```yaml diff --git a/doc/development/documentation/versions.md b/doc/development/documentation/versions.md index 3679c731a77..acc0e870d24 100644 --- a/doc/development/documentation/versions.md +++ b/doc/development/documentation/versions.md @@ -205,7 +205,7 @@ You can say that we plan to remove a feature. ### Legal disclaimer for future features -If you **must** write about features we have not yet delivered, put this exact disclaimer near the content it applies to. +If you **must** write about features we have not yet delivered, put this exact disclaimer about forward-looking statements near the content it applies to. ```markdown DISCLAIMER: @@ -227,6 +227,6 @@ As with all projects, the items mentioned on this page are subject to change or The development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc. -If all of the content on the page is not available, use the disclaimer once at the top of the page. +If all of the content on the page is not available, use the disclaimer about forward-looking statements once at the top of the page. If the content in a topic is not ready, use the disclaimer in the topic. diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index ff55d7404cd..221d6b89b20 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -1382,6 +1382,47 @@ RSpec.configure do |config| end ``` +### Testing Ruby constants + +When testing code that uses Ruby constants, focus the test on the behavior that depends on the constant, +rather than testing the values of the constant. + +For example, the following is preferred because it tests the behavior of the class method `.categories`. + +```ruby + describe '.categories' do + it 'gets CE unique category names' do + expect(described_class.categories).to include( + 'deploy_token_packages', + 'user_packages', + # ... + 'kubernetes_agent' + ) + end + end +``` + +On the other hand, testing the value of the constant itself, often only repeats the values +in the code and the test, which provides little value. + +```ruby + describe CATEGORIES do + it 'has values' do + expect(CATEGORIES).to eq([ + 'deploy_token_packages', + 'user_packages', + # ... + 'kubernetes_agent' + ]) + end +end +``` + +In critical cases where an error on a constant could have a catastrophic impact, +testing the constant values might be useful as an added safeguard. For example, +if it could bring down the entire GitLab service, cause a customer to be billed more than they should be, +or [cause the universe to implode](../contributing/verify/index.md#do-not-cause-our-universe-to-implode). + ### Factories GitLab uses [factory_bot](https://github.com/thoughtbot/factory_bot) as a test fixture replacement. diff --git a/doc/operations/incident_management/incident_timeline_events.md b/doc/operations/incident_management/incident_timeline_events.md index c43de2e81e2..8ea962da35f 100644 --- a/doc/operations/incident_management/incident_timeline_events.md +++ b/doc/operations/incident_management/incident_timeline_events.md @@ -16,7 +16,7 @@ Incident timelines are an important part of record keeping for incidents. Timelines can show executives and external viewers what happened during an incident, and which steps were taken for it to be resolved. -## View the event timeline +## View the timeline Incident timeline events are listed in ascending order of the date and time. They are grouped with dates and are listed in ascending order of the time when they occurred: @@ -30,7 +30,7 @@ To view the event timeline of an incident: 1. Select an incident. 1. Select the **Timeline** tab. -## Create a timeline event +## Create an event You can create a timeline event in many ways in GitLab. @@ -70,7 +70,7 @@ To create a timeline event from a comment on the incident: The comment is shown on the incident timeline as a timeline event. -## Delete a timeline event +## Delete an event You can also delete timeline events. diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md index 652cf4a2018..58cf333bb2d 100644 --- a/doc/policy/maintenance.md +++ b/doc/policy/maintenance.md @@ -12,8 +12,8 @@ patch, and security releases. New releases are announced on the [GitLab blog](ht Our current policy is: -- Backporting bug fixes for **only the current stable release** at any given time. (See [patch releases](#patch-releases).) -- Backporting security fixes **to the previous two monthly releases in addition to the current stable release**. (See [security releases](#security-releases).) +- Backporting bug fixes for **only the current stable release** at any given time - see [patch releases](#patch-releases) below. +- Backporting security fixes **to the previous two monthly releases in addition to the current stable release**. In some circumstances (outlined in [security releases](#security-releases) below) we may address a security vulnerability using the [patch release](#patch-releases) process or regular monthly release process, that is, providing an update to the current stable release only, with no backports. In rare cases, release managers may make an exception and backport to more than the last two monthly releases. See @@ -132,13 +132,16 @@ To request backporting to more than one stable release for consideration, raise ### Security releases Security releases are a special kind of patch release that only include security -fixes and patches (see below) for the previous two monthly releases in addition to the current stable release. +fixes and patches for the previous two monthly releases in addition to the current stable release. For very serious security issues, there is [precedent](https://about.gitlab.com/releases/2016/05/02/cve-2016-4340-patches/) to backport security fixes to even more monthly releases of GitLab. This decision is made on a case-by-case basis. +In some circumstances we may choose to address a vulnerability using the [patch release](#patch-releases) process or the regular monthly release process, that is, updating the current stable release only, with no backports. Factors influencing this decision include very low likelihood of exploitation, low impact, fix complexity and risk to stability. We will **always address +high and critical** security issues with a security release. + ## More information You may also want to read our: diff --git a/doc/topics/application_development_platform/index.md b/doc/topics/application_development_platform/index.md index fac9f963a98..524ba2aaf6d 100644 --- a/doc/topics/application_development_platform/index.md +++ b/doc/topics/application_development_platform/index.md @@ -1,67 +1,11 @@ --- -stage: none -group: unassigned -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +redirect_to: '../../user/infrastructure/index.md' +remove_date: '2022-12-08' --- -# Application Development Platform **(FREE)** +This document was moved to [another location](../../user/infrastructure/index.md). -The GitLab Application Development Platform refers to the set of GitLab features used to create, configure, and manage -a complete software development environment. It provides development, operations, and security teams with a robust feature set aimed at supporting best practices out of the box. - -## Overview - -The GitLab Application Development Platform aims to: - -- Reduce and even eliminate the time it takes for an Operations team - to provide a full environment for software developers. -- Get developers up and running fast so they can focus on writing - great applications with a robust development feature set. -- Provide best-of-breed security features so that applications developed - with GitLab are not affected by vulnerabilities that may lead to security - problems and unintended use. - -It is comprised of the following high-level elements: - -1. Compute -1. Build, test, and deploy a wide range of applications -1. Security -1. Observability - -We believe the use of these common building blocks equate to big gains for teams of all sizes, resulting from the adoption -of newer, more efficient, more profitable, and less error-prone techniques for shipping software applications. - -### Compute - -Because at GitLab we are [cloud-native first](https://about.gitlab.com/handbook/product/#cloud-native-first) our -Application Development Platform initially focuses on providing robust support for Kubernetes, with other platforms -to follow. Teams can bring their own clusters and we additionally make it easy to create new infrastructure -with various cloud providers. - -### Build, test, deploy - -In order to provide modern DevOps workflows, our Application Development Platform relies on -[Auto DevOps](../autodevops/index.md) to provide those workflows. Auto DevOps works with -any Kubernetes cluster; you're not limited to running on GitLab infrastructure. Additionally, Auto DevOps offers -an incremental consumption path. Because it is [composable](../autodevops/customize.md#using-components-of-auto-devops), -you can use as much or as little of the default pipeline as you'd like, and deeply customize without having to integrate a completely different platform. - -### Security - -The Application Development Platform helps you ensure that the applications you create are not affected by vulnerabilities -that may lead to security problems and unintended use. This can be achieved by making use of the embedded security features of Auto DevOps, -which inform security teams and developers if there is something to consider changing in their apps -before it is too late to create a preventative fix. The following features are included: - -- [Auto SAST (Static Application Security Testing)](../autodevops/stages.md#auto-sast) -- [Auto Dependency Scanning](../autodevops/stages.md#auto-dependency-scanning) -- [Auto Container Scanning](../autodevops/stages.md#auto-container-scanning) -- [Auto DAST (Dynamic Application Security Testing)](../autodevops/stages.md#auto-dast) - -### Observability - -Performance is a critical aspect of the user experience, and ensuring your application is responsive and available is everyone's -responsibility. The Application Development Platform integrates key performance analytics and feedback -into GitLab, automatically. The following features are included: - -- [Auto Monitoring](../autodevops/stages.md#auto-monitoring) +<!-- This redirect file can be deleted after <2022-12-08>. --> +<!-- Redirects that point to other docs in the same project expire in three months. --> +<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. --> +<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html --> diff --git a/doc/user/admin_area/settings/floc.md b/doc/user/admin_area/settings/floc.md index dccab461b85..9381ea90b9c 100644 --- a/doc/user/admin_area/settings/floc.md +++ b/doc/user/admin_area/settings/floc.md @@ -24,8 +24,8 @@ To enable it: 1. On the top bar, select **Menu > Admin**. 1. On the left sidebar, select **Settings > General**. -1. Expand **Federated Learning of Cohorts**. -1. Check the box. +1. Expand **Federated Learning of Cohorts (FLoC)**. +1. Select the **Participate in FLoC** checkbox. 1. Select **Save changes**. <!-- ## Troubleshooting diff --git a/doc/user/permissions.md b/doc/user/permissions.md index a14a3843d4f..a4d83f76d6c 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -88,6 +88,8 @@ The following table lists project permissions available for each role: | [Incident Management](../operations/incident_management/index.md):<br>View [escalation policies](../operations/incident_management/escalation_policies.md) | | ✓ | ✓ | ✓ | ✓ | | [Incident Management](../operations/incident_management/index.md):<br>Manage [on-call schedules](../operations/incident_management/oncall_schedules.md) | | | | ✓ | ✓ | | [Incident Management](../operations/incident_management/index.md):<br>Manage [escalation policies](../operations/incident_management/escalation_policies.md) | | | | ✓ | ✓ | +| [Issue boards](project/issue_board.md):<br>Create or delete lists | | ✓ | ✓ | ✓ | ✓ | +| [Issue boards](project/issue_board.md):<br>Move issues between lists | | ✓ | ✓ | ✓ | ✓ | | [Issues](project/issues/index.md):<br>Add Labels | ✓ (*15*) | ✓ | ✓ | ✓ | ✓ | | [Issues](project/issues/index.md):<br>Assign | ✓ (*15*) | ✓ | ✓ | ✓ | ✓ | | [Issues](project/issues/index.md):<br>Create (*18*) | ✓ | ✓ | ✓ | ✓ | ✓ | @@ -348,11 +350,6 @@ from pushing to a protected branch. Read through the documentation on Find the current permissions on the value stream analytics dashboard, as described in [related documentation](analytics/value_stream_analytics.md#access-permissions-for-value-stream-analytics). -### Issue board permissions - -Find the current permissions for interacting with the issue board feature in the -[issue boards permissions page](project/issue_board.md#permissions). - ### File Locking permissions **(PREMIUM)** The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located. diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 787e990526d..3e980717313 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -8,14 +8,15 @@ info: To determine the technical writer assigned to the Stage/Group associated w The issue board is a software project management tool used to plan, organize, and visualize a workflow for a feature or product release. -It can be used as a [Kanban](https://en.wikipedia.org/wiki/Kanban_(development)) or a + +You can use it as a [Kanban](https://en.wikipedia.org/wiki/Kanban_(development)) or a [Scrum](https://en.wikipedia.org/wiki/Scrum_(software_development)) board. -It pairs issue tracking and project management, keeping everything together, -so that you don't need to jump between different platforms to organize your workflow. +Issue boards pair issue tracking and project management, keeping everything together, +so you can organize your workflow on a single platform. -Issue boards build on the existing [issue tracking functionality](issues/index.md) and -[labels](labels.md). Your issues appear as cards in vertical lists, organized by their assigned +Issue boards use [issues](issues/index.md) and [labels](labels.md). +Your issues appear as cards in vertical lists, organized by their assigned labels, [milestones](#milestone-lists), or [assignees](#assignee-lists). Issue boards help you to visualize and manage your entire process in GitLab. @@ -31,21 +32,20 @@ boards in the same project. ![GitLab issue board - Core](img/issue_boards_core_v14_1.png) -Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), -as shown in the following table: +Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/): | Tier | Number of project issue boards | Number of [group issue boards](#group-issue-boards) | [Configurable issue boards](#configurable-issue-boards) | [Assignee lists](#assignee-lists) | | -------- | ------------------------------ | --------------------------------------------------- | ------------------------------------------------------- | --------------------------------- | -| Free | Multiple | 1 | No | No | -| Premium | Multiple | Multiple | Yes | Yes | -| Ultimate | Multiple | Multiple | Yes | Yes | +| Free | Multiple | 1 | **{dotted-circle}** No | **{dotted-circle}** No | +| Premium | Multiple | Multiple | **{check-circle}** Yes | **{check-circle}** Yes | +| Ultimate | Multiple | Multiple | **{check-circle}** Yes | **{check-circle}** Yes | -To learn more, visit [GitLab Enterprise features for issue boards](#gitlab-enterprise-features-for-issue-boards) below. +Read more about [GitLab Enterprise features for issue boards](#gitlab-enterprise-features-for-issue-boards). ![GitLab issue board - Premium](img/issue_boards_premium_v14_1.png) <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> -Watch a [video presentation](https://youtu.be/vjccjHI7aGI) of +Watch a [video presentation](https://youtu.be/vjccjHI7aGI) (April 2020) of the issue board feature. ## Multiple issue boards @@ -68,17 +68,25 @@ GitLab automatically loads the last board you visited. ### Create an issue board +Prerequisites: + +- You must have at least the Reporter role for the project. + To create a new issue board: -1. Select the dropdown with the current board name in the upper left corner of the issue boards page. +1. Select the dropdown list with the current board name in the upper left corner of the issue boards page. 1. Select **Create new board**. 1. Enter the new board's name and select its scope: milestone, labels, assignee, or weight. ### Delete an issue board +Prerequisites: + +- You must have at least the Reporter role for the project. + To delete the currently active issue board: -1. Select the dropdown with the current board name in the upper left corner of the issue boards page. +1. Select the dropdown list with the current board name in the upper left corner of the issue boards page. 1. Select **Delete board**. 1. Select **Delete** to confirm. @@ -192,12 +200,11 @@ card includes: - Issue number - Assignee -## Permissions +## Ordering issues in a list -Users with at least the Reporter role can use all the functionality of the -issue board feature to create or delete lists. They can also drag issues from one list to another. +Prerequisites: -## Ordering issues in a list +- You must have at least the Reporter role for the project. When an issue is created, the system assigns a relative order value that is greater than the maximum value of that issue's project or root group. This means the issue will be at the bottom of any issue list that @@ -275,16 +282,19 @@ Users on GitLab Free can use a single group issue board. ### Assignee lists **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5784) in GitLab 11.0. - As in a regular list showing all issues with a chosen label, you can add an assignee list that shows all issues assigned to a user. -You can have a board with both label lists and assignee lists. To add an -assignee list: +You can have a board with both label lists and assignee lists. + +Prerequisites: + +- You must have at least the Reporter role for the project. + +To add an assignee list: 1. Select **Create list**. 1. Select **Assignee**. -1. In the dropdown, select a user. +1. In the dropdown list, select a user. 1. Select **Add to board**. Now that the assignee list is added, you can assign or unassign issues to that user @@ -295,14 +305,18 @@ To remove an assignee list, just as with a label list, select the trash icon. ### Milestone lists **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6469) in GitLab 11.2. - You're also able to create lists of a milestone. These are lists that filter issues by the assigned -milestone, giving you more freedom and visibility on the issue board. To add a milestone list: +milestone, giving you more freedom and visibility on the issue board. + +Prerequisites: + +- You must have at least the Reporter role for the project. + +To add a milestone list: 1. Select **Create list**. 1. Select **Milestone**. -1. In the dropdown, select a milestone. +1. In the dropdown list, select a milestone. 1. Select **Add to board**. Like the assignee lists, you're able to [drag issues](#move-issues-and-lists) @@ -316,14 +330,17 @@ As in other list types, select the trash icon to remove a list. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250479) in GitLab 13.11 [with a flag](../../administration/feature_flags.md) named `iteration_board_lists`. Enabled by default. > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75404) in GitLab 14.6. Feature flag `iteration_board_lists` removed. -You're also able to create lists of an iteration. -These lists filter issues by the assigned iteration. +You can create lists of issues in an iteration. + +Prerequisites: + +- You must have at least the Reporter role for the project. To add an iteration list: 1. Select **Create list**. 1. Select **Iteration**. -1. In the dropdown, select an iteration. +1. In the dropdown list, select an iteration. 1. Select **Add to board**. Like the milestone lists, you're able to [drag issues](#move-issues-and-lists) @@ -344,9 +361,13 @@ This feature is available both at the project and group level. <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> For a video overview, see [Epics Swimlanes Walkthrough - 13.6](https://www.youtube.com/watch?v=nHC7-kz5P2g) (November 2020). +Prerequisites: + +- You must have at least the Reporter role for the project. + To group issues by epic in an issue board: -1. Select the **Group by** dropdown button. +1. Select **Group by**. 1. Select **Epic**. ![Epics Swimlanes](img/epics_swimlanes_v14_1.png) @@ -375,8 +396,7 @@ You can also [drag issues](#move-issues-and-lists) to change their position and ## Work In Progress limits **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11403) in GitLab 12.7 -> - Moved to GitLab Premium in 13.9. +> Moved to GitLab Premium in 13.9. You can set a Work In Progress (WIP) limit for each issue list on an issue board. When a limit is set, the list's header shows the number of issues in the list and the soft limit of issues. @@ -389,6 +409,10 @@ Examples: - You have a list with five issues with a limit of five. When you move another issue to that list, the list's header displays **6/5**, with the six shown in red. +Prerequisites: + +- You must have at least the Reporter role for the project. + To set a WIP limit for a list: 1. Navigate to a Project or Group board of which you're a member. @@ -399,8 +423,7 @@ To set a WIP limit for a list: ## Blocked issues **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34723) in GitLab 12.8. -> - [View blocking issues when hovering over blocked icon](https://gitlab.com/gitlab-org/gitlab/-/issues/210452) in GitLab 13.10. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210452) in GitLab 13.10: View blocking issues when hovering over the "blocked" icon. If an issue is [blocked by another issue](issues/related_issues.md#blocking-issues), an icon appears next to its title to indicate its blocked status. @@ -423,9 +446,6 @@ When you hover over the blocked icon (**{issue-block}**), a detailed information - Change issue labels (by dragging an issue between lists). - Close an issue (by dragging it to the **Closed** list). -If you're not able to do some of the things above, make sure you have the right -[permissions](#permissions). - ### Edit an issue > Editing title, iteration, and confidentiality [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/248908) in GitLab 14.1. @@ -433,6 +453,10 @@ If you're not able to do some of the things above, make sure you have the right You can edit an issue without leaving the board view. To open the right sidebar, select an issue card (not its title). +Prerequisites: + +- You must have at least the Reporter role for the project. + You can edit the following issue attributes in the right sidebar: - Assignees @@ -462,6 +486,10 @@ at the end of the lists, before **Closed**. To move and reorder lists, drag them Removing a list doesn't have any effect on issues and labels, as it's just the list view that's removed. You can always create it again later if you need. +Prerequisites: + +- You must have at least the Reporter role for the project. + To remove a list from an issue board: 1. On the top of the list you want to remove, select the **List settings** icon (**{settings}**). @@ -473,6 +501,10 @@ To remove a list from an issue board: > The **Add issues** button was [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57329) in GitLab 13.11. +Prerequisites: + +- You must have at least the Reporter role for the project. + If your board is scoped to one or more attributes, go to the issues you want to add and apply the same attributes as your board scope. @@ -488,6 +520,11 @@ The issue should now show in the `Doing` list on your issue board. > The **Remove from board** button was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/229507) in GitLab 13.10. When an issue should no longer belong to a list, you can remove it. + +Prerequisites: + +- You must have at least the Reporter role for the project. + The steps depend on the scope of the list: 1. To open the right sidebar, select the issue card. @@ -502,6 +539,10 @@ The steps depend on the scope of the list: You can use the filters on top of your issue board to show only the results you want. It's similar to the filtering used in the [issue tracker](issues/index.md). +Prerequisites: + +- You must have at least the Reporter role for the project. + You can filter by the following: - Assignee @@ -593,8 +634,7 @@ and the target list. ### Multi-select issue cards -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18954) in GitLab 12.4. -> - [Placed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61955) behind a [feature flag](../feature_flags.md), disabled by default in GitLab 14.0. +> [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61955) behind a [feature flag](../feature_flags.md) named `board_multi_select` in GitLab 14.0. Disabled by default. FLAG: On self-managed GitLab, by default this feature is not available. To make it available, ask an @@ -605,6 +645,10 @@ The feature is not ready for production use. You can select multiple issue cards, then drag the group to another position within the list, or to another list. This makes it faster to reorder many issues at once. +Prerequisites: + +- You must have at least the Reporter role for the project. + To select and move multiple cards: 1. Select each card with <kbd>Control</kbd>+`Click` on Windows or Linux, or <kbd>Command</kbd>+`Click` on MacOS. @@ -612,22 +656,6 @@ To select and move multiple cards: ![Multi-select Issue Cards](img/issue_boards_multi_select_v12_4.png) -### First time using an issue board - -> - The automatic creation of the **To Do** and **Doing** lists [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202144) in GitLab 13.5. -> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/270583) in GitLab 13.7. In GitLab 13.7 and later, the **To Do** and **Doing** columns are not automatically created. - -WARNING: -This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/270583) in GitLab 13.7. -The **To Do** and **Doing** columns are no longer automatically created. - -In GitLab 13.5 and 13.6, the first time you open an issue board, you are presented with the default lists -(**Open**, **To Do**, **Doing**, and **Closed**). - -If the **To Do** and **Doing** labels don't exist in the project or group, they are created, and -their lists appear as empty. If any of them already exists, the list is filled with the issues that -have that label. - ## Tips A few things to remember: diff --git a/glfm_specification/example_snapshots/examples_index.yml b/glfm_specification/example_snapshots/examples_index.yml index 3d4efcfbd7a..fd3e350f58a 100644 --- a/glfm_specification/example_snapshots/examples_index.yml +++ b/glfm_specification/example_snapshots/examples_index.yml @@ -2054,21 +2054,33 @@ 07_05_00__gitlab_specific_markdown__video__002: spec_txt_example_position: 687 source_specification: gitlab -08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__001: +07_06_00__gitlab_specific_markdown__table_of_contents__001: spec_txt_example_position: 688 source_specification: gitlab -08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__002: +07_06_00__gitlab_specific_markdown__table_of_contents__002: spec_txt_example_position: 689 source_specification: gitlab -08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__003: +07_06_00__gitlab_specific_markdown__table_of_contents__003: spec_txt_example_position: 690 source_specification: gitlab -08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__004: +07_06_00__gitlab_specific_markdown__table_of_contents__004: spec_txt_example_position: 691 source_specification: gitlab -08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__005: +08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__001: spec_txt_example_position: 692 source_specification: gitlab -08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__006: +08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__002: spec_txt_example_position: 693 source_specification: gitlab +08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__003: + spec_txt_example_position: 694 + source_specification: gitlab +08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__004: + spec_txt_example_position: 695 + source_specification: gitlab +08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__005: + spec_txt_example_position: 696 + source_specification: gitlab +08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__006: + spec_txt_example_position: 697 + source_specification: gitlab diff --git a/glfm_specification/example_snapshots/html.yml b/glfm_specification/example_snapshots/html.yml index 1df9354822a..e683c37a7ac 100644 --- a/glfm_specification/example_snapshots/html.yml +++ b/glfm_specification/example_snapshots/html.yml @@ -7850,6 +7850,83 @@ wysiwyg: |- <pre>[video]: video.mov "video title"</pre> <p><span class="media-container video-container"><video src="video.mov" controls="true" data-setup="{}" data-title="video"></video><a href="video.mov">video</a></span></p> +07_06_00__gitlab_specific_markdown__table_of_contents__001: + canonical: | + <nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + <ul> + <li><a href="#heading-2">Heading 2</a></li> + </ul> + </ul> + </nav> + <h1>Heading 1</h1> + <h2>Heading 2</h2> + static: |- + <ul class="section-nav"><li> + <a href="#heading-1">Heading 1</a><ul><li><a href="#heading-2">Heading 2</a></li></ul> + </li></ul> + <h1 data-sourcepos="3:1-3:11" dir="auto"> + <a id="user-content-heading-1" class="anchor" href="#heading-1" aria-hidden="true"></a>Heading 1</h1> + <h2 data-sourcepos="5:1-5:12" dir="auto"> + <a id="user-content-heading-2" class="anchor" href="#heading-2" aria-hidden="true"></a>Heading 2</h2> + wysiwyg: |- + <div class="table-of-contents gl-border-1 gl-border-solid gl-text-center gl-border-gray-100 gl-mb-5">Table of contents</div> + <h1>Heading 1</h1> + <h2>Heading 2</h2> +07_06_00__gitlab_specific_markdown__table_of_contents__002: + canonical: | + <nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + <ul> + <li><a href="#heading-2">Heading 2</a></li> + </ul> + </ul> + </nav> + <h1>Heading 1</h1> + <h2>Heading 2</h2> + static: |- + <ul class="section-nav"><li> + <a href="#heading-1">Heading 1</a><ul><li><a href="#heading-2">Heading 2</a></li></ul> + </li></ul> + <h1 data-sourcepos="3:1-3:11" dir="auto"> + <a id="user-content-heading-1" class="anchor" href="#heading-1" aria-hidden="true"></a>Heading 1</h1> + <h2 data-sourcepos="5:1-5:12" dir="auto"> + <a id="user-content-heading-2" class="anchor" href="#heading-2" aria-hidden="true"></a>Heading 2</h2> + wysiwyg: |- + <div class="table-of-contents gl-border-1 gl-border-solid gl-text-center gl-border-gray-100 gl-mb-5">Table of contents</div> + <h1>Heading 1</h1> + <h2>Heading 2</h2> +07_06_00__gitlab_specific_markdown__table_of_contents__003: + canonical: | + <p>[[<em>TOC</em>]]text</p> + <p>text[TOC]</p> + static: |- + <p data-sourcepos="1:1-2:4" dir="auto">[[<em>TOC</em>]] + text</p> + <p data-sourcepos="4:1-5:5" dir="auto">text + [TOC]</p> + wysiwyg: |- + <p>[[<em>TOC</em>]] + text</p> + <p>text + [TOC]</p> +07_06_00__gitlab_specific_markdown__table_of_contents__004: + canonical: | + <nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + </ul> + </nav> + <h1>Heading 1</h1> + static: |- + <ul class="section-nav"><li><a href="#heading-1">Heading 1</a></li></ul> + <h1 data-sourcepos="3:1-3:11" dir="auto"> + <a id="user-content-heading-1" class="anchor" href="#heading-1" aria-hidden="true"></a>Heading 1</h1> + wysiwyg: |- + <div class="table-of-contents gl-border-1 gl-border-solid gl-text-center gl-border-gray-100 gl-mb-5">Table of contents</div> + <h1>Heading 1</h1> 08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__001: canonical: | <p><a href="groups-test-file">groups-test-file</a></p> diff --git a/glfm_specification/example_snapshots/markdown.yml b/glfm_specification/example_snapshots/markdown.yml index e928c5a977e..9af824e120f 100644 --- a/glfm_specification/example_snapshots/markdown.yml +++ b/glfm_specification/example_snapshots/markdown.yml @@ -2239,6 +2239,28 @@ [video]: video.mov "video title" ![video][video] +07_06_00__gitlab_specific_markdown__table_of_contents__001: | + [TOC] + + # Heading 1 + + ## Heading 2 +07_06_00__gitlab_specific_markdown__table_of_contents__002: | + [[_TOC_]] + + # Heading 1 + + ## Heading 2 +07_06_00__gitlab_specific_markdown__table_of_contents__003: | + [[_TOC_]] + text + + text + [TOC] +07_06_00__gitlab_specific_markdown__table_of_contents__004: |2 + [[_TOC_]] + + # Heading 1 08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__001: | [groups-test-file](/uploads/groups-test-file) 08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__002: | diff --git a/glfm_specification/example_snapshots/prosemirror_json.yml b/glfm_specification/example_snapshots/prosemirror_json.yml index 7928d50ae96..1051b5ccdd7 100644 --- a/glfm_specification/example_snapshots/prosemirror_json.yml +++ b/glfm_specification/example_snapshots/prosemirror_json.yml @@ -20890,6 +20890,130 @@ } ] } +07_06_00__gitlab_specific_markdown__table_of_contents__001: |- + { + "type": "doc", + "content": [ + { + "type": "tableOfContents" + }, + { + "type": "heading", + "attrs": { + "level": 1 + }, + "content": [ + { + "type": "text", + "text": "Heading 1" + } + ] + }, + { + "type": "heading", + "attrs": { + "level": 2 + }, + "content": [ + { + "type": "text", + "text": "Heading 2" + } + ] + } + ] + } +07_06_00__gitlab_specific_markdown__table_of_contents__002: |- + { + "type": "doc", + "content": [ + { + "type": "tableOfContents" + }, + { + "type": "heading", + "attrs": { + "level": 1 + }, + "content": [ + { + "type": "text", + "text": "Heading 1" + } + ] + }, + { + "type": "heading", + "attrs": { + "level": 2 + }, + "content": [ + { + "type": "text", + "text": "Heading 2" + } + ] + } + ] + } +07_06_00__gitlab_specific_markdown__table_of_contents__003: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "[[" + }, + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "TOC" + }, + { + "type": "text", + "text": "]]\ntext" + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "text\n[TOC]" + } + ] + } + ] + } +07_06_00__gitlab_specific_markdown__table_of_contents__004: |- + { + "type": "doc", + "content": [ + { + "type": "tableOfContents" + }, + { + "type": "heading", + "attrs": { + "level": 1 + }, + "content": [ + { + "type": "text", + "text": "Heading 1" + } + ] + } + ] + } 08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__001: |- Not yet implemented. See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92507#note_1068159236 08_01_00__examples_using_internal_extensions__markdown_preview_api_request_overrides__002: |- diff --git a/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt b/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt index d27f809aaf3..10a46dcca6b 100644 --- a/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt +++ b/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt @@ -209,7 +209,7 @@ GLFM renders image elements as an audio player as long as the resource’s file one of the following supported audio extensions `.mp3`, `.oga`, `.ogg`, `.spx`, and `.wav`. Audio ignore the alternative text part of an image declaration. -```````````````````````````````` example gitlab audio +```````````````````````````````` example gitlab ![audio](audio.oga "audio title") . <p><audio src="audio.oga" title="audio title"></audio></p> @@ -217,7 +217,7 @@ Audio ignore the alternative text part of an image declaration. Reference definitions work audio as well: -```````````````````````````````` example gitlab audio +```````````````````````````````` example gitlab [audio]: audio.oga "audio title" ![audio][audio] @@ -235,7 +235,7 @@ one of the following supported video extensions `.mp4`, `.m4v`, `.mov`, `.webm` Videos ignore the alternative text part of an image declaration. -```````````````````````````````` example gitlab video +```````````````````````````````` example gitlab ![video](video.m4v "video title") . <p><video src="video.m4v" title="video title"></video></p> @@ -243,7 +243,7 @@ Videos ignore the alternative text part of an image declaration. Reference definitions work video as well: -```````````````````````````````` example gitlab video +```````````````````````````````` example gitlab [video]: video.mov "video title" ![video][video] @@ -251,6 +251,82 @@ Reference definitions work video as well: <p><video src="video.mov" title="video title"></video></p> ```````````````````````````````` +## Table of contents + +See +[table of contents](https://docs.gitlab.com/ee/user/markdown.html#table-of-contents) +in the GitLab Flavored Markdown documentation. + +A table of contents is an unordered list that links to subheadings in the document. +Add either the `[[_TOC_]]` or `[TOC]` tag on its own line. + +```````````````````````````````` example gitlab +[TOC] + +# Heading 1 + +## Heading 2 +. +<nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + <ul> + <li><a href="#heading-2">Heading 2</a></li> + </ul> + </ul> +</nav> +<h1>Heading 1</h1> +<h2>Heading 2</h2> +```````````````````````````````` + +```````````````````````````````` example gitlab +[[_TOC_]] + +# Heading 1 + +## Heading 2 +. +<nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + <ul> + <li><a href="#heading-2">Heading 2</a></li> + </ul> + </ul> +</nav> +<h1>Heading 1</h1> +<h2>Heading 2</h2> +```````````````````````````````` + +A table of contents is a block element. It should preceded and followed by a blank +line. + +```````````````````````````````` example gitlab +[[_TOC_]] +text + +text +[TOC] +. +<p>[[<em>TOC</em>]]text</p> +<p>text[TOC]</p> +```````````````````````````````` + +A table of contents can be indented with up to three spaces. + +```````````````````````````````` example gitlab + [[_TOC_]] + +# Heading 1 +. +<nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + </ul> +</nav> +<h1>Heading 1</h1> +```````````````````````````````` + # Examples Using Internal Extensions ## Markdown Preview API Request Overrides diff --git a/glfm_specification/output/spec.txt b/glfm_specification/output/spec.txt index d79a8d6b675..af4eba06758 100644 --- a/glfm_specification/output/spec.txt +++ b/glfm_specification/output/spec.txt @@ -9811,7 +9811,7 @@ GLFM renders image elements as an audio player as long as the resource’s file one of the following supported audio extensions `.mp3`, `.oga`, `.ogg`, `.spx`, and `.wav`. Audio ignore the alternative text part of an image declaration. -```````````````````````````````` example gitlab audio +```````````````````````````````` example gitlab ![audio](audio.oga "audio title") . <p><audio src="audio.oga" title="audio title"></audio></p> @@ -9819,7 +9819,7 @@ Audio ignore the alternative text part of an image declaration. Reference definitions work audio as well: -```````````````````````````````` example gitlab audio +```````````````````````````````` example gitlab [audio]: audio.oga "audio title" ![audio][audio] @@ -9837,7 +9837,7 @@ one of the following supported video extensions `.mp4`, `.m4v`, `.mov`, `.webm` Videos ignore the alternative text part of an image declaration. -```````````````````````````````` example gitlab video +```````````````````````````````` example gitlab ![video](video.m4v "video title") . <p><video src="video.m4v" title="video title"></video></p> @@ -9845,7 +9845,7 @@ Videos ignore the alternative text part of an image declaration. Reference definitions work video as well: -```````````````````````````````` example gitlab video +```````````````````````````````` example gitlab [video]: video.mov "video title" ![video][video] @@ -9853,6 +9853,82 @@ Reference definitions work video as well: <p><video src="video.mov" title="video title"></video></p> ```````````````````````````````` +## Table of contents + +See +[table of contents](https://docs.gitlab.com/ee/user/markdown.html#table-of-contents) +in the GitLab Flavored Markdown documentation. + +A table of contents is an unordered list that links to subheadings in the document. +Add either the `[[_TOC_]]` or `[TOC]` tag on its own line. + +```````````````````````````````` example gitlab +[TOC] + +# Heading 1 + +## Heading 2 +. +<nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + <ul> + <li><a href="#heading-2">Heading 2</a></li> + </ul> + </ul> +</nav> +<h1>Heading 1</h1> +<h2>Heading 2</h2> +```````````````````````````````` + +```````````````````````````````` example gitlab +[[_TOC_]] + +# Heading 1 + +## Heading 2 +. +<nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + <ul> + <li><a href="#heading-2">Heading 2</a></li> + </ul> + </ul> +</nav> +<h1>Heading 1</h1> +<h2>Heading 2</h2> +```````````````````````````````` + +A table of contents is a block element. It should preceded and followed by a blank +line. + +```````````````````````````````` example gitlab +[[_TOC_]] +text + +text +[TOC] +. +<p>[[<em>TOC</em>]]text</p> +<p>text[TOC]</p> +```````````````````````````````` + +A table of contents can be indented with up to three spaces. + +```````````````````````````````` example gitlab + [[_TOC_]] + +# Heading 1 +. +<nav> + <ul> + <li><a href="#heading-1">Heading 1</a></li> + </ul> +</nav> +<h1>Heading 1</h1> +```````````````````````````````` + # Examples Using Internal Extensions ## Markdown Preview API Request Overrides diff --git a/lib/gitlab/background_migration/mailers/unconfirm_mailer.rb b/lib/gitlab/background_migration/mailers/unconfirm_mailer.rb index 3605b157f4f..2bf631c6c7d 100644 --- a/lib/gitlab/background_migration/mailers/unconfirm_mailer.rb +++ b/lib/gitlab/background_migration/mailers/unconfirm_mailer.rb @@ -11,7 +11,7 @@ module Gitlab @user = user @verification_from_mail = Gitlab.config.gitlab.email_from - mail( + mail_with_locale( template_path: 'unconfirm_mailer', template_name: 'unconfirm_notification_email', to: @user.notification_email_or_default, diff --git a/lib/gitlab/ci/variables/helpers.rb b/lib/gitlab/ci/variables/helpers.rb index 7cc727bb3ea..a77ca71b9cf 100644 --- a/lib/gitlab/ci/variables/helpers.rb +++ b/lib/gitlab/ci/variables/helpers.rb @@ -16,7 +16,7 @@ module Gitlab def transform_to_yaml_variables(vars) vars.to_h.map do |key, value| - { key: key.to_s, value: value, public: true } + { key: key.to_s, value: value } end end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index 0a1a61363f1..7bc4be66a18 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -269,14 +269,23 @@ module Gitlab end def consume_find_local_branches_response(response) - response.flat_map do |message| - message.branches.map do |gitaly_branch| - Gitlab::Git::Branch.new( - @repository, - gitaly_branch.name.dup, - gitaly_branch.commit_id, - commit_from_local_branches_response(gitaly_branch) - ) + if Feature.enabled?(:gitaly_simplify_find_local_branches_response, type: :undefined) + response.flat_map do |message| + message.local_branches.map do |branch| + target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit) + Gitlab::Git::Branch.new(@repository, branch.name, branch.target_commit.id, target_commit) + end + end + else + response.flat_map do |message| + message.branches.map do |gitaly_branch| + Gitlab::Git::Branch.new( + @repository, + gitaly_branch.name.dup, + gitaly_branch.commit_id, + commit_from_local_branches_response(gitaly_branch) + ) + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2a269562d9b..621500f4d87 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -72,6 +72,11 @@ msgstr "" msgid "\"%{repository_name}\" size (%{repository_size}) is larger than the limit of %{limit}." msgstr "" +msgid "#%d commit" +msgid_plural "#%d commits" +msgstr[0] "" +msgstr[1] "" + msgid "#%{issueIid} (closed)" msgstr "" @@ -16652,13 +16657,13 @@ msgstr "" msgid "Flags" msgstr "" -msgid "FloC|Configure whether you want to participate in FloC." +msgid "FloC|Configure whether you want to participate in FLoC. %{floc_link_start}What is FLoC?%{floc_link_end}" msgstr "" -msgid "FloC|Enable FloC (Federated Learning of Cohorts)" +msgid "FloC|Federated Learning of Cohorts (FLoC)" msgstr "" -msgid "FloC|Federated Learning of Cohorts" +msgid "FloC|Participate in FLoC" msgstr "" msgid "FlowdockService|Enter your Flowdock token." @@ -26871,6 +26876,9 @@ msgstr "" msgid "Notify|%{commit_link} in %{mr_link}" msgstr "" +msgid "Notify|%{commits_text} from branch `%{target_branch}`" +msgstr "" + msgid "Notify|%{invite_email}, now known as %{user_name}, has accepted your invitation to join the %{target_name} %{target_model_name}." msgstr "" @@ -26886,12 +26894,18 @@ msgstr "" msgid "Notify|%{paragraph_start}Hi %{name}!%{paragraph_end} %{paragraph_start}A new public key was added to your account:%{paragraph_end} %{paragraph_start}title: %{key_title}%{paragraph_end} %{paragraph_start}If this key was added in error, you can remove it under %{removal_link}%{paragraph_end}" msgstr "" +msgid "Notify|%{updated_by_user_name} pushed new commits to merge request %{mr_link}" +msgstr "" + msgid "Notify|A new GPG key was added to your account:" msgstr "" msgid "Notify|All discussions on merge request %{mr_link} were resolved by %{name}" msgstr "" +msgid "Notify|And %{total_stripped_new_commits_count} more" +msgstr "" + msgid "Notify|Assignee changed from %{fromNames} to %{toNames}" msgstr "" @@ -29308,9 +29322,6 @@ msgstr "" msgid "Pipeline|Date" msgstr "" -msgid "Pipeline|Detached merge request pipeline" -msgstr "" - msgid "Pipeline|Failed" msgstr "" @@ -29320,6 +29331,9 @@ msgstr "" msgid "Pipeline|Manual" msgstr "" +msgid "Pipeline|Merge request pipeline" +msgstr "" + msgid "Pipeline|Merge train pipeline" msgstr "" @@ -33983,6 +33997,9 @@ msgstr "" msgid "Runners|Executor" msgstr "" +msgid "Runners|Filter projects" +msgstr "" + msgid "Runners|Get started with runners" msgstr "" diff --git a/package.json b/package.json index 7ae17f15fea..3ffd9b17e25 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "3.3.0", - "@gitlab/ui": "43.13.0", + "@gitlab/ui": "43.14.0", "@gitlab/visual-review-tools": "1.7.3", "@gitlab/web-ide": "0.0.1-dev-20220815034418", "@rails/actioncable": "6.1.4-7", @@ -212,9 +212,10 @@ "cheerio": "^1.0.0-rc.9", "commander": "^2.20.3", "custom-jquery-matchers": "^2.1.0", - "eslint": "8.22.0", + "eslint": "8.23.0", "eslint-import-resolver-jest": "3.0.2", "eslint-import-resolver-webpack": "0.13.2", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-unsanitized": "^4.0.1", "gettext-extractor": "^3.5.3", diff --git a/spec/controllers/admin/cohorts_controller_spec.rb b/spec/controllers/admin/cohorts_controller_spec.rb index d271276a3e4..766073977c6 100644 --- a/spec/controllers/admin/cohorts_controller_spec.rb +++ b/spec/controllers/admin/cohorts_controller_spec.rb @@ -13,5 +13,17 @@ RSpec.describe Admin::CohortsController do it_behaves_like 'tracking unique visits', :index do let(:target_id) { 'i_analytics_cohorts' } end + + it_behaves_like 'Snowplow event tracking' do + subject { get :index } + + let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } + let(:category) { described_class.name } + let(:action) { 'perform_analytics_usage_action' } + let(:label) { 'redis_hll_counters.analytics.analytics_total_unique_counts_monthly' } + let(:property) { 'i_analytics_cohorts' } + let(:namespace) { nil } + let(:project) { nil } + end end end diff --git a/spec/controllers/projects/settings/integration_hook_logs_controller_spec.rb b/spec/controllers/projects/settings/integration_hook_logs_controller_spec.rb index 8261461e8aa..9569fe22b1f 100644 --- a/spec/controllers/projects/settings/integration_hook_logs_controller_spec.rb +++ b/spec/controllers/projects/settings/integration_hook_logs_controller_spec.rb @@ -5,7 +5,8 @@ require 'spec_helper' RSpec.describe Projects::Settings::IntegrationHookLogsController do let(:project) { create(:project, :repository) } let(:user) { create(:user) } - let(:integration) { create(:drone_ci_integration, project: project) } + let(:service_hook) { create(:service_hook) } + let(:integration) { create(:drone_ci_integration, project: project, service_hook: service_hook) } let(:log) { create(:web_hook_log, web_hook: integration.service_hook) } let(:log_params) do { diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 1d3effd4a2a..c2a0e528ea7 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -219,7 +219,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do shared_examples 'pipeline widget' do it 'shows head pipeline information', :sidekiq_might_not_need_inline do within '.ci-widget-content' do - expect(page).to have_content("Detached merge request pipeline ##{pipeline.id} pending for #{pipeline.short_sha}") + expect(page).to have_content("Merge request pipeline ##{pipeline.id} pending for #{pipeline.short_sha}") end end end diff --git a/spec/features/projects/pipelines/legacy_pipeline_spec.rb b/spec/features/projects/pipelines/legacy_pipeline_spec.rb index f36fe1c778c..250a336469c 100644 --- a/spec/features/projects/pipelines/legacy_pipeline_spec.rb +++ b/spec/features/projects/pipelines/legacy_pipeline_spec.rb @@ -73,9 +73,9 @@ RSpec.describe 'Pipeline', :js do visit_pipeline expect(page).to have_selector('.js-pipeline-graph') - expect(page).to have_content('Build') - expect(page).to have_content('Test') - expect(page).to have_content('Deploy') + expect(page).to have_content('build') + expect(page).to have_content('test') + expect(page).to have_content('deploy') expect(page).to have_content('Retry') expect(page).to have_content('Cancel running') end @@ -668,9 +668,9 @@ RSpec.describe 'Pipeline', :js do it 'shows the pipeline graph' do expect(page).to have_selector('.js-pipeline-graph') - expect(page).to have_content('Build') - expect(page).to have_content('Test') - expect(page).to have_content('Deploy') + expect(page).to have_content('build') + expect(page).to have_content('test') + expect(page).to have_content('deploy') expect(page).to have_content('Retry') expect(page).to have_content('Cancel running') end diff --git a/spec/features/projects/pipelines/legacy_pipelines_spec.rb b/spec/features/projects/pipelines/legacy_pipelines_spec.rb index 2b3a6569c56..2e0ea695ab3 100644 --- a/spec/features/projects/pipelines/legacy_pipelines_spec.rb +++ b/spec/features/projects/pipelines/legacy_pipelines_spec.rb @@ -652,10 +652,10 @@ RSpec.describe 'Pipelines', :js do expect(page).to have_link(pipeline.user.name, href: user_path(pipeline.user)) # stages - expect(page).to have_text('Build') - expect(page).to have_text('Test') - expect(page).to have_text('Deploy') - expect(page).to have_text('External') + expect(page).to have_text('build') + expect(page).to have_text('test') + expect(page).to have_text('deploy') + expect(page).to have_text('external') # builds expect(page).to have_text('rspec') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index a8b582ba486..51a6fbc4d36 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -72,9 +72,9 @@ RSpec.describe 'Pipeline', :js do visit_pipeline expect(page).to have_selector('.js-pipeline-graph') - expect(page).to have_content('Build') - expect(page).to have_content('Test') - expect(page).to have_content('Deploy') + expect(page).to have_content('build') + expect(page).to have_content('test') + expect(page).to have_content('deploy') expect(page).to have_content('Retry') expect(page).to have_content('Cancel running') end @@ -793,9 +793,9 @@ RSpec.describe 'Pipeline', :js do it 'shows the pipeline graph' do expect(page).to have_selector('.js-pipeline-graph') - expect(page).to have_content('Build') - expect(page).to have_content('Test') - expect(page).to have_content('Deploy') + expect(page).to have_content('build') + expect(page).to have_content('test') + expect(page).to have_content('deploy') expect(page).to have_content('Retry') expect(page).to have_content('Cancel running') end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index d5705d1da04..404e51048bc 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -635,10 +635,10 @@ RSpec.describe 'Pipelines', :js do expect(page).to have_link(pipeline.user.name, href: user_path(pipeline.user)) # stages - expect(page).to have_text('Build') - expect(page).to have_text('Test') - expect(page).to have_text('Deploy') - expect(page).to have_text('External') + expect(page).to have_text('build') + expect(page).to have_text('test') + expect(page).to have_text('deploy') + expect(page).to have_text('external') # builds expect(page).to have_text('rspec') diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js index 61a6fd0debe..bc43af9bd8b 100644 --- a/spec/frontend/content_editor/remark_markdown_processing_spec.js +++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js @@ -23,6 +23,7 @@ import Sourcemap from '~/content_editor/extensions/sourcemap'; import Strike from '~/content_editor/extensions/strike'; import Table from '~/content_editor/extensions/table'; import TableHeader from '~/content_editor/extensions/table_header'; +import TableOfContents from '~/content_editor/extensions/table_of_contents'; import TableRow from '~/content_editor/extensions/table_row'; import TableCell from '~/content_editor/extensions/table_cell'; import TaskList from '~/content_editor/extensions/task_list'; @@ -61,6 +62,7 @@ const tiptapEditor = createTestEditor({ TableRow, TableHeader, TableCell, + TableOfContents, TaskList, TaskItem, Video, @@ -98,6 +100,7 @@ const { tableRow, tableHeader, tableCell, + tableOfContents, taskItem, taskList, video, @@ -130,6 +133,7 @@ const { tableCell: { nodeType: TableCell.name }, tableHeader: { nodeType: TableHeader.name }, tableRow: { nodeType: TableRow.name }, + tableOfContents: { nodeType: TableOfContents.name }, taskItem: { nodeType: TaskItem.name }, taskList: { nodeType: TaskList.name }, video: { nodeType: Video.name }, @@ -1294,6 +1298,14 @@ content expectedDoc: doc(diagram({ ...source(markdown), language }, 'content')), }; }), + { + markdown: '[[_TOC_]]', + expectedDoc: doc(tableOfContents(source('[[_TOC_]]'))), + }, + { + markdown: '[TOC]', + expectedDoc: doc(tableOfContents(source('[TOC]'))), + }, ]; const runOnly = examples.find((example) => example.only === true); diff --git a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js index fc88c0ea445..bd48b7fdd23 100644 --- a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js +++ b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js @@ -34,6 +34,7 @@ import Table from '~/content_editor/extensions/table'; import TableCell from '~/content_editor/extensions/table_cell'; import TableHeader from '~/content_editor/extensions/table_header'; import TableRow from '~/content_editor/extensions/table_row'; +import TableOfContents from '~/content_editor/extensions/table_of_contents'; import TaskItem from '~/content_editor/extensions/task_item'; import TaskList from '~/content_editor/extensions/task_list'; import Video from '~/content_editor/extensions/video'; @@ -75,6 +76,7 @@ const tiptapEditor = createTestEditor({ TableCell, TableHeader, TableRow, + TableOfContents, TaskItem, TaskList, Video, diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index 34784a88ba9..454b3814d8c 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -299,7 +299,7 @@ describe('Pipeline graph wrapper', () => { const groupsInFirstColumn = mockPipelineResponse.data.project.pipeline.stages.nodes[0].groups.nodes.length; expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn); - expect(getStageColumnTitle().text()).toBe('Build'); + expect(getStageColumnTitle().text()).toBe('build'); await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW); expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn + 1); expect(getStageColumnTitle().text()).toBe(''); diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js index 99e8ea9d0a4..b081258edc0 100644 --- a/spec/frontend/pipelines/graph/stage_column_component_spec.js +++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js @@ -126,9 +126,9 @@ describe('stage column component', () => { }); }); - it('capitalizes and escapes name', () => { - expect(findStageColumnTitle().text()).toBe( - 'Test <img src=x onerror=alert(document.domain)>', + it('escapes name', () => { + expect(findStageColumnTitle().html()).toContain( + 'test <img src=x onerror=alert(document.domain)>', ); }); diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js index c988fb8477d..eca042cae86 100644 --- a/spec/frontend/runner/components/runner_projects_spec.js +++ b/spec/frontend/runner/components/runner_projects_spec.js @@ -1,4 +1,4 @@ -import { GlSkeletonLoader } from '@gitlab/ui'; +import { GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -8,7 +8,9 @@ import { createAlert } from '~/flash'; import { sprintf } from '~/locale'; import { I18N_ASSIGNED_PROJECTS, - I18N_NONE, + I18N_CLEAR_FILTER_PROJECTS, + I18N_FILTER_PROJECTS, + I18N_NO_PROJECTS_FOUND, RUNNER_DETAILS_PROJECTS_PAGE_SIZE, } from '~/runner/constants'; import RunnerProjects from '~/runner/components/runner_projects.vue'; @@ -35,6 +37,7 @@ describe('RunnerProjects', () => { const findHeading = () => wrapper.find('h3'); const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoader); + const findGlSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType); const findRunnerAssignedItems = () => wrapper.findAllComponents(RunnerAssignedItem); const findRunnerPagination = () => wrapper.findComponent(RunnerPagination); @@ -64,10 +67,21 @@ describe('RunnerProjects', () => { expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1); expect(mockRunnerProjectsQuery).toHaveBeenCalledWith({ id: mockRunner.id, + search: '', first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE, }); }); + it('Shows a filter box', () => { + createComponent(); + + expect(findGlSearchBoxByType().attributes()).toMatchObject({ + clearbuttontitle: I18N_CLEAR_FILTER_PROJECTS, + debounce: '500', + placeholder: I18N_FILTER_PROJECTS, + }); + }); + describe('When there are projects assigned', () => { beforeEach(async () => { mockRunnerProjectsQuery.mockResolvedValueOnce(runnerProjectsData); @@ -110,6 +124,7 @@ describe('RunnerProjects', () => { expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(2); expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({ id: mockRunner.id, + search: '', first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE, after: 'AFTER_CURSOR', }); @@ -123,10 +138,51 @@ describe('RunnerProjects', () => { expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(3); expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({ id: mockRunner.id, + search: '', last: RUNNER_DETAILS_PROJECTS_PAGE_SIZE, before: 'BEFORE_CURSOR', }); }); + + it('When user filters after paginating, the first page is requested', async () => { + findGlSearchBoxByType().vm.$emit('input', 'my search'); + await waitForPromises(); + + expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(3); + expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({ + id: mockRunner.id, + search: 'my search', + first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE, + }); + }); + }); + + describe('When user filters', () => { + it('Filtered results are requested', async () => { + expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1); + + findGlSearchBoxByType().vm.$emit('input', 'my search'); + await waitForPromises(); + + expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(2); + expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({ + id: mockRunner.id, + search: 'my search', + first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE, + }); + }); + + it('Filtered results are not requested for short searches', async () => { + expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1); + + findGlSearchBoxByType().vm.$emit('input', 'm'); + await waitForPromises(); + + findGlSearchBoxByType().vm.$emit('input', 'my'); + await waitForPromises(); + + expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1); + }); }); }); @@ -136,10 +192,11 @@ describe('RunnerProjects', () => { expect(findGlSkeletonLoading().exists()).toBe(true); - expect(wrapper.findByText(I18N_NONE).exists()).toBe(false); + expect(wrapper.findByText(I18N_NO_PROJECTS_FOUND).exists()).toBe(false); expect(findRunnerAssignedItems().length).toBe(0); expect(findRunnerPagination().attributes('disabled')).toBe('true'); + expect(findGlSearchBoxByType().props('isLoading')).toBe(true); }); }); @@ -168,7 +225,7 @@ describe('RunnerProjects', () => { }); it('Shows a "None" label', () => { - expect(wrapper.findByText(I18N_NONE).exists()).toBe(true); + expect(wrapper.findByText(I18N_NO_PROJECTS_FOUND).exists()).toBe(true); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js index d9bb60e876a..7f0173b7445 100644 --- a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js @@ -262,13 +262,13 @@ describe('MRWidgetPipeline', () => { }); describe('for a detached merge request pipeline', () => { - it('renders a pipeline widget that reads "Detached merge request pipeline <ID> <status> for <SHA>"', () => { - pipeline.details.name = 'Detached merge request pipeline'; + it('renders a pipeline widget that reads "Merge request pipeline <ID> <status> for <SHA>"', () => { + pipeline.details.name = 'Merge request pipeline'; pipeline.merge_request_event_type = 'detached'; factory(); - const expected = `Detached merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`; + const expected = `Merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`; const actual = trimText(findPipelineInfoContainer().text()); expect(actual).toBe(expected); diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 890ba51157a..b6cb07bf119 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -97,15 +97,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:attributes) do { name: 'rspec', ref: 'master', - job_variables: [{ key: 'VAR1', value: 'var 1', public: true }, - { key: 'VAR2', value: 'var 2', public: true }], + job_variables: [{ key: 'VAR1', value: 'var 1' }, + { key: 'VAR2', value: 'var 2' }], rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] } end it do - is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true }, - { key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }]) + is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' }, + { key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }]) end end @@ -114,13 +114,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do { name: 'rspec', ref: 'master', - job_variables: [{ key: 'VARIABLE', value: 'value', public: true }], + job_variables: [{ key: 'VARIABLE', value: 'value' }], tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE'] } end it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) } - it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value', public: true }]) } + it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) } end context 'with cache:key' do @@ -257,19 +257,19 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:attributes) do { name: 'rspec', ref: 'master', - yaml_variables: [{ key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }], - job_variables: [{ key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }], + yaml_variables: [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }], + job_variables: [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }], root_variables_inheritance: root_variables_inheritance } end context 'when the pipeline has variables' do let(:root_variables) do - [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true }, - { key: 'VAR2', value: 'var pipeline 2', public: true }, - { key: 'VAR3', value: 'var pipeline 3', public: true }, - { key: 'VAR4', value: 'new var pipeline 4', public: true }] + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var pipeline 2' }, + { key: 'VAR3', value: 'var pipeline 3' }, + { key: 'VAR4', value: 'new var pipeline 4' }] end context 'when root_variables_inheritance is true' do @@ -277,10 +277,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns calculated yaml variables' do expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true }, - { key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }, - { key: 'VAR4', value: 'new var pipeline 4', public: true }] + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }, + { key: 'VAR4', value: 'new var pipeline 4' }] ) end end @@ -290,8 +290,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns job variables' do expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }] + [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }] ) end end @@ -301,9 +301,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns calculated yaml variables' do expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true }, - { key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }] + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }] ) end end @@ -314,8 +314,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns seed yaml variables' do expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }]) + [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }]) end end end @@ -324,8 +324,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:attributes) do { name: 'rspec', ref: 'master', - yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true }], - job_variables: [{ key: 'VAR1', value: 'var 1', public: true }], + yaml_variables: [{ key: 'VAR1', value: 'var 1' }], + job_variables: [{ key: 'VAR1', value: 'var 1' }], root_variables_inheritance: root_variables_inheritance, rules: rules } end @@ -338,14 +338,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end it 'recalculates the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true }, - { key: 'VAR2', value: 'new var 2', public: true }) + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' }, + { key: 'VAR2', value: 'new var 2' }) end end context 'when the rules use root variables' do let(:root_variables) do - [{ key: 'VAR2', value: 'var pipeline 2', public: true }] + [{ key: 'VAR2', value: 'var pipeline 2' }] end let(:rules) do @@ -353,15 +353,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end it 'recalculates the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true }, - { key: 'VAR2', value: 'overridden var 2', public: true }) + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' }, + { key: 'VAR2', value: 'overridden var 2' }) end context 'when the root_variables_inheritance is false' do let(:root_variables_inheritance) { false } it 'does not recalculate the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1', public: true }) + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' }) end end end diff --git a/spec/lib/gitlab/ci/variables/helpers_spec.rb b/spec/lib/gitlab/ci/variables/helpers_spec.rb index fc1055751bd..ece456ee2d3 100644 --- a/spec/lib/gitlab/ci/variables/helpers_spec.rb +++ b/spec/lib/gitlab/ci/variables/helpers_spec.rb @@ -15,9 +15,9 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do end let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value22', public: true }, - { key: 'key3', value: 'value3', public: true }] + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] end subject { described_class.merge_variables(current_variables, new_variables) } @@ -43,8 +43,8 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do context 'when new variables is nil' do let(:new_variables) {} let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value2', public: true }] + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }] end it { is_expected.to eq(result) } @@ -57,8 +57,8 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do end let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value2', public: true }] + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }] end subject { described_class.transform_to_yaml_variables(variables) } @@ -74,8 +74,8 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do describe '.transform_from_yaml_variables' do let(:variables) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value2', public: true }] + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }] end let(:result) do @@ -127,9 +127,9 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do let(:inheritance) { true } let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value22', public: true }, - { key: 'key3', value: 'value3', public: true }] + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] end subject { described_class.inherit_yaml_variables(from: from, to: to, inheritance: inheritance) } @@ -140,8 +140,8 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do let(:inheritance) { false } let(:result) do - [{ key: 'key2', value: 'value22', public: true }, - { key: 'key3', value: 'value3', public: true }] + [{ key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] end it { is_expected.to eq(result) } @@ -151,8 +151,8 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do let(:inheritance) { ['key2'] } let(:result) do - [{ key: 'key2', value: 'value22', public: true }, - { key: 'key3', value: 'value3', public: true }] + [{ key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] end it { is_expected.to eq(result) } diff --git a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb index 8416501e949..f7a0905d9da 100644 --- a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb @@ -72,8 +72,8 @@ module Gitlab it 'returns calculated variables with root and job variables' do is_expected.to match_array([ - { key: 'VAR1', value: 'value 11', public: true }, - { key: 'VAR2', value: 'value 2', public: true } + { key: 'VAR1', value: 'value 11' }, + { key: 'VAR2', value: 'value 2' } ]) end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index b68b12d8f43..cc327f5b5f1 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -448,7 +448,7 @@ module Gitlab it 'parses the root:variables as #root_variables' do expect(subject.root_variables) - .to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true }) + .to contain_exactly({ key: 'SUPPORTED', value: 'parsed' }) end end @@ -490,7 +490,7 @@ module Gitlab it 'parses the root:variables as #root_variables' do expect(subject.root_variables) - .to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true }) + .to contain_exactly({ key: 'SUPPORTED', value: 'parsed' }) end end @@ -1077,8 +1077,8 @@ module Gitlab it 'returns job variables' do expect(job_variables).to contain_exactly( - { key: 'VAR1', value: 'value1', public: true }, - { key: 'VAR2', value: 'value2', public: true } + { key: 'VAR1', value: 'value1' }, + { key: 'VAR2', value: 'value2' } ) expect(root_variables_inheritance).to eq(true) end @@ -1154,9 +1154,9 @@ module Gitlab it 'returns job variables' do expect(job_variables).to contain_exactly( - { key: 'VAR1', value: 'value1', public: true }, - { key: 'VAR2', value: 'value2', public: true }, - { key: 'VAR3', value: '123', public: true } + { key: 'VAR1', value: 'value1' }, + { key: 'VAR2', value: 'value2' }, + { key: 'VAR3', value: '123' } ) expect(root_variables_inheritance).to eq(true) end @@ -1232,21 +1232,21 @@ module Gitlab expect(config_processor.builds[0]).to include( name: 'test1', options: { script: ['test'] }, - job_variables: [{ key: 'VAR1', value: 'test1 var 1', public: true }, - { key: 'VAR2', value: 'test2 var 2', public: true }] + job_variables: [{ key: 'VAR1', value: 'test1 var 1' }, + { key: 'VAR2', value: 'test2 var 2' }] ) expect(config_processor.builds[1]).to include( name: 'test2', options: { script: ['test'] }, - job_variables: [{ key: 'VAR1', value: 'base var 1', public: true }, - { key: 'VAR2', value: 'test2 var 2', public: true }] + job_variables: [{ key: 'VAR1', value: 'base var 1' }, + { key: 'VAR2', value: 'test2 var 2' }] ) expect(config_processor.builds[2]).to include( name: 'test3', options: { script: ['test'] }, - job_variables: [{ key: 'VAR1', value: 'base var 1', public: true }] + job_variables: [{ key: 'VAR1', value: 'base var 1' }] ) expect(config_processor.builds[3]).to include( diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 277276bb1d3..bca549b9574 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -156,35 +156,84 @@ RSpec.describe Gitlab::GitalyClient::RefService do end describe '#local_branches' do - it 'sends a find_local_branches message' do - expect_any_instance_of(Gitaly::RefService::Stub) - .to receive(:find_local_branches) - .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) - .and_return([]) + let(:remote_name) { 'my_remote' } - client.local_branches - end + shared_examples 'common examples' do + it 'sends a find_local_branches message' do + target_commits = create_list(:gitaly_commit, 4) + branches = target_commits.each_with_index.map do |gitaly_commit, i| + Gitaly::FindLocalBranchResponse.new( + name: "#{remote_name}/#{i}", + commit: gitaly_commit, + commit_author: Gitaly::FindLocalBranchCommitAuthor.new( + name: gitaly_commit.author.name, + email: gitaly_commit.author.email, + date: gitaly_commit.author.date, + timezone: gitaly_commit.author.timezone + ), + commit_committer: Gitaly::FindLocalBranchCommitAuthor.new( + name: gitaly_commit.committer.name, + email: gitaly_commit.committer.email, + date: gitaly_commit.committer.date, + timezone: gitaly_commit.committer.timezone + ) + ) + end + local_branches = target_commits.each_with_index.map do |gitaly_commit, i| + Gitaly::Branch.new(name: "#{remote_name}/#{i}", target_commit: gitaly_commit) + end + response = [ + Gitaly::FindLocalBranchesResponse.new(branches: branches[0, 2], local_branches: local_branches[0, 2]), + Gitaly::FindLocalBranchesResponse.new(branches: branches[2, 2], local_branches: local_branches[2, 2]) + ] - it 'parses and sends the sort parameter' do - expect_any_instance_of(Gitaly::RefService::Stub) - .to receive(:find_local_branches) - .with(gitaly_request_with_params(sort_by: :UPDATED_DESC), kind_of(Hash)) - .and_return([]) + expect_any_instance_of(Gitaly::RefService::Stub) + .to receive(:find_local_branches) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(response) + + subject = client.local_branches - client.local_branches(sort_by: 'updated_desc') + expect(subject.length).to be(target_commits.length) + end + + it 'parses and sends the sort parameter' do + expect_any_instance_of(Gitaly::RefService::Stub) + .to receive(:find_local_branches) + .with(gitaly_request_with_params(sort_by: :UPDATED_DESC), kind_of(Hash)) + .and_return([]) + + client.local_branches(sort_by: 'updated_desc') + end + + it 'translates known mismatches on sort param values' do + expect_any_instance_of(Gitaly::RefService::Stub) + .to receive(:find_local_branches) + .with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash)) + .and_return([]) + + client.local_branches(sort_by: 'name_asc') + end + + it 'raises an argument error if an invalid sort_by parameter is passed' do + expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError) + end end - it 'translates known mismatches on sort param values' do - expect_any_instance_of(Gitaly::RefService::Stub) - .to receive(:find_local_branches) - .with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash)) - .and_return([]) + context 'when feature flag :gitaly_simplify_find_local_branches_response is enabled' do + before do + stub_feature_flags(gitaly_simplify_find_local_branches_response: true) + end - client.local_branches(sort_by: 'name_asc') + it_behaves_like 'common examples' end - it 'raises an argument error if an invalid sort_by parameter is passed' do - expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError) + context 'when feature flag :gitaly_simplify_find_local_branches_response is disabled' do + before do + stub_feature_flags(gitaly_simplify_find_local_branches_response: false) + end + + it_behaves_like 'common examples' end end diff --git a/spec/lib/gitlab/web_hooks/rate_limiter_spec.rb b/spec/lib/gitlab/web_hooks/rate_limiter_spec.rb index b25ce4ea9da..3a5864e1832 100644 --- a/spec/lib/gitlab/web_hooks/rate_limiter_spec.rb +++ b/spec/lib/gitlab/web_hooks/rate_limiter_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::WebHooks::RateLimiter, :clean_gitlab_redis_rate_limiting let_it_be(:plan) { create(:default_plan) } let_it_be_with_reload(:project_hook) { create(:project_hook) } let_it_be_with_reload(:system_hook) { create(:system_hook) } - let_it_be_with_reload(:integration_hook) { create(:jenkins_integration).service_hook } + let_it_be_with_reload(:integration_hook) { create(:service_hook) } let_it_be(:limit) { 1 } using RSpec::Parameterized::TableSyntax diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 8beb54bca4d..1f53c472c5c 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -167,6 +167,17 @@ RSpec.describe Notify do is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end end + + context 'when sent with a non default locale' do + let(:email_obj) { create(:email, :confirmed, user_id: recipient.id, email: '123@abc') } + let(:recipient) { create(:user, preferred_language: :zh_CN) } + + it 'is translated into zh_CN' do + recipient.notification_email = email_obj.email + recipient.save! + is_expected.to have_body_text '指派人从 <strong>Previous Assignee</strong> 更改为 <strong>John Doe</strong>' + end + end end describe 'that have been relabeled' do diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb index 8b1bc33d8be..5edd9c2d023 100644 --- a/spec/mailers/repository_check_mailer_spec.rb +++ b/spec/mailers/repository_check_mailer_spec.rb @@ -14,6 +14,15 @@ RSpec.describe RepositoryCheckMailer do expect(mail).to deliver_to admins.map(&:email) end + it 'email with I18n.default_locale' do + admins = [create(:admin, preferred_language: :zh_CN), create(:admin, preferred_language: :zh_CN)] + + mail = described_class.notify(3) + + expect(mail).to deliver_to admins.map(&:email) + expect(mail).to have_subject 'GitLab Admin | 3 projects failed their last repository check' + end + it 'omits blocked admins' do blocked = create(:admin, :blocked) admins = create_list(:admin, 3) diff --git a/spec/models/integrations/drone_ci_spec.rb b/spec/models/integrations/drone_ci_spec.rb index 905fee075ad..c43d969eec8 100644 --- a/spec/models/integrations/drone_ci_spec.rb +++ b/spec/models/integrations/drone_ci_spec.rb @@ -115,6 +115,7 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do it_behaves_like Integrations::HasWebHook do include_context :drone_ci_integration + let(:drone_url) { 'https://cloud.drone.io' } let(:integration) { drone } let(:hook_url) { "#{drone_url}/hook?owner=#{project.namespace.full_path}&name=#{project.path}&access_token=#{token}" } diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb index a278d4dad83..4539c3d06f6 100644 --- a/spec/presenters/ci/pipeline_presenter_spec.rb +++ b/spec/presenters/ci/pipeline_presenter_spec.rb @@ -100,7 +100,7 @@ RSpec.describe Ci::PipelinePresenter do context 'for a detached merge request pipeline' do let(:event_type) { :detached } - it { is_expected.to eq('Detached merge request pipeline') } + it { is_expected.to eq('Merge request pipeline') } end context 'for a merged result pipeline' do diff --git a/spec/services/ci/create_downstream_pipeline_service_spec.rb b/spec/services/ci/create_downstream_pipeline_service_spec.rb index c990d195e51..e2c42ab04d0 100644 --- a/spec/services/ci/create_downstream_pipeline_service_spec.rb +++ b/spec/services/ci/create_downstream_pipeline_service_spec.rb @@ -5,9 +5,12 @@ require 'spec_helper' RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do include Ci::SourcePipelineHelpers - let_it_be(:user) { create(:user) } + # Using let_it_be on user and projects for these specs can cause + # spec-ordering failures due to the project-based permissions + # associating them. They should be recreated every time. + let(:user) { create(:user) } let(:upstream_project) { create(:project, :repository) } - let_it_be(:downstream_project, refind: true) { create(:project, :repository) } + let(:downstream_project) { create(:project, :repository) } let!(:upstream_pipeline) do create(:ci_pipeline, :running, project: upstream_project) diff --git a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb index 49adaf5ef53..513cbbed6cd 100644 --- a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb +++ b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Ci::CreatePipelineService, '#execute', :yaml_processor_feature_fl expect(pipeline.statuses).to match_array [test, bridge] expect(bridge.options).to eq(expected_bridge_options) expect(bridge.yaml_variables) - .to include(key: 'CROSS', value: 'downstream', public: true) + .to include(key: 'CROSS', value: 'downstream') end end diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb index 2f693edeb53..64b2dcb8d36 100644 --- a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb +++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb @@ -7,30 +7,6 @@ RSpec.shared_examples Integrations::HasWebHook do it { is_expected.to have_one(:service_hook).inverse_of(:integration).with_foreign_key(:service_id) } end - describe 'callbacks' do - it 'calls #update_web_hook! when enabled' do - expect(integration).to receive(:update_web_hook!) - - integration.active = true - integration.save! - end - - it 'does not call #update_web_hook! when disabled' do - expect(integration).not_to receive(:update_web_hook!) - - integration.active = false - integration.save! - end - - it 'does not call #update_web_hook! when validation fails' do - expect(integration).not_to receive(:update_web_hook!) - - integration.active = true - integration.project = nil - expect(integration.save).to be(false) - end - end - describe '#hook_url' do it 'returns a string' do expect(integration.hook_url).to be_a(String) diff --git a/yarn.lock b/yarn.lock index 0aa2aaef360..c449af70a4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1007,14 +1007,14 @@ ts-node "^9" tslib "^2" -"@eslint/eslintrc@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" - integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== +"@eslint/eslintrc@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.1.tgz#de0807bfeffc37b964a7d0400e0c348ce5a2543d" + integrity sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.3.2" + espree "^9.4.0" globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1064,10 +1064,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.3.0.tgz#99b044484fcf3d5a6431281e320e2405540ff5a9" integrity sha512-S8Hqf+ms8aNrSgmci9SVoIyj/0qQnizU5uV5vUPAOwiufMDFDyI5qfcgn4EYZ6mnju3LiO+ReSL/PPTD4qNgHA== -"@gitlab/ui@43.13.0": - version "43.13.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.13.0.tgz#7e4e7d41287cfba8a46dbdd3c8ba998a853a9ad2" - integrity sha512-y0BrVKsqRBEQMrsJseakBeMrFHVMTg7DVMa3tbdkKkrruV8SYOsX8wLrv20taDhiMlceKRB8lF5gLTPPHLwCGA== +"@gitlab/ui@43.14.0": + version "43.14.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.14.0.tgz#3f68f73977ad1e5403326af3dc9f6569f527c06b" + integrity sha512-tUqJ4c77m0rr3py9yEJG3+kANzs0Kao03Yi/7aaNsYMjzYBtt5V0fm0iQrv9plUl+vli2k+TFNYqf5MVxu6XZA== dependencies: "@popperjs/core" "^2.11.2" bootstrap-vue "2.20.1" @@ -1277,6 +1277,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d" integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" @@ -5289,14 +5294,15 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.22.0: - version "8.22.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.22.0.tgz#78fcb044196dfa7eef30a9d65944f6f980402c48" - integrity sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA== +eslint@8.23.0: + version "8.23.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.0.tgz#a184918d288820179c6041bb3ddcc99ce6eea040" + integrity sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA== dependencies: - "@eslint/eslintrc" "^1.3.0" + "@eslint/eslintrc" "^1.3.1" "@humanwhocodes/config-array" "^0.10.4" "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" + "@humanwhocodes/module-importer" "^1.0.1" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -5306,7 +5312,7 @@ eslint@8.22.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.3" + espree "^9.4.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -5332,12 +5338,11 @@ eslint@8.22.0: strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^9.3.1, espree@^9.3.2, espree@^9.3.3: - version "9.3.3" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d" - integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng== +espree@^9.3.1, espree@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" + integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" @@ -11808,7 +11813,7 @@ uvu@^0.5.0: kleur "^4.0.3" sade "^1.7.3" -v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0: +v8-compile-cache@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== |