diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/main.js | 4 | ||||
-rw-r--r-- | app/controllers/user_callouts_controller.rb | 5 | ||||
-rw-r--r-- | app/graphql/types/permission_types/project.rb | 3 | ||||
-rw-r--r-- | app/helpers/markup_helper.rb | 2 | ||||
-rw-r--r-- | app/helpers/user_callouts_helper.rb | 9 | ||||
-rw-r--r-- | app/models/ci/bridge.rb | 122 | ||||
-rw-r--r-- | app/models/concerns/prometheus_adapter.rb | 7 | ||||
-rw-r--r-- | app/models/environment.rb | 9 | ||||
-rw-r--r-- | app/models/project.rb | 1 | ||||
-rw-r--r-- | app/models/prometheus_alert.rb | 81 | ||||
-rw-r--r-- | app/models/user.rb | 7 | ||||
-rw-r--r-- | app/models/user_callout.rb | 3 | ||||
-rw-r--r-- | app/views/clusters/clusters/_cluster.html.haml | 2 | ||||
-rw-r--r-- | app/views/layouts/_page.html.haml | 1 | ||||
-rw-r--r-- | app/views/shared/_check_recovery_settings.html.haml | 6 |
15 files changed, 256 insertions, 6 deletions
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 1e07469bd7a..8fb973b1c1f 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -36,6 +36,7 @@ import initSearchAutocomplete from './search_autocomplete'; import GlFieldErrors from './gl_field_errors'; import initUserPopovers from './user_popovers'; import initBroadcastNotifications from './broadcast_notification'; +import PersistentUserCallout from './persistent_user_callout'; import { initUserTracking } from './tracking'; import { __ } from './locale'; @@ -108,6 +109,9 @@ function deferredInitialisation() { initUserTracking(); initBroadcastNotifications(); + const recoverySettingsCallout = document.querySelector('.js-recovery-settings-callout'); + PersistentUserCallout.factory(recoverySettingsCallout); + if (document.querySelector('.search')) initSearchAutocomplete(); addSelectOnFocusBehaviour('.js-select-on-focus'); diff --git a/app/controllers/user_callouts_controller.rb b/app/controllers/user_callouts_controller.rb index ebf1dd8ca02..4ee75218db1 100644 --- a/app/controllers/user_callouts_controller.rb +++ b/app/controllers/user_callouts_controller.rb @@ -2,7 +2,10 @@ class UserCalloutsController < ApplicationController def create - if ensure_callout.persisted? + callout = ensure_callout + + if callout.persisted? + callout.update(dismissed_at: Time.now) respond_to do |format| format.json { head :ok } end diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb index 094c72fa812..f773fce0c63 100644 --- a/app/graphql/types/permission_types/project.rb +++ b/app/graphql/types/permission_types/project.rb @@ -16,7 +16,8 @@ module Types :create_deployment, :push_to_delete_protected_branch, :admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki, - :create_pages, :destroy_pages, :read_pages_content, :admin_operations + :create_pages, :destroy_pages, :read_pages_content, :admin_operations, + :read_merge_request permission_field :create_snippet diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index e42ea3861b8..a0228c6bd94 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -153,7 +153,7 @@ module MarkupHelper other_markup_unsafe(file_name, text, context) end rescue StandardError => e - Gitlab::ErrorTracking.track_exception(e, project_id: @project&.id, file_name: file_name, context: context) + Gitlab::ErrorTracking.track_exception(e, project_id: @project&.id, file_name: file_name) simple_format(text) end diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index b3eee25674b..ab691916706 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -22,6 +22,9 @@ module UserCalloutsHelper def render_dashboard_gold_trial(user) end + def render_account_recovery_regular_check + end + def show_suggest_popover? !user_dismissed?(SUGGEST_POPOVER_DISMISSED) end @@ -32,8 +35,10 @@ module UserCalloutsHelper private - def user_dismissed?(feature_name) - current_user&.callouts&.find_by(feature_name: UserCallout.feature_names[feature_name]) + def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil) + return false unless current_user + + current_user.dismissed_callout?(feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than) end end diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index e6d41dd2779..abd59741e89 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -4,19 +4,78 @@ module Ci class Bridge < Ci::Processable include Ci::Contextable include Ci::PipelineDelegator + include Ci::Metadatable include Importable include AfterCommitQueue include HasRef include Gitlab::Utils::StrongMemoize + InvalidBridgeTypeError = Class.new(StandardError) + belongs_to :project belongs_to :trigger_request + has_many :sourced_pipelines, class_name: "::Ci::Sources::Pipeline", + foreign_key: :source_job_id + validates :ref, presence: true + # rubocop:disable Cop/ActiveRecordSerialize + serialize :options + serialize :yaml_variables, ::Gitlab::Serializer::Ci::Variables + # rubocop:enable Cop/ActiveRecordSerialize + + state_machine :status do + event :manual do + transition all => :manual + end + + event :scheduled do + transition all => :scheduled + end + end + def self.retry(bridge, current_user) raise NotImplementedError end + def inherit_status_from_downstream!(pipeline) + case pipeline.status + when 'success' + self.success! + when 'failed', 'canceled', 'skipped' + self.drop! + else + false + end + end + + def downstream_pipeline_params + return child_params if triggers_child_pipeline? + return cross_project_params if downstream_project.present? + + {} + end + + def downstream_project + strong_memoize(:downstream_project) do + if downstream_project_path + ::Project.find_by_full_path(downstream_project_path) + elsif triggers_child_pipeline? + project + end + end + end + + def downstream_project_path + strong_memoize(:downstream_project_path) do + options&.dig(:trigger, :project) + end + end + + def triggers_child_pipeline? + yaml_for_downstream.present? + end + def tags [:bridge] end @@ -55,7 +114,68 @@ module Ci end def yaml_for_downstream - nil + strong_memoize(:yaml_for_downstream) do + includes = options&.dig(:trigger, :include) + YAML.dump('include' => includes) if includes + end + end + + def target_ref + branch = options&.dig(:trigger, :branch) + return unless branch + + scoped_variables.to_runner_variables.yield_self do |all_variables| + ::ExpandVariables.expand(branch, all_variables) + end + end + + def dependent? + strong_memoize(:dependent) do + options&.dig(:trigger, :strategy) == 'depend' + end + end + + def downstream_variables + variables = scoped_variables.concat(pipeline.persisted_variables) + + variables.to_runner_variables.yield_self do |all_variables| + yaml_variables.to_a.map do |hash| + { key: hash[:key], value: ::ExpandVariables.expand(hash[:value], all_variables) } + end + end + end + + private + + def cross_project_params + { + project: downstream_project, + source: :pipeline, + target_revision: { + ref: target_ref || downstream_project.default_branch + }, + execute_params: { ignore_skip_ci: true } + } + end + + def child_params + parent_pipeline = pipeline + + { + project: project, + source: :parent_pipeline, + target_revision: { + ref: parent_pipeline.ref, + checkout_sha: parent_pipeline.sha, + before: parent_pipeline.before_sha, + source_sha: parent_pipeline.source_sha, + target_sha: parent_pipeline.target_sha + }, + execute_params: { + ignore_skip_ci: true, + bridge: self + } + } end end end diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb index 3deb7edba58..2c171eecbd5 100644 --- a/app/models/concerns/prometheus_adapter.rb +++ b/app/models/concerns/prometheus_adapter.rb @@ -58,5 +58,12 @@ module PrometheusAdapter def build_query_args(*args) args.map { |arg| arg.respond_to?(:id) ? arg.id : arg } end + + def clear_prometheus_reactive_cache!(query_name, *args) + query_class = query_klass_for(query_name) + query_args = build_query_args(*args) + + clear_reactive_cache!(query_class.name, *query_args) + end end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 2d480345b5a..b29543ded32 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -11,6 +11,7 @@ class Environment < ApplicationRecord has_many :deployments, -> { visible }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :successful_deployments, -> { success }, class_name: 'Deployment' + has_many :prometheus_alerts, inverse_of: :environment has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment' has_one :last_deployable, through: :last_deployment, source: 'deployable', source_type: 'CommitStatus' @@ -105,6 +106,14 @@ class Environment < ApplicationRecord find_or_create_by(name: name) end + def clear_prometheus_reactive_cache!(query_name) + cluster_prometheus_adapter&.clear_prometheus_reactive_cache!(query_name, self) + end + + def cluster_prometheus_adapter + @cluster_prometheus_adapter ||= ::Gitlab::Prometheus::Adapter.new(project, deployment_platform&.cluster).cluster_prometheus_adapter + end + def predefined_variables Gitlab::Ci::Variables::Collection.new .append(key: 'CI_ENVIRONMENT_NAME', value: name) diff --git a/app/models/project.rb b/app/models/project.rb index 667618ae1bd..78c3114ce9c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -244,6 +244,7 @@ class Project < ApplicationRecord has_many :management_clusters, class_name: 'Clusters::Cluster', foreign_key: :management_project_id, inverse_of: :management_project has_many :prometheus_metrics + has_many :prometheus_alerts, inverse_of: :project # Container repositories need to remove data from the container registry, # which is not managed by the DB. Hence we're still using dependent: :destroy diff --git a/app/models/prometheus_alert.rb b/app/models/prometheus_alert.rb new file mode 100644 index 00000000000..1014231102f --- /dev/null +++ b/app/models/prometheus_alert.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +class PrometheusAlert < ApplicationRecord + include Sortable + + OPERATORS_MAP = { + lt: "<", + eq: "==", + gt: ">" + }.freeze + + belongs_to :environment, validate: true, inverse_of: :prometheus_alerts + belongs_to :project, validate: true, inverse_of: :prometheus_alerts + belongs_to :prometheus_metric, validate: true, inverse_of: :prometheus_alerts + + has_many :prometheus_alert_events, inverse_of: :prometheus_alert + has_many :related_issues, through: :prometheus_alert_events + + after_save :clear_prometheus_adapter_cache! + after_destroy :clear_prometheus_adapter_cache! + + validates :environment, :project, :prometheus_metric, presence: true + validate :require_valid_environment_project! + validate :require_valid_metric_project! + + enum operator: { lt: 0, eq: 1, gt: 2 } + + delegate :title, :query, to: :prometheus_metric + + scope :for_metric, -> (metric) { where(prometheus_metric: metric) } + scope :for_project, -> (project) { where(project_id: project) } + scope :for_environment, -> (environment) { where(environment_id: environment) } + + def self.distinct_projects + sub_query = self.group(:project_id).select(1) + self.from(sub_query) + end + + def self.operator_to_enum(op) + OPERATORS_MAP.invert.fetch(op) + end + + def full_query + "#{query} #{computed_operator} #{threshold}" + end + + def computed_operator + OPERATORS_MAP.fetch(operator.to_sym) + end + + def to_param + { + "alert" => title, + "expr" => full_query, + "for" => "5m", + "labels" => { + "gitlab" => "hook", + "gitlab_alert_id" => prometheus_metric_id + } + } + end + + private + + def clear_prometheus_adapter_cache! + environment.clear_prometheus_reactive_cache!(:additional_metrics_environment) + end + + def require_valid_environment_project! + return if project == environment&.project + + errors.add(:environment, "invalid project") + end + + def require_valid_metric_project! + return if prometheus_metric&.common? + return if project == prometheus_metric&.project + + errors.add(:prometheus_metric, "invalid project") + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 3512e663f4a..aa7e825d516 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1644,6 +1644,13 @@ class User < ApplicationRecord end # End of signup_flow experiment methods + def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil) + callouts = self.callouts.with_feature_name(feature_name) + callouts = callouts.with_dismissed_after(ignore_dismissal_earlier_than) if ignore_dismissal_earlier_than + + callouts.any? + end + # @deprecated alias_method :owned_or_masters_groups, :owned_or_maintainers_groups diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index 027ee44c6a9..82f82356cb4 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -12,4 +12,7 @@ class UserCallout < ApplicationRecord presence: true, uniqueness: { scope: :user_id }, inclusion: { in: UserCallout.feature_names.keys } + + scope :with_feature_name, -> (feature_name) { where(feature_name: UserCallout.feature_names[feature_name]) } + scope :with_dismissed_after, -> (dismissed_after) { where('dismissed_at > ?', dismissed_after) } end diff --git a/app/views/clusters/clusters/_cluster.html.haml b/app/views/clusters/clusters/_cluster.html.haml index 04afc38a056..9b6c0c20080 100644 --- a/app/views/clusters/clusters/_cluster.html.haml +++ b/app/views/clusters/clusters/_cluster.html.haml @@ -4,6 +4,8 @@ .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster") .table-mobile-content = cluster.item_link(clusterable, html_options: { data: { qa_selector: 'cluster', qa_cluster_name: cluster.name } }) + - if cluster.status_name == :creating + .spinner.ml-2.align-middle.has-tooltip{ title: s_("ClusterIntegration|Cluster being created") } - unless cluster.enabled? %span.badge.badge-danger Connection disabled .table-section.section-25 diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 443a73f5cce..2b2ffd6abeb 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -11,6 +11,7 @@ = render "layouts/nav/classification_level_banner" = yield :flash_message = render "shared/ping_consent" + = render_account_recovery_regular_check - unless @hide_breadcrumbs = render "layouts/nav/breadcrumbs" .d-flex diff --git a/app/views/shared/_check_recovery_settings.html.haml b/app/views/shared/_check_recovery_settings.html.haml new file mode 100644 index 00000000000..e3de34a5ab9 --- /dev/null +++ b/app/views/shared/_check_recovery_settings.html.haml @@ -0,0 +1,6 @@ +.gl-alert.gl-alert-warning.js-recovery-settings-callout{ role: 'alert', data: { feature_id: "account_recovery_regular_check", dismiss_endpoint: user_callouts_path, defer_links: "true" } } + %button.js-close.gl-alert-dismiss.gl-cursor-pointer{ type: 'button', 'aria-label' => _('Dismiss') } + = sprite_icon('close', size: 16, css_class: 'gl-icon') + .gl-alert-body + - account_link_start = '<a class="deferred-link" href="%{url}">'.html_safe % { url: profile_account_path } + = _("Please ensure your account's %{account_link_start}recovery settings%{account_link_end} are up to date.").html_safe % { account_link_start: account_link_start, account_link_end: '</a>'.html_safe } |