diff --git a/.rubocop_todo/gitlab/bounded_contexts.yml b/.rubocop_todo/gitlab/bounded_contexts.yml index 3a4f85cae55e96b4b48ef94f57c5fc997acd628c..90b002692177a29a81d618b5453f4a414fbe604b 100644 --- a/.rubocop_todo/gitlab/bounded_contexts.yml +++ b/.rubocop_todo/gitlab/bounded_contexts.yml @@ -3495,6 +3495,7 @@ Gitlab/BoundedContexts: - 'ee/lib/ee/sidebars/groups/menus/settings_menu.rb' - 'ee/lib/ee/sidebars/groups/menus/work_items_menu.rb' - 'ee/lib/ee/sidebars/groups/panel.rb' + - 'ee/lib/ee/sidebars/groups/super_sidebar_panel.rb' - 'ee/lib/ee/sidebars/projects/menus/analytics_menu.rb' - 'ee/lib/ee/sidebars/projects/menus/ci_cd_menu.rb' - 'ee/lib/ee/sidebars/projects/menus/issues_menu.rb' @@ -3590,6 +3591,7 @@ Gitlab/BoundedContexts: - 'ee/lib/sidebars/groups/menus/security_compliance_menu.rb' - 'ee/lib/sidebars/groups/menus/wiki_menu.rb' - 'ee/lib/sidebars/groups/menus/work_item_epics_menu.rb' + - 'ee/lib/sidebars/groups/super_sidebar_menus/duo_agents_menu.rb' - 'ee/lib/sidebars/projects/menus/learn_gitlab_menu.rb' - 'ee/lib/sidebars/projects/super_sidebar_menus/duo_agents_menu.rb' - 'ee/lib/sidebars/user_settings/menus/profile_billing_menu.rb' diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml index 04cca56e384b9408db91b2bf265c99686c1554dc..358d93d8900c8eb58abd2b0f71f97c89938aca46 100644 --- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml +++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml @@ -72,7 +72,6 @@ RSpec/FactoryBot/AvoidCreate: - 'ee/spec/lib/sidebars/groups/menus/security_compliance_menu_spec.rb' - 'ee/spec/lib/sidebars/groups/menus/wiki_menu_spec.rb' - 'ee/spec/lib/sidebars/groups/menus/work_item_epics_menu_spec.rb' - - 'ee/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb' - 'ee/spec/mailers/ci_minutes_usage_mailer_spec.rb' - 'ee/spec/mailers/credentials_inventory_mailer_spec.rb' - 'ee/spec/mailers/devise_mailer_spec.rb' diff --git a/.rubocop_todo/rspec/receive_messages.yml b/.rubocop_todo/rspec/receive_messages.yml index 3ef2111554ef66475e6368490371ba28f651c255..27c59556019d35d0e256a9b08e19bd47a643dec2 100644 --- a/.rubocop_todo/rspec/receive_messages.yml +++ b/.rubocop_todo/rspec/receive_messages.yml @@ -64,7 +64,6 @@ RSpec/ReceiveMessages: - 'ee/spec/lib/gitlab/vulnerability_scanning/finding_builder_spec.rb' - 'ee/spec/lib/google_cloud/artifact_registry/client_spec.rb' - 'ee/spec/lib/omni_auth/strategies/group_saml_spec.rb' - - 'ee/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb' - 'ee/spec/migrations/geo/resync_direct_upload_job_artifact_registry_spec.rb' - 'ee/spec/models/approval_state_spec.rb' - 'ee/spec/models/ci/build_spec.rb' diff --git a/ee/app/assets/javascripts/ai/duo_agents_platform/constants.js b/ee/app/assets/javascripts/ai/duo_agents_platform/constants.js index d84e0157d0d960a20d92e421add3cbeb99eb2799..fd942797798c4492380319fe86e449f5f312a812 100644 --- a/ee/app/assets/javascripts/ai/duo_agents_platform/constants.js +++ b/ee/app/assets/javascripts/ai/duo_agents_platform/constants.js @@ -7,6 +7,7 @@ export const TOOL_MESSAGE_TYPE = 'tool'; export const AGENT_PLATFORM_INDEX_COMPONENT_NAME = 'DuoAgentPlatformIndex'; export const AGENT_PLATFORM_PROJECT_PAGE = 'project'; +export const AGENT_PLATFORM_GROUP_PAGE = 'group'; export const AGENT_PLATFORM_USER_PAGE = 'user'; export const AGENT_PLATFORM_STATUS_ICON = { diff --git a/ee/app/assets/javascripts/ai/duo_agents_platform/index.js b/ee/app/assets/javascripts/ai/duo_agents_platform/index.js index dbcbae5875fa7c4cb074e4f64970cc6182f8d69f..c0b37c1bd5acf1fd7a2ec8c6edaabc437522b029 100644 --- a/ee/app/assets/javascripts/ai/duo_agents_platform/index.js +++ b/ee/app/assets/javascripts/ai/duo_agents_platform/index.js @@ -34,7 +34,7 @@ export const initDuoAgentsPlatformPage = ({ namespaceDatasetProperties = [], nam el, provide: { exploreAiCatalogPath, - flowTriggersEventTypeOptions: JSON.parse(flowTriggersEventTypeOptions), + flowTriggersEventTypeOptions: JSON.parse(flowTriggersEventTypeOptions || '[]'), ...namespaceProvideData, }, }); diff --git a/ee/app/assets/javascripts/ai/duo_agents_platform/namespace/group/index.js b/ee/app/assets/javascripts/ai/duo_agents_platform/namespace/group/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c97c003362eab323c33e1e5c5f2a31c4e37aa0b7 --- /dev/null +++ b/ee/app/assets/javascripts/ai/duo_agents_platform/namespace/group/index.js @@ -0,0 +1,9 @@ +import { initDuoAgentsPlatformPage } from '../../index'; +import { AGENT_PLATFORM_GROUP_PAGE } from '../../constants'; + +export const initDuoAgentsPlatformGroupPage = () => { + initDuoAgentsPlatformPage({ + namespace: AGENT_PLATFORM_GROUP_PAGE, + namespaceDatasetProperties: ['groupPath', 'groupId'], + }); +}; diff --git a/ee/app/assets/javascripts/pages/groups/duo_agents_platform/index.js b/ee/app/assets/javascripts/pages/groups/duo_agents_platform/index.js new file mode 100644 index 0000000000000000000000000000000000000000..aee7957805deec57eaceb6e32305e02aacb86358 --- /dev/null +++ b/ee/app/assets/javascripts/pages/groups/duo_agents_platform/index.js @@ -0,0 +1,3 @@ +import { initDuoAgentsPlatformGroupPage } from 'ee/ai/duo_agents_platform/namespace/group'; + +initDuoAgentsPlatformGroupPage(); diff --git a/ee/app/controllers/groups/duo_agents_platform_controller.rb b/ee/app/controllers/groups/duo_agents_platform_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..cb26e708a35041508976e9926e2cd5fbbc343e93 --- /dev/null +++ b/ee/app/controllers/groups/duo_agents_platform_controller.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Groups + class DuoAgentsPlatformController < Groups::ApplicationController + feature_category :duo_agent_platform + before_action :check_access + before_action do + push_frontend_feature_flag(:ai_catalog_flows, current_user) + end + + def show; end + + private + + def check_access + return render_404 unless group&.duo_features_enabled && current_user.can?(:duo_workflow, group) + + return unless specific_vueroute? + + render_404 unless authorized_for_route? + end + + def specific_vueroute? + %w[flows].include?(duo_agents_platform_params[:vueroute]) + end + + def authorized_for_route? + case duo_agents_platform_params[:vueroute] + when 'flows' + Feature.enabled?(:global_ai_catalog, current_user) && + Feature.enabled?(:ai_catalog_flows, current_user) + end + end + + def duo_agents_platform_params + params.permit(:vueroute) + end + end +end diff --git a/ee/app/helpers/ee/projects/duo_agents_platform_helper.rb b/ee/app/helpers/ee/projects/duo_agents_platform_helper.rb index d1efca2cd3165cc9788ae0ffa57e06c3914a9397..83a2e95684dee73cb44ef469e179db79f40e8c76 100644 --- a/ee/app/helpers/ee/projects/duo_agents_platform_helper.rb +++ b/ee/app/helpers/ee/projects/duo_agents_platform_helper.rb @@ -12,6 +12,15 @@ def duo_agents_platform_data(project) flow_triggers_event_type_options: ai_flow_triggers_event_type_options } end + + def duo_agents_group_data(group) + { + agents_platform_base_route: group_automate_path(group), + group_id: group.id, + group_path: group.full_path, + explore_ai_catalog_path: explore_ai_catalog_path + } + end end end end diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 8f33178ab0902bce4f45bd8e1f71ae695bc7b269..49fbf9f23830a28769b21c4da9f3d96411f2a6b1 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -1040,6 +1040,10 @@ module GroupPolicy prevent :create_group_link end + condition(:duo_workflow_enabled) do + ::Feature.enabled?(:duo_workflow, @user) + end + with_scope :subject condition(:duo_workflow_available) do @subject.duo_features_enabled && @@ -1047,6 +1051,10 @@ module GroupPolicy @user&.allowed_to_use?(:duo_agent_platform) end + rule { duo_workflow_enabled & duo_workflow_available & can?(:developer_access) }.policy do + enable :duo_workflow + end + rule { duo_workflow_available & can?(:admin_group) }.policy do enable :admin_duo_workflow end diff --git a/ee/app/views/groups/duo_agents_platform/show.html.haml b/ee/app/views/groups/duo_agents_platform/show.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..b5382b3a85de18ffffd4ce561ba2f974dd8be46e --- /dev/null +++ b/ee/app/views/groups/duo_agents_platform/show.html.haml @@ -0,0 +1,4 @@ +- page_title _('Automate') +- @skip_current_level_breadcrumb = true + +#js-duo-agents-platform-page{ data: duo_agents_group_data(@group) } diff --git a/ee/config/routes/group.rb b/ee/config/routes/group.rb index 7d691833026f58f10104e75f9fac49b5876249de..f0ca549e32ee13a0331aaefada034a2a85c28020 100644 --- a/ee/config/routes/group.rb +++ b/ee/config/routes/group.rb @@ -282,6 +282,11 @@ end end + scope :automate do + get '/(*vueroute)' => 'duo_agents_platform#show', as: :automate, format: false + get 'flows', to: 'duo_agents_platform#show', as: :automate_flows, format: false + end + draw :virtual_registries end end diff --git a/ee/lib/ee/sidebars/groups/super_sidebar_panel.rb b/ee/lib/ee/sidebars/groups/super_sidebar_panel.rb new file mode 100644 index 0000000000000000000000000000000000000000..66af3888933776b3a9493c50302ad3b5a141219b --- /dev/null +++ b/ee/lib/ee/sidebars/groups/super_sidebar_panel.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module EE + module Sidebars + module Groups + module SuperSidebarPanel + extend ::Gitlab::Utils::Override + + override :configure_menus + def configure_menus + super + + insert_menu_after( + ::Sidebars::Groups::SuperSidebarMenus::PlanMenu, + ::Sidebars::Groups::SuperSidebarMenus::DuoAgentsMenu.new(context) + ) + end + end + end + end +end diff --git a/ee/lib/sidebars/groups/super_sidebar_menus/duo_agents_menu.rb b/ee/lib/sidebars/groups/super_sidebar_menus/duo_agents_menu.rb new file mode 100644 index 0000000000000000000000000000000000000000..8ea0a4c12e5d8a883d4b31396783eab99e95dd47 --- /dev/null +++ b/ee/lib/sidebars/groups/super_sidebar_menus/duo_agents_menu.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Sidebars + module Groups + module SuperSidebarMenus + class DuoAgentsMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + return false unless current_user&.can?(:duo_workflow, context.group) && + context.group.duo_features_enabled && + show_flows_menu_item? + + add_item(ai_catalog_flows_menu_item) if show_flows_menu_item? + + true + end + + override :title + def title + s_('DuoAgentsPlatform|Automate') + end + + override :sprite_icon + def sprite_icon + 'tanuki-ai' + end + + override :active_routes + def active_routes + { controller: :duo_agents_platform } + end + + private + + def show_flows_menu_item? + Feature.enabled?(:global_ai_catalog, context.current_user) && + Feature.enabled?(:ai_catalog_flows, context.current_user) + end + + def ai_catalog_flows_menu_item + ::Sidebars::MenuItem.new( + title: s_('AICatalog|Flows'), + link: group_automate_flows_path(context.group), + active_routes: nil, + item_id: :ai_flows + ) + end + end + end + end +end diff --git a/ee/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb b/ee/spec/lib/ee/sidebars/groups/super_sidebar_panel_spec.rb similarity index 64% rename from ee/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb rename to ee/spec/lib/ee/sidebars/groups/super_sidebar_panel_spec.rb index 7e8af4cab56c8f45bf056296f8bd29daafd70bb1..3a3a00275dde1dbb6995919849a7ba0999aea073 100644 --- a/ee/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb +++ b/ee/spec/lib/ee/sidebars/groups/super_sidebar_panel_spec.rb @@ -4,7 +4,7 @@ RSpec.describe Sidebars::Groups::SuperSidebarPanel, feature_category: :navigation do let(:user) { build_stubbed(:user) } - let(:group) { create(:group, owners: user) } + let(:group) { build(:group, owners: user) } let(:context) do Sidebars::Groups::Context.new( @@ -17,25 +17,26 @@ ) end - subject { described_class.new(context) } + subject(:panel) { described_class.new(context) } # We want to enable _all_ possible menu items for these specs before do # Give the user access to everything and enable every feature allow(Ability).to receive(:allowed?).and_return(true) - allow(group).to receive(:licensed_feature_available?).and_return(true) # Needed to show Container Registry items allow(::Gitlab.config.registry).to receive(:enabled).and_return(true) # Needed to show Billing allow(::Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?).and_return(true) # Needed to show LDAP Group Sync allow(::Gitlab::Auth::Ldap::Config).to receive(:group_sync_enabled?).and_return(true) - # Needed for Domain Verification entry - allow(group).to receive(:domain_verification_available?).and_return(true) # Needed for GitLab Duo menu item stub_licensed_features(code_suggestions: true) add_on = create(:gitlab_subscription_add_on) create(:gitlab_subscription_add_on_purchase, quantity: 50, namespace: group, add_on: add_on) + # Enable licensed features, Domain Verification, and Duo Agent Platform + allow(group).to receive_messages(licensed_feature_available?: true, domain_verification_available?: true, + duo_features_enabled: true) + stub_feature_flags(global_ai_catalog: true, ai_catalog_flows: true) # Needed for Roles and permissions stub_saas_features(gitlab_com_subscriptions: true) # Needed for virtual registry @@ -44,6 +45,21 @@ stub_feature_flags(contributions_analytics_dashboard: false) end + describe '#renderable_menus' do + it 'includes DuoAgentsMenu' do + menu_classes = panel.instance_variable_get(:@menus).map(&:class) + expect(menu_classes).to include(Sidebars::Groups::SuperSidebarMenus::DuoAgentsMenu) + end + + it 'positions DuoAgentsMenu after PlanMenu' do + menus = panel.instance_variable_get(:@menus).map(&:class) + plan_index = menus.index(Sidebars::Groups::SuperSidebarMenus::PlanMenu) + duo_agents_index = menus.index(Sidebars::Groups::SuperSidebarMenus::DuoAgentsMenu) + + expect(plan_index).to be < duo_agents_index + end + end + it_behaves_like 'a panel with uniquely identifiable menu items' it_behaves_like 'a panel with all menu_items categorized' it_behaves_like 'a panel without placeholders' diff --git a/ee/spec/lib/ee/sidebars/projects/super_sidebar_panel_spec.rb b/ee/spec/lib/ee/sidebars/projects/super_sidebar_panel_spec.rb index 4fbdbbf37a28534d362c5f4a0ccab110a979d042..70a9c823236b672121855960793c512197eaebe9 100644 --- a/ee/spec/lib/ee/sidebars/projects/super_sidebar_panel_spec.rb +++ b/ee/spec/lib/ee/sidebars/projects/super_sidebar_panel_spec.rb @@ -36,7 +36,7 @@ ) end - subject { described_class.new(context) } + subject(:panel) { described_class.new(context) } # We want to enable _all_ possible menu items for these specs before do @@ -54,6 +54,24 @@ project.update!(service_desk_enabled: true) stub_feature_flags(hide_incident_management_features: false) stub_feature_flags(hide_error_tracking_features: false) + # Needed for Duo Agent Platform menu items + allow(project).to receive(:duo_features_enabled).and_return(true) + stub_feature_flags(global_ai_catalog: true, ai_catalog_flows: true) + end + + describe '#renderable_menus' do + it 'includes DuoAgentsMenu in EE' do + menus = panel.instance_variable_get(:@menus).map(&:class) + expect(menus).to include(Sidebars::Projects::SuperSidebarMenus::DuoAgentsMenu) + end + + it 'positions DuoAgentsMenu after PlanMenu' do + menus = panel.instance_variable_get(:@menus).map(&:class) + plan_index = menus.index(Sidebars::Projects::SuperSidebarMenus::PlanMenu) + duo_agents_index = menus.index(Sidebars::Projects::SuperSidebarMenus::DuoAgentsMenu) + + expect(plan_index).to be < duo_agents_index + end end it_behaves_like 'a panel with uniquely identifiable menu items' diff --git a/ee/spec/lib/sidebars/groups/super_sidebar_menus/duo_agents_menu_spec.rb b/ee/spec/lib/sidebars/groups/super_sidebar_menus/duo_agents_menu_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..3f50e31f9469e51fc3ada644591867acbf2419f8 --- /dev/null +++ b/ee/spec/lib/sidebars/groups/super_sidebar_menus/duo_agents_menu_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Groups::SuperSidebarMenus::DuoAgentsMenu, feature_category: :duo_agent_platform do + let_it_be(:group) { build_stubbed(:group) } + let_it_be(:user) { build_stubbed(:user) } + let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group) } + + subject(:menu) { described_class.new(context) } + + describe '#configure_menu_items' do + using RSpec::Parameterized::TableSyntax + + where(:duo_features_enabled, :ai_catalog, :ai_catalog_flows_ff, :configure_result, :expected_items) do + true | true | true | true | [:ai_flows] + true | true | false | false | [] + true | false | true | false | [] + false | true | true | false | [] + end + + with_them do + before do + allow(group).to receive(:duo_features_enabled).and_return(duo_features_enabled) + stub_feature_flags(global_ai_catalog: ai_catalog) + stub_feature_flags(ai_catalog_flows: ai_catalog_flows_ff) + allow(user).to receive(:can?).with(:duo_workflow, group).and_return(true) + end + + it "returns correct configure result" do + expect(menu.configure_menu_items).to eq(configure_result) + end + + it "renders expected menu items" do + expect(menu.renderable_items.size).to eq(expected_items.size) + + if expected_items.any? + expect(menu.renderable_items.map(&:item_id)).to match_array(expected_items) + else + expect(menu.renderable_items).to be_empty + end + end + end + end + + describe "when user does not have `duo_workflow` ability" do + before do + allow(user).to receive(:can?).with(:duo_workflow, group).and_return(false) + end + + it('does not render any menu items') do + expect(menu.configure_menu_items).to be false + end + end + + describe '#title' do + it 'returns correct title' do + expect(menu.title).to eq('Automate') + end + end + + describe '#sprite_icon' do + it 'returns correct icon' do + expect(menu.sprite_icon).to eq('tanuki-ai') + end + end + + describe 'flows menu item' do + before do + allow(group).to receive(:duo_features_enabled).and_return(true) + allow(user).to receive(:can?).with(:duo_workflow, group).and_return(true) + + menu.configure_menu_items + end + + let(:flows_menu_item) { menu.renderable_items.find { |item| item.item_id == :ai_flows } } + + it 'has correct title' do + expect(flows_menu_item.title).to eq('Flows') + end + + it 'has correct link' do + expect(flows_menu_item.link).to eq("/groups/#{group.full_path}/-/automate/flows") + end + + it 'has correct active routes' do + expect(flows_menu_item.active_routes).to be_nil + end + + it 'has correct item id' do + expect(flows_menu_item.item_id).to eq(:ai_flows) + end + end +end diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index 90f28c23f4e104461324413a8c0b822da25e5df1..8273b66eabb4903159238fd338f892ae76898b9e 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -4981,6 +4981,64 @@ def create_member_role(member, abilities = member_role_abilities) end end + describe 'duo_workflow' do + let(:policy) { :duo_workflow } + + where(:duo_workflow_feature_flag, :stage_check_available, :duo_features_enabled, :current_user, :match_expected_result) do + true | true | true | ref(:owner) | be_allowed(:duo_workflow) + true | true | true | ref(:maintainer) | be_allowed(:duo_workflow) + true | true | true | ref(:developer) | be_allowed(:duo_workflow) + true | true | true | ref(:planner) | be_disallowed(:duo_workflow) + true | true | true | ref(:reporter) | be_disallowed(:duo_workflow) + true | true | true | ref(:guest) | be_disallowed(:duo_workflow) + true | false | true | ref(:owner) | be_disallowed(:duo_workflow) + true | false | true | ref(:maintainer) | be_disallowed(:duo_workflow) + true | false | true | ref(:developer) | be_disallowed(:duo_workflow) + true | false | true | ref(:planner) | be_disallowed(:duo_workflow) + true | false | true | ref(:reporter) | be_disallowed(:duo_workflow) + true | false | true | ref(:guest) | be_disallowed(:duo_workflow) + false | true | true | ref(:owner) | be_disallowed(:duo_workflow) + false | true | true | ref(:maintainer) | be_disallowed(:duo_workflow) + false | true | true | ref(:developer) | be_disallowed(:duo_workflow) + false | true | true | ref(:planner) | be_disallowed(:duo_workflow) + false | true | true | ref(:reporter) | be_disallowed(:duo_workflow) + false | true | true | ref(:guest) | be_disallowed(:duo_workflow) + false | false | true | ref(:owner) | be_disallowed(:duo_workflow) + false | false | true | ref(:maintainer) | be_disallowed(:duo_workflow) + false | false | true | ref(:developer) | be_disallowed(:duo_workflow) + false | false | true | ref(:planner) | be_disallowed(:duo_workflow) + false | false | true | ref(:reporter) | be_disallowed(:duo_workflow) + false | false | true | ref(:guest) | be_disallowed(:duo_workflow) + false | false | false | ref(:owner) | be_disallowed(:duo_workflow) + false | false | false | ref(:maintainer) | be_disallowed(:duo_workflow) + false | false | false | ref(:developer) | be_disallowed(:duo_workflow) + false | false | false | ref(:planner) | be_disallowed(:duo_workflow) + false | false | false | ref(:reporter) | be_disallowed(:duo_workflow) + false | false | false | ref(:guest) | be_disallowed(:duo_workflow) + end + + with_them do + before do + stub_feature_flags(duo_workflow: duo_workflow_feature_flag) + allow(::Gitlab::Llm::StageCheck).to receive(:available?).with(group, :duo_workflow).and_return(stage_check_available) + allow(group).to receive(:duo_features_enabled).and_return(duo_features_enabled) + allow(current_user).to receive(:allowed_to_use?).with(:duo_agent_platform).and_return(true) + end + + it { is_expected.to match_expected_result } + end + + context 'when user is not allowed to use duo_agent_platform' do + before do + allow(current_user).to receive(:allowed_to_use?).with(:duo_agent_platform).and_return(false) + end + + let(:current_user) { developer } + + it { is_expected.to be_disallowed(:duo_workflow) } + end + end + describe 'admin_duo_workflow' do let(:policy) { :admin_duo_workflow } diff --git a/ee/spec/requests/groups/duo_agents_platform_controller_spec.rb b/ee/spec/requests/groups/duo_agents_platform_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0847faeea71aea0446a2bee723f5829ce037e641 --- /dev/null +++ b/ee/spec/requests/groups/duo_agents_platform_controller_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Groups::DuoAgentsPlatform', feature_category: :duo_agent_platform do + let(:group) { create(:group) } + let(:user) { create(:user) } + + before do + group.add_developer(user) + group.namespace_settings.update!(duo_features_enabled: true) + + sign_in(user) + allow(user).to receive(:can?).and_return(true) + allow(user).to receive(:can?).with(:duo_workflow, group).and_return(true) + end + + describe 'GET /:group/-/automate' do + context 'when user has access to duo_workflow' do + it 'renders successfully' do + get group_automate_flows_path(group) + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when user does not have access to duo_workflow' do + before do + allow(user).to receive(:can?).with(:duo_workflow, group).and_return(false) + end + + it 'does not render' do + get group_automate_flows_path(group) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when duo_features_enabled setting is disabled for the group' do + before do + group.namespace_settings.update!(duo_features_enabled: false) + end + + it 'returns 404' do + get group_automate_flows_path(group) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when vueroute is flows' do + context 'when ai_catalog_flows feature is enabled' do + before do + stub_feature_flags(global_ai_catalog: true, ai_catalog_flows: true) + end + + it 'returns successfully' do + get group_automate_flows_path(group) + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when ai_catalog_flows is disabled' do + before do + stub_feature_flags(global_ai_catalog: true, ai_catalog_flows: false) + end + + it 'returns 404' do + get group_automate_flows_path(group) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when global_ai_catalog is disabled' do + before do + stub_feature_flags(global_ai_catalog: false, ai_catalog_flows: true) + end + + it 'returns 404' do + get group_automate_flows_path(group) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end +end diff --git a/lib/sidebars/groups/super_sidebar_panel.rb b/lib/sidebars/groups/super_sidebar_panel.rb index bf4cc5e846e15268f3b6daca8c411f277cf45851..54bb30c5563e283004ef2eca7cf66e0396c26993 100644 --- a/lib/sidebars/groups/super_sidebar_panel.rb +++ b/lib/sidebars/groups/super_sidebar_panel.rb @@ -43,3 +43,5 @@ def super_sidebar_context_header end end end + +Sidebars::Groups::SuperSidebarPanel.prepend_mod_with('Sidebars::Groups::SuperSidebarPanel') diff --git a/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb b/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb index 970cd3897479c82023cba4b91b626f45fccc9029..2aa092f09bf6c7c8be19c4322e762410a6084a51 100644 --- a/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb +++ b/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb @@ -47,7 +47,7 @@ end it "is exposed as a renderable menu" do - expect(subject.instance_variable_get(:@menus).map(&:class)).to eq(category_menu) + expect(subject.instance_variable_get(:@menus).map(&:class)).to include(*category_menu) end end