diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 031910b1cdb39474692c98d9f25764982ecdac65..fd537e520c7b14cc730abc8bb295dc0a1cd75444 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -16,6 +16,7 @@ import { featureAccessLevel, CVE_ID_REQUEST_BUTTON_I18N, featureAccessLevelDescriptions, + modelExperimentsHelpPath, } from '../constants'; import { toggleHiddenClassBySelector } from '../external'; import ProjectFeatureSetting from './project_feature_setting.vue'; @@ -57,6 +58,10 @@ export default { packageRegistryForEveryoneLabel: s__( 'ProjectSettings|Allow anyone to pull from Package Registry', ), + modelExperimentsLabel: s__('ProjectSettings|Model experiments'), + modelExperimentsHelpText: s__( + 'ProjectSettings|Track machine learning model experiments and artefacts.', + ), pagesLabel: s__('ProjectSettings|Pages'), ciCdLabel: __('CI/CD'), repositoryLabel: s__('ProjectSettings|Repository'), @@ -77,6 +82,7 @@ export default { VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER, + modelExperimentsHelpPath, components: { ProjectFeatureSetting, @@ -246,6 +252,7 @@ export default { forkingAccessLevel: featureAccessLevel.EVERYONE, mergeRequestsAccessLevel: featureAccessLevel.EVERYONE, packageRegistryAccessLevel: featureAccessLevel.EVERYONE, + modelExperimentsAccessLevel: featureAccessLevel.EVERYONE, buildsAccessLevel: featureAccessLevel.EVERYONE, wikiAccessLevel: featureAccessLevel.EVERYONE, snippetsAccessLevel: featureAccessLevel.EVERYONE, @@ -392,6 +399,10 @@ export default { ) { this.packageRegistryAccessLevel = featureAccessLevel.PROJECT_MEMBERS; } + this.modelExperimentsAccessLevel = Math.min( + featureAccessLevel.PROJECT_MEMBERS, + this.wikiAccessLevel, + ); this.wikiAccessLevel = Math.min(featureAccessLevel.PROJECT_MEMBERS, this.wikiAccessLevel); this.snippetsAccessLevel = Math.min( featureAccessLevel.PROJECT_MEMBERS, @@ -458,6 +469,8 @@ export default { this.buildsAccessLevel = featureAccessLevel.EVERYONE; if (this.wikiAccessLevel > featureAccessLevel.NOT_ENABLED) this.wikiAccessLevel = featureAccessLevel.EVERYONE; + if (this.modelExperimentsAccessLevel > featureAccessLevel.NOT_ENABLED) + this.modelExperimentsAccessLevel = featureAccessLevel.EVERYONE; if (this.snippetsAccessLevel > featureAccessLevel.NOT_ENABLED) this.snippetsAccessLevel = featureAccessLevel.EVERYONE; if (this.pagesAccessLevel === featureAccessLevel.PROJECT_MEMBERS) @@ -898,6 +911,19 @@ export default { name="project[project_feature_attributes][package_registry_access_level]" /> + + + do if ::Gitlab.config.packages.enabled diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 47d8d0eef3e30713b022329abf8819fe65cee767..12d177513536e6d895f08fbf19260ab8c66d3871 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -162,6 +162,11 @@ class ProjectPolicy < BasePolicy with_scope :subject condition(:service_desk_enabled) { @subject.service_desk_enabled? } + with_scope :subject + condition(:model_experiments_enabled) do + Feature.enabled?(:ml_experiment_tracking, @subject) && @subject.feature_available?(:model_experiments, @user) + end + with_scope :subject condition(:resource_access_token_feature_available) do resource_access_token_feature_available? @@ -220,6 +225,7 @@ class ProjectPolicy < BasePolicy feature_flags releases infrastructure + model_experiments ] features.each do |f| @@ -892,6 +898,10 @@ class ProjectPolicy < BasePolicy enable :add_catalog_resource end + rule { model_experiments_enabled }.policy do + enable :read_model_experiments + end + private def user_is_user? diff --git a/db/migrate/20230522132239_add_model_experiments_access_level_to_project_feature.rb b/db/migrate/20230522132239_add_model_experiments_access_level_to_project_feature.rb new file mode 100644 index 0000000000000000000000000000000000000000..a34b8a15521c0f3794453493d61f087960c1b34b --- /dev/null +++ b/db/migrate/20230522132239_add_model_experiments_access_level_to_project_feature.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddModelExperimentsAccessLevelToProjectFeature < Gitlab::Database::Migration[2.1] + OPERATIONS_DEFAULT_VALUE = 20 + + enable_lock_retries! + + def change + add_column :project_features, + :model_experiments_access_level, + :integer, + null: false, + default: OPERATIONS_DEFAULT_VALUE + end +end diff --git a/db/schema_migrations/20230522132239 b/db/schema_migrations/20230522132239 new file mode 100644 index 0000000000000000000000000000000000000000..365eb1606cb4552a8335da5023b3eda92a25b01d --- /dev/null +++ b/db/schema_migrations/20230522132239 @@ -0,0 +1 @@ +7cda2cca39c53859e84bb7ecf3a2a9817f598486632a3cdd922dde6057b5c930 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 7fee4c30bcc80a8bf24344ae8721ba2ae4af751a..12eb34f06ffac11de9ac81acae1afcc534d2930b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -20899,7 +20899,8 @@ CREATE TABLE project_features ( infrastructure_access_level integer DEFAULT 20 NOT NULL, feature_flags_access_level integer DEFAULT 20 NOT NULL, environments_access_level integer DEFAULT 20 NOT NULL, - releases_access_level integer DEFAULT 20 NOT NULL + releases_access_level integer DEFAULT 20 NOT NULL, + model_experiments_access_level integer DEFAULT 20 NOT NULL ); CREATE SEQUENCE project_features_id_seq diff --git a/lib/api/ml/mlflow/entrypoint.rb b/lib/api/ml/mlflow/entrypoint.rb index 880b1efeb5a763968b3cf3617a80e64944c8c4f3..048234eccd1d4fb4d40dd0d841ae29718b7c50fa 100644 --- a/lib/api/ml/mlflow/entrypoint.rb +++ b/lib/api/ml/mlflow/entrypoint.rb @@ -26,7 +26,7 @@ class Entrypoint < ::API::Base authenticate! - not_found! unless Feature.enabled?(:ml_experiment_tracking, user_project) + not_found! unless can?(current_user, :read_model_experiments, user_project) end rescue_from ActiveRecord::ActiveRecordError do |e| diff --git a/lib/sidebars/projects/menus/packages_registries_menu.rb b/lib/sidebars/projects/menus/packages_registries_menu.rb index 39adbdce63b8bd22bb22449ae35be67f5da0ba8f..f41b7ce1a7307c364bbb47dc60fc5597ed031fa4 100644 --- a/lib/sidebars/projects/menus/packages_registries_menu.rb +++ b/lib/sidebars/projects/menus/packages_registries_menu.rb @@ -91,7 +91,7 @@ def harbor_registry_menu_item end def model_experiments_menu_item - if Feature.disabled?(:ml_experiment_tracking, context.project) + unless can?(context.current_user, :read_model_experiments, context.project) return ::Sidebars::NilMenuItem.new(item_id: :model_experiments) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8df4d6eeffbe29eed6778fb40c662b710d40eae2..b7c9ae71552427fcc14786c0a8774ffc387c9f22 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -35608,6 +35608,9 @@ msgstr "" msgid "ProjectSettings|Merging is only allowed when the source branch is up-to-date with its target." msgstr "" +msgid "ProjectSettings|Model experiments" +msgstr "" + msgid "ProjectSettings|Monitor" msgstr "" @@ -35791,6 +35794,9 @@ msgstr "" msgid "ProjectSettings|Topics are publicly visible even on private projects. Do not include sensitive information in topic names. %{linkStart}Learn more%{linkEnd}." msgstr "" +msgid "ProjectSettings|Track machine learning model experiments and artefacts." +msgstr "" + msgid "ProjectSettings|Transfer project" msgstr "" diff --git a/rubocop/cop/gitlab/feature_available_usage.rb b/rubocop/cop/gitlab/feature_available_usage.rb index df4409c27e051a5e18f4f7ff5148f52cee565bb6..cbfb89d3053c5ee794714d4ada32f8887707023c 100644 --- a/rubocop/cop/gitlab/feature_available_usage.rb +++ b/rubocop/cop/gitlab/feature_available_usage.rb @@ -28,6 +28,7 @@ class FeatureAvailableUsage < RuboCop::Cop::Base feature_flags releases infrastructure + model_experiments ].freeze EE_FEATURES = %i[requirements].freeze ALL_FEATURES = (FEATURES + EE_FEATURES).freeze diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index d1c4cbbe59113e95109c58fe321fb6a31ae67c13..2d22cca463c5f86c94b4d7cd7f69b086e4d329d6 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -985,6 +985,7 @@ def update_project_feature releases_access_level monitor_access_level infrastructure_access_level + model_experiments_access_level ] end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 1b485e47127a516353da483c261ad9e2e60042ea..6e3e119ddabae9b0a845247d39e7b54217f32c45 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -42,6 +42,7 @@ feature_flags_access_level { ProjectFeature::ENABLED } releases_access_level { ProjectFeature::ENABLED } infrastructure_access_level { ProjectFeature::ENABLED } + model_experiments_access_level { ProjectFeature::ENABLED } # we can't assign the delegated `#ci_cd_settings` attributes directly, as the # `#ci_cd_settings` relation needs to be created first diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js index a7a1e649cd00ed04c3efa3b8cdcc69cb8ef3f191..28287d0d9c27f830e758e8e5ea32c4d19b777d78 100644 --- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js +++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js @@ -139,6 +139,8 @@ describe('Settings Panel', () => { const findMonitorSettings = () => wrapper.findComponent({ ref: 'monitor-settings' }); const findMonitorVisibilityInput = () => findMonitorSettings().findComponent(ProjectFeatureSetting); + const findModelExperimentsSettings = () => + wrapper.findComponent({ ref: 'model-experiments-settings' }); describe('Project Visibility', () => { it('should set the project visibility help path', () => { @@ -802,4 +804,11 @@ describe('Settings Panel', () => { expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.EVERYONE); }); }); + describe('Model experiments', () => { + wrapper = mountComponent({}); + + it('shows model experiments toggle', () => { + expect(findModelExperimentsSettings().exists()).toBe(true); + }); + }); }); diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 3eb1090c9dcfed4a9f3384420ccb0f165099801a..04c8e79d7d6d84df7ae570b55c1ba176ebf2f615 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -1045,7 +1045,8 @@ def license_name environmentsAccessLevel: project.project_feature.environments_access_level, featureFlagsAccessLevel: project.project_feature.feature_flags_access_level, releasesAccessLevel: project.project_feature.releases_access_level, - infrastructureAccessLevel: project.project_feature.infrastructure_access_level + infrastructureAccessLevel: project.project_feature.infrastructure_access_level, + modelExperimentsAccessLevel: project.project_feature.model_experiments_access_level ) end diff --git a/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb b/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb index 860206dc6af9f3d6879c8c5d62932d784c9413de..b917208bac1b5fd39576b3899bdaacfa5300e946 100644 --- a/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb @@ -185,18 +185,25 @@ describe 'Model experiments' do let(:item_id) { :model_experiments } - context 'when :ml_experiment_tracking is enabled' do - it 'shows the menu item' do - stub_feature_flags(ml_experiment_tracking: true) + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(user, :read_model_experiments, project) + .and_return(model_experiments_enabled) + end + + context 'when user can access model experiments' do + let(:model_experiments_enabled) { true } + it 'shows the menu item' do is_expected.not_to be_nil end end - context 'when :ml_experiment_tracking is disabled' do - it 'does not show the menu item' do - stub_feature_flags(ml_experiment_tracking: false) + context 'when user does not have access model experiments' do + let(:model_experiments_enabled) { false } + it 'does not show the menu item' do is_expected.to be_nil end end diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index 87bfdd1577351441f5663ec4cd0649c53dfbdd48..03aee3ade8d65bc47a9264815938d45844bcce4a 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -30,6 +30,7 @@ specify { expect(subject.releases_access_level).to eq(ProjectFeature::ENABLED) } specify { expect(subject.package_registry_access_level).to eq(ProjectFeature::ENABLED) } specify { expect(subject.container_registry_access_level).to eq(ProjectFeature::ENABLED) } + specify { expect(subject.model_experiments_access_level).to eq(ProjectFeature::ENABLED) } end describe 'PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 32158ef9509199c60c78f9ea9c89a54b554ecd8f..7845c56538ef1988f5b837651d39160dd375f7ca 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1057,6 +1057,7 @@ it { is_expected.to delegate_method(:container_registry_enabled?).to(:project_feature) } it { is_expected.to delegate_method(:container_registry_access_level).to(:project_feature) } it { is_expected.to delegate_method(:environments_access_level).to(:project_feature) } + it { is_expected.to delegate_method(:model_experiments_access_level).to(:project_feature) } it { is_expected.to delegate_method(:feature_flags_access_level).to(:project_feature) } it { is_expected.to delegate_method(:releases_access_level).to(:project_feature) } it { is_expected.to delegate_method(:infrastructure_access_level).to(:project_feature) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index ae2a11bdbf0a1b24d3f8cd1d9fb7f1295eba5306..ed78599caa11db198e68163bae8ec4b20ce8b94e 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -3263,6 +3263,32 @@ def permissions_abilities(role) specify { is_expected.to be_disallowed(:read_namespace_catalog) } end + describe ':read_model_experiments' do + using RSpec::Parameterized::TableSyntax + + where(:ff_ml_experiment_tracking, :current_user, :access_level, :allowed) do + false | ref(:owner) | Featurable::ENABLED | false + true | ref(:guest) | Featurable::ENABLED | true + true | ref(:guest) | Featurable::PRIVATE | true + true | ref(:guest) | Featurable::DISABLED | false + true | ref(:non_member) | Featurable::ENABLED | true + true | ref(:non_member) | Featurable::PRIVATE | false + true | ref(:non_member) | Featurable::DISABLED | false + end + with_them do + before do + stub_feature_flags(ml_experiment_tracking: ff_ml_experiment_tracking) + project.project_feature.update!(model_experiments_access_level: access_level) + end + + if params[:allowed] + it { is_expected.to be_allowed(:read_model_experiments) } + else + it { is_expected.not_to be_allowed(:read_model_experiments) } + end + end + end + private def project_subject(project_type) diff --git a/spec/requests/api/ml/mlflow/experiments_spec.rb b/spec/requests/api/ml/mlflow/experiments_spec.rb index 1a2577e69e7775d18226f3cb10fc9c2f3a0e99f5..fc2e814752c09f40e418bd42a125f9e21756103e 100644 --- a/spec/requests/api/ml/mlflow/experiments_spec.rb +++ b/spec/requests/api/ml/mlflow/experiments_spec.rb @@ -20,7 +20,6 @@ end let(:current_user) { developer } - let(:ff_value) { true } let(:access_token) { tokens[:write] } let(:headers) { { 'Authorization' => "Bearer #{access_token.token}" } } let(:project_id) { project.id } @@ -52,10 +51,6 @@ response end - before do - stub_feature_flags(ml_experiment_tracking: ff_value) - end - describe 'GET /projects/:id/ml/mlflow/api/2.0/mlflow/experiments/get' do let(:experiment_iid) { experiment.iid.to_s } let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/experiments/get?experiment_id=#{experiment_iid}" } diff --git a/spec/requests/api/ml/mlflow/runs_spec.rb b/spec/requests/api/ml/mlflow/runs_spec.rb index 746372b79786bb7f1fcd9666417a7d0a06a4b120..a85fe4d867a434495b8cd3da1ad0fb2bc26a8ebd 100644 --- a/spec/requests/api/ml/mlflow/runs_spec.rb +++ b/spec/requests/api/ml/mlflow/runs_spec.rb @@ -26,7 +26,6 @@ end let(:current_user) { developer } - let(:ff_value) { true } let(:access_token) { tokens[:write] } let(:headers) { { 'Authorization' => "Bearer #{access_token.token}" } } let(:project_id) { project.id } @@ -40,10 +39,6 @@ response end - before do - stub_feature_flags(ml_experiment_tracking: ff_value) - end - RSpec.shared_examples 'MLflow|run_id param error cases' do context 'when run id is not passed' do let(:params) { {} } diff --git a/spec/requests/projects/ml/candidates_controller_spec.rb b/spec/requests/projects/ml/candidates_controller_spec.rb index 78c8e99e3f34fb0d7486516fb76de83f75ab4ee2..eec7af990637dc5f5f66984298f3a28f1e73f9f1 100644 --- a/spec/requests/projects/ml/candidates_controller_spec.rb +++ b/spec/requests/projects/ml/candidates_controller_spec.rb @@ -10,11 +10,13 @@ let(:ff_value) { true } let(:candidate_iid) { candidate.iid } + let(:model_experiments_enabled) { true } before do - stub_feature_flags(ml_experiment_tracking: false) - stub_feature_flags(ml_experiment_tracking: project) if ff_value - + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(user, :read_model_experiments, project) + .and_return(model_experiments_enabled) sign_in(user) end @@ -32,9 +34,9 @@ end end - shared_examples '404 if feature flag disabled' do - context 'when :ml_experiment_tracking disabled' do - let(:ff_value) { false } + shared_examples '404 when model experiments is unavailable' do + context 'when user does not have access' do + let(:model_experiments_enabled) { false } it_behaves_like 'renders 404' end @@ -59,7 +61,7 @@ end it_behaves_like '404 if candidate does not exist' - it_behaves_like '404 if feature flag disabled' + it_behaves_like '404 when model experiments is unavailable' end describe 'DELETE #destroy' do @@ -81,7 +83,7 @@ end it_behaves_like '404 if candidate does not exist' - it_behaves_like '404 if feature flag disabled' + it_behaves_like '404 when model experiments is unavailable' end private diff --git a/spec/requests/projects/ml/experiments_controller_spec.rb b/spec/requests/projects/ml/experiments_controller_spec.rb index 5a8496a250a486698e9e292fd64065e1ee92fed3..e2d26e84f752d8e09b1da3b4bf509bf1de6167b9 100644 --- a/spec/requests/projects/ml/experiments_controller_spec.rb +++ b/spec/requests/projects/ml/experiments_controller_spec.rb @@ -3,27 +3,25 @@ require 'spec_helper' RSpec.describe Projects::Ml::ExperimentsController, feature_category: :mlops do - let_it_be(:project_with_feature) { create(:project, :repository) } - let_it_be(:user) { project_with_feature.first_owner } - let_it_be(:project_without_feature) do - create(:project, :repository).tap { |p| p.add_developer(user) } - end - + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.first_owner } let_it_be(:experiment) do - create(:ml_experiments, project: project_with_feature, user: user).tap do |e| + create(:ml_experiments, project: project, user: user).tap do |e| create(:ml_candidates, experiment: e, user: user) end end let(:params) { basic_params } let(:ff_value) { true } - let(:project) { project_with_feature } let(:basic_params) { { namespace_id: project.namespace.to_param, project_id: project } } let(:experiment_iid) { experiment.iid } + let(:model_experiments_enabled) { true } before do - stub_feature_flags(ml_experiment_tracking: false) - stub_feature_flags(ml_experiment_tracking: project_with_feature) if ff_value + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(user, :read_model_experiments, project) + .and_return(model_experiments_enabled) sign_in(user) end @@ -42,9 +40,9 @@ end end - shared_examples '404 if feature flag disabled' do - context 'when :ml_experiment_tracking disabled' do - let(:ff_value) { false } + shared_examples '404 when model experiments is unavailable' do + context 'when user does not have access' do + let(:model_experiments_enabled) { false } it_behaves_like 'renders 404' end @@ -71,7 +69,7 @@ describe 'pagination' do let_it_be(:experiments) do - create_list(:ml_experiments, 3, project: project_with_feature) + create_list(:ml_experiments, 3, project: project) end let(:params) { basic_params.merge(id: experiment.iid) } @@ -102,19 +100,7 @@ end end - context 'when :ml_experiment_tracking is disabled for the project' do - let(:project) { project_without_feature } - - before do - list_experiments - end - - it 'responds with a 404' do - expect(response).to have_gitlab_http_status(:not_found) - end - end - - it_behaves_like '404 if feature flag disabled' do + it_behaves_like '404 when model experiments is unavailable' do before do list_experiments end @@ -225,7 +211,7 @@ end it_behaves_like '404 if experiment does not exist' - it_behaves_like '404 if feature flag disabled' + it_behaves_like '404 when model experiments is unavailable' end end @@ -257,14 +243,14 @@ end it_behaves_like '404 if experiment does not exist' - it_behaves_like '404 if feature flag disabled' + it_behaves_like '404 when model experiments is unavailable' end end end describe 'DELETE #destroy' do let_it_be(:experiment_for_deletion) do - create(:ml_experiments, project: project_with_feature, user: user).tap do |e| + create(:ml_experiments, project: project, user: user).tap do |e| create(:ml_candidates, experiment: e, user: user) end end @@ -282,7 +268,7 @@ end it_behaves_like '404 if experiment does not exist' - it_behaves_like '404 if feature flag disabled' + it_behaves_like '404 when model experiments is unavailable' end private diff --git a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb index 2ca62698dafd29f5aabd8d4ad4d94cd6c7b78d12..f2c38d70508cab49f9b0eace14e6bb07d89e705a 100644 --- a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb @@ -47,8 +47,13 @@ end end - context 'when ff is disabled' do - let(:ff_value) { false } + context 'when model experiments is unavailable' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(current_user, :read_model_experiments, project) + .and_return(false) + end it "is Not Found" do is_expected.to have_gitlab_http_status(:not_found)