Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-10-13 09:11:55 +00:00
parent 56a177ed56
commit bc2f7ab125
72 changed files with 1033 additions and 127 deletions

View File

@ -34,10 +34,6 @@ brakeman-sast:
rules: !reference [".reports:rules:brakeman-sast", rules]
allow_failure: true
nodejs-scan-sast:
rules: !reference [".reports:rules:nodejs-scan-sast", rules]
allow_failure: true
semgrep-sast:
rules: !reference [".reports:rules:semgrep-sast", rules]
allow_failure: true

View File

@ -1265,15 +1265,6 @@
- '**/*.rb'
- '**/Gemfile'
.reports:rules:nodejs-scan-sast:
rules:
- if: $SAST_DISABLED
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /nodejs-scan/
when: never
- changes:
- '**/package.json'
.reports:rules:gosec-sast:
rules:
- if: $SAST_DISABLED

View File

@ -1 +1 @@
14.3.3
14.4.0

View File

@ -0,0 +1,8 @@
import $ from 'jquery';
import GLForm from '~/gl_form';
import initFilePickers from '~/file_pickers';
import ZenMode from '~/zen_mode';
new GLForm($('.js-project-topic-form')); // eslint-disable-line no-new
initFilePickers();
new ZenMode(); // eslint-disable-line no-new

View File

@ -0,0 +1,8 @@
import $ from 'jquery';
import GLForm from '~/gl_form';
import initFilePickers from '~/file_pickers';
import ZenMode from '~/zen_mode';
new GLForm($('.js-project-topic-form')); // eslint-disable-line no-new
initFilePickers();
new ZenMode(); // eslint-disable-line no-new

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class Admin::Topics::AvatarsController < Admin::ApplicationController
feature_category :projects
def destroy
@topic = Projects::Topic.find(params[:topic_id])
@topic.remove_avatar!
@topic.save
redirect_to edit_admin_topic_path(@topic), status: :found
end
end

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
class Admin::TopicsController < Admin::ApplicationController
include SendFileUpload
include PreviewMarkdown
before_action :topic, only: [:edit, :update]
feature_category :projects
def index
@topics = Projects::TopicsFinder.new(params: params.permit(:search)).execute.page(params[:page]).without_count
end
def new
@topic = Projects::Topic.new
end
def edit
end
def create
@topic = Projects::Topic.new(topic_params)
if @topic.save
redirect_to edit_admin_topic_path(@topic), notice: _('Topic %{topic_name} was successfully created.') % { topic_name: @topic.name }
else
render "new"
end
end
def update
if @topic.update(topic_params)
redirect_to edit_admin_topic_path(@topic), notice: _('Topic was successfully updated.')
else
render "edit"
end
end
private
def topic
@topic ||= Projects::Topic.find(params[:id])
end
def topic_params
params.require(:projects_topic).permit(allowed_topic_params)
end
def allowed_topic_params
[
:avatar,
:description,
:name
]
end
end

View File

@ -13,6 +13,7 @@ class UploadsController < ApplicationController
"group" => Group,
"appearance" => Appearance,
"personal_snippet" => PersonalSnippet,
"projects/topic" => Projects::Topic,
nil => PersonalSnippet
}.freeze
@ -54,6 +55,8 @@ class UploadsController < ApplicationController
!secret? || can?(current_user, :update_user, model)
when Appearance
true
when Projects::Topic
true
else
permission = "read_#{model.class.underscore}".to_sym
@ -85,7 +88,7 @@ class UploadsController < ApplicationController
def cache_settings
case model
when User, Appearance
when User, Appearance, Projects::Topic
[5.minutes, { public: true, must_revalidate: false }]
when Project, Group
[5.minutes, { private: true, must_revalidate: true }]

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
# Used to filter project topics by a set of params
#
# Arguments:
# params:
# search: string
module Projects
class TopicsFinder
def initialize(params: {})
@params = params
end
def execute
topics = Projects::Topic.order_by_total_projects_count
by_search(topics)
end
private
attr_reader :current_user, :params
def by_search(topics)
return topics unless params[:search].present?
topics.search(params[:search]).reorder_by_similarity(params[:search])
end
end
end

View File

@ -9,6 +9,10 @@ module AvatarsHelper
source_icon(group, options)
end
def topic_icon(topic, options = {})
source_icon(topic, options)
end
# Takes both user and email and returns the avatar_icon by
# user (preferred) or email.
def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true)

View File

@ -7,7 +7,6 @@ module Ci
self.limit_name = 'ci_registered_group_runners'
self.limit_scope = :group
self.limit_relation = :recent_runners
self.limit_feature_flag = :ci_runner_limits
self.limit_feature_flag_for_override = :ci_runner_limits_override
belongs_to :runner, inverse_of: :runner_namespaces

View File

@ -7,7 +7,6 @@ module Ci
self.limit_name = 'ci_registered_project_runners'
self.limit_scope = :project
self.limit_relation = :recent_runners
self.limit_feature_flag = :ci_runner_limits
self.limit_feature_flag_for_override = :ci_runner_limits_override
belongs_to :runner, inverse_of: :runner_projects

View File

@ -18,6 +18,7 @@ module Avatarable
prepend ShadowMethods
include ObjectStorage::BackgroundMove
include Gitlab::Utils::StrongMemoize
include ApplicationHelper
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validates :avatar, file_size: { maximum: MAXIMUM_FILE_SIZE }, if: :avatar_changed?

View File

@ -3,6 +3,6 @@
module Projects
class ProjectTopic < ApplicationRecord
belongs_to :project
belongs_to :topic
belongs_to :topic, counter_cache: :total_projects_count
end
end

View File

@ -1,13 +1,30 @@
# frozen_string_literal: true
require 'carrierwave/orm/activerecord'
module Projects
class Topic < ApplicationRecord
include Avatarable
include Gitlab::SQL::Pattern
validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
validates :description, length: { maximum: 1024 }
has_many :project_topics, class_name: 'Projects::ProjectTopic'
has_many :projects, through: :project_topics
scope :order_by_total_projects_count, -> { order(total_projects_count: :desc).order(id: :asc) }
scope :reorder_by_similarity, -> (search) do
order_expression = Gitlab::Database::SimilarityScore.build_expression(search: search, rules: [
{ column: arel_table['name'] }
])
reorder(order_expression.desc, arel_table['total_projects_count'].desc, arel_table['id'])
end
class << self
def search(query)
fuzzy_search(query, [:name])
end
end
end
end

View File

@ -0,0 +1,40 @@
= gitlab_ui_form_for @topic, url: url, html: { multipart: true, class: 'js-project-topic-form gl-show-field-errors common-note-form js-quick-submit js-requires-input' }, authenticity_token: true do |f|
= form_errors(@topic)
.form-group
= f.label :name do
= _("Topic name")
= f.text_field :name, placeholder: _('My topic'), class: 'form-control input-lg', data: { qa_selector: 'topic_name_field' },
required: true,
title: _('Please fill in a name for your topic.'),
autofocus: true
.form-group
= f.label :description, _("Description")
= render layout: 'shared/md_preview', locals: { url: preview_markdown_admin_topics_path, referenced_users: false } do
= render 'shared/zen', f: f, attr: :description,
classes: 'note-textarea',
placeholder: _('Write a description…'),
supports_quick_actions: false,
supports_autocomplete: false,
qa_selector: 'topic_form_description'
= render 'shared/notes/hints', supports_file_upload: false
.form-group.gl-mt-3.gl-mb-3
= f.label :avatar, _('Topic avatar'), class: 'gl-display-block'
- if @topic.avatar?
.avatar-container.rect-avatar.s90
= topic_icon(@topic, alt: _('Topic avatar'), class: 'avatar topic-avatar s90')
= render 'shared/choose_avatar_button', f: f
- if @topic.avatar?
= link_to _('Remove avatar'), admin_topic_avatar_path(@topic), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'gl-button btn btn-danger-secondary gl-mt-2'
- if @topic.new_record?
.form-actions
= f.submit _('Create topic'), class: "gl-button btn btn-confirm"
= link_to _('Cancel'), admin_topics_path, class: "gl-button btn btn-default btn-cancel"
- else
.form-actions
= f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
= link_to _('Cancel'), admin_topics_path, class: "gl-button btn btn-cancel"

View File

@ -0,0 +1,17 @@
- topic = local_assigns.fetch(:topic)
%li.topic-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'topic_row_content' } }
.avatar-container.rect-avatar.s40.gl-flex-shrink-0
= topic_icon(topic, class: "avatar s40")
.gl-min-w-0.gl-flex-grow-1
.title
= topic.name
.stats.gl-text-gray-500.gl-flex-shrink-0.gl-display-none.gl-sm-display-flex
%span.gl-ml-5.has-tooltip{ title: n_('%d project', '%d projects', topic.total_projects_count) % topic.total_projects_count }
= sprite_icon('bookmark', css_class: 'gl-vertical-align-text-bottom')
= number_with_delimiter(topic.total_projects_count)
.controls.gl-flex-shrink-0.gl-ml-5
= link_to _('Edit'), edit_admin_topic_path(topic), id: "edit_#{dom_id(topic)}", class: 'btn gl-button btn-default'

View File

@ -0,0 +1,4 @@
- page_title _("Edit"), @topic.name, _("Topics")
%h3.page-title= _('Edit topic: %{topic_name}') % { topic_name: @topic.name }
%hr
= render 'form', url: admin_topic_path(@topic)

View File

@ -0,0 +1,19 @@
- page_title _("Topics")
= form_tag admin_topics_path, method: :get do |f|
.gl-py-3.gl-display-flex.gl-flex-direction-column-reverse.gl-md-flex-direction-row.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
.gl-flex-grow-1.gl-mt-3.gl-md-mt-0
.inline.gl-w-full.gl-md-w-auto
- search = params.fetch(:search, nil)
.search-field-holder
= search_field_tag :search, search, class: "form-control gl-form-input search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: _('Search by name'), data: { qa_selector: 'topic_search_field' }
= sprite_icon('search', css_class: 'search-icon')
.nav-controls
= link_to new_admin_topic_path, class: "gl-button btn btn-confirm gl-w-full gl-md-w-auto" do
= _('New topic')
%ul.content-list
= render partial: 'topic', collection: @topics
= paginate_collection @topics
- if @topics.empty?
= render 'shared/empty_states/topics'

View File

@ -0,0 +1,4 @@
- page_title _("New topic")
%h3.page-title= _('New topic')
%hr
= render 'form', url: admin_topics_path(@topic)

View File

@ -7,7 +7,7 @@
%span.sidebar-context-title
= _('Admin Area')
%ul.sidebar-top-level-items{ data: { qa_selector: 'admin_sidebar_overview_submenu_content' } }
= nav_link(controller: %w(dashboard admin admin/projects users groups jobs runners gitaly_servers cohorts), html_options: {class: 'home'}) do
= nav_link(controller: %w(dashboard admin admin/projects users groups admin/topics jobs runners gitaly_servers cohorts), html_options: {class: 'home'}) do
= link_to admin_root_path, class: 'has-sub-items' do
.nav-icon-container
= sprite_icon('overview')
@ -35,6 +35,10 @@
= link_to admin_groups_path, title: _('Groups'), data: { qa_selector: 'groups_overview_link' } do
%span
= _('Groups')
= nav_link(controller: [:admin, 'admin/topics']) do
= link_to admin_topics_path, title: _('Topics'), data: { qa_selector: 'topics_overview_link' } do
%span
= _('Topics')
= nav_link path: 'jobs#index' do
= link_to admin_jobs_path, title: _('Jobs') do
%span

View File

@ -2,7 +2,7 @@
- @no_breadcrumb_container = true
- @no_container = true
- @content_wrapper_class = "#{@content_wrapper_class} gl-relative"
- @content_class = "issue-boards-content js-focus-mode-board"
- @content_class = "js-focus-mode-board"
- is_epic_board = board.to_type == "EpicBoard"
- if is_epic_board
- breadcrumb_title _("Epic Boards")

View File

@ -0,0 +1,7 @@
.row.empty-state
.col-12
.svg-content
= image_tag 'illustrations/labels.svg', data: { qa_selector: 'svg_content' }
.text-content.gl-text-center.gl-pt-0!
%h4= _('There are no topics to show.')
%p= _('Add topics to projects to help users find them.')

View File

@ -1,4 +1,5 @@
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
- supports_file_upload = local_assigns.fetch(:supports_file_upload, true)
.comment-toolbar.clearfix
.toolbar-text
= link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank'
@ -10,33 +11,34 @@
is
supported
%span.uploading-container
%span.uploading-progress-container.hide
= sprite_icon('media', css_class: 'gl-icon gl-vertical-align-text-bottom')
%span.attaching-file-message
-# Populated by app/assets/javascripts/dropzone_input.js
%span.uploading-progress 0%
= loading_icon(css_class: 'align-text-bottom gl-mr-2')
%span.uploading-error-container.hide
%span.uploading-error-icon
- if supports_file_upload
%span.uploading-container
%span.uploading-progress-container.hide
= sprite_icon('media', css_class: 'gl-icon gl-vertical-align-text-bottom')
%span.uploading-error-message
-# Populated by app/assets/javascripts/dropzone_input.js
%button.btn.gl-button.btn-link.gl-vertical-align-baseline.retry-uploading-link
%span.gl-button-text
= _("Try again")
= _("or")
%button.btn.gl-button.btn-link.attach-new-file.markdown-selector.gl-vertical-align-baseline
%span.gl-button-text
= _("attach a new file")
= _(".")
%span.attaching-file-message
-# Populated by app/assets/javascripts/dropzone_input.js
%span.uploading-progress 0%
= loading_icon(css_class: 'align-text-bottom gl-mr-2')
%button.btn.gl-button.btn-link.button-attach-file.markdown-selector.button-attach-file.gl-vertical-align-text-bottom
= sprite_icon('media')
%span.gl-button-text
= _("Attach a file")
%span.uploading-error-container.hide
%span.uploading-error-icon
= sprite_icon('media', css_class: 'gl-icon gl-vertical-align-text-bottom')
%span.uploading-error-message
-# Populated by app/assets/javascripts/dropzone_input.js
%button.btn.gl-button.btn-link.gl-vertical-align-baseline.retry-uploading-link
%span.gl-button-text
= _("Try again")
= _("or")
%button.btn.gl-button.btn-link.attach-new-file.markdown-selector.gl-vertical-align-baseline
%span.gl-button-text
= _("attach a new file")
= _(".")
%button.btn.gl-button.btn-link.button-cancel-uploading-files.gl-vertical-align-baseline.hide
%span.gl-button-text
= _("Cancel")
%button.btn.gl-button.btn-link.button-attach-file.markdown-selector.button-attach-file.gl-vertical-align-text-bottom
= sprite_icon('media')
%span.gl-button-text
= _("Attach a file")
%button.btn.gl-button.btn-link.button-cancel-uploading-files.gl-vertical-align-baseline.hide
%span.gl-button-text
= _("Cancel")

View File

@ -0,0 +1,7 @@
= form_tag page_filter_path, method: :get, class: "topic-filter-form js-topic-filter-form", id: 'topic-filter-form' do |f|
= search_field_tag :search, params[:search],
placeholder: s_('Filter by name'),
class: 'topic-filter-form-field form-control input-short',
spellcheck: false,
id: 'topic-filter-form-field',
autofocus: local_assigns[:autofocus]

View File

@ -1,8 +0,0 @@
---
name: ci_runner_limits
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60157
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329438
milestone: '13.12'
type: development
group: group::runner
default_enabled: false

View File

@ -7,7 +7,8 @@ product_stage: package
product_group: group::package
product_category: container registry
value_type: number
status: deprecated
status: removed
milestone_removed: "14.4"
time_frame: all
data_source: redis
distribution:

View File

@ -61,6 +61,13 @@ namespace :admin do
end
end
resources :topics, only: [:index, :new, :create, :edit, :update] do
resource :avatar, controller: 'topics/avatars', only: [:destroy]
collection do
post :preview_markdown
end
end
resources :deploy_keys, only: [:index, :new, :create, :edit, :update, :destroy]
resources :hooks, only: [:index, :create, :edit, :update, :destroy] do

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true
scope path: :uploads do
# Note attachments and User/Group/Project avatars
# Note attachments and User/Group/Project/Topic avatars
get "-/system/:model/:mounted_as/:id/:filename",
to: "uploads#show",
constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: %r{[^/]+} }
constraints: { model: %r{note|user|group|project|projects\/topic}, mounted_as: /avatar|attachment/, filename: %r{[^/]+} }
# show uploads for models, snippets (notes) available for now
get '-/system/:model/:id/:secret/:filename',

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddTopicsNameGinIndex < Gitlab::Database::Migration[1.0]
INDEX_NAME = 'index_topics_on_name_trigram'
disable_ddl_transaction!
def up
add_concurrent_index :topics, :name, name: INDEX_NAME, using: :gin, opclass: { name: :gin_trgm_ops }
end
def down
remove_concurrent_index_by_name :topics, INDEX_NAME
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddTopicsTotalProjectsCountCache < Gitlab::Database::Migration[1.0]
def up
add_column :topics, :total_projects_count, :bigint, null: false, default: 0
end
def down
remove_column :topics, :total_projects_count
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddTopicsTotalProjectsCountIndex < Gitlab::Database::Migration[1.0]
INDEX_NAME = 'index_topics_total_projects_count'
disable_ddl_transaction!
def up
add_concurrent_index :topics, [:total_projects_count, :id], order: { total_projects_count: :desc }, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :topics, INDEX_NAME
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
class RecreateIndexSecurityCiBuildsOnNameAndIdParserFeatures < Gitlab::Database::Migration[1.0]
TABLE = "ci_builds"
OLD_INDEX_NAME = "index_security_ci_builds_on_name_and_id_parser_features"
NEW_INDEX_NAME = "index_security_ci_builds_on_name_and_id_parser_features_broken"
COLUMNS = %i[name id]
CONSTRAINTS = "(name::text = ANY (ARRAY['container_scanning'::character varying::text,
'dast'::character varying::text,
'dependency_scanning'::character varying::text,
'license_management'::character varying::text,
'sast'::character varying::text,
'secret_detection'::character varying::text,
'coverage_fuzzing'::character varying::text,
'license_scanning'::character varying::text])
) AND type::text = 'Ci::Build'::text"
enable_lock_retries!
def up
rename_index(TABLE, OLD_INDEX_NAME, NEW_INDEX_NAME)
prepare_async_index TABLE, COLUMNS, name: OLD_INDEX_NAME, where: CONSTRAINTS
end
def down
unprepare_async_index TABLE, COLUMNS, name: OLD_INDEX_NAME
rename_index(TABLE, NEW_INDEX_NAME, OLD_INDEX_NAME)
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class SchedulePopulateTopicsTotalProjectsCountCache < Gitlab::Database::Migration[1.0]
MIGRATION = 'PopulateTopicsTotalProjectsCountCache'
BATCH_SIZE = 10_000
DELAY_INTERVAL = 2.minutes
disable_ddl_transaction!
def up
queue_background_migration_jobs_by_range_at_intervals(
define_batchable_model('topics'),
MIGRATION,
DELAY_INTERVAL,
batch_size: BATCH_SIZE,
track_jobs: true
)
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
e035616201329b7610e8c3a647bc01c52ce722790ea7bb88d4a38bc0feb4737e

View File

@ -0,0 +1 @@
37016ec5e5ab1bd8d2bd8020f98277b3ad9f450b833ce3ebde70aebce5130a26

View File

@ -0,0 +1 @@
0d6ec7c1d96f32c645ddc051d8e3b3bd0ad759c52c8938888287b1c6b57d27a3

View File

@ -0,0 +1 @@
918852db691546e4e93a933789968115ac98b5757d480ed1e09118508e6024d5

View File

@ -0,0 +1 @@
19efbbf7aab5837e33ff72d87e101a76da7eeb1d60c05ffc0ceddad1d0cbc69c

View File

@ -19667,6 +19667,7 @@ CREATE TABLE topics (
updated_at timestamp with time zone NOT NULL,
avatar text,
description text,
total_projects_count bigint DEFAULT 0 NOT NULL,
CONSTRAINT check_26753fb43a CHECK ((char_length(avatar) <= 255)),
CONSTRAINT check_5d1a07c8c8 CHECK ((char_length(description) <= 1024)),
CONSTRAINT check_7a90d4c757 CHECK ((char_length(name) <= 255))
@ -26518,7 +26519,7 @@ CREATE UNIQUE INDEX index_scim_oauth_access_tokens_on_group_id_and_token_encrypt
CREATE INDEX index_secure_ci_builds_on_user_id_name_created_at ON ci_builds USING btree (user_id, name, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text, ('secret_detection'::character varying)::text])));
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features_broken ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
CREATE INDEX index_security_findings_on_confidence ON security_findings USING btree (confidence);
@ -26742,6 +26743,10 @@ CREATE UNIQUE INDEX index_token_with_ivs_on_hashed_token ON token_with_ivs USING
CREATE UNIQUE INDEX index_topics_on_name ON topics USING btree (name);
CREATE INDEX index_topics_on_name_trigram ON topics USING gin (name gin_trgm_ops);
CREATE INDEX index_topics_total_projects_count ON topics USING btree (total_projects_count DESC, id);
CREATE UNIQUE INDEX index_trending_projects_on_project_id ON trending_projects USING btree (project_id);
CREATE INDEX index_u2f_registrations_on_key_handle ON u2f_registrations USING btree (key_handle);

View File

@ -534,7 +534,11 @@ Plan.default.actual_limits.update!(pages_file_entries: 100)
### Number of registered runners per scope
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321368) in GitLab 13.12.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321368) in GitLab 13.12. Disabled by default.
> - Enabled on GitLab.com in GitLab 14.3.
> - Enabled on self-managed in GitLab 14.4.
> - Feature flag `ci_runner_limits` removed in GitLab 14.4. You can still use `ci_runner_limits_override`
to remove limits for a given scope.
The total number of registered runners is limited at the group and project levels. Each time a new runner is registered,
GitLab checks these limits against runners that have been active in the last 3 months.

View File

@ -230,7 +230,7 @@ reports.
## Monthly release process (versions)
The docs website supports versions and each month we add the latest one to the list.
For more information, read about the [monthly release process](https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#monthly-documentation-releases).
For more information, read about the [monthly release process](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/releases.md).
## Review Apps for documentation merge requests

View File

@ -28,6 +28,8 @@ There are many places where file uploading is used, according to contexts:
- LFS Objects
- Merge request diffs
- Design Management design thumbnails
- Topic
- Topic avatars
## Disk storage
@ -42,6 +44,7 @@ they are still not 100% standardized. You can see them below:
| User avatars | yes | `uploads/-/system/user/avatar/:id/:filename` | `AvatarUploader` | User |
| User snippet attachments | yes | `uploads/-/system/personal_snippet/:id/:random_hex/:filename` | `PersonalFileUploader` | Snippet |
| Project avatars | yes | `uploads/-/system/project/avatar/:id/:filename` | `AvatarUploader` | Project |
| Topic avatars | yes | `uploads/-/system/projects/topic/avatar/:id/:filename` | `AvatarUploader` | Topic |
| Issues/MR/Notes Markdown attachments | yes | `uploads/:project_path_with_namespace/:random_hex/:filename` | `FileUploader` | Project |
| Issues/MR/Notes Legacy Markdown attachments | no | `uploads/-/system/note/attachment/:id/:filename` | `AttachmentUploader` | Note |
| Design Management design thumbnails | yes | `uploads/-/system/design_management/action/image_v432x230/:id/:filename` | `DesignManagement::DesignV432x230Uploader` | DesignManagement::Action |

View File

@ -231,7 +231,6 @@ graph RL;
1-16["brakeman-sast"];
1-17["eslint-sast"];
1-18["kubesec-sast"];
1-19["nodejs-scan-sast"];
1-20["secrets-sast"];
1-21["static-analysis (14 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"
@ -333,7 +332,6 @@ graph RL;
1-16["brakeman-sast"];
1-17["eslint-sast"];
1-18["kubesec-sast"];
1-19["nodejs-scan-sast"];
1-20["secrets-sast"];
1-21["static-analysis (14 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"
@ -459,7 +457,6 @@ graph RL;
1-16["brakeman-sast"];
1-17["eslint-sast"];
1-18["kubesec-sast"];
1-19["nodejs-scan-sast"];
1-20["secrets-sast"];
1-21["static-analysis (14 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"

View File

@ -338,6 +338,7 @@ Below is a list of Mattermost versions for GitLab 11.10 and later:
| 14.1 | 5.36 |
| 14.2 | 5.37 |
| 14.3 | 5.38 |
| 14.4 | 5.39 |
NOTE:
When upgrading the Mattermost version, it is essential to check the

View File

@ -18,21 +18,23 @@ To add a deprecation, use the example.yml file in `/data/deprecations/templates`
then run `bin/rake gitlab:docs:compile_deprecations`.
-->
## 15.2
## 14.4
### NFS for Git repository storage deprecated
### Rename Task Runner pod to Toolbox
With the general availability of Gitaly Cluster ([introduced in GitLab 13.0](https://about.gitlab.com/releases/2020/05/22/gitlab-13-0-released/)), we have deprecated development (bugfixes, performance improvements, etc) for NFS for Git repository storage in GitLab 14.0. We will continue to provide technical support for NFS for Git repositories throughout 14.x, but we will remove all support for NFS in GitLab 15.0. Please see our official [Statement of Support](https://about.gitlab.com/support/statement-of-support.html#gitaly-and-nfs) for further information.
The Task Runner pod is used to execute periodic housekeeping tasks within the GitLab application and is often confused with the GitLab Runner. Thus, [Task Runner will be renamed to Toolbox](https://gitlab.com/groups/gitlab-org/charts/-/epics/25).
Gitaly Cluster offers tremendous benefits for our customers such as:
This will result in the rename of the sub-chart: `gitlab/task-runner` to `gitlab/toolbox`. Resulting pods will be named along the lines of `{{ .Release.Name }}-toolbox`, which will often be `gitlab-toolbox`. They will be locatable with the label `app=toolbox`.
- [Variable replication factors](https://docs.gitlab.com/ee/administration/gitaly/index.html#replication-factor).
- [Strong consistency](https://docs.gitlab.com/ee/administration/gitaly/index.html#strong-consistency).
- [Distributed read capabilities](https://docs.gitlab.com/ee/administration/gitaly/index.html#distributed-reads).
Announced: 2021-08-22
We encourage customers currently using NFS for Git repositories to plan their migration by reviewing our documentation on [migrating to Gitaly Cluster](https://docs.gitlab.com/ee/administration/gitaly/index.html#migrate-to-gitaly-cluster).
## 14.6
Announced: 2021-06-22
### Release CLI be distributed as a generic package
The [release-cli](https://gitlab.com/gitlab-org/release-cli) will be released as a [generic package](https://gitlab.com/gitlab-org/release-cli/-/packages) starting in GitLab 14.2. We will continue to deploy it as a binary to S3 until GitLab 14.5 and stop distributing it in S3 in GitLab 14.6.
Announced: 2021-08-22
## 15.0
@ -74,20 +76,18 @@ We decided to remove the GitLab Serverless features as they never really resonat
Announced: 2021-09-22
## 14.6
## 15.2
### Release CLI be distributed as a generic package
### NFS for Git repository storage deprecated
The [release-cli](https://gitlab.com/gitlab-org/release-cli) will be released as a [generic package](https://gitlab.com/gitlab-org/release-cli/-/packages) starting in GitLab 14.2. We will continue to deploy it as a binary to S3 until GitLab 14.5 and stop distributing it in S3 in GitLab 14.6.
With the general availability of Gitaly Cluster ([introduced in GitLab 13.0](https://about.gitlab.com/releases/2020/05/22/gitlab-13-0-released/)), we have deprecated development (bugfixes, performance improvements, etc) for NFS for Git repository storage in GitLab 14.0. We will continue to provide technical support for NFS for Git repositories throughout 14.x, but we will remove all support for NFS in GitLab 15.0. Please see our official [Statement of Support](https://about.gitlab.com/support/statement-of-support.html#gitaly-and-nfs) for further information.
Announced: 2021-08-22
Gitaly Cluster offers tremendous benefits for our customers such as:
## 14.4
- [Variable replication factors](https://docs.gitlab.com/ee/administration/gitaly/index.html#replication-factor).
- [Strong consistency](https://docs.gitlab.com/ee/administration/gitaly/index.html#strong-consistency).
- [Distributed read capabilities](https://docs.gitlab.com/ee/administration/gitaly/index.html#distributed-reads).
### Rename Task Runner pod to Toolbox
We encourage customers currently using NFS for Git repositories to plan their migration by reviewing our documentation on [migrating to Gitaly Cluster](https://docs.gitlab.com/ee/administration/gitaly/index.html#migrate-to-gitaly-cluster).
The Task Runner pod is used to execute periodic housekeeping tasks within the GitLab application and is often confused with the GitLab Runner. Thus, [Task Runner will be renamed to Toolbox](https://gitlab.com/groups/gitlab-org/charts/-/epics/25).
This will result in the rename of the sub-chart: `gitlab/task-runner` to `gitlab/toolbox`. Resulting pods will be named along the lines of `{{ .Release.Name }}-toolbox`, which will often be `gitlab-toolbox`. They will be locatable with the label `app=toolbox`.
Announced: 2021-08-22
Announced: 2021-06-22

View File

@ -24,7 +24,7 @@ The Admin Area is made up of the following sections:
| Section | Description |
|:-----------------------------------------------|:------------|
| **{overview}** [Overview](#overview-section) | View your GitLab [Dashboard](#admin-area-dashboard), and administer [projects](#administering-projects), [users](#administering-users), [groups](#administering-groups), [jobs](#administering-jobs), [runners](#administering-runners), and [Gitaly servers](#administering-gitaly-servers). |
| **{overview}** [Overview](#overview-section) | View your GitLab [Dashboard](#admin-area-dashboard), and administer [projects](#administering-projects), [users](#administering-users), [groups](#administering-groups), [topics](#administering-topics), [jobs](#administering-jobs), [runners](#administering-runners), and [Gitaly servers](#administering-gitaly-servers). |
| **{monitor}** Monitoring | View GitLab [system information](#system-information), and information on [background jobs](#background-jobs), [logs](#logs), [health checks](monitoring/health_check.md), [requests profiles](#requests-profiles), and [audit events](#audit-events). |
| **{messages}** Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
| **{hook}** System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
@ -237,6 +237,26 @@ insensitive, and applies partial matching.
To [Create a new group](../group/index.md#create-a-group) click **New group**.
### Administering Topics
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340920) in GitLab 14.4.
You can administer all topics in the GitLab instance from the Admin Area's Topics page.
To access the Topics page:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Overview > Topics**.
For each topic, the page displays their name and number of projects labeled with the topic.
To create a new topic, select **New topic**.
To edit a topic, select **Edit** in that topic's row.
To search for topics by name, enter your criteria in the search box. The topic search is case
insensitive, and applies partial matching.
### Administering Jobs
You can administer all jobs in the GitLab instance from the Admin Area's Jobs page.

View File

@ -302,11 +302,12 @@ best practices. You can customize this flow by adding, hiding or re-ordering sta
To create a value stream:
1. Navigate to your group's **Analytics > Value Stream**.
1. Click the Value stream dropdown and select **Create new Value Stream**
1. Fill in a name for the new Value Stream
- You can [customize the stages](#creating-a-value-stream-with-stages)
1. Click the **Create Value Stream** button.
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Analytics > Value Stream**.
1. In the top right, select the dropdown list and then **Create new Value Stream**.
1. Enter a name for the new Value Stream.
- You can [customize the stages](#creating-a-value-stream-with-stages).
1. Select **Create Value Stream**.
![New value stream](img/new_value_stream_v13_12.png "Creating a new value stream")
@ -324,18 +325,19 @@ add stages as desired.
To create a value stream with stages:
1. Go to your group and select **Analytics > Value Stream**.
1. Select the Value Stream dropdown and select **Create new Value Stream**.
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Analytics > Value Stream**.
1. In the top right, select the dropdown list and then **Create new Value Stream**.
1. Select either **Create from default template** or **Create from no template**.
- Default stages in the value stream can be hidden or re-ordered.
- You can hide or re-order default stages in the value stream.
![Default stage actions](img/vsa_default_stage_v13_10.png "Default stage actions")
- New stages can be added by clicking the 'Add another stage' button.
- The name, start and end events for the stage can be selected
- You can add new stages by selecting **Add another stage**.
- You can select the name and start and end events for the stage.
![Custom stage actions](img/vsa_custom_stage_v13_10.png "Custom stage actions")
1. Select the **Create Value Stream** button to save the value stream.
1. Select **Create Value Stream**.
#### Label-based stages
@ -358,8 +360,9 @@ In this example, we'd like to measure times for deployment from a staging enviro
After you create a value stream, you can customize it to suit your purposes. To edit a value stream:
1. Go to your group and select **Analytics > Value Stream**.
1. Find and select the relevant value stream from the value stream dropdown.
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Analytics > Value Stream**.
1. In the top right, select the dropdown list and then select the relevant value stream.
1. Next to the value stream dropdown, select **Edit**.
The edit form is populated with the value stream details.
1. Optional:
@ -377,10 +380,11 @@ After you create a value stream, you can customize it to suit your purposes. To
To delete a custom value stream:
1. Navigate to your group's **Analytics > Value Stream**.
1. Click the Value stream dropdown and select the value stream you would like to delete.
1. Click the **Delete (name of value stream)**.
1. Click the **Delete** button to confirm.
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Analytics > Value Stream**.
1. In the top right, select the dropdown list and then select the value stream you would like to delete.
1. Select **Delete (name of value stream)**.
1. To confirm, select **Delete**.
![Delete value stream](img/delete_value_stream_v13_12.png "Deleting a custom value stream")
@ -413,7 +417,7 @@ select up to a total of 15 labels.
## Permissions
To access Group-level Value Stream Analytics, users must have Reporter access or above.
To access Group-level Value Stream Analytics, users must have at least the Reporter role.
You can [read more about permissions](../../permissions.md) in general.

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
SUB_BATCH_SIZE = 1_000
# The class to populates the total projects counter cache of topics
class PopulateTopicsTotalProjectsCountCache
# Temporary AR model for topics
class Topic < ActiveRecord::Base
include EachBatch
self.table_name = 'topics'
end
def perform(start_id, stop_id)
Topic.where(id: start_id..stop_id).each_batch(of: SUB_BATCH_SIZE) do |batch|
ActiveRecord::Base.connection.execute(<<~SQL)
WITH batched_relation AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (#{batch.select(:id).limit(SUB_BATCH_SIZE).to_sql})
UPDATE topics
SET total_projects_count = (SELECT COUNT(*) FROM project_topics WHERE topic_id = batched_relation.id)
FROM batched_relation
WHERE topics.id = batched_relation.id
SQL
end
end
end
end
end

View File

@ -73,7 +73,7 @@ module Gitlab
# To prevent this from happening, we scope sticking to all the
# models that support load balancing. In the future (if we
# determined this to be OK) we may be able to relax this.
LoadBalancing.base_models.map do |model|
::Gitlab::Database::LoadBalancing.base_models.map do |model|
[model, :user, warden.user.id]
end
elsif env[STICK_OBJECT].present?

View File

@ -74,7 +74,7 @@ module Gitlab
def unstick_or_continue_sticking(namespace, id)
return if all_caught_up?(namespace, id)
Session.current.use_primary!
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
end
# Select a replica that has caught up with the primary. If one has not been
@ -83,14 +83,14 @@ module Gitlab
replica_selected =
select_caught_up_replicas(namespace, id)
Session.current.use_primary! unless replica_selected
::Gitlab::Database::LoadBalancing::Session.current.use_primary! unless replica_selected
end
# Starts sticking to the primary for the given namespace and id, using
# the latest WAL pointer from the primary.
def stick(namespace, id)
mark_primary_write_location(namespace, id)
Session.current.use_primary!
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
end
def bulk_stick(namespace, ids)
@ -100,7 +100,7 @@ module Gitlab
end
end
Session.current.use_primary!
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
end
def with_primary_write_location

View File

@ -9,6 +9,7 @@ module Gitlab
ee/lib/ee/peek
lib/peek
lib/gitlab/database
lib/gitlab/gitaly_client.rb
lib/gitlab/gitaly_client/call.rb
lib/gitlab/instrumentation/redis_interceptor.rb
].freeze

View File

@ -2108,6 +2108,9 @@ msgstr ""
msgid "Add to tree"
msgstr ""
msgid "Add topics to projects to help users find them."
msgstr ""
msgid "Add trigger"
msgstr ""
@ -9708,6 +9711,9 @@ msgstr ""
msgid "Create tag %{tagName}"
msgstr ""
msgid "Create topic"
msgstr ""
msgid "Create user"
msgstr ""
@ -12369,6 +12375,9 @@ msgstr ""
msgid "Edit title and description"
msgstr ""
msgid "Edit topic: %{topic_name}"
msgstr ""
msgid "Edit user: %{user_name}"
msgstr ""
@ -22390,6 +22399,9 @@ msgstr ""
msgid "My company or team"
msgstr ""
msgid "My topic"
msgstr ""
msgid "My-Reaction"
msgstr ""
@ -22883,6 +22895,9 @@ msgstr ""
msgid "New test case"
msgstr ""
msgid "New topic"
msgstr ""
msgid "New users set to external"
msgstr ""
@ -25571,6 +25586,9 @@ msgstr ""
msgid "Please fill in a descriptive name for your group."
msgstr ""
msgid "Please fill in a name for your topic."
msgstr ""
msgid "Please fill out this field."
msgstr ""
@ -34347,6 +34365,9 @@ msgstr ""
msgid "There are no projects shared with this group yet"
msgstr ""
msgid "There are no topics to show."
msgstr ""
msgid "There are no variables yet."
msgstr ""
@ -35839,6 +35860,21 @@ msgstr ""
msgid "TopNav|Go back"
msgstr ""
msgid "Topic %{topic_name} was successfully created."
msgstr ""
msgid "Topic avatar"
msgstr ""
msgid "Topic name"
msgstr ""
msgid "Topic was successfully updated."
msgstr ""
msgid "Topics"
msgstr ""
msgid "Topics (optional)"
msgstr ""
@ -38705,6 +38741,9 @@ msgstr ""
msgid "Write a description or drag your files here…"
msgstr ""
msgid "Write a description…"
msgstr ""
msgid "Write milestone description..."
msgstr ""
@ -39619,6 +39658,21 @@ msgstr[1] ""
msgid "Your username is %{username}."
msgstr ""
msgid "ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page."
msgstr ""
msgid "ZenTaoIntegration|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}ZenTao%{linkEnd}."
msgstr ""
msgid "ZenTaoIntegration|This is a ZenTao user."
msgstr ""
msgid "ZenTaoIntegration|This issue is synchronized with ZenTao"
msgstr ""
msgid "ZenTaoIntegration|ZenTao user"
msgstr ""
msgid "ZentaoIntegration|Base URL of the Zentao instance."
msgstr ""

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::Topics::AvatarsController do
let(:user) { create(:admin) }
let(:topic) { create(:topic, avatar: fixture_file_upload("spec/fixtures/dk.png")) }
before do
sign_in(user)
controller.instance_variable_set(:@topic, topic)
end
it 'removes avatar from DB by calling destroy' do
delete :destroy, params: { topic_id: topic.id }
@topic = assigns(:topic)
expect(@topic.avatar.present?).to be_falsey
expect(@topic).to be_valid
end
end

View File

@ -0,0 +1,131 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::TopicsController do
let_it_be(:topic) { create(:topic, name: 'topic') }
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
before do
sign_in(admin)
end
describe 'GET #index' do
it 'renders the template' do
get :index
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('index')
end
context 'as a normal user' do
before do
sign_in(user)
end
it 'renders a 404 error' do
get :index
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET #new' do
it 'renders the template' do
get :new
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('new')
end
context 'as a normal user' do
before do
sign_in(user)
end
it 'renders a 404 error' do
get :new
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET #edit' do
it 'renders the template' do
get :edit, params: { id: topic.id }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('edit')
end
context 'as a normal user' do
before do
sign_in(user)
end
it 'renders a 404 error' do
get :edit, params: { id: topic.id }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'POST #create' do
it 'creates topic' do
expect do
post :create, params: { projects_topic: { name: 'test' } }
end.to change { Projects::Topic.count }.by(1)
end
it 'shows error message for invalid topic' do
post :create, params: { projects_topic: { name: nil } }
errors = assigns[:topic].errors
expect(errors).to contain_exactly(errors.full_message(:name, I18n.t('errors.messages.blank')))
end
context 'as a normal user' do
before do
sign_in(user)
end
it 'renders a 404 error' do
post :create, params: { projects_topic: { name: 'test' } }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'PUT #update' do
it 'updates topic' do
put :update, params: { id: topic.id, projects_topic: { name: 'test' } }
expect(response).to redirect_to(edit_admin_topic_path(topic))
expect(topic.reload.name).to eq('test')
end
it 'shows error message for invalid topic' do
put :update, params: { id: topic.id, projects_topic: { name: nil } }
errors = assigns[:topic].errors
expect(errors).to contain_exactly(errors.full_message(:name, I18n.t('errors.messages.blank')))
end
context 'as a normal user' do
before do
sign_in(user)
end
it 'renders a 404 error' do
put :update, params: { id: topic.id, projects_topic: { name: 'test' } }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end

View File

@ -599,6 +599,46 @@ RSpec.describe UploadsController do
end
end
context "when viewing a topic avatar" do
let!(:topic) { create(:topic, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
context "when signed in" do
before do
sign_in(user)
end
it "responds with status 200" do
get :show, params: { model: "projects/topic", mounted_as: "avatar", id: topic.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(:ok)
end
it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: "projects/topic", mounted_as: "avatar", id: topic.id, filename: "dk.png" }
response
end
end
end
context "when not signed in" do
it "responds with status 200" do
get :show, params: { model: "projects/topic", mounted_as: "avatar", id: topic.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(:ok)
end
it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: "projects/topic", mounted_as: "avatar", id: topic.id, filename: "dk.png" }
response
end
end
end
end
context 'Appearance' do
context 'when viewing a custom header logo' do
let!(:appearance) { create :appearance, header_logo: fixture_file_upload('spec/fixtures/dk.png', 'image/png') }

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::TopicsFinder do
let_it_be(:user) { create(:user) }
let!(:topic1) { create(:topic, name: 'topicB') }
let!(:topic2) { create(:topic, name: 'topicC') }
let!(:topic3) { create(:topic, name: 'topicA') }
let!(:project1) { create(:project, namespace: user.namespace, topic_list: 'topicC, topicA, topicB') }
let!(:project2) { create(:project, namespace: user.namespace, topic_list: 'topicC, topicA') }
let!(:project3) { create(:project, namespace: user.namespace, topic_list: 'topicC') }
describe '#execute' do
it 'returns topics' do
topics = described_class.new.execute
expect(topics).to eq([topic2, topic3, topic1])
end
context 'filter by name' do
using RSpec::Parameterized::TableSyntax
where(:search, :result) do
'topic' | %w[topicC topicA topicB]
'pic' | %w[topicC topicA topicB]
'B' | %w[]
'cB' | %w[]
'icB' | %w[topicB]
'topicA' | %w[topicA]
'topica' | %w[topicA]
end
with_them do
it 'returns filtered topics' do
topics = described_class.new(params: { search: search }).execute
expect(topics.map(&:name)).to eq(result)
end
end
end
end
end

View File

@ -110,6 +110,42 @@
"lib/gitlab/jira/middleware.rb:19:in `call'"
],
"warnings": []
}, {
"start": 9081.502219885,
"feature": "commit_service#find_commit",
"duration": 6.678,
"request": "{:repository=>\n {:storage_name=>\"nfs-file-cny01\",\n :relative_path=>\n \"@hashed/a6/80/a68072e80f075e89bc74a300101a9e71e8363bdb542182580162553462480a52.git\",\n :git_object_directory=>\"\",\n :git_alternate_object_directories=>[],\n :gl_repository=>\"project-278964\",\n :gl_project_path=>\"gitlab-org/gitlab\"},\n :revision=>\"master\"}\n",
"rpc": "find_commit",
"backtrace": [
"lib/gitlab/gitaly_client/call.rb:30:in `call'",
"lib/gitlab/gitaly_client.rb:167:in `call'",
"lib/gitlab/gitaly_client/commit_service.rb:520:in `call_find_commit'",
"lib/gitlab/gitaly_client/commit_service.rb:354:in `find_commit'",
"lib/gitlab/git/commit.rb:74:in `block in find_commit'",
"lib/gitlab/git/wraps_gitaly_errors.rb:7:in `wrapped_gitaly_errors'",
"lib/gitlab/git/commit.rb:73:in `find_commit'",
"lib/gitlab/git/rugged_impl/commit.rb:41:in `find_commit'",
"lib/gitlab/git/commit.rb:65:in `find'",
"lib/gitlab/git/repository.rb:789:in `commit'",
"app/services/branches/diverging_commit_counts_service.rb:21:in `diverging_commit_counts'",
"app/services/branches/diverging_commit_counts_service.rb:11:in `call'",
"app/controllers/projects/branches_controller.rb:57:in `block (4 levels) in diverging_commit_counts'",
"app/controllers/projects/branches_controller.rb:57:in `to_h'",
"app/controllers/projects/branches_controller.rb:57:in `block (3 levels) in diverging_commit_counts'",
"lib/gitlab/gitaly_client.rb:325:in `allow_n_plus_1_calls'",
"app/controllers/projects/branches_controller.rb:56:in `block (2 levels) in diverging_commit_counts'",
"app/controllers/projects/branches_controller.rb:51:in `diverging_commit_counts'",
"ee/lib/gitlab/ip_address_state.rb:10:in `with'",
"ee/app/controllers/ee/application_controller.rb:44:in `set_current_ip_address'",
"app/controllers/application_controller.rb:497:in `set_current_admin'",
"lib/gitlab/session.rb:11:in `with_session'",
"app/controllers/application_controller.rb:488:in `set_session_storage'",
"app/controllers/application_controller.rb:482:in `set_locale'",
"app/controllers/application_controller.rb:476:in `set_current_context'",
"ee/lib/omni_auth/strategies/group_saml.rb:41:in `other_phase'",
"lib/gitlab/jira/middleware.rb:19:in `call'"
],
"warnings": []
}
],
"warnings": []

View File

@ -1,12 +1,12 @@
import { GlModal, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json';
import AddReviewItemsModal from '~/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue';
import * as actions from '~/add_context_commits_modal/store/actions';
import mutations from '~/add_context_commits_modal/store/mutations';
import defaultState from '~/add_context_commits_modal/store/state';
import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
const localVue = createLocalVue();
localVue.use(Vuex);
@ -18,7 +18,7 @@ describe('AddContextCommitsModal', () => {
const removeContextCommits = jest.fn();
const resetModalState = jest.fn();
const searchCommits = jest.fn();
const { commit } = getDiffWithCommit();
const { commit } = getDiffWithCommit;
const createWrapper = (props = {}) => {
store = new Vuex.Store({

View File

@ -1,12 +1,12 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json';
import ReviewTabContainer from '~/add_context_commits_modal/components/review_tab_container.vue';
import CommitItem from '~/diffs/components/commit_item.vue';
import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
describe('ReviewTabContainer', () => {
let wrapper;
const { commit } = getDiffWithCommit();
const { commit } = getDiffWithCommit;
const createWrapper = (props = {}) => {
wrapper = shallowMount(ReviewTabContainer, {

View File

@ -1,10 +1,10 @@
import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json';
import { TEST_HOST } from 'helpers/test_constants';
import * as types from '~/add_context_commits_modal/store/mutation_types';
import mutations from '~/add_context_commits_modal/store/mutations';
import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
describe('AddContextCommitsModalStoreMutations', () => {
const { commit } = getDiffWithCommit();
const { commit } = getDiffWithCommit;
describe('SET_BASE_CONFIG', () => {
it('should set contextCommitsPath, mergeRequestIid and projectId', () => {
const state = {};

View File

@ -1,10 +1,10 @@
import { mount } from '@vue/test-utils';
import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json';
import { TEST_HOST } from 'helpers/test_constants';
import { trimText } from 'helpers/text_helper';
import Component from '~/diffs/components/commit_item.vue';
import { getTimeago } from '~/lib/utils/datetime_utility';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import getDiffWithCommit from '../mock_data/diff_with_commit';
jest.mock('~/user_popovers');
@ -18,7 +18,7 @@ describe('diffs/components/commit_item', () => {
let wrapper;
const timeago = getTimeago();
const { commit } = getDiffWithCommit();
const { commit } = getDiffWithCommit;
const getTitleElement = () => wrapper.find('.commit-row-message.item-title');
const getDescElement = () => wrapper.find('pre.commit-row-description');

View File

@ -1,11 +1,11 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { trimText } from 'helpers/text_helper';
import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
import { createStore } from '~/mr_notes/stores';
import getDiffWithCommit from '../mock_data/diff_with_commit';
import diffsMockData from '../mock_data/merge_request_diffs';
const localVue = createLocalVue();
@ -22,7 +22,7 @@ describe('CompareVersions', () => {
let wrapper;
let store;
const targetBranchName = 'tmp-wine-dev';
const { commit } = getDiffWithCommit();
const { commit } = getDiffWithCommit;
const createWrapper = (props = {}, commitArgs = {}, createCommit = true) => {
if (createCommit) {
@ -150,7 +150,7 @@ describe('CompareVersions', () => {
describe('commit', () => {
beforeEach(() => {
store.state.diffs.commit = getDiffWithCommit().commit;
store.state.diffs.commit = getDiffWithCommit.commit;
createWrapper();
});

View File

@ -1,5 +0,0 @@
import fixture from 'test_fixtures/merge_request_diffs/with_commit.json';
export default function getDiffWithCommit() {
return fixture;
}

View File

@ -7,7 +7,7 @@ RSpec.describe AvatarsHelper do
let_it_be(:user) { create(:user) }
describe '#project_icon & #group_icon' do
describe '#project_icon, #group_icon, #topic_icon' do
shared_examples 'resource with a default avatar' do |source_type|
it 'returns a default avatar div' do
expect(public_send("#{source_type}_icon", *helper_args))
@ -71,6 +71,18 @@ RSpec.describe AvatarsHelper do
let(:helper_args) { [resource] }
end
end
context 'when providing a topic' do
it_behaves_like 'resource with a default avatar', 'topic' do
let(:resource) { create(:topic, name: 'foo') }
let(:helper_args) { [resource] }
end
it_behaves_like 'resource with a custom avatar', 'topic' do
let(:resource) { create(:topic, avatar: File.open(uploaded_image_temp_path)) }
let(:helper_args) { [resource] }
end
end
end
describe '#avatar_icon_for' do

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::PopulateTopicsTotalProjectsCountCache, schema: 20211006060436 do
it 'correctly populates total projects count cache' do
namespaces = table(:namespaces)
projects = table(:projects)
topics = table(:topics)
project_topics = table(:project_topics)
group = namespaces.create!(name: 'group', path: 'group')
project_1 = projects.create!(namespace_id: group.id)
project_2 = projects.create!(namespace_id: group.id)
project_3 = projects.create!(namespace_id: group.id)
topic_1 = topics.create!(name: 'Topic1')
topic_2 = topics.create!(name: 'Topic2')
topic_3 = topics.create!(name: 'Topic3')
topic_4 = topics.create!(name: 'Topic4')
project_topics.create!(project_id: project_1.id, topic_id: topic_1.id)
project_topics.create!(project_id: project_1.id, topic_id: topic_3.id)
project_topics.create!(project_id: project_2.id, topic_id: topic_3.id)
project_topics.create!(project_id: project_1.id, topic_id: topic_4.id)
project_topics.create!(project_id: project_2.id, topic_id: topic_4.id)
project_topics.create!(project_id: project_3.id, topic_id: topic_4.id)
subject.perform(topic_1.id, topic_4.id)
expect(topic_1.reload.total_projects_count).to eq(1)
expect(topic_2.reload.total_projects_count).to eq(0)
expect(topic_3.reload.total_projects_count).to eq(2)
expect(topic_4.reload.total_projects_count).to eq(3)
end
end

View File

@ -32,6 +32,10 @@ RSpec.describe Gitlab::PerformanceBar::Stats do
.with({ duration_ms: 23.709, filename: 'lib/gitlab/gitaly_client/commit_service.rb',
method_path: 'lib/gitlab/gitaly_client/commit_service.rb:each',
count: 1, request_id: 'foo', query_type: 'gitaly' })
expect(logger).to receive(:info)
.with({ duration_ms: 6.678, filename: 'lib/gitlab/gitaly_client/commit_service.rb',
method_path: 'lib/gitlab/gitaly_client/commit_service.rb:call_find_commit',
count: 1, request_id: 'foo', query_type: 'gitaly' })
expect(logger).to receive(:info)
.with({ duration_ms: 0.155, filename: 'lib/feature.rb',
method_path: 'lib/feature.rb:enabled?',

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!('schedule_populate_topics_total_projects_count_cache')
RSpec.describe SchedulePopulateTopicsTotalProjectsCountCache do
let(:topics) { table(:topics) }
let!(:topic_1) { topics.create!(name: 'Topic1') }
let!(:topic_2) { topics.create!(name: 'Topic2') }
let!(:topic_3) { topics.create!(name: 'Topic3') }
describe '#up' do
before do
stub_const("#{described_class}::BATCH_SIZE", 2)
end
it 'schedules BackfillProjectsWithCoverage background jobs', :aggregate_failures do
Sidekiq::Testing.fake! do
freeze_time do
migrate!
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, topic_1.id, topic_2.id)
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, topic_3.id, topic_3.id)
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
end
end
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe RecreateIndexSecurityCiBuildsOnNameAndIdParserFeatures, :migration do
let(:db) { described_class.new }
let(:pg_class) { table(:pg_class) }
let(:pg_index) { table(:pg_index) }
let(:async_indexes) { table(:postgres_async_indexes) }
it "recreates index" do
reversible_migration do |migration|
migration.before -> {
expect(async_indexes.where(name: described_class::OLD_INDEX_NAME).exists?).to be false
expect(db.index_exists?(described_class::TABLE, described_class::COLUMNS, name: described_class::OLD_INDEX_NAME)).to be true
expect(db.index_exists?(described_class::TABLE, described_class::COLUMNS, name: described_class::NEW_INDEX_NAME)).to be false
}
migration.after -> {
expect(async_indexes.where(name: described_class::OLD_INDEX_NAME).exists?).to be true
expect(db.index_exists?(described_class::TABLE, described_class::COLUMNS, name: described_class::OLD_INDEX_NAME)).to be false
expect(db.index_exists?(described_class::TABLE, described_class::COLUMNS, name: described_class::NEW_INDEX_NAME)).to be true
}
end
end
end

View File

@ -3,12 +3,18 @@
require 'spec_helper'
RSpec.describe Projects::Topic do
let_it_be(:topic, reload: true) { create(:topic) }
let_it_be(:topic, reload: true) { create(:topic, name: 'topic') }
subject { topic }
it { expect(subject).to be_valid }
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Avatarable) }
end
describe 'associations' do
it { is_expected.to have_many(:project_topics) }
it { is_expected.to have_many(:projects) }
@ -20,4 +26,74 @@ RSpec.describe Projects::Topic do
it { is_expected.to validate_length_of(:name).is_at_most(255) }
it { is_expected.to validate_length_of(:description).is_at_most(1024) }
end
describe 'scopes' do
describe 'order_by_total_projects_count' do
let!(:topic1) { create(:topic, name: 'topicB') }
let!(:topic2) { create(:topic, name: 'topicC') }
let!(:topic3) { create(:topic, name: 'topicA') }
let!(:project1) { create(:project, topic_list: 'topicC, topicA, topicB') }
let!(:project2) { create(:project, topic_list: 'topicC, topicA') }
let!(:project3) { create(:project, topic_list: 'topicC') }
it 'sorts topics by total_projects_count' do
topics = described_class.order_by_total_projects_count
expect(topics.map(&:name)).to eq(%w[topicC topicA topicB topic])
end
end
describe 'reorder_by_similarity' do
let!(:topic1) { create(:topic, name: 'my-topic') }
let!(:topic2) { create(:topic, name: 'other') }
let!(:topic3) { create(:topic, name: 'topic2') }
it 'sorts topics by similarity' do
topics = described_class.reorder_by_similarity('topic')
expect(topics.map(&:name)).to eq(%w[topic my-topic topic2 other])
end
end
end
describe '#search' do
it 'returns topics with a matching name' do
expect(described_class.search(topic.name)).to eq([topic])
end
it 'returns topics with a partially matching name' do
expect(described_class.search(topic.name[0..2])).to eq([topic])
end
it 'returns topics with a matching name regardless of the casing' do
expect(described_class.search(topic.name.upcase)).to eq([topic])
end
end
describe '#avatar_type' do
it "is true if avatar is image" do
topic.update_attribute(:avatar, 'uploads/avatar.png')
expect(topic.avatar_type).to be_truthy
end
it "is false if avatar is html page" do
topic.update_attribute(:avatar, 'uploads/avatar.html')
topic.avatar_type
expect(topic.errors.added?(:avatar, "file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico, webp")).to be true
end
end
describe '#avatar_url' do
context 'when avatar file is uploaded' do
before do
topic.update!(avatar: fixture_file_upload("spec/fixtures/dk.png"))
end
it 'shows correct avatar url' do
expect(topic.avatar_url).to eq(topic.avatar.url)
expect(topic.avatar_url(only_path: false)).to eq([Gitlab.config.gitlab.url, topic.avatar.url].join)
end
end
end
end

View File

@ -58,6 +58,15 @@ RSpec.describe 'layouts/nav/sidebar/_admin' do
it_behaves_like 'page has active sub tab', 'Users'
end
context 'on topics' do
before do
allow(controller).to receive(:controller_name).and_return('admin/topics')
end
it_behaves_like 'page has active tab', 'Overview'
it_behaves_like 'page has active sub tab', 'Topics'
end
context 'on messages' do
before do
allow(controller).to receive(:controller_name).and_return('broadcast_messages')

View File

@ -20,7 +20,7 @@ module Deprecations
YAML.load_file(file)
end
deprecations = VersionSorter.rsort(deprecations) { |d| d["removal_milestone"] }
deprecations = VersionSorter.sort(deprecations) { |d| d["removal_milestone"] }
milestones = deprecations.map { |d| d["removal_milestone"] }.uniq