diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-31 15:07:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-31 15:07:02 +0300 |
commit | 74ecf758e30be848144df1672b5080a29fafbc0a (patch) | |
tree | 0bc719293526a2384b9db6638d42d49f1f5c9c9c | |
parent | 26d2324ac136c7a28235789ff9a0b2974b5f7c7b (diff) |
Add latest changes from gitlab-org/gitlab@master
62 files changed, 418 insertions, 1678 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index e1fc280bd1c..81b69b0e210 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -1452,7 +1452,7 @@ ee/lib/ee/api/entities/project.rb /app/workers/releases/create_evidence_worker.rb /app/workers/releases/manage_evidence_worker.rb -[Fulfillment::Utilization] @sheldonled @aalakkad @kpalchyk +^[Fulfillment::Utilization] @sheldonled @aalakkad @kpalchyk /ee/app/assets/javascripts/usage_quotas/components/ /ee/app/assets/javascripts/usage_quotas/seats/ /ee/app/assets/javascripts/usage_quotas/storage/ diff --git a/.gitlab/issue_templates/Feature Proposal - lean.md b/.gitlab/issue_templates/Feature Proposal - lean.md index 7b7402e4d18..c82907a2401 100644 --- a/.gitlab/issue_templates/Feature Proposal - lean.md +++ b/.gitlab/issue_templates/Feature Proposal - lean.md @@ -47,6 +47,13 @@ Create tracking issue using the Snowplow event tracking template. See https://gi --> +### Does this feature require an audit event? + +<!--- Checkout these docs to know more +https://docs.gitlab.com/ee/development/audit_event_guide/#what-are-audit-events +https://docs.gitlab.com/ee/administration/audit_events.html +---> + <!-- Label reminders Use the following resources to find the appropriate labels: - Use only one tier label choosing the lowest tier this is intended for diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 175bca2ceaf..bb0bf7e2371 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -911b7a1827241f74f6df9b87ae7d27b83da69d64 +2a6efda45f56010b4cb83773434c277444fd53ce diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index c07a0b69110..7c71d1e9f4c 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -v16.3.0-rc3 +v16.3.0-rc4 @@ -205,7 +205,7 @@ gem 'asciidoctor', '~> 2.0.18' gem 'asciidoctor-include-ext', '~> 0.4.0', require: false gem 'asciidoctor-plantuml', '~> 0.0.16' gem 'asciidoctor-kroki', '~> 0.8.0', require: false -gem 'rouge', '~> 4.1.2' +gem 'rouge', '~> 4.1.3' gem 'truncato', '~> 0.7.12' gem 'nokogiri', '~> 1.15', '>= 1.15.3' diff --git a/Gemfile.checksum b/Gemfile.checksum index 4545017edf8..5481b316f75 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -520,7 +520,7 @@ {"name":"rexml","version":"3.2.5","platform":"ruby","checksum":"a33c3bf95fda7983ec7f05054f3a985af41dbc25a0339843bd2479e93cabb123"}, {"name":"rinku","version":"2.0.0","platform":"ruby","checksum":"3e695aaf9f24baba3af45823b5c427b58a624582132f18482320e2737f9f8a85"}, {"name":"rotp","version":"6.2.0","platform":"ruby","checksum":"239a2eefba6f1bd4157b2c735d0f975598e0ef94823eea2f35d103d2e5cc0787"}, -{"name":"rouge","version":"4.1.2","platform":"ruby","checksum":"3b4ca60e4ac6e36be2deb0359cba04278ba15bdd2b1fbbb66bbc19cae517d55f"}, +{"name":"rouge","version":"4.1.3","platform":"ruby","checksum":"9c8663db26e05e52b3b0286daacae73ebb361c1bd31d7febd8c57087faa0b9a5"}, {"name":"rqrcode","version":"0.7.0","platform":"ruby","checksum":"8b3a5cba9cc199ba2d781a7c767cb55679f29a3621aa0506a799cec3760d16a1"}, {"name":"rqrcode-rails3","version":"0.1.7","platform":"ruby","checksum":"6f0582f26485123e5ed6f2a8a2871f00d86d353e0f58c8429a5a13212bcf48c4"}, {"name":"rspec","version":"3.12.0","platform":"ruby","checksum":"ccc41799a43509dc0be84070e3f0410ac95cbd480ae7b6c245543eb64162399c"}, diff --git a/Gemfile.lock b/Gemfile.lock index 16c7c04a34e..cb96cb5c07c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1340,7 +1340,7 @@ GEM rexml (3.2.5) rinku (2.0.0) rotp (6.2.0) - rouge (4.1.2) + rouge (4.1.3) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) @@ -1972,7 +1972,7 @@ DEPENDENCIES responders (~> 3.0) retriable (~> 3.1.2) rexml (~> 3.2.5) - rouge (~> 4.1.2) + rouge (~> 4.1.3) rqrcode-rails3 (~> 0.1.7) rspec-benchmark (~> 0.6.0) rspec-parameterized (~> 1.0) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 388135726a0..56e4b22ded2 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -274,8 +274,6 @@ class Projects::BlobController < Projects::ApplicationController @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path, literal_pathspec: true) @code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path) - allow_lfs_direct_download - render 'show' end @@ -320,30 +318,6 @@ class Projects::BlobController < Projects::ApplicationController current_user&.id end - def allow_lfs_direct_download - return unless directly_downloading_lfs_object? && content_security_policy_enabled? - return unless (lfs_object = @project.lfs_objects.find_by_oid(@blob.lfs_oid)) - - request.content_security_policy.directives['connect-src'] ||= [] - request.content_security_policy.directives['connect-src'] << lfs_src(lfs_object) - end - - def directly_downloading_lfs_object? - Gitlab.config.lfs.enabled && - !Gitlab.config.lfs.object_store.proxy_download && - @blob&.stored_externally? - end - - def content_security_policy_enabled? - Gitlab.config.gitlab.content_security_policy.enabled - end - - def lfs_src(lfs_object) - file = lfs_object.file - file = file.cdn_enabled_url(request.remote_ip) if file.respond_to?(:cdn_enabled_url) - file.url - end - alias_method :tracking_project_source, :project def tracking_namespace_source diff --git a/app/controllers/projects/performance_monitoring/dashboards_controller.rb b/app/controllers/projects/performance_monitoring/dashboards_controller.rb deleted file mode 100644 index 1255ec1dde2..00000000000 --- a/app/controllers/projects/performance_monitoring/dashboards_controller.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -module Projects - module PerformanceMonitoring - class DashboardsController < ::Projects::ApplicationController - include BlobHelper - - before_action :check_repository_available! - before_action :validate_required_params! - - rescue_from ActionController::ParameterMissing do |exception| - respond_error(http_status: :bad_request, message: _('Request parameter %{param} is missing.') % { param: exception.param }) - end - - feature_category :metrics - urgency :low - - def create - return not_found if Feature.enabled?(:remove_monitor_metrics) - - result = ::Metrics::Dashboard::CloneDashboardService.new(project, current_user, dashboard_params).execute - - if result[:status] == :success - respond_success(result) - else - respond_error(result) - end - end - - def update - return not_found if Feature.enabled?(:remove_monitor_metrics) - - result = ::Metrics::Dashboard::UpdateDashboardService.new(project, current_user, dashboard_params.merge(file_content_params)).execute - - if result[:status] == :success - respond_update_success(result) - else - respond_error(result) - end - end - - private - - def respond_success(result) - set_web_ide_link_notice(result.dig(:dashboard, :path)) - respond_to do |format| - format.json { render status: result.delete(:http_status), json: result } - end - end - - def respond_error(result) - respond_to do |format| - format.json { render json: { error: result[:message] }, status: result[:http_status] } - end - end - - def set_web_ide_link_notice(new_dashboard_path) - web_ide_link_start = "<a href=\"#{ide_edit_path(project, redirect_safe_branch_name, new_dashboard_path)}\">" - message = _("Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}.") % { web_ide_link_start: web_ide_link_start, web_ide_link_end: "</a>" } - flash[:notice] = message.html_safe - end - - def respond_update_success(result) - set_web_ide_link_update_notice(result.dig(:dashboard, :path)) - respond_to do |format| - format.json { render status: result.delete(:http_status), json: result } - end - end - - def set_web_ide_link_update_notice(new_dashboard_path) - web_ide_link_start = "<a href=\"#{ide_edit_path(project, redirect_safe_branch_name, new_dashboard_path)}\">" - message = _("Your dashboard has been updated. You can %{web_ide_link_start}edit it here%{web_ide_link_end}.") % { web_ide_link_start: web_ide_link_start, web_ide_link_end: "</a>" } - flash[:notice] = message.html_safe - end - - def validate_required_params! - params.require(%i[branch file_name dashboard commit_message]) - end - - def redirect_safe_branch_name - repository.find_branch(params[:branch]).name - end - - def dashboard_params - params.permit(%i[branch file_name dashboard commit_message]).to_h - end - - def file_content_params - params.permit( - file_content: [ - :dashboard, - panel_groups: [ - :group, - :priority, - panels: [ - :type, - :title, - :y_label, - :weight, - metrics: [ - :id, - :unit, - :label, - :query, - :query_range - ] - ] - ] - ] - ) - end - end - end -end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 2d6b51a49fc..af62ff8313d 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -252,7 +252,7 @@ class SearchController < ApplicationController end def search_type - 'basic' + search_service.search_type end end diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index 219dcf29563..cd768ba8a7b 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -54,7 +54,6 @@ module EnvironmentsHelper { 'settings_path' => edit_project_settings_integration_path(project, 'prometheus'), 'clusters_path' => project_clusters_path(project), - 'dashboards_endpoint' => project_performance_monitoring_dashboards_path(project, format: :json), 'default_branch' => project.default_branch, 'project_path' => project_path(project), 'tags_path' => project_tags_path(project), diff --git a/app/models/concerns/issuable_link.rb b/app/models/concerns/issuable_link.rb index 7f29083d6c6..5cb5f410a77 100644 --- a/app/models/concerns/issuable_link.rb +++ b/app/models/concerns/issuable_link.rb @@ -53,7 +53,7 @@ module IssuableLink return unless source && target if self.class.base_class.find_by(source: target, target: source) - errors.add(:source, "is already related to this #{self.class.issuable_type}") + errors.add(:source, "is already related to this #{self.class.issuable_type.to_s.humanize(capitalize: false)}") end end end diff --git a/app/models/concerns/linkable_item.rb b/app/models/concerns/linkable_item.rb new file mode 100644 index 00000000000..ce45549aca0 --- /dev/null +++ b/app/models/concerns/linkable_item.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# == LinkableItem concern +# +# Contains common functionality shared between related issue links and related work item links +# +# Used by IssueLink, WorkItems::RelatedWorkItemLink +# +module LinkableItem + extend ActiveSupport::Concern + include FromUnion + include IssuableLink + + included do + validate :check_existing_parent_link + + scope :for_source, ->(item) { where(source_id: item.id) } + scope :for_target, ->(item) { where(target_id: item.id) } + scope :for_items, ->(source, target) do + where(source: source, target: target).or(where(source: target, target: source)) + end + + private + + def check_existing_parent_link + return unless source && target + + existing_relation = WorkItems::ParentLink.for_parents([source, target]).for_children([source, target]) + return if existing_relation.none? + + errors.add( + :source, + format( + _('is a parent or child of this %{item}'), + item: self.class.issuable_type.to_s.humanize(capitalize: false) + ) + ) + end + end +end diff --git a/app/models/issue.rb b/app/models/issue.rb index 6e48dcab9ed..2b280f62ee6 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -542,18 +542,18 @@ class Issue < ApplicationRecord end def related_issues(current_user, preload: nil) - related_issues = ::Issue - .select(['issues.*', 'issue_links.id AS issue_link_id', - 'issue_links.link_type as issue_link_type_value', - 'issue_links.target_id as issue_link_source_id', - 'issue_links.created_at as issue_link_created_at', - 'issue_links.updated_at as issue_link_updated_at']) - .joins("INNER JOIN issue_links ON - (issue_links.source_id = issues.id AND issue_links.target_id = #{id}) - OR - (issue_links.target_id = issues.id AND issue_links.source_id = #{id})") - .preload(preload) - .reorder('issue_link_id') + related_issues = self.class + .select(['issues.*', 'issue_links.id AS issue_link_id', + 'issue_links.link_type as issue_link_type_value', + 'issue_links.target_id as issue_link_source_id', + 'issue_links.created_at as issue_link_created_at', + 'issue_links.updated_at as issue_link_updated_at']) + .joins("INNER JOIN issue_links ON + (issue_links.source_id = issues.id AND issue_links.target_id = #{id}) + OR + (issue_links.target_id = issues.id AND issue_links.source_id = #{id})") + .preload(preload) + .reorder('issue_link_id') related_issues = yield related_issues if block_given? diff --git a/app/models/issue_link.rb b/app/models/issue_link.rb index af55a5dec91..1c596ad0341 100644 --- a/app/models/issue_link.rb +++ b/app/models/issue_link.rb @@ -1,18 +1,11 @@ # frozen_string_literal: true class IssueLink < ApplicationRecord - include FromUnion - include IssuableLink + include LinkableItem belongs_to :source, class_name: 'Issue' belongs_to :target, class_name: 'Issue' - scope :for_source_issue, ->(issue) { where(source_id: issue.id) } - scope :for_target_issue, ->(issue) { where(target_id: issue.id) } - scope :for_issues, ->(source, target) do - where(source: source, target: target).or(where(source: target, target: source)) - end - class << self def issuable_type :issue diff --git a/app/models/work_items/parent_link.rb b/app/models/work_items/parent_link.rb index 5dff9e8e8d5..d9e3690b6fc 100644 --- a/app/models/work_items/parent_link.rb +++ b/app/models/work_items/parent_link.rb @@ -19,8 +19,10 @@ module WorkItems validate :validate_same_project validate :validate_max_children validate :validate_confidentiality + validate :check_existing_related_link scope :for_parents, ->(parent_ids) { where(work_item_parent_id: parent_ids) } + scope :for_children, ->(children_ids) { where(work_item: children_ids) } class << self def has_public_children?(parent_id) @@ -109,5 +111,14 @@ module WorkItems errors.add :work_item, _('is already present in ancestors') end end + + def check_existing_related_link + return unless work_item && work_item_parent + + existing_link = WorkItems::RelatedWorkItemLink.for_items(work_item, work_item_parent) + return if existing_link.none? + + errors.add(:work_item, _('cannot assign a linked work item as a parent')) + end end end diff --git a/app/models/work_items/related_work_item_link.rb b/app/models/work_items/related_work_item_link.rb new file mode 100644 index 00000000000..1da776fa3bd --- /dev/null +++ b/app/models/work_items/related_work_item_link.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module WorkItems + class RelatedWorkItemLink < ApplicationRecord + include LinkableItem + + self.table_name = 'issue_links' + + belongs_to :source, class_name: 'WorkItem' + belongs_to :target, class_name: 'WorkItem' + + class << self + extend ::Gitlab::Utils::Override + + override :issuable_type + def issuable_type + :work_item + end + end + end +end diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index c1599ceef6e..e26e3d0835b 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -123,10 +123,10 @@ module Issues end def rewrite_related_issues - source_issue_links = IssueLink.for_source_issue(original_entity) + source_issue_links = IssueLink.for_source(original_entity) source_issue_links.update_all(source_id: new_entity.id) - target_issue_links = IssueLink.for_target_issue(original_entity) + target_issue_links = IssueLink.for_target(original_entity) target_issue_links.update_all(target_id: new_entity.id) end diff --git a/app/services/metrics/dashboard/clone_dashboard_service.rb b/app/services/metrics/dashboard/clone_dashboard_service.rb deleted file mode 100644 index b5b46b1280b..00000000000 --- a/app/services/metrics/dashboard/clone_dashboard_service.rb +++ /dev/null @@ -1,170 +0,0 @@ -# frozen_string_literal: true - -# Copies system dashboard definition in .yml file into designated -# .yml file inside `.gitlab/dashboards` -module Metrics - module Dashboard - class CloneDashboardService < ::BaseService - include Stepable - include Gitlab::Utils::StrongMemoize - - ALLOWED_FILE_TYPE = '.yml' - USER_DASHBOARDS_DIR = ::Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT - SEQUENCES = { - ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH => [ - ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter, - ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter - ].freeze - }.freeze - - steps :check_push_authorized, - :check_branch_name, - :check_file_type, - :check_dashboard_template, - :create_file, - :refresh_repository_method_caches - - def execute - execute_steps - end - - private - - def check_push_authorized(result) - return error(_('You are not allowed to push into this branch. Create another branch or open a merge request.'), :forbidden) unless push_authorized? - - success(result) - end - - def check_branch_name(result) - return error(_('There was an error creating the dashboard, branch name is invalid.'), :bad_request) unless valid_branch_name? - return error(_('There was an error creating the dashboard, branch named: %{branch} already exists.') % { branch: params[:branch] }, :bad_request) unless new_or_default_branch? - - success(result) - end - - def check_file_type(result) - return error(_('The file name should have a .yml extension'), :bad_request) unless target_file_type_valid? - - success(result) - end - - # Only allow out of the box metrics dashboards to be cloned. This can be - # changed to allow cloning of any metrics dashboard, if desired. - # However, only metrics dashboards should be allowed. If any file is - # allowed to be cloned, this will become a security risk. - def check_dashboard_template(result) - return error(_('Not found.'), :not_found) unless dashboard_service&.out_of_the_box_dashboard? - - success(result) - end - - def create_file(result) - create_file_response = ::Files::CreateService.new(project, current_user, dashboard_attrs).execute - - if create_file_response[:status] == :success - success(result.merge(create_file_response)) - else - wrap_error(create_file_response) - end - end - - def refresh_repository_method_caches(result) - repository.refresh_method_caches([:metrics_dashboard]) - - success(result.merge(http_status: :created, dashboard: dashboard_details)) - end - - def dashboard_service - strong_memoize(:dashboard_service) do - Gitlab::Metrics::Dashboard::ServiceSelector.call(dashboard_service_options) - end - end - - def dashboard_attrs - { - commit_message: params[:commit_message], - file_path: new_dashboard_path, - file_content: new_dashboard_content, - encoding: 'text', - branch_name: branch, - start_branch: repository.branch_exists?(branch) ? branch : project.default_branch - } - end - - def dashboard_details - { - path: new_dashboard_path, - display_name: ::Metrics::Dashboard::CustomDashboardService.name_for_path(new_dashboard_path), - default: false, - system_dashboard: false - } - end - - def push_authorized? - Gitlab::UserAccess.new(current_user, container: project).can_push_to_branch?(branch) - end - - def dashboard_template - @dashboard_template ||= params[:dashboard] - end - - def branch - @branch ||= params[:branch] - end - - def new_or_default_branch? - !repository.branch_exists?(params[:branch]) || project.default_branch == params[:branch] - end - - def valid_branch_name? - Gitlab::GitRefValidator.validate(params[:branch]) - end - - def new_dashboard_path - @new_dashboard_path ||= File.join(USER_DASHBOARDS_DIR, file_name) - end - - def file_name - @file_name ||= File.basename(params[:file_name]) - end - - def target_file_type_valid? - File.extname(params[:file_name]) == ALLOWED_FILE_TYPE - end - - def wrap_error(result) - if result[:message] == 'A file with this name already exists' - error(_("A file with '%{file_name}' already exists in %{branch} branch") % { file_name: file_name, branch: branch }, :bad_request) - else - result - end - end - - def new_dashboard_content - ::Gitlab::Metrics::Dashboard::Processor - .new(project, raw_dashboard, sequence, {}) - .process.deep_stringify_keys.to_yaml - end - - def repository - @repository ||= project.repository - end - - def raw_dashboard - dashboard_service.new(project, current_user, dashboard_service_options).raw_dashboard - end - - def dashboard_service_options - { - embedded: false, - dashboard_path: dashboard_template - } - end - - def sequence - SEQUENCES[dashboard_template] || [] - end - end - end -end diff --git a/app/services/metrics/dashboard/dynamic_embed_service.rb b/app/services/metrics/dashboard/dynamic_embed_service.rb deleted file mode 100644 index a94538668c1..00000000000 --- a/app/services/metrics/dashboard/dynamic_embed_service.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -# Responsible for returning a filtered project dashboard -# containing only the request-provided metrics. The result -# is then cached for future requests. Metrics are identified -# based on a combination of identifiers for now, but the ideal -# would be similar to the approach in DefaultEmbedService, but -# a single unique identifier is not currently available across -# all metric types (custom, project-defined, cluster, or system). -# -# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards. -module Metrics - module Dashboard - class DynamicEmbedService < ::Metrics::Dashboard::BaseEmbedService - include Gitlab::Utils::StrongMemoize - - class << self - # Determines whether the provided params are sufficient - # to uniquely identify a panel from a yml-defined dashboard. - # - # See https://docs.gitlab.com/ee/operations/metrics/dashboards/index.html - # for additional info on defining custom dashboards. - def valid_params?(params) - [ - embedded?(params[:embedded]), - params[:group].present?, - params[:title].present?, - params[:y_label] - ].all? - end - end - - # Returns a new dashboard with only the matching - # metrics from the system dashboard, stripped of groups. - # @return [Hash] - def get_raw_dashboard - not_found! if panels.empty? - - { 'panel_groups' => [{ 'panels' => panels }] } - end - - private - - def panels - strong_memoize(:panels) do - not_found! unless base_dashboard - not_found! unless groups = base_dashboard['panel_groups'] - not_found! unless matching_group = find_group(groups) - not_found! unless all_panels = matching_group['panels'] - - find_panels(all_panels) - end - end - - def base_dashboard - strong_memoize(:base_dashboard) do - Gitlab::Metrics::Dashboard::Finder.find_raw(project, dashboard_path: dashboard_path) - end - end - - def find_group(groups) - groups.find do |candidate_group| - candidate_group['group'] == group - end - end - - def find_panels(all_panels) - all_panels.select do |panel| - panel['title'] == title && panel['y_label'] == y_label - end - end - - def not_found! - panels_not_found!(identifiers) - end - end - end -end diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb index 22a882c4648..8f1f78beb5b 100644 --- a/app/services/projects/prometheus/alerts/notify_service.rb +++ b/app/services/projects/prometheus/alerts/notify_service.rb @@ -80,8 +80,7 @@ module Projects def valid_alert_manager_token?(token, integration) valid_for_alerts_endpoint?(token, integration) || - valid_for_manual?(token) || - valid_for_cluster?(token) + valid_for_manual?(token) end def valid_for_manual?(token) @@ -109,44 +108,6 @@ module Projects compare_token(token, integration.token) end - def valid_for_cluster?(token) - cluster_integration = find_cluster_integration(project) - return false unless cluster_integration - - cluster_integration_token = cluster_integration.alert_manager_token - - if token - compare_token(token, cluster_integration_token) - else - cluster_integration_token.nil? - end - end - - def find_cluster_integration(project) - alert_id = gitlab_alert_id - return unless alert_id - - alert = find_alert(project, alert_id) - return unless alert - - cluster = alert.environment.deployment_platform&.cluster - return unless cluster&.enabled? - return unless cluster.integration_prometheus_available? - - cluster.integration_prometheus - end - - def find_alert(project, metric) - Projects::Prometheus::AlertsFinder - .new(project: project, metric: metric) - .execute - .first - end - - def gitlab_alert_id - alerts&.first&.dig('labels', 'gitlab_alert_id') - end - def compare_token(expected, actual) return unless expected && actual diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 433e9b0da6d..3d413ed9f7b 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -44,6 +44,10 @@ class SearchService project.blank? && group.blank? end + def search_type + 'basic' + end + def show_snippets? strong_memoize(:show_snippets) do params[:snippets] == 'true' diff --git a/config/routes/project.rb b/config/routes/project.rb index 5831d5d331d..b514830e438 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -337,14 +337,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end - namespace :performance_monitoring do - resources :dashboards, only: [:create] do - collection do - put '/:file_name', to: 'dashboards#update', constraints: { file_name: /.+\.yml/ } - end - end - end - resources :alert_management, only: [:index] do member do get 'details(/*page)', to: 'alert_management#details', as: 'details' diff --git a/db/docs/issue_links.yml b/db/docs/issue_links.yml index ed21e9c177b..810e5d940c8 100644 --- a/db/docs/issue_links.yml +++ b/db/docs/issue_links.yml @@ -2,6 +2,7 @@ table_name: issue_links classes: - IssueLink +- WorkItems::RelatedWorkItemLink feature_categories: - team_planning description: Links two issues by relationship type, which can be related or blocking diff --git a/db/migrate/20230724185321_pm_affected_packages_add_versions_attribute.rb b/db/migrate/20230724185321_pm_affected_packages_add_versions_attribute.rb new file mode 100644 index 00000000000..f1adb65324b --- /dev/null +++ b/db/migrate/20230724185321_pm_affected_packages_add_versions_attribute.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class PmAffectedPackagesAddVersionsAttribute < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def change + add_column :pm_affected_packages, :versions, :jsonb, default: [], null: false + end +end diff --git a/db/post_migrate/20230724212040_add_temporary_indexes_for_orphaned_approval_rules.rb b/db/post_migrate/20230724212040_add_temporary_indexes_for_orphaned_approval_rules.rb new file mode 100644 index 00000000000..69e5f7d48ac --- /dev/null +++ b/db/post_migrate/20230724212040_add_temporary_indexes_for_orphaned_approval_rules.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class AddTemporaryIndexesForOrphanedApprovalRules < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + LICENSE_SCANNING = 2 + SCAN_FINDING = 4 + + TMP_PROJECT_INDEX_NAME = 'tmp_idx_orphaned_approval_project_rules' + TMP_MR_INDEX_NAME = 'tmp_idx_orphaned_approval_merge_request_rules' + + def up + add_concurrent_index('approval_project_rules', :id, where: query_condition, name: TMP_PROJECT_INDEX_NAME) + add_concurrent_index('approval_merge_request_rules', :id, where: query_condition, name: TMP_MR_INDEX_NAME) + end + + def down + remove_concurrent_index_by_name('approval_project_rules', TMP_PROJECT_INDEX_NAME) + remove_concurrent_index_by_name('approval_merge_request_rules', TMP_MR_INDEX_NAME) + end + + private + + def query_condition + "report_type IN (#{LICENSE_SCANNING}, #{SCAN_FINDING}) AND security_orchestration_policy_configuration_id IS NULL" + end +end diff --git a/db/schema_migrations/20230724185321 b/db/schema_migrations/20230724185321 new file mode 100644 index 00000000000..b5ffbeb5734 --- /dev/null +++ b/db/schema_migrations/20230724185321 @@ -0,0 +1 @@ +73dc6e44071a82ff0e6237eaf8619af20944ecb959de803779372138d63ee194
\ No newline at end of file diff --git a/db/schema_migrations/20230724212040 b/db/schema_migrations/20230724212040 new file mode 100644 index 00000000000..f404e1e6d09 --- /dev/null +++ b/db/schema_migrations/20230724212040 @@ -0,0 +1 @@ +ccd668aefe3e5f1a138be29a6084bd59d058c75ad268eeebba4db9bbf0f6ad7e
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 6a3aa2b4801..ba104d2623d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -20431,6 +20431,7 @@ CREATE TABLE pm_affected_packages ( affected_range text NOT NULL, fixed_versions text[] DEFAULT '{}'::text[], overridden_advisory_fields jsonb DEFAULT '{}'::jsonb NOT NULL, + versions jsonb DEFAULT '[]'::jsonb NOT NULL, CONSTRAINT check_5dd528a2be CHECK ((char_length(package_name) <= 256)), CONSTRAINT check_80dea16c7b CHECK ((char_length(affected_range) <= 512)), CONSTRAINT check_d1d4646298 CHECK ((char_length(solution) <= 2048)), @@ -33828,6 +33829,10 @@ CREATE INDEX tmp_idx_for_feedback_comment_processing ON vulnerability_feedback U CREATE INDEX tmp_idx_for_vulnerability_feedback_migration ON vulnerability_feedback USING btree (id) WHERE ((migrated_to_state_transition = false) AND (feedback_type = 0)); +CREATE INDEX tmp_idx_orphaned_approval_merge_request_rules ON approval_merge_request_rules USING btree (id) WHERE ((report_type = ANY (ARRAY[2, 4])) AND (security_orchestration_policy_configuration_id IS NULL)); + +CREATE INDEX tmp_idx_orphaned_approval_project_rules ON approval_project_rules USING btree (id) WHERE ((report_type = ANY (ARRAY[2, 4])) AND (security_orchestration_policy_configuration_id IS NULL)); + CREATE INDEX tmp_idx_packages_on_project_id_when_npm_not_pending_destruction ON packages_packages USING btree (project_id) WHERE ((package_type = 2) AND (status <> 4)); CREATE INDEX tmp_idx_vuln_reads_where_dismissal_reason_null ON vulnerability_reads USING btree (id) WHERE ((state = 2) AND (dismissal_reason IS NULL)); diff --git a/doc/architecture/blueprints/remote_development/index.md b/doc/architecture/blueprints/remote_development/index.md index ce55f23f828..c7d1ec29add 100644 --- a/doc/architecture/blueprints/remote_development/index.md +++ b/doc/architecture/blueprints/remote_development/index.md @@ -483,6 +483,40 @@ RestartRequested : User has requested a workspace restart.\n**desired_state** wi RestartRequested -left-> Running : status=Running ``` +## Injecting environment variables and files into a workspace + +Like CI, there is a need to inject environment variables and files into a workspace. These environment variables and files will be frozen in time during workspace creation to ensure the same values are injected into the workspace every time it starts/restarts. Thus, a new database table, on the lines of `ci_job_variables` will be required. This table will contain the following columns - + +- `key` - To store the name of the environment variable or the file. +- `encrypted_value` - To store the encrypted value of the environment variable or the file. +- `encrypted_value_iv` - To store the initialization vector used for encryption. +- `workspace_id` - To reference the workspace the environment variable or the file is to be injected into. +- `variable_type` - To store whether this data is to be injected as an environment variable or a file. + +To perform the encryption, a secret key would be required. This would be uniquely generated for each workpsace upon creation. +Having a unique secret key which is used for encrypting the corresponding workspace's environment variable and file data in the workspace, improves the security profile. + +Because of the nature of reconciliation loop between Agent and Rails, it is not scalable to decrypt these values at Rails side for each request. +Instead, the `key`, `encrypted_value` and `encrypted_value_iv` of each environment variable of the workspace are sent to the Agent along with the workspace's `secret_key` +for the Agent to decrypt them in place. + +To optimize this further, the data about the environment variables and files along with the secret key will only be sent when required i.e. + +- When new workspace creation request has been received from the user and an Agent initiates a Partial Reonciliation request +- When an Agent initiates a Full Reconciliation request + +When a workspace is created from a project, it will inherit all the variables from the group/subgroup/project hierarchy which are defined under +[`Settings > CI/CD > Variables`](../../../ci/variables/index.md#define-a-cicd-variable-in-the-ui). This aspect will be generalized to allow for defining `Variables` +which will be inherited in both CI/CD and Workspaces. + +A user will also be able to define, at a user level, environment variables and files to be injected into each workspace created by them. + +When a new workspace is created, a new personal access token associated to the user who created the workspace will be generated. +This personal access token will be tied to the lifecycle of the workspace and will be injected into the workspace as an environment variable or a file +to allow for cloning private projects and supporting transparent Git operations from within the workspace out-of-the-box among other things. + +More details about the implementation details can be found in this [epic](https://gitlab.com/groups/gitlab-org/-/epics/10882). + ## Workspace user traffic authentication and authorization We need to only allow certain users to access workspaces. Currently, we are restricting this to the creator/owner of the workspace. After the workspace is created, it needs to be exposed to the network so that the user can connect to it. diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md index f378b0ae301..c064626dda7 100644 --- a/doc/user/profile/notifications.md +++ b/doc/user/profile/notifications.md @@ -199,6 +199,8 @@ Users are notified of the following events: | Two-factor authentication disabled | User | Security email, always sent. | | User added to group | User | Sent when user is added to group. | | User added to project | User | Sent when user is added to project. | +| Group access expired | Group members | Sent when user's access to a group expires in seven days. _[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12704) in GitLab 16.3._ | +| Project access expired | Project members | Sent when user's access to a project expires in seven days. _[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12704) in GitLab 16.3._ | ## Notifications on issues, merge requests, and epics @@ -331,6 +333,13 @@ The participants are: - Authors of comments on the design. - Anyone that is [mentioned](../discussions/index.md#mentions) in a comment on the design. +## Notifications on group or project access expiration + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12704) in GitLab 16.3. + +GitLab sends an email notification if a user's access to a group or project expires in seven days. +This reminds group or project members to extend their access duration if they want to. + ## Opt out of all GitLab emails If you no longer wish to receive any email notifications: diff --git a/lib/api/entities/commit_status.rb b/lib/api/entities/commit_status.rb index df6a41895ff..14ec3ba461b 100644 --- a/lib/api/entities/commit_status.rb +++ b/lib/api/entities/commit_status.rb @@ -18,6 +18,7 @@ module API expose :finished_at, documentation: { type: 'dateTime', example: '2016-01-21T08:40:25.832Z' } expose :allow_failure, documentation: { type: 'boolean', example: false } expose :coverage, documentation: { type: 'number', format: 'float', example: 98.29 } + expose :pipeline_id, documentation: { type: 'integer', example: 101 } expose :author, using: Entities::UserBasic end diff --git a/lib/gitlab/alert_management/payload.rb b/lib/gitlab/alert_management/payload.rb index de34a0f5d47..c6244124022 100644 --- a/lib/gitlab/alert_management/payload.rb +++ b/lib/gitlab/alert_management/payload.rb @@ -18,31 +18,20 @@ module Gitlab # @param monitoring_tool [String] # @param integration [AlertManagement::HttpIntegration] def parse(project, payload, monitoring_tool: nil, integration: nil) - payload_class = payload_class_for( - monitoring_tool: monitoring_tool || payload&.dig('monitoring_tool'), - payload: payload - ) + payload_class = payload_class_for(monitoring_tool: monitoring_tool || payload&.dig('monitoring_tool')) payload_class.new(project: project, payload: payload, integration: integration) end private - def payload_class_for(monitoring_tool:, payload:) + def payload_class_for(monitoring_tool:) if monitoring_tool == MONITORING_TOOLS[:prometheus] - if gitlab_managed_prometheus?(payload) - ::Gitlab::AlertManagement::Payload::ManagedPrometheus - else - ::Gitlab::AlertManagement::Payload::Prometheus - end + ::Gitlab::AlertManagement::Payload::Prometheus else ::Gitlab::AlertManagement::Payload::Generic end end - - def gitlab_managed_prometheus?(payload) - payload&.dig('labels', 'gitlab_alert_id').present? - end end end end diff --git a/lib/gitlab/alert_management/payload/managed_prometheus.rb b/lib/gitlab/alert_management/payload/managed_prometheus.rb deleted file mode 100644 index 4ed21108d3e..00000000000 --- a/lib/gitlab/alert_management/payload/managed_prometheus.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -# Attribute mapping for alerts via prometheus alerting integration, -# and for which payload includes gitlab-controlled attributes. -module Gitlab - module AlertManagement - module Payload - class ManagedPrometheus < ::Gitlab::AlertManagement::Payload::Prometheus - attribute :gitlab_prometheus_alert_id, - paths: %w(labels gitlab_prometheus_alert_id), - type: :integer - attribute :metric_id, - paths: %w(labels gitlab_alert_id), - type: :integer - - def gitlab_alert - strong_memoize(:gitlab_alert) do - next unless metric_id || gitlab_prometheus_alert_id - - alerts = Projects::Prometheus::AlertsFinder - .new(project: project, metric: metric_id, id: gitlab_prometheus_alert_id) - .execute - - next if alerts.blank? || alerts.size > 1 - - alerts.first - end - end - - def full_query - gitlab_alert&.full_query || super - end - - def environment - gitlab_alert&.environment || super - end - - private - - def plain_gitlab_fingerprint - [metric_id, starts_at_raw].join('/') - end - end - end - end -end diff --git a/lib/gitlab/metrics/dashboard/service_selector.rb b/lib/gitlab/metrics/dashboard/service_selector.rb index 632f6ce6160..d192fc04e89 100644 --- a/lib/gitlab/metrics/dashboard/service_selector.rb +++ b/lib/gitlab/metrics/dashboard/service_selector.rb @@ -15,7 +15,6 @@ module Gitlab SERVICES = [ ::Metrics::Dashboard::GitlabAlertEmbedService, ::Metrics::Dashboard::CustomMetricEmbedService, - ::Metrics::Dashboard::DynamicEmbedService, ::Metrics::Dashboard::DefaultEmbedService, ::Metrics::Dashboard::SystemDashboardService, ::Metrics::Dashboard::CustomDashboardService diff --git a/lib/gitlab/metrics/global_search_slis.rb b/lib/gitlab/metrics/global_search_slis.rb index c361d755a12..530bebd72ab 100644 --- a/lib/gitlab/metrics/global_search_slis.rb +++ b/lib/gitlab/metrics/global_search_slis.rb @@ -11,6 +11,7 @@ module Gitlab BASIC_CODE_TARGET_S = 27.538 ADVANCED_CONTENT_TARGET_S = 2.452 ADVANCED_CODE_TARGET_S = 15.52 + ZOEKT_TARGET_S = 15.52 def initialize_slis! Gitlab::Metrics::Sli::Apdex.initialize_sli(:global_search, possible_labels) @@ -42,6 +43,8 @@ module Gitlab ADVANCED_CONTENT_TARGET_S elsif search_type == 'advanced' && code_search?(search_scope) ADVANCED_CODE_TARGET_S + elsif search_type == 'zoekt' && code_search?(search_scope) + ZOEKT_TARGET_S end end diff --git a/lib/gitlab/quick_actions/relate_actions.rb b/lib/gitlab/quick_actions/relate_actions.rb index 058c1e7e9bf..294ddd985de 100644 --- a/lib/gitlab/quick_actions/relate_actions.rb +++ b/lib/gitlab/quick_actions/relate_actions.rb @@ -36,7 +36,7 @@ module Gitlab extract_references(issue_param, :issue).first end command :unlink do |issue| - link = IssueLink.for_issues(quick_action_target, issue).first + link = IssueLink.for_items(quick_action_target, issue).first if link call_link_service(IssueLinks::DestroyService.new(link, current_user)) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a860f00e658..96fb9d44511 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1795,9 +1795,6 @@ msgstr "" msgid "A file was not found." msgstr "" -msgid "A file with '%{file_name}' already exists in %{branch} branch" -msgstr "" - msgid "A file with this name already exists." msgstr "" @@ -4258,6 +4255,12 @@ msgstr "" msgid "AdvancedSearch|You are using outdated code search mappings. To improve code search quality, we recommend you use %{reindexing_link_start}zero-downtime reindexing%{link_end} or %{recreate_link_start}re-create your index%{link_end}." msgstr "" +msgid "AdvancedSearch|You have %{count} pending %{migrations_link_start}advanced search migrations%{link_end} that are obsolete. These migrations might affect your search experience. To resolve the issue, you must %{recreate_link_start}recreate your index%{link_end}." +msgstr "" + +msgid "AdvancedSearch|You have pending obsolete migrations" +msgstr "" + msgid "After a successful password update you will be redirected to login screen." msgstr "" @@ -39390,9 +39393,6 @@ msgstr "" msgid "Request details" msgstr "" -msgid "Request parameter %{param} is missing." -msgstr "" - msgid "Request review from" msgstr "" @@ -47153,12 +47153,6 @@ msgstr "" msgid "There was a problem updating the keep latest artifacts setting." msgstr "" -msgid "There was an error creating the dashboard, branch name is invalid." -msgstr "" - -msgid "There was an error creating the dashboard, branch named: %{branch} already exists." -msgstr "" - msgid "There was an error creating the issue" msgstr "" @@ -54002,12 +53996,6 @@ msgstr "" msgid "Your current password is required to register a two-factor authenticator app." msgstr "" -msgid "Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}." -msgstr "" - -msgid "Your dashboard has been updated. You can %{web_ide_link_start}edit it here%{web_ide_link_end}." -msgstr "" - msgid "Your deployment services will be broken, you will need to manually fix the services after renaming." msgstr "" @@ -54432,6 +54420,9 @@ msgstr "" msgid "can't reference a branch that does not exist" msgstr "" +msgid "cannot assign a linked work item as a parent" +msgstr "" + msgid "cannot assign a non-confidential work item to a confidential parent. Make the work item confidential and try again." msgstr "" @@ -55196,6 +55187,9 @@ msgstr "" msgid "is" msgstr "" +msgid "is a parent or child of this %{item}" +msgstr "" + msgid "is already associated to a GitLab Issue. New issue will not be associated." msgstr "" diff --git a/qa/Gemfile b/qa/Gemfile index 1aa0785620a..887d7939fd3 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -24,7 +24,7 @@ gem 'rotp', '~> 6.2.2' gem 'parallel', '~> 1.23' gem 'rainbow', '~> 3.1.1' gem 'rspec-parameterized', '~> 1.0.0' -gem 'octokit', '~> 6.1.1' +gem 'octokit', '~> 7.0.0' gem "faraday-retry", "~> 2.2" gem 'zeitwerk', '~> 2.6', '>= 2.6.8' gem 'influxdb-client', '~> 2.9' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 4a29d81d5b9..d61eda6ff1e 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -213,7 +213,7 @@ GEM nokogiri (1.15.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) - octokit (6.1.1) + octokit (7.0.0) faraday (>= 1, < 3) sawyer (~> 0.9) oj (3.13.23) @@ -354,7 +354,7 @@ DEPENDENCIES influxdb-client (~> 2.9) knapsack (~> 4.0) nokogiri (~> 1.15, >= 1.15.3) - octokit (~> 6.1.1) + octokit (~> 7.0.0) parallel (~> 1.23) parallel_tests (~> 4.2, >= 4.2.1) pry-byebug (~> 3.10.1) diff --git a/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb b/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb deleted file mode 100644 index 02407e31756..00000000000 --- a/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb +++ /dev/null @@ -1,287 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Projects::PerformanceMonitoring::DashboardsController, feature_category: :metrics do - let_it_be(:user) { create(:user) } - let_it_be(:namespace) { create(:namespace) } - - let_it_be(:project) { create(:project, :repository, namespace: namespace) } - let(:repository) { project.repository } - let(:branch) { double(name: branch_name) } - let(:commit_message) { 'test' } - let(:branch_name) { "#{Time.current.to_i}_dashboard_new_branch" } - let(:dashboard) { 'config/prometheus/common_metrics.yml' } - let(:file_name) { 'custom_dashboard.yml' } - let(:params) do - { - namespace_id: namespace, - project_id: project, - dashboard: dashboard, - file_name: file_name, - commit_message: commit_message, - branch: branch_name, - format: :json - } - end - - before do - stub_feature_flags(remove_monitor_metrics: false) - end - - describe 'POST #create' do - context 'authenticated user' do - before do - sign_in(user) - end - - context 'project with repository feature' do - context 'with rights to push to the repository' do - before do - project.add_maintainer(user) - end - - context 'valid parameters' do - it 'delegates cloning to ::Metrics::Dashboard::CloneDashboardService' do - allow(controller).to receive(:repository).and_return(repository) - allow(repository).to receive(:find_branch).and_return(branch) - dashboard_attrs = { - dashboard: dashboard, - file_name: file_name, - commit_message: commit_message, - branch: branch_name - } - - service_instance = instance_double(::Metrics::Dashboard::CloneDashboardService) - expect(::Metrics::Dashboard::CloneDashboardService).to receive(:new).with(project, user, dashboard_attrs).and_return(service_instance) - expect(service_instance).to receive(:execute).and_return(status: :success, http_status: :created, dashboard: { path: 'dashboard/path' }) - - post :create, params: params - end - - context 'request format json' do - it 'returns services response' do - allow(::Metrics::Dashboard::CloneDashboardService).to receive(:new).and_return(double(execute: { status: :success, dashboard: { path: ".gitlab/dashboards/#{file_name}" }, http_status: :created })) - allow(controller).to receive(:repository).and_return(repository) - allow(repository).to receive(:find_branch).and_return(branch) - - post :create, params: params - - expect(response).to have_gitlab_http_status :created - expect(controller).to set_flash[:notice].to eq("Your dashboard has been copied. You can <a href=\"/-/ide/project/#{project.full_path}/edit/#{branch_name}/-/.gitlab/dashboards/#{file_name}\">edit it here</a>.") - expect(json_response).to eq('status' => 'success', 'dashboard' => { 'path' => ".gitlab/dashboards/#{file_name}" }) - end - - context 'Metrics::Dashboard::CloneDashboardService failure' do - it 'returns json with failure message', :aggregate_failures do - allow(::Metrics::Dashboard::CloneDashboardService).to receive(:new).and_return(double(execute: { status: :error, message: 'something went wrong', http_status: :bad_request })) - - post :create, params: params - - expect(response).to have_gitlab_http_status :bad_request - expect(json_response).to eq('error' => 'something went wrong') - end - end - - %w(commit_message file_name dashboard).each do |param| - context "param #{param} is missing" do - let(param.to_s) { nil } - - it 'responds with bad request status and error message', :aggregate_failures do - post :create, params: params - - expect(response).to have_gitlab_http_status :bad_request - expect(json_response).to eq('error' => "Request parameter #{param} is missing.") - end - end - end - - context "param branch_name is missing" do - let(:branch_name) { nil } - - it 'responds with bad request status and error message', :aggregate_failures do - post :create, params: params - - expect(response).to have_gitlab_http_status :bad_request - expect(json_response).to eq('error' => "Request parameter branch is missing.") - end - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns 404 not found' do - post :create, params: params - - expect(response).to have_gitlab_http_status :not_found - end - end - end - end - end - - context 'without rights to push to repository' do - before do - project.add_guest(user) - end - - it 'responds with :forbidden status code' do - post :create, params: params - - expect(response).to have_gitlab_http_status :forbidden - end - end - end - - context 'project without repository feature' do - let_it_be(:project) { create(:project, namespace: namespace) } - - it 'responds with :not_found status code' do - post :create, params: params - - expect(response).to have_gitlab_http_status :not_found - end - end - end - end - - describe 'PUT #update' do - context 'authenticated user' do - before do - sign_in(user) - end - - let(:file_content) do - { - "dashboard" => "Dashboard Title", - "panel_groups" => [{ - "group" => "Group Title", - "panels" => [{ - "type" => "area-chart", - "title" => "Chart Title", - "y_label" => "Y-Axis", - "metrics" => [{ - "id" => "metric_of_ages", - "unit" => "count", - "label" => "Metric of Ages", - "query_range" => "http_requests_total" - }] - }] - }] - } - end - - let(:params) do - { - namespace_id: namespace, - project_id: project, - dashboard: dashboard, - file_name: file_name, - file_content: file_content, - commit_message: commit_message, - branch: branch_name, - format: :json - } - end - - context 'project with repository feature' do - context 'with rights to push to the repository' do - before do - project.add_maintainer(user) - end - - context 'valid parameters' do - context 'request format json' do - let(:update_dashboard_service_params) { params.except(:namespace_id, :project_id, :format) } - - let(:update_dashboard_service_results) do - { - status: :success, - http_status: :created, - dashboard: { - path: ".gitlab/dashboards/custom_dashboard.yml", - display_name: "custom_dashboard.yml", - default: false, - system_dashboard: false - } - } - end - - let(:update_dashboard_service) { instance_double(::Metrics::Dashboard::UpdateDashboardService, execute: update_dashboard_service_results) } - - it 'returns path to new file' do - allow(controller).to receive(:repository).and_return(repository) - allow(repository).to receive(:find_branch).and_return(branch) - allow(::Metrics::Dashboard::UpdateDashboardService).to receive(:new).with(project, user, update_dashboard_service_params).and_return(update_dashboard_service) - - put :update, params: params - - expect(response).to have_gitlab_http_status :created - expect(controller).to set_flash[:notice].to eq("Your dashboard has been updated. You can <a href=\"/-/ide/project/#{project.full_path}/edit/#{branch_name}/-/.gitlab/dashboards/#{file_name}\">edit it here</a>.") - expect(json_response).to eq('status' => 'success', 'dashboard' => { 'default' => false, 'display_name' => "custom_dashboard.yml", 'path' => ".gitlab/dashboards/#{file_name}", 'system_dashboard' => false }) - end - - context 'UpdateDashboardService failure' do - it 'returns json with failure message' do - allow(::Metrics::Dashboard::UpdateDashboardService).to receive(:new).and_return(double(execute: { status: :error, message: 'something went wrong', http_status: :bad_request })) - - put :update, params: params - - expect(response).to have_gitlab_http_status :bad_request - expect(json_response).to eq('error' => 'something went wrong') - end - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns 404 not found' do - put :update, params: params - - expect(response).to have_gitlab_http_status :not_found - end - end - end - end - - context 'missing branch' do - let(:branch_name) { nil } - - it 'raises responds with :bad_request status code and error message' do - put :update, params: params - - expect(response).to have_gitlab_http_status :bad_request - expect(json_response).to eq('error' => "Request parameter branch is missing.") - end - end - end - - context 'without rights to push to repository' do - before do - project.add_guest(user) - end - - it 'responds with :forbidden status code' do - put :update, params: params - - expect(response).to have_gitlab_http_status :forbidden - end - end - end - - context 'project without repository feature' do - let_it_be(:project) { create(:project, namespace: namespace) } - - it 'responds with :not_found status code' do - put :update, params: params - - expect(response).to have_gitlab_http_status :not_found - end - end - end - end -end diff --git a/spec/factories/work_items/related_work_item_links.rb b/spec/factories/work_items/related_work_item_links.rb new file mode 100644 index 00000000000..327323af803 --- /dev/null +++ b/spec/factories/work_items/related_work_item_links.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :work_item_link, class: 'WorkItems::RelatedWorkItemLink' do + source factory: :work_item + target factory: :work_item + end +end diff --git a/spec/frontend/ide/components/ide_review_spec.js b/spec/frontend/ide/components/ide_review_spec.js index 7ae8cfac935..59b87144e70 100644 --- a/spec/frontend/ide/components/ide_review_spec.js +++ b/spec/frontend/ide/components/ide_review_spec.js @@ -14,6 +14,7 @@ Vue.use(Vuex); describe('IDE review mode', () => { let wrapper; let store; + let dispatch; beforeEach(() => { store = createStore(); @@ -25,27 +26,28 @@ describe('IDE review mode', () => { loading: false, }); + dispatch = jest.spyOn(store, 'dispatch'); + wrapper = mount(keepAlive(IdeReview), { store, }); }); + const findEditorModeDropdown = () => wrapper.findComponent(EditorModeDropdown); + it('renders list of files', () => { expect(wrapper.text()).toContain('fileName'); }); describe('activated', () => { - let inititializeSpy; - beforeEach(async () => { - inititializeSpy = jest.spyOn(wrapper.findComponent(IdeReview).vm, 'initialize'); store.state.viewer = 'editor'; await wrapper.vm.reactivate(); }); it('re initializes the component', () => { - expect(inititializeSpy).toHaveBeenCalled(); + expect(dispatch).toHaveBeenCalledWith('updateViewer', 'diff'); }); it('updates viewer to "diff" by default', () => { @@ -81,7 +83,7 @@ describe('IDE review mode', () => { }); it('renders edit dropdown', () => { - expect(wrapper.findComponent(EditorModeDropdown).exists()).toBe(true); + expect(findEditorModeDropdown().exists()).toBe(true); }); it('renders merge request link & IID', async () => { diff --git a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb b/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb deleted file mode 100644 index d7184c89933..00000000000 --- a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::AlertManagement::Payload::ManagedPrometheus do - let_it_be(:project) { create(:project) } - - let(:raw_payload) { {} } - - let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) } - - it_behaves_like 'subclass has expected api' - - shared_context 'with gitlab alert' do - let_it_be(:gitlab_alert) { create(:prometheus_alert, project: project) } - let(:metric_id) { gitlab_alert.prometheus_metric_id.to_s } - let(:alert_id) { gitlab_alert.id.to_s } - end - - describe '#metric_id' do - subject { parsed_payload.metric_id } - - it { is_expected.to be_nil } - - context 'with gitlab_alert_id' do - let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => '12' } } } - - it { is_expected.to eq(12) } - end - end - - describe '#gitlab_prometheus_alert_id' do - subject { parsed_payload.gitlab_prometheus_alert_id } - - it { is_expected.to be_nil } - - context 'with gitlab_alert_id' do - let(:raw_payload) { { 'labels' => { 'gitlab_prometheus_alert_id' => '12' } } } - - it { is_expected.to eq(12) } - end - end - - describe '#gitlab_alert' do - subject { parsed_payload.gitlab_alert } - - context 'without alert info in payload' do - it { is_expected.to be_nil } - end - - context 'with metric id in payload' do - let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } } - let(:metric_id) { '-1' } - - context 'without matching alert' do - it { is_expected.to be_nil } - end - - context 'with matching alert' do - include_context 'with gitlab alert' - - it { is_expected.to eq(gitlab_alert) } - - context 'when unclear which alert applies' do - # With multiple alerts for different environments, - # we can't be sure which prometheus alert the payload - # belongs to - let_it_be(:another_alert) do - create(:prometheus_alert, - prometheus_metric: gitlab_alert.prometheus_metric, - project: project) - end - - it { is_expected.to be_nil } - end - end - end - - context 'with alert id' do - # gitlab_prometheus_alert_id is a stronger identifier, - # but was added after gitlab_alert_id; we won't - # see it without gitlab_alert_id also present - let(:raw_payload) do - { - 'labels' => { - 'gitlab_alert_id' => metric_id, - 'gitlab_prometheus_alert_id' => alert_id - } - } - end - - context 'without matching alert' do - let(:alert_id) { '-1' } - let(:metric_id) { '-1' } - - it { is_expected.to be_nil } - end - - context 'with matching alerts' do - include_context 'with gitlab alert' - - it { is_expected.to eq(gitlab_alert) } - end - end - end - - describe '#full_query' do - subject { parsed_payload.full_query } - - it { is_expected.to be_nil } - - context 'with gitlab alert' do - include_context 'with gitlab alert' - - let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } } - - it { is_expected.to eq(gitlab_alert.full_query) } - end - - context 'with sufficient fallback info' do - let(:raw_payload) { { 'generatorURL' => 'http://localhost:9090/graph?g0.expr=vector%281%29' } } - - it { is_expected.to eq('vector(1)') } - end - end - - describe '#environment' do - subject { parsed_payload.environment } - - context 'with gitlab alert' do - include_context 'with gitlab alert' - - let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } } - - it { is_expected.to eq(gitlab_alert.environment) } - end - - context 'with sufficient fallback info' do - let_it_be(:environment) { create(:environment, project: project, name: 'production') } - - let(:raw_payload) do - { - 'labels' => { - 'gitlab_alert_id' => '-1', - 'gitlab_environment_name' => 'production' - } - } - end - - it { is_expected.to eq(environment) } - end - end -end diff --git a/spec/lib/gitlab/alert_management/payload_spec.rb b/spec/lib/gitlab/alert_management/payload_spec.rb index efde7ed3772..fe14e6ae53c 100644 --- a/spec/lib/gitlab/alert_management/payload_spec.rb +++ b/spec/lib/gitlab/alert_management/payload_spec.rb @@ -19,12 +19,6 @@ RSpec.describe Gitlab::AlertManagement::Payload do let(:payload) { { 'monitoring_tool' => 'Prometheus' } } it { is_expected.to be_a Gitlab::AlertManagement::Payload::Prometheus } - - context 'with gitlab-managed attributes' do - let(:payload) { { 'monitoring_tool' => 'Prometheus', 'labels' => { 'gitlab_alert_id' => '12' } } } - - it { is_expected.to be_a Gitlab::AlertManagement::Payload::ManagedPrometheus } - end end context 'with the payload specifying an unknown tool' do @@ -43,12 +37,6 @@ RSpec.describe Gitlab::AlertManagement::Payload do context 'with an externally managed prometheus payload' do it { is_expected.to be_a Gitlab::AlertManagement::Payload::Prometheus } end - - context 'with a self-managed prometheus payload' do - let(:payload) { { 'labels' => { 'gitlab_alert_id' => '14' } } } - - it { is_expected.to be_a Gitlab::AlertManagement::Payload::ManagedPrometheus } - end end context 'as an unknown tool' do diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb index 991f8eff684..a8769098dd5 100644 --- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb @@ -80,20 +80,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store end context 'as a project-defined panel' do - let(:dashboard_path) { '.gitlab/dashboard/test.yml' } - let(:params) do - { - environment: environment, - embedded: true, - dashboard_path: dashboard_path, - group: 'Group A', - title: 'Super Chart A1', - y_label: 'y_label' - } - end - - it_behaves_like 'misconfigured dashboard service response', :not_found - context 'when the metric exists' do let(:project) { project_with_dashboard(dashboard_path) } diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb index cbf8a40d4fc..82f3b1bfce9 100644 --- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb @@ -35,33 +35,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::ServiceSelector do it { is_expected.to be Metrics::Dashboard::DefaultEmbedService } end - context 'when all the chart identifiers are provided' do - let(:arguments) do - { - embedded: true, - dashboard_path: '.gitlab/dashboards/test.yml', - group: 'Important Metrics', - title: 'Total Requests', - y_label: 'req/sec' - } - end - - it { is_expected.to be Metrics::Dashboard::DynamicEmbedService } - end - - context 'when all chart params expect dashboard_path are provided' do - let(:arguments) do - { - embedded: true, - group: 'Important Metrics', - title: 'Total Requests', - y_label: 'req/sec' - } - end - - it { is_expected.to be Metrics::Dashboard::DynamicEmbedService } - end - context 'with a system dashboard and "custom" group' do let(:arguments) do { diff --git a/spec/lib/gitlab/metrics/global_search_slis_spec.rb b/spec/lib/gitlab/metrics/global_search_slis_spec.rb index 5248cd08770..68793db6e41 100644 --- a/spec/lib/gitlab/metrics/global_search_slis_spec.rb +++ b/spec/lib/gitlab/metrics/global_search_slis_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Metrics::GlobalSearchSlis do +RSpec.describe Gitlab::Metrics::GlobalSearchSlis, feature_category: :global_search do using RSpec::Parameterized::TableSyntax describe '#initialize_slis!' do @@ -92,6 +92,7 @@ RSpec.describe Gitlab::Metrics::GlobalSearchSlis do 'basic' | true | 27.538 'advanced' | false | 2.452 'advanced' | true | 15.52 + 'zoekt' | true | 15.52 end with_them do diff --git a/spec/mailers/emails/projects_spec.rb b/spec/mailers/emails/projects_spec.rb index 1f0f09f7ca2..9518672939b 100644 --- a/spec/mailers/emails/projects_spec.rb +++ b/spec/mailers/emails/projects_spec.rb @@ -124,41 +124,6 @@ RSpec.describe Emails::Projects do end end - context 'with gitlab alerting rule' do - let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) } - let_it_be(:environment) { prometheus_alert.environment } - - let(:alert) { create(:alert_management_alert, :prometheus, :from_payload, payload: payload, project: project) } - let(:title) { "#{prometheus_alert.title} #{prometheus_alert.computed_operator} #{prometheus_alert.threshold}" } - - before do - payload['labels'] = { - 'gitlab_alert_id' => prometheus_alert.prometheus_metric_id, - 'alertname' => prometheus_alert.title - } - end - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like 'a user cannot unsubscribe through footer link' - it_behaves_like 'shows the incident issues url' - - it 'has expected subject' do - is_expected.to have_subject("#{project.name} | Alert: #{environment.name}: #{title} for 5 minutes") - end - - it 'has expected content' do - is_expected.to have_body_text('An alert has been triggered') - is_expected.to have_body_text(project.full_path) - is_expected.to have_body_text(alert.details_url) - is_expected.to have_body_text('Environment:') - is_expected.to have_body_text(environment.name) - is_expected.to have_body_text('Metric:') - is_expected.to have_body_text(prometheus_alert.full_query) - is_expected.not_to have_body_text('Description:') - end - end - context 'resolved' do let_it_be(:alert) { create(:alert_management_alert, :resolved, project: project) } diff --git a/spec/models/issue_link_spec.rb b/spec/models/issue_link_spec.rb index d69a3f2954c..7b06bec7b31 100644 --- a/spec/models/issue_link_spec.rb +++ b/spec/models/issue_link_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe IssueLink do +RSpec.describe IssueLink, feature_category: :portfolio_management do + let_it_be(:project) { create(:project) } + it_behaves_like 'issuable link' do let_it_be_with_reload(:issuable_link) { create(:issue_link) } let_it_be(:issuable) { create(:issue) } @@ -14,46 +16,11 @@ RSpec.describe IssueLink do it { expect(described_class.issuable_type).to eq(:issue) } end - describe 'Scopes' do - let_it_be(:issue1) { create(:issue) } - let_it_be(:issue2) { create(:issue) } - - describe '.for_source_issue' do - it 'includes linked issues for source issue' do - source_issue = create(:issue) - issue_link_1 = create(:issue_link, source: source_issue, target: issue1) - issue_link_2 = create(:issue_link, source: source_issue, target: issue2) - - result = described_class.for_source_issue(source_issue) - - expect(result).to contain_exactly(issue_link_1, issue_link_2) - end - end - - describe '.for_target_issue' do - it 'includes linked issues for target issue' do - target_issue = create(:issue) - issue_link_1 = create(:issue_link, source: issue1, target: target_issue) - issue_link_2 = create(:issue_link, source: issue2, target: target_issue) - - result = described_class.for_target_issue(target_issue) - - expect(result).to contain_exactly(issue_link_1, issue_link_2) - end - end - - describe '.for_issues' do - let_it_be(:issue) { create(:issue) } - let_it_be(:source_link) { create(:issue_link, source: issue, target: issue1) } - let_it_be(:target_link) { create(:issue_link, source: issue2, target: issue) } - - it 'includes links when issue is source' do - expect(described_class.for_issues(issue, issue1)).to contain_exactly(source_link) - end - - it 'includes links when issue is target' do - expect(described_class.for_issues(issue, issue2)).to contain_exactly(target_link) - end - end + it_behaves_like 'includes LinkableItem concern' do + let_it_be(:item) { create(:issue, project: project) } + let_it_be(:item1) { create(:issue, project: project) } + let_it_be(:item2) { create(:issue, project: project) } + let_it_be(:link_factory) { :issue_link } + let_it_be(:item_type) { 'issue' } end end diff --git a/spec/models/work_items/parent_link_spec.rb b/spec/models/work_items/parent_link_spec.rb index d7f87da1965..3fcfa856db4 100644 --- a/spec/models/work_items/parent_link_spec.rb +++ b/spec/models/work_items/parent_link_spec.rb @@ -18,9 +18,9 @@ RSpec.describe WorkItems::ParentLink, feature_category: :portfolio_management do it { is_expected.to validate_uniqueness_of(:work_item) } describe 'hierarchy' do - let_it_be(:issue) { build(:work_item, project: project) } + let_it_be(:issue) { create(:work_item, project: project) } let_it_be(:incident) { build(:work_item, :incident, project: project) } - let_it_be(:task1) { build(:work_item, :task, project: project) } + let_it_be(:task1) { create(:work_item, :task, project: project) } let_it_be(:task2) { build(:work_item, :task, project: project) } it 'is valid if issue parent has task child' do @@ -158,6 +158,38 @@ RSpec.describe WorkItems::ParentLink, feature_category: :portfolio_management do end end end + + context 'when parent is already linked' do + shared_examples 'invalid link' do |link_factory| + let_it_be(:parent_link) { build(:parent_link, work_item_parent: issue, work_item: task1) } + let(:error_msg) { 'cannot assign a linked work item as a parent' } + + context 'when parent is the link target' do + before do + create(link_factory, source_id: task1.id, target_id: issue.id) + end + + it do + expect(parent_link).not_to be_valid + expect(parent_link.errors[:work_item]).to include(error_msg) + end + end + + context 'when parent is the link source' do + before do + create(link_factory, source_id: issue.id, target_id: task1.id) + end + + it do + expect(parent_link).not_to be_valid + expect(parent_link.errors[:work_item]).to include(error_msg) + end + end + end + + it_behaves_like 'invalid link', :work_item_link + it_behaves_like 'invalid link', :issue_link + end end end @@ -178,6 +210,14 @@ RSpec.describe WorkItems::ParentLink, feature_category: :portfolio_management do expect(result).to include(link1, link2) end end + + describe 'for_children' do + it 'includes the correct records' do + result = described_class.for_children([task1.id, task2.id]) + + expect(result).to include(link1, link2) + end + end end context 'with confidential work items' do diff --git a/spec/models/work_items/related_work_item_link_spec.rb b/spec/models/work_items/related_work_item_link_spec.rb new file mode 100644 index 00000000000..612bdfd937f --- /dev/null +++ b/spec/models/work_items/related_work_item_link_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::RelatedWorkItemLink, type: :model, feature_category: :portfolio_management do + let_it_be(:project) { create(:project) } + let_it_be(:issue) { create(:work_item, :issue, project: project) } + + it_behaves_like 'issuable link' do + let_it_be_with_reload(:issuable_link) { create(:work_item_link) } + let_it_be(:issuable) { issue } + let(:issuable_class) { 'WorkItem' } + let(:issuable_link_factory) { :work_item_link } + end + + it_behaves_like 'includes LinkableItem concern' do + let_it_be(:item) { create(:work_item, project: project) } + let_it_be(:item1) { create(:work_item, project: project) } + let_it_be(:item2) { create(:work_item, project: project) } + let_it_be(:link_factory) { :work_item_link } + let_it_be(:item_type) { 'work item' } + end + + describe '.issuable_type' do + it { expect(described_class.issuable_type).to eq(:work_item) } + end +end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 7540e19e278..2f0e64cd4da 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -152,6 +152,7 @@ RSpec.describe API::CommitStatuses, feature_category: :continuous_integration do expect(json_response['ref']).not_to be_empty expect(json_response['target_url']).to be_nil expect(json_response['description']).to be_nil + expect(json_response['pipeline_id']).not_to be_nil if status == 'failed' expect(CommitStatus.find(json_response['id'])).to be_api_failure diff --git a/spec/requests/projects/blob_spec.rb b/spec/requests/projects/blob_spec.rb deleted file mode 100644 index 7d62619e76a..00000000000 --- a/spec/requests/projects/blob_spec.rb +++ /dev/null @@ -1,87 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Blobs', feature_category: :source_code_management do - let_it_be(:project) { create(:project, :public, :repository, lfs: true) } - - describe 'GET /:namespace_id/:project_id/-/blob/:id' do - subject(:request) do - get namespace_project_blob_path(namespace_id: project.namespace, project_id: project, id: id) - end - - context 'with LFS file' do - let(:id) { 'master/files/lfs/lfs_object.iso' } - let(:object_store_host) { 'http://127.0.0.1:9000' } - let(:connect_src) do - csp = response.headers['Content-Security-Policy'] - csp.split('; ').find { |src| src.starts_with?('connect-src') } - end - - let(:gitlab_config) do - Gitlab.config.gitlab.deep_merge( - 'content_security_policy' => { - 'enabled' => content_security_policy_enabled - } - ) - end - - let(:lfs_config) do - Gitlab.config.lfs.deep_merge( - 'enabled' => lfs_enabled, - 'object_store' => { - 'remote_directory' => 'lfs-objects', - 'enabled' => true, - 'proxy_download' => proxy_download, - 'connection' => { - 'endpoint' => object_store_host, - 'path_style' => true - } - } - ) - end - - before do - stub_config_setting(gitlab_config) - stub_lfs_setting(lfs_config) - stub_lfs_object_storage(proxy_download: proxy_download) - - request - end - - describe 'directly downloading lfs file' do - let(:lfs_enabled) { true } - let(:proxy_download) { false } - let(:content_security_policy_enabled) { true } - - it { expect(response).to have_gitlab_http_status(:success) } - - it { expect(connect_src).to include(object_store_host) } - - context 'when lfs is disabled' do - let(:lfs_enabled) { false } - - it { expect(response).to have_gitlab_http_status(:success) } - - it { expect(connect_src).not_to include(object_store_host) } - end - - context 'when content_security_policy is disabled' do - let(:content_security_policy_enabled) { false } - - it { expect(response).to have_gitlab_http_status(:success) } - - it { expect(connect_src).not_to include(object_store_host) } - end - - context 'when proxy download is enabled' do - let(:proxy_download) { true } - - it { expect(response).to have_gitlab_http_status(:success) } - - it { expect(connect_src).not_to include(object_store_host) } - end - end - end - end -end diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb index eb5f3808021..9fe77bf2b17 100644 --- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb +++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb @@ -66,22 +66,6 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService, feature_category: expect(alert.environment).to eq(environment) end end - - context 'prometheus alert given' do - let(:prometheus_alert) { create(:prometheus_alert, project: project) } - let(:alert) { project.alert_management_alerts.last } - - before do - payload['labels']['gitlab_alert_id'] = prometheus_alert.prometheus_metric_id - end - - it 'sets the prometheus alert and environment' do - execute - - expect(alert.prometheus_alert).to eq(prometheus_alert) - expect(alert.environment).to eq(prometheus_alert.environment) - end - end end context 'when alert payload is invalid' do diff --git a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb deleted file mode 100644 index 0818bdd8b9c..00000000000 --- a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb +++ /dev/null @@ -1,190 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_store_caching, feature_category: :metrics do - include MetricsDashboardHelpers - - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :repository) } - let_it_be(:environment) { create(:environment, project: project) } - - describe '#execute' do - subject(:service_call) { described_class.new(project, user, params).execute } - - let(:commit_message) { 'test' } - let(:branch) { "dashboard_new_branch" } - let(:dashboard) { 'config/prometheus/common_metrics.yml' } - let(:file_name) { 'custom_dashboard.yml' } - let(:file_content_hash) { YAML.safe_load(File.read(dashboard)) } - let(:params) do - { - dashboard: dashboard, - file_name: file_name, - commit_message: commit_message, - branch: branch - } - end - - context 'user does not have push right to repository' do - it_behaves_like 'misconfigured dashboard service response with stepable', :forbidden, 'You are not allowed to push into this branch. Create another branch or open a merge request.' - end - - context 'with rights to push to the repository' do - before do - project.add_maintainer(user) - end - - context 'wrong target file extension' do - let(:file_name) { 'custom_dashboard.txt' } - - it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'The file name should have a .yml extension' - end - - context 'wrong source dashboard file' do - let(:dashboard) { 'config/prometheus/common_metrics_123.yml' } - - it_behaves_like 'misconfigured dashboard service response with stepable', :not_found, 'Not found.' - end - - context 'path traversal attack attempt' do - let(:dashboard) { 'config/prometheus/../database.yml' } - - it_behaves_like 'misconfigured dashboard service response with stepable', :not_found, 'Not found.' - end - - context 'path traversal attack attempt on target file' do - let(:file_name) { '../../custom_dashboard.yml' } - let(:dashboard_attrs) do - { - commit_message: commit_message, - branch_name: branch, - start_branch: project.default_branch, - encoding: 'text', - file_path: ".gitlab/dashboards/custom_dashboard.yml", - file_content: file_content_hash.to_yaml - } - end - - it 'strips target file name to safe value', :aggregate_failures do - allow(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).and_return(double(process: file_content_hash)) - service_instance = instance_double(::Files::CreateService) - expect(::Files::CreateService).to receive(:new).with(project, user, dashboard_attrs).and_return(service_instance) - expect(service_instance).to receive(:execute).and_return(status: :success) - - service_call - end - end - - context 'valid parameters' do - before do - allow(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).and_return(double(process: file_content_hash)) - end - - it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH, - [ - ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter, - ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter - ] - - context 'selected branch already exists' do - let(:branch) { 'existing_branch' } - - before do - project.repository.add_branch(user, branch, 'master') - end - - it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'There was an error creating the dashboard, branch named: existing_branch already exists.' - - # temporary not available function for first iteration - # follow up issue https://gitlab.com/gitlab-org/gitlab/issues/196237 which - # require this feature - # it 'pass correct params to Files::CreateService', :aggregate_failures do - # project.repository.add_branch(user, branch, 'master') - # - # service_instance = instance_double(::Files::CreateService) - # expect(::Files::CreateService).to receive(:new).with(project, user, dashboard_attrs).and_return(service_instance) - # expect(service_instance).to receive(:execute).and_return(status: :success) - # - # service_call - # end - end - - context 'blank branch name' do - let(:branch) { '' } - - it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'There was an error creating the dashboard, branch name is invalid.' - end - - context 'dashboard file already exists' do - let(:branch) { 'custom_dashboard' } - - before do - Files::CreateService.new( - project, - user, - commit_message: 'Create custom dashboard custom_dashboard.yml', - branch_name: 'master', - start_branch: 'master', - file_path: ".gitlab/dashboards/custom_dashboard.yml", - file_content: File.read('config/prometheus/common_metrics.yml') - ).execute - end - - it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, "A file with 'custom_dashboard.yml' already exists in custom_dashboard branch" - end - - it 'extends dashboard template path to absolute url' do - allow(::Files::CreateService).to receive(:new).and_return(double(execute: { status: :success })) - - expect_file_read(Rails.root.join('config/prometheus/common_metrics.yml'), content: '') - - service_call - end - - context 'Files::CreateService success' do - before do - allow(::Files::CreateService).to receive(:new).and_return(double(execute: { status: :success })) - end - - it 'clears dashboards cache' do - expect(project.repository).to receive(:refresh_method_caches).with([:metrics_dashboard]) - - service_call - end - - it 'returns success', :aggregate_failures do - result = service_call - dashboard_details = { - path: '.gitlab/dashboards/custom_dashboard.yml', - display_name: 'custom_dashboard.yml', - default: false, - system_dashboard: false - } - - expect(result[:status]).to be :success - expect(result[:http_status]).to be :created - expect(result[:dashboard]).to match dashboard_details - end - end - - context 'Files::CreateService fails' do - before do - allow(::Files::CreateService).to receive(:new).and_return(double(execute: { status: :error })) - end - - it 'does NOT clear dashboards cache' do - expect(project.repository).not_to receive(:refresh_method_caches) - - service_call - end - - it 'returns error' do - result = service_call - expect(result[:status]).to be :error - end - end - end - end - end -end diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb deleted file mode 100644 index 1643f552a70..00000000000 --- a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb +++ /dev/null @@ -1,158 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching, - feature_category: :metrics do - include MetricsDashboardHelpers - - let_it_be(:project) { build(:project) } - let_it_be(:user) { create(:user) } - let_it_be(:environment) { create(:environment, project: project) } - - before do - project.add_maintainer(user) if user - end - - let(:dashboard_path) { '.gitlab/dashboards/test.yml' } - let(:group) { 'Group A' } - let(:title) { 'Super Chart A1' } - let(:y_label) { 'y_label' } - - describe '.valid_params?' do - let(:valid_params) do - { - embedded: true, - dashboard_path: dashboard_path, - group: group, - title: title, - y_label: y_label - } - end - - subject { described_class.valid_params?(params) } - - let(:params) { valid_params } - - it { is_expected.to be_truthy } - - context 'missing embedded' do - let(:params) { valid_params.except(:embedded) } - - it { is_expected.to be_falsey } - end - - context 'not embedded' do - let(:params) { valid_params.merge(embedded: 'false') } - - it { is_expected.to be_falsey } - end - - context 'undefined dashboard' do - let(:params) { valid_params.except(:dashboard_path) } - - it { is_expected.to be_truthy } - end - - context 'missing dashboard' do - let(:dashboard) { '' } - - it { is_expected.to be_truthy } - end - - context 'missing group' do - let(:group) { '' } - - it { is_expected.to be_falsey } - end - - context 'missing title' do - let(:title) { '' } - - it { is_expected.to be_falsey } - end - - context 'undefined y-axis label' do - let(:params) { valid_params.except(:y_label) } - - it { is_expected.to be_falsey } - end - end - - describe '#get_dashboard' do - let(:service_params) do - [ - project, - user, - { - environment: environment, - dashboard_path: dashboard_path, - group: group, - title: title, - y_label: y_label - } - ] - end - - let(:service_call) { described_class.new(*service_params).get_dashboard } - - context 'when the dashboard does not exist' do - it_behaves_like 'misconfigured dashboard service response', :not_found - end - - context 'when the dashboard is exists' do - let(:project) { project_with_dashboard(dashboard_path) } - - it_behaves_like 'valid embedded dashboard service response' - it_behaves_like 'raises error for users with insufficient permissions' - - it 'caches the unprocessed dashboard for subsequent calls' do - expect(YAML).to receive(:safe_load).once.and_call_original - - described_class.new(*service_params).get_dashboard - described_class.new(*service_params).get_dashboard - end - - context 'when the specified group is not present on the dashboard' do - let(:group) { 'Group Not Found' } - - it_behaves_like 'misconfigured dashboard service response', :not_found - end - - context 'when the specified title is not present on the dashboard' do - let(:title) { 'Title Not Found' } - - it_behaves_like 'misconfigured dashboard service response', :not_found - end - - context 'when the specified y-axis label is not present on the dashboard' do - let(:y_label) { 'Y-Axis Not Found' } - - it_behaves_like 'misconfigured dashboard service response', :not_found - end - end - - shared_examples 'uses system dashboard' do - it 'uses the overview dashboard' do - expect(Gitlab::Metrics::Dashboard::Finder) - .to receive(:find_raw) - .with(project, dashboard_path: system_dashboard_path) - .once - - service_call - end - end - - context 'when the dashboard is nil' do - let(:dashboard_path) { nil } - - it_behaves_like 'uses system dashboard' - end - - context 'when the dashboard is not present' do - let(:dashboard_path) { '' } - - it_behaves_like 'uses system dashboard' - end - end -end diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb index cc1f83ddc2b..73932887cd9 100644 --- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb +++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb @@ -17,91 +17,12 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService, feature_category: :i subject { service.execute(token_input) } context 'with valid payload' do - let_it_be(:alert_firing) { create(:prometheus_alert, project: project) } - let_it_be(:alert_resolved) { create(:prometheus_alert, project: project) } - let_it_be(:cluster, reload: true) { create(:cluster, :provided_by_user, projects: [project]) } - - let(:payload_raw) { prometheus_alert_payload(firing: [alert_firing], resolved: [alert_resolved]) } + let(:payload_raw) { prometheus_alert_payload(firing: ['Alert A'], resolved: ['Alert B']) } let(:payload) { ActionController::Parameters.new(payload_raw).permit! } let(:payload_alert_firing) { payload_raw['alerts'].first } let(:token) { 'token' } let(:source) { 'Prometheus' } - context 'with environment specific clusters' do - let(:prd_cluster) do - cluster - end - - let(:stg_cluster) do - create(:cluster, :provided_by_user, projects: [project], enabled: true, environment_scope: 'stg/*') - end - - let(:stg_environment) do - create(:environment, project: project, name: 'stg/1') - end - - let(:alert_firing) do - create(:prometheus_alert, project: project, environment: stg_environment) - end - - before do - create(:clusters_integrations_prometheus, cluster: prd_cluster, alert_manager_token: token) - create(:clusters_integrations_prometheus, cluster: stg_cluster, alert_manager_token: nil) - end - - context 'without token' do - let(:token_input) { nil } - - include_examples 'processes one firing and one resolved prometheus alerts' - end - - context 'with token' do - it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized - end - end - - context 'with project specific cluster using prometheus integration' do - where(:cluster_enabled, :integration_enabled, :configured_token, :token_input, :result) do - true | true | token | token | :success - true | true | nil | nil | :success - true | true | token | 'x' | :failure - true | true | token | nil | :failure - true | false | token | token | :failure - false | true | token | token | :failure - false | nil | nil | token | :failure - end - - with_them do - before do - cluster.update!(enabled: cluster_enabled) - - unless integration_enabled.nil? - create( - :clusters_integrations_prometheus, - cluster: cluster, - enabled: integration_enabled, - alert_manager_token: configured_token - ) - end - end - - case result = params[:result] - when :success - include_examples 'processes one firing and one resolved prometheus alerts' - when :failure - it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized - else - raise "invalid result: #{result.inspect}" - end - end - end - - context 'without project specific cluster' do - let_it_be(:cluster) { create(:cluster, enabled: true) } - - it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized - end - context 'with manual prometheus installation' do where(:alerting_setting, :configured_token, :token_input, :result) do true | token | token | :success @@ -230,7 +151,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService, feature_category: :i context 'with multiple firing alerts and resolving alerts' do let(:payload_raw) do - prometheus_alert_payload(firing: [alert_firing, alert_firing], resolved: [alert_resolved]) + prometheus_alert_payload(firing: ['Alert A', 'Alert A'], resolved: ['Alert B']) end it 'processes Prometheus alerts' do @@ -248,7 +169,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService, feature_category: :i context 'when payload exceeds max amount of processable alerts' do # We are defining 2 alerts in payload_raw above let(:max_alerts) { 1 } - let(:fingerprint) { prometheus_alert_payload_fingerprint(alert_resolved) } + let(:fingerprint) { prometheus_alert_payload_fingerprint('Alert A') } before do stub_const("#{described_class}::PROCESS_MAX_ALERTS", max_alerts) diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index c937a93c6ef..4ba704532e6 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -89,6 +89,12 @@ RSpec.describe SearchService, feature_category: :global_search do end end + describe '#search_type' do + subject { described_class.new(user, search: valid_search).search_type } + + it { is_expected.to eq('basic') } + end + describe '#show_snippets?' do context 'when :snippets is \'true\'' do it 'returns true' do diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb index e1f5e6dee14..da80f6f08c2 100644 --- a/spec/support/helpers/prometheus_helpers.rb +++ b/spec/support/helpers/prometheus_helpers.rb @@ -240,12 +240,11 @@ module PrometheusHelpers def prometheus_alert_payload(firing: [], resolved: []) status = firing.any? ? 'firing' : 'resolved' alerts = firing + resolved - alert_name = alerts.first&.title || '' - prometheus_metric_id = alerts.first&.prometheus_metric_id&.to_s + alert_name = alerts.first || '' alerts_map = \ - firing.map { |alert| prometheus_map_alert_payload('firing', alert) } + - resolved.map { |alert| prometheus_map_alert_payload('resolved', alert) } + firing.map { |title| prometheus_map_alert_payload('firing', title) } + + resolved.map { |title| prometheus_map_alert_payload('resolved', title) } # See https://prometheus.io/docs/alerting/configuration/#%3Cwebhook_config%3E { @@ -257,9 +256,7 @@ module PrometheusHelpers 'alertname' => alert_name }, 'commonLabels' => { - 'alertname' => alert_name, - 'gitlab' => 'hook', - 'gitlab_alert_id' => prometheus_metric_id + 'alertname' => alert_name }, 'commonAnnotations' => {}, 'externalURL' => '', @@ -267,22 +264,21 @@ module PrometheusHelpers } end - def prometheus_alert_payload_fingerprint(prometheus_alert) + def prometheus_alert_payload_fingerprint(title) # timestamp is hard-coded in #prometheus_map_alert_payload - fingerprint = "#{prometheus_alert.prometheus_metric_id}/2018-09-24T08:57:31.095725221Z" + # sample fingerprint format comes from AlertManagement::Payload::Prometheus + fingerprint = ["2018-09-24T08:57:31.095725221Z", title].join('/') Gitlab::AlertManagement::Fingerprint.generate(fingerprint) end private - def prometheus_map_alert_payload(status, alert) + def prometheus_map_alert_payload(status, title) { 'status' => status, 'labels' => { - 'alertname' => alert.title, - 'gitlab' => 'hook', - 'gitlab_alert_id' => alert.prometheus_metric_id.to_s + 'alertname' => title }, 'annotations' => {}, 'startsAt' => '2018-09-24T08:57:31.095725221Z', diff --git a/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb b/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb new file mode 100644 index 00000000000..efd27a051fe --- /dev/null +++ b/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'includes LinkableItem concern' do + describe 'validation' do + let_it_be(:task) { create(:work_item, :task, project: project) } + let_it_be(:issue) { create(:work_item, :issue, project: project) } + + subject(:link) { build(link_factory, source_id: source.id, target_id: target.id) } + + describe '#check_existing_parent_link' do + shared_examples 'invalid due to existing link' do + it do + is_expected.to be_invalid + expect(link.errors.messages[:source]).to include("is a parent or child of this #{item_type}") + end + end + + context 'without existing link parent' do + let(:source) { issue } + let(:target) { task } + + it 'is valid' do + is_expected.to be_valid + expect(link.errors).to be_empty + end + end + + context 'with existing link parent' do + let_it_be(:relationship) { create(:parent_link, work_item_parent: issue, work_item: task) } + + it_behaves_like 'invalid due to existing link' do + let(:source) { issue } + let(:target) { task } + end + + it_behaves_like 'invalid due to existing link' do + let(:source) { task } + let(:target) { issue } + end + end + end + end + + describe 'Scopes' do + describe '.for_source' do + it 'includes linked items for source' do + source = item + link_1 = create(link_factory, source: source, target: item1) + link_2 = create(link_factory, source: source, target: item2) + + result = described_class.for_source(source) + + expect(result).to contain_exactly(link_1, link_2) + end + end + + describe '.for_target' do + it 'includes linked items for target' do + target = item + link_1 = create(link_factory, source: item1, target: target) + link_2 = create(link_factory, source: item2, target: target) + + result = described_class.for_target(target) + + expect(result).to contain_exactly(link_1, link_2) + end + end + + describe '.for_items' do + let_it_be(:source_link) { create(link_factory, source: item, target: item1) } + let_it_be(:target_link) { create(link_factory, source: item2, target: item) } + + it 'includes links when item is source' do + expect(described_class.for_items(item, item1)).to contain_exactly(source_link) + end + + it 'includes links when item is target' do + expect(described_class.for_items(item, item2)).to contain_exactly(target_link) + end + end + end +end diff --git a/spec/support/shared_examples/models/issuable_link_shared_examples.rb b/spec/support/shared_examples/models/issuable_link_shared_examples.rb index 42c7be5ddc3..af96b77edaf 100644 --- a/spec/support/shared_examples/models/issuable_link_shared_examples.rb +++ b/spec/support/shared_examples/models/issuable_link_shared_examples.rb @@ -7,8 +7,8 @@ # issuable_link_factory RSpec.shared_examples 'issuable link' do describe 'Associations' do - it { is_expected.to belong_to(:source).class_name(issuable.class.name) } - it { is_expected.to belong_to(:target).class_name(issuable.class.name) } + it { is_expected.to belong_to(:source).class_name(issuable_class) } + it { is_expected.to belong_to(:target).class_name(issuable_class) } end describe 'Validation' do @@ -27,7 +27,8 @@ RSpec.shared_examples 'issuable link' do issuable_link = create_issuable_link(subject.target, subject.source) expect(issuable_link).to be_invalid - expect(issuable_link.errors[:source]).to include("is already related to this #{issuable.class.name.downcase}") + expect(issuable_link.errors[:source]) + .to include("is already related to this #{issuable.issuable_type.humanize(capitalize: false)}") end context 'when it relates to itself' do |