Commit 7dd78c45 authored by Toon Claes's avatar Toon Claes

Merge branch 'mwaw/211329-add-annotation-model-and-relation' into 'master'

Resolve "Backend: Basic Implementation of annotations CRUD"

Closes #211329

See merge request gitlab-org/gitlab!27583
parents 4d2d7099 da5a9dc9
......@@ -59,6 +59,7 @@ module Clusters
has_one_cluster_application :elastic_stack
has_many :kubernetes_namespaces
has_many :metrics_dashboard_annotations, class_name: 'Metrics::Dashboard::Annotation', inverse_of: :cluster
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :provider_aws, update_only: true
......
......@@ -18,6 +18,7 @@ class Environment < ApplicationRecord
has_many :successful_deployments, -> { success }, class_name: 'Deployment'
has_many :active_deployments, -> { active }, class_name: 'Deployment'
has_many :prometheus_alerts, inverse_of: :environment
has_many :metrics_dashboard_annotations, class_name: 'Metrics::Dashboard::Annotation', inverse_of: :environment
has_many :self_managed_prometheus_alert_events, inverse_of: :environment
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
......
# frozen_string_literal: true
module Metrics
module Dashboard
class Annotation < ApplicationRecord
self.table_name = 'metrics_dashboard_annotations'
belongs_to :environment, inverse_of: :metrics_dashboard_annotations
belongs_to :cluster, class_name: 'Clusters::Cluster', inverse_of: :metrics_dashboard_annotations
validates :starting_at, presence: true
validates :description, presence: true, length: { maximum: 255 }
validates :dashboard_path, presence: true, length: { maximum: 255 }
validates :panel_xid, length: { maximum: 255 }
validate :single_ownership
validate :orphaned_annotation
private
def single_ownership
return if cluster.nil? ^ environment.nil?
errors.add(:base, s_("Metrics::Dashboard::Annotation|Annotation can't belong to both a cluster and an environment at the same time"))
end
def orphaned_annotation
return if cluster.present? || environment.present?
errors.add(:base, s_("Metrics::Dashboard::Annotation|Annotation must belong to a cluster or an environment"))
end
end
end
end
......@@ -75,6 +75,9 @@ class GroupPolicy < BasePolicy
rule { developer }.policy do
enable :admin_milestone
enable :read_package
enable :create_metrics_dashboard_annotation
enable :delete_metrics_dashboard_annotation
enable :update_metrics_dashboard_annotation
end
rule { reporter }.policy do
......@@ -82,6 +85,7 @@ class GroupPolicy < BasePolicy
enable :admin_label
enable :admin_list
enable :admin_issue
enable :read_metrics_dashboard_annotation
end
rule { maintainer }.policy do
......
# frozen_string_literal: true
module Metrics
module Dashboard
class AnnotationPolicy < BasePolicy
delegate { @subject.cluster }
delegate { @subject.environment }
end
end
end
......@@ -224,6 +224,7 @@ class ProjectPolicy < BasePolicy
enable :read_sentry_issue
enable :update_sentry_issue
enable :read_prometheus
enable :read_metrics_dashboard_annotation
end
# We define `:public_user_access` separately because there are cases in gitlab-ee
......@@ -276,6 +277,9 @@ class ProjectPolicy < BasePolicy
enable :update_deployment
enable :create_release
enable :update_release
enable :create_metrics_dashboard_annotation
enable :delete_metrics_dashboard_annotation
enable :update_metrics_dashboard_annotation
end
rule { can?(:developer_access) & user_confirmed? }.policy do
......
# frozen_string_literal: true
# Create Metrics::Dashboard::Annotation entry based on matched dashboard_path, environment, cluster
module Metrics
module Dashboard
module Annotations
class CreateService < ::BaseService
include Stepable
steps :authorize_environment_access,
:authorize_cluster_access,
:parse_dashboard_path,
:create
def initialize(user, params)
@user, @params = user, params
end
def execute
execute_steps
end
private
attr_reader :user, :params
def authorize_environment_access(options)
if environment.nil? || Ability.allowed?(user, :create_metrics_dashboard_annotation, project)
options[:environment] = environment
success(options)
else
error(s_('Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected environment'))
end
end
def authorize_cluster_access(options)
if cluster.nil? || Ability.allowed?(user, :create_metrics_dashboard_annotation, cluster)
options[:cluster] = cluster
success(options)
else
error(s_('Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected cluster'))
end
end
def parse_dashboard_path(options)
dashboard_path = params[:dashboard_path]
Gitlab::Metrics::Dashboard::Finder.find_raw(project, dashboard_path: dashboard_path)
options[:dashboard_path] = dashboard_path
success(options)
rescue Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
error(s_('Metrics::Dashboard::Annotation|Dashboard with requested path can not be found'))
end
def create(options)
annotation = Annotation.new(options.slice(:environment, :cluster, :dashboard_path).merge(params.slice(:description, :starting_at, :ending_at)))
if annotation.save
success(annotation: annotation)
else
error(annotation.errors)
end
end
def environment
params[:environment]
end
def cluster
params[:cluster]
end
def project
(environment || cluster)&.project
end
end
end
end
end
# frozen_string_literal: true
# Delete Metrics::Dashboard::Annotation entry
module Metrics
module Dashboard
module Annotations
class DeleteService < ::BaseService
include Stepable
steps :authorize_action,
:delete
def initialize(user, annotation)
@user, @annotation = user, annotation
end
def execute
execute_steps
end
private
attr_reader :user, :annotation
def authorize_action(_options)
if Ability.allowed?(user, :delete_metrics_dashboard_annotation, annotation)
success
else
error(s_('Metrics::Dashboard::Annotation|You are not authorized to delete this annotation'))
end
end
def delete(_options)
if annotation.destroy
success
else
error(s_('Metrics::Dashboard::Annotation|Annotation has not been deleted'))
end
end
end
end
end
end
---
title: Add metrics dashboard annotation model, relation, policy, create and delete services. To provide interface for create and delete operations.
merge_request: 27583
author:
type: added
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CreateMetricsDashboardAnnotations < ActiveRecord::Migration[6.0]
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
create_table :metrics_dashboard_annotations do |t|
t.datetime_with_timezone :starting_at, null: false
t.datetime_with_timezone :ending_at
t.references :environment, index: false, foreign_key: { on_delete: :cascade }, null: true
t.references :cluster, index: false, foreign_key: { on_delete: :cascade }, null: true
t.string :dashboard_path, null: false, limit: 255
t.string :panel_xid, limit: 255
t.text :description, null: false, limit: 255
t.index %i(environment_id dashboard_path starting_at ending_at), where: 'environment_id IS NOT NULL', name: "index_metrics_dashboard_annotations_on_environment_id_and_3_col"
t.index %i(cluster_id dashboard_path starting_at ending_at), where: 'cluster_id IS NOT NULL', name: "index_metrics_dashboard_annotations_on_cluster_id_and_3_columns"
end
end
end
......@@ -3872,6 +3872,26 @@ CREATE SEQUENCE public.merge_trains_id_seq
ALTER SEQUENCE public.merge_trains_id_seq OWNED BY public.merge_trains.id;
CREATE TABLE public.metrics_dashboard_annotations (
id bigint NOT NULL,
starting_at timestamp with time zone NOT NULL,
ending_at timestamp with time zone,
environment_id bigint,
cluster_id bigint,
dashboard_path character varying(255) NOT NULL,
panel_xid character varying(255),
description text NOT NULL
);
CREATE SEQUENCE public.metrics_dashboard_annotations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.metrics_dashboard_annotations_id_seq OWNED BY public.metrics_dashboard_annotations.id;
CREATE TABLE public.milestone_releases (
milestone_id bigint NOT NULL,
release_id bigint NOT NULL
......@@ -7196,6 +7216,8 @@ ALTER TABLE ONLY public.merge_requests_closing_issues ALTER COLUMN id SET DEFAUL
ALTER TABLE ONLY public.merge_trains ALTER COLUMN id SET DEFAULT nextval('public.merge_trains_id_seq'::regclass);
ALTER TABLE ONLY public.metrics_dashboard_annotations ALTER COLUMN id SET DEFAULT nextval('public.metrics_dashboard_annotations_id_seq'::regclass);
ALTER TABLE ONLY public.milestones ALTER COLUMN id SET DEFAULT nextval('public.milestones_id_seq'::regclass);
ALTER TABLE ONLY public.namespace_statistics ALTER COLUMN id SET DEFAULT nextval('public.namespace_statistics_id_seq'::regclass);
......@@ -7974,6 +7996,9 @@ ALTER TABLE ONLY public.merge_requests
ALTER TABLE ONLY public.merge_trains
ADD CONSTRAINT merge_trains_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.metrics_dashboard_annotations
ADD CONSTRAINT metrics_dashboard_annotations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.milestones
ADD CONSTRAINT milestones_pkey PRIMARY KEY (id);
......@@ -9459,6 +9484,10 @@ CREATE INDEX index_merge_trains_on_pipeline_id ON public.merge_trains USING btre
CREATE INDEX index_merge_trains_on_user_id ON public.merge_trains USING btree (user_id);
CREATE INDEX index_metrics_dashboard_annotations_on_cluster_id_and_3_columns ON public.metrics_dashboard_annotations USING btree (cluster_id, dashboard_path, starting_at, ending_at) WHERE (cluster_id IS NOT NULL);
CREATE INDEX index_metrics_dashboard_annotations_on_environment_id_and_3_col ON public.metrics_dashboard_annotations USING btree (environment_id, dashboard_path, starting_at, ending_at) WHERE (environment_id IS NOT NULL);
CREATE INDEX index_milestone_releases_on_release_id ON public.milestone_releases USING btree (release_id);
CREATE INDEX index_milestones_on_description_trigram ON public.milestones USING gin (description public.gin_trgm_ops);
......@@ -11063,6 +11092,9 @@ ALTER TABLE ONLY public.suggestions
ALTER TABLE ONLY public.requirements
ADD CONSTRAINT fk_rails_33fed8aa4e FOREIGN KEY (author_id) REFERENCES public.users(id) ON DELETE SET NULL;
ALTER TABLE ONLY public.metrics_dashboard_annotations
ADD CONSTRAINT fk_rails_345ab51043 FOREIGN KEY (cluster_id) REFERENCES public.clusters(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.wiki_page_slugs
ADD CONSTRAINT fk_rails_358b46be14 FOREIGN KEY (wiki_page_meta_id) REFERENCES public.wiki_page_meta(id) ON DELETE CASCADE;
......@@ -11582,6 +11614,9 @@ ALTER TABLE ONLY public.clusters
ALTER TABLE ONLY public.analytics_cycle_analytics_group_stages
ADD CONSTRAINT fk_rails_ae5da3409b FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.metrics_dashboard_annotations
ADD CONSTRAINT fk_rails_aeb11a7643 FOREIGN KEY (environment_id) REFERENCES public.environments(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.pool_repositories
ADD CONSTRAINT fk_rails_af3f8c5d62 FOREIGN KEY (shard_id) REFERENCES public.shards(id) ON DELETE RESTRICT;
......@@ -12911,6 +12946,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200318175008
20200319071702
20200319123041
20200319124127
20200319203901
20200320112455
20200320123839
......
......@@ -12698,6 +12698,27 @@ msgstr ""
msgid "Metrics for environment"
msgstr ""
msgid "Metrics::Dashboard::Annotation|Annotation can't belong to both a cluster and an environment at the same time"
msgstr ""
msgid "Metrics::Dashboard::Annotation|Annotation has not been deleted"
msgstr ""
msgid "Metrics::Dashboard::Annotation|Annotation must belong to a cluster or an environment"
msgstr ""
msgid "Metrics::Dashboard::Annotation|Dashboard with requested path can not be found"
msgstr ""
msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected cluster"
msgstr ""
msgid "Metrics::Dashboard::Annotation|You are not authorized to create annotation for selected environment"
msgstr ""
msgid "Metrics::Dashboard::Annotation|You are not authorized to delete this annotation"
msgstr ""
msgid "Metrics|Add metric"
msgstr ""
......
# frozen_string_literal: true
FactoryBot.define do
factory :metrics_dashboard_annotation, class: '::Metrics::Dashboard::Annotation' do
description { "Dashbaord annoation description" }
dashboard_path { "custom_dashbaord.yml" }
starting_at { Time.current }
environment
trait :with_cluster do
cluster
environment { nil }
end
end
end
......@@ -27,6 +27,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
it { is_expected.to have_many(:kubernetes_namespaces) }
it { is_expected.to have_one(:cluster_project) }
it { is_expected.to have_many(:deployment_clusters) }
it { is_expected.to have_many(:metrics_dashboard_annotations) }
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
......
......@@ -17,6 +17,7 @@ describe Environment, :use_clean_rails_memory_store_caching do
it { is_expected.to belong_to(:project).required }
it { is_expected.to have_many(:deployments) }
it { is_expected.to have_many(:metrics_dashboard_annotations) }
it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
it { is_expected.to delegate_method(:manual_actions).to(:last_deployment) }
......
# frozen_string_literal: true
require 'spec_helper'
describe Metrics::Dashboard::Annotation do
describe 'associations' do
it { is_expected.to belong_to(:environment).inverse_of(:metrics_dashboard_annotations) }
it { is_expected.to belong_to(:cluster).class_name('Clusters::Cluster').inverse_of(:metrics_dashboard_annotations) }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:description) }
it { is_expected.to validate_presence_of(:dashboard_path) }
it { is_expected.to validate_presence_of(:starting_at) }
it { is_expected.to validate_length_of(:dashboard_path).is_at_most(255) }
it { is_expected.to validate_length_of(:panel_xid).is_at_most(255) }
it { is_expected.to validate_length_of(:description).is_at_most(255) }
context 'orphaned annotation' do
subject { build(:metrics_dashboard_annotation, environment: nil) }
it { is_expected.not_to be_valid }
it 'reports error about both missing relations' do
subject.valid?
expect(subject.errors.full_messages).to include(/Annotation must belong to a cluster or an environment/)
end
end
context 'environments annotation' do
subject { build(:metrics_dashboard_annotation) }
it { is_expected.to be_valid }
end
context 'clusters annotation' do
subject { build(:metrics_dashboard_annotation, :with_cluster) }
it { is_expected.to be_valid }
end
context 'annotation with shared ownership' do
subject { build(:metrics_dashboard_annotation, :with_cluster, environment: build(:environment) ) }
it 'reports error about both shared ownership' do
subject.valid?
expect(subject.errors.full_messages).to include(/Annotation can't belong to both a cluster and an environment at the same time/)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Metrics::Dashboard::AnnotationPolicy, :models do
shared_examples 'metrics dashboard annotation policy' do
context 'when guest' do
before do
project.add_guest(user)
end
it { expect(policy).to be_disallowed :read_metrics_dashboard_annotation }
it { expect(policy).to be_disallowed :create_metrics_dashboard_annotation }
it { expect(policy).to be_disallowed :update_metrics_dashboard_annotation }
it { expect(policy).to be_disallowed :delete_metrics_dashboard_annotation }
end
context 'when reporter' do
before do
project.add_reporter(user)
end
it { expect(policy).to be_allowed :read_metrics_dashboard_annotation }
it { expect(policy).to be_disallowed :create_metrics_dashboard_annotation }
it { expect(policy).to be_disallowed :update_metrics_dashboard_annotation }
it { expect(policy).to be_disallowed :delete_metrics_dashboard_annotation }
end
context 'when developer' do
before do
project.add_developer(user)
end
it { expect(policy).to be_allowed :read_metrics_dashboard_annotation }
it { expect(policy).to be_allowed :create_metrics_dashboard_annotation }
it { expect(policy).to be_allowed :update_metrics_dashboard_annotation }
it { expect(policy).to be_allowed :delete_metrics_dashboard_annotation }
end
context 'when maintainer' do
before do
project.add_maintainer(user)
end
it { expect(policy).to be_allowed :read_metrics_dashboard_annotation }
it { expect(policy).to be_allowed :create_metrics_dashboard_annotation }
it { expect(policy).to be_allowed :update_metrics_dashboard_annotation }
it { expect(policy).to be_allowed :delete_metrics_dashboard_annotation }
end
end
describe 'rules' do
context 'environments annotation' do
let(:annotation) { create(:metrics_dashboard_annotation, environment: environment) }
let(:environment) { create(:environment) }
let!(:project) { environment.project }
let(:user) { create(:user) }
let(:policy) { described_class.new(user, annotation) }
it_behaves_like 'metrics dashboard annotation policy'
end
context 'cluster annotation' do
let(:annotation) { create(:metrics_dashboard_annotation, environment: nil, cluster: cluster) }
let(:cluster) { create(:cluster, :project) }
let(:project) { cluster.project }
let(:user) { create(:user) }
let(:policy) { described_class.new(user, annotation) }
it_behaves_like 'metrics dashboard annotation policy'
end
end
end
......@@ -28,7 +28,7 @@ describe ProjectPolicy do
download_code fork_project create_snippet update_issue
admin_issue admin_label admin_list read_commit_status read_build
read_container_image read_pipeline read_environment read_deployment
read_merge_request download_wiki_code read_sentry_issue
read_merge_request download_wiki_code read_sentry_issue read_metrics_dashboard_annotation
]
end
......@@ -43,6 +43,7 @@ describe ProjectPolicy do
update_pipeline create_merge_request_from create_wiki push_code
resolve_note create_container_image update_container_image destroy_container_image
create_environment update_environment create_deployment update_deployment create_release update_release
create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation
]
end
......
# frozen_string_literal: true
require 'spec_helper'
describe Metrics::Dashboard::Annotations::CreateService do
let_it_be(:user) { create(:user) }
let(:description) { 'test annotation' }
let(:dashboard_path) { 'config/prometheus/common_metrics.yml' }
let(:starting_at) { 15.minutes.ago }
let(:ending_at) { nil }
let(:service_instance) { described_class.new(user, annotation_params) }
let(:annotation_params) do
{
environment: environment,
cluster: cluster,
description: description,
dashboard_path: dashboard_path,
starting_at: starting_at,
ending_at: ending_at
}
end
shared_examples 'executed annotation creation' do
it 'returns success response', :aggregate_failures do
annotation = instance_double(::Metrics::Dashboard::Annotation)
allow(::Metrics::Dashboard::Annotation).to receive(:new).and_return(annotation)
allow(annotation).to receive(:save).and_return(true)
response = service_instance.execute
expect(response[:status]).to be :success
expect(response[:annotation]).to be annotation
end
it 'creates annotation', :aggregate_failures do
annotation = instance_double(::Metrics::Dashboard::Annotation)
expect(::Metrics::Dashboard::Annotation)
.to receive(:new).with(annotation_params).and_return(annotation)
expect(annotation).to receive(:save).and_return(true)
service_instance.execute
end
end
shared_examples 'prevented annotation creation' do |message|
it 'returns error response', :aggregate_failures do
response = service_instance.execute
expect(response[:status]).to be :error
expect(response[:message]).to eql message
end
it 'does not change db state' do
expect(::Metrics::Dashboard::Annotation).not_to receive(:new)
service_instance.execute
end
end
shared_examples 'annotation creation failure' do
it 'returns error response', :aggregate_failures do
annotation = instance_double(::Metrics::Dashboard::Annotation)
expect(annotation).to receive(:errors).and_return('Model validation error')
expect(::Metrics::Dashboard::Annotation)
.to receive(:new).with(annotation_params).and_return(annotation)
expect(annotation).to receive(:save).and_return(false)
response = service_instance.execute
expect(response[:status]).to be :error
expect(response[:message]).to eql 'Model validation error'
end
end
describe '.execute' do
context 'with environment' do
let(:environment) { create(:environment) }
let(:cluster) { nil }
context 'with anonymous user' do
it_behaves_like 'prevented annotation creation', 'You are not authorized to create annotation for selected environment'
end
context 'with maintainer user' do
before do
environment.project.add_maintainer(user)
end
it_behaves_like 'executed annotation creation'
end
end
context 'with cluster' do
let(:environment) { nil }
context 'with anonymous user' do
let(:cluster) { create(:cluster, :project) }
it_behaves_like 'prevented annotation creation', 'You are not authorized to create annotation for selected cluster'
end
context 'with maintainer user' do
let(:cluster) { create(:cluster, :project) }
before do
cluster.project.add_maintainer(user)
end
it_behaves_like 'executed annotation creation'
end
context 'with owner user' do
let(:cluster) { create(:cluster, :group) }
before do
cluster.group.add_owner(user)
end
it_behaves_like 'executed annotation creation'
end
end
context 'non cluster nor environment is supplied' do
let(:environment) { nil }
let(:cluster) { nil }
it_behaves_like 'annotation creation failure'
end
context 'missing dashboard_path' do
let(:cluster) { create(:cluster, :project) }
let(:environment) { nil }
let(:dashboard_path) { nil }
context 'with maintainer user' do
before do
cluster.project.add_maintainer(user)
end
it_behaves_like 'annotation creation failure'
end
end
context 'incorrect dashboard_path' do
let(:cluster) { create(:cluster, :project) }
let(:environment) { nil }
let(:dashboard_path) { 'something_incorrect.yml' }
context 'with maintainer user' do
before do
cluster.project.add_maintainer(user)
end
it_behaves_like 'prevented annotation creation', 'Dashboard with requested path can not be found'
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Metrics::Dashboard::Annotations::DeleteService do
let(:user) { create(:user) }
let(:service_instance) { described_class.new(user, annotation) }
shared_examples 'executed annotation deletion' do
it 'returns success response', :aggregate_failures do
expect(annotation).to receive(:destroy).and_return(true)
response = service_instance.execute
expect(response[:status]).to be :success
end
end
shared_examples 'prevented annotation deletion' do |message|
it 'returns error response', :aggregate_failures do
response = service_instance.execute
expect(response[:status]).to be :error
expect(response[:message]).to eql message
end
it 'does not change db state' do
expect(annotation).not_to receive(:destroy)
service_instance.execute
end
end
describe '.execute' do
context 'with specific environment' do
let(:annotation) { create(:metrics_dashboard_annotation, environment: environment) }
let(:environment) { create(:environment) }
context 'with anonymous user' do
it_behaves_like 'prevented annotation deletion', 'You are not authorized to delete this annotation'
end
context 'with maintainer user' do
before do
environment.project.add_maintainer(user)
end
it_behaves_like 'executed annotation deletion'
context 'annotation failed to delete' do
it 'returns error response', :aggregate_failures do
allow(annotation).to receive(:destroy).and_return(false)
response = service_instance.execute
expect(response[:status]).to be :error
expect(response[:message]).to eql 'Annotation has not been deleted'
end
end
end
end
context 'with specific cluster' do
let(:annotation) { create(:metrics_dashboard_annotation, cluster: cluster, environment: nil) }
context 'with anonymous user' do
let(:cluster) { create(:cluster, :project) }
it_behaves_like 'prevented annotation deletion', 'You are not authorized to delete this annotation'
end
context 'with maintainer user' do
let(:cluster) { create(:cluster, :project) }
before do
cluster.project.add_maintainer(user)
end
it_behaves_like 'executed annotation deletion'
end
context 'with owner user' do
let(:cluster) { create(:cluster, :group) }
before do
cluster.group.add_owner(user)
end
it_behaves_like 'executed annotation deletion'
end
end
end
end
......@@ -18,8 +18,8 @@ RSpec.shared_context 'GroupPolicy context' do
]
end
let(:read_group_permissions) { %i[read_label read_list read_milestone read_board] }
let(:reporter_permissions) { %i[admin_label read_container_image] }
let(:developer_permissions) { [:admin_milestone] }
let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation] }
let(:developer_permissions) { %i[admin_milestone create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation] }
let(:maintainer_permissions) do
%i[
create_projects
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment