diff --git a/config/feature_flags/wip/observability_logs.yml b/config/feature_flags/wip/observability_logs.yml new file mode 100644 index 0000000000000000000000000000000000000000..8b262d268b37d983df69d577607b00a46b2c6721 --- /dev/null +++ b/config/feature_flags/wip/observability_logs.yml @@ -0,0 +1,8 @@ +--- +name: observability_logs +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142989 +rollout_issue_url: https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2643 +milestone: '16.9' +type: wip +group: group::observability +default_enabled: false diff --git a/ee/app/controllers/projects/logs_controller.rb b/ee/app/controllers/projects/logs_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..5370bf695c7735cc9e87f2f0574970c6544bad99 --- /dev/null +++ b/ee/app/controllers/projects/logs_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Projects + class LogsController < Projects::ApplicationController + include ::Observability::ContentSecurityPolicy + + feature_category :metrics + + before_action :authorize_read_observability_logs! + + def index; end + end +end diff --git a/ee/app/helpers/projects/observability_helper.rb b/ee/app/helpers/projects/observability_helper.rb index 83390863ce04e2708a8016957bc27d15f6793a61..bc0a991f52f9f96b93cef675cd6e6f98d4792c5d 100644 --- a/ee/app/helpers/projects/observability_helper.rb +++ b/ee/app/helpers/projects/observability_helper.rb @@ -25,6 +25,10 @@ def observability_tracing_details_model(project, trace_id) end end + def observability_logs_view_model(project) + generate_model(project) + end + private def generate_model(project) @@ -46,7 +50,8 @@ def shared_model(project) operationsUrl: ::Gitlab::Observability.operations_url(project), metricsUrl: ::Gitlab::Observability.metrics_url(project), metricsSearchUrl: ::Gitlab::Observability.metrics_search_url(project), - metricsSearchMetadataUrl: ::Gitlab::Observability.metrics_search_metadata_url(project) + metricsSearchMetadataUrl: ::Gitlab::Observability.metrics_search_metadata_url(project), + logsSearchUrl: ::Gitlab::Observability.logs_search_url(project) } } end diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb index 56a9c9fae171016156007e24cfe3b31adef547ee..583ee36c96dce433a910e81722f64b99f8685603 100644 --- a/ee/app/models/gitlab_subscriptions/features.rb +++ b/ee/app/models/gitlab_subscriptions/features.rb @@ -128,6 +128,7 @@ class Features merge_trains metrics_reports metrics_observability + logs_observability multiple_alert_http_integrations multiple_approval_rules multiple_group_issue_boards diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 8ea26d93947a6e507ba149407c047c4460eb1052..fae93bd1f73eedcf087351631377921105e26114 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -348,6 +348,11 @@ module ProjectPolicy @subject.licensed_feature_available?(:metrics_observability) end + condition(:observability_logs_enabled) do + ::Feature.enabled?(:observability_logs, @subject.root_namespace, type: :wip) && + @subject.licensed_feature_available?(:logs_observability) + end + # We are overriding the already defined condition in CE version # to allow Guest users with member roles to access the merge requests. condition(:merge_requests_disabled) do @@ -871,6 +876,10 @@ module ProjectPolicy enable :read_observability_metrics end + rule { can?(:reporter_access) & observability_logs_enabled }.policy do + enable :read_observability_logs + end + rule { ci_cancellation_maintainers_only & ~can?(:maintainer_access) }.policy do prevent :cancel_pipeline prevent :cancel_build diff --git a/ee/app/views/projects/logs/index.html.haml b/ee/app/views/projects/logs/index.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..2da9001225f7c8fa201396d0e4d7e0abc0618f9c --- /dev/null +++ b/ee/app/views/projects/logs/index.html.haml @@ -0,0 +1,5 @@ +- page_title s_('ObservabilityLogs|Logs') +- @content_wrapper_class = "#{@content_wrapper_class} gl-pb-0" +- @no_container = true + +#js-observability-logs{ data: { view_model: observability_logs_view_model(@project) } } diff --git a/ee/config/routes/project.rb b/ee/config/routes/project.rb index 3bc123425fb63275ad0f552a35ea21cdedcb39e1..7620d24eb335858131180758c3e181712c90a7d4 100644 --- a/ee/config/routes/project.rb +++ b/ee/config/routes/project.rb @@ -153,6 +153,8 @@ resources :metrics, only: [:index, :show], constraints: { id: %r{[^/]+}, type: /\w+/ }, controller: :metrics + resources :logs, only: [:index], controller: :logs + namespace :ml do resources :agents, only: [:index, :new, :show], controller: 'agents', param: :agent_id end diff --git a/ee/lib/ee/gitlab/observability.rb b/ee/lib/ee/gitlab/observability.rb index 769f0df7fb2308116864fcc5883379d36e367938..b4a943e9560759cdfa8ef0801bb86e16d21ad55a 100644 --- a/ee/lib/ee/gitlab/observability.rb +++ b/ee/lib/ee/gitlab/observability.rb @@ -34,6 +34,10 @@ def metrics_search_url(project) def metrics_search_metadata_url(project) "#{::Gitlab::Observability.observability_url}/v3/query/#{project.id}/metrics/searchmetadata" end + + def logs_search_url(project) + "#{::Gitlab::Observability.observability_url}/v3/query/#{project.id}/logs/search" + end end end end diff --git a/ee/lib/ee/sidebars/projects/menus/monitor_menu.rb b/ee/lib/ee/sidebars/projects/menus/monitor_menu.rb index 7f3efd5668a231a768c651a35dc1325b9426bd1c..686f8339829b81196c24ddf5bee69856650183b8 100644 --- a/ee/lib/ee/sidebars/projects/menus/monitor_menu.rb +++ b/ee/lib/ee/sidebars/projects/menus/monitor_menu.rb @@ -13,6 +13,7 @@ def configure_menu_items insert_item_before(:error_tracking, tracing_menu_item) insert_item_after(:tracing, metrics_menu_item) + insert_item_after(:logs, logs_menu_item) insert_item_after(:incidents, on_call_schedules_menu_item) insert_item_after(:on_call_schedules, escalation_policies_menu_item) @@ -76,6 +77,20 @@ def metrics_menu_item item_id: :metrics ) end + + def logs_menu_item + unless can?(context.current_user, :read_observability_logs, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :logs) + end + + ::Sidebars::MenuItem.new( + title: s_('ObservabilityLogs|Logs'), + link: project_logs_path(context.project), + super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::MonitorMenu, + active_routes: { controller: :logs }, + item_id: :logs + ) + end end end end diff --git a/ee/spec/helpers/projects/observability_helper_spec.rb b/ee/spec/helpers/projects/observability_helper_spec.rb index 1fdeb4952b77a48d773a29e7294bde2e05eb182c..69d905411b28943d0d32f6b141264bac39ea73f0 100644 --- a/ee/spec/helpers/projects/observability_helper_spec.rb +++ b/ee/spec/helpers/projects/observability_helper_spec.rb @@ -19,7 +19,8 @@ operationsUrl: Gitlab::Observability.operations_url(project), metricsUrl: Gitlab::Observability.metrics_url(project), metricsSearchUrl: Gitlab::Observability.metrics_search_url(project), - metricsSearchMetadataUrl: Gitlab::Observability.metrics_search_metadata_url(project) + metricsSearchMetadataUrl: Gitlab::Observability.metrics_search_metadata_url(project), + logsSearchUrl: Gitlab::Observability.logs_search_url(project) } end @@ -68,4 +69,14 @@ .to eq(expected_json) end end + + describe '#observability_logs_view_model' do + it 'generates the correct JSON' do + expected_json = { + apiConfig: expected_api_config + }.to_json + + expect(helper.observability_logs_view_model(project)).to eq(expected_json) + end + end end diff --git a/ee/spec/lib/ee/gitlab/observability_spec.rb b/ee/spec/lib/ee/gitlab/observability_spec.rb index 1998614d0eea53b257ead6f234d2169167256f54..a4d5a7a2d8fcaac9d5a1db8b6811a4ebe13c41bd 100644 --- a/ee/spec/lib/ee/gitlab/observability_spec.rb +++ b/ee/spec/lib/ee/gitlab/observability_spec.rb @@ -51,4 +51,10 @@ it { is_expected.to eq("#{described_class.observability_url}/v3/query/#{project.id}/metrics/searchmetadata") } end + + describe '.logs_search_url' do + subject { described_class.logs_search_url(project) } + + it { is_expected.to eq("#{described_class.observability_url}/v3/query/#{project.id}/logs/search") } + end end diff --git a/ee/spec/lib/ee/sidebars/projects/menus/monitor_menu_spec.rb b/ee/spec/lib/ee/sidebars/projects/menus/monitor_menu_spec.rb index 38502d778903ba9897b1107b549a28673ae33b96..af8000c6dd41e1a231741f0f875b29b41ec401c5 100644 --- a/ee/spec/lib/ee/sidebars/projects/menus/monitor_menu_spec.rb +++ b/ee/spec/lib/ee/sidebars/projects/menus/monitor_menu_spec.rb @@ -111,5 +111,40 @@ it { is_expected.to be_nil } end end + + describe 'Logs', feature_category: :metrics do + let(:item_id) { :logs } + let(:user) { build(:user) } + let(:role) { :reporter } + + before do + stub_licensed_features(logs_observability: true) + stub_member_access_level(project, role => user) + end + + it { is_expected.not_to be_nil } + + describe 'when feature flag is disabled' do + before do + stub_feature_flags(observability_logs: false) + end + + it { is_expected.to be_nil } + end + + describe 'when unlicensed' do + before do + stub_licensed_features(logs_observability: false) + end + + it { is_expected.to be_nil } + end + + describe 'when user does not have permissions' do + let(:role) { :guest } + + it { is_expected.to be_nil } + end + end end end diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index d63536b8984e28b1c93fd65df355e9fcb1c44adf..4ad932606e9f430563ef1ed552c5151f52516ba6 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -3242,6 +3242,49 @@ def create_member_role(member, abilities = member_role_abilities) end end + describe 'read_observability_logs policy' do + let(:current_user) { reporter } + + before do + stub_licensed_features(logs_observability: true) + end + + describe 'when feature flag is disabled' do + before do + stub_feature_flags(observability_logs: false) + end + + it { is_expected.to be_disallowed(:read_observability_logs) } + end + + describe 'when feature flag is enabled for root namespace' do + before do + stub_feature_flags(observability_logs: false) + stub_feature_flags(observability_logs: project.root_namespace) + end + + it { is_expected.to be_allowed(:read_observability_logs) } + end + + describe 'when the project does not have the correct license' do + before do + stub_licensed_features(logs_observability: false) + end + + it { is_expected.to be_disallowed(:read_observability_logs) } + end + + describe 'when the user does not have permission' do + let(:current_user) { guest } + + it { is_expected.to be_disallowed(:read_observability_logs) } + end + + describe 'when the user has permission' do + it { is_expected.to be_allowed(:read_observability_logs) } + end + end + describe 'generate_cube_query policy' do using RSpec::Parameterized::TableSyntax diff --git a/ee/spec/requests/projects/logs_controller_spec.rb b/ee/spec/requests/projects/logs_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..29b68a4090cd78821731b021dcbead9113569242 --- /dev/null +++ b/ee/spec/requests/projects/logs_controller_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::LogsController, feature_category: :metrics do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:user) { create(:user) } + let(:path) { nil } + let(:observability_logs_ff) { true } + let(:expected_api_config) do + { + oauthUrl: Gitlab::Observability.oauth_url, + provisioningUrl: Gitlab::Observability.provisioning_url(project), + tracingUrl: Gitlab::Observability.tracing_url(project), + tracingAnalyticsUrl: Gitlab::Observability.tracing_analytics_url(project), + servicesUrl: Gitlab::Observability.services_url(project), + operationsUrl: Gitlab::Observability.operations_url(project), + metricsUrl: Gitlab::Observability.metrics_url(project), + metricsSearchUrl: Gitlab::Observability.metrics_search_url(project), + metricsSearchMetadataUrl: Gitlab::Observability.metrics_search_metadata_url(project), + logsSearchUrl: Gitlab::Observability.logs_search_url(project) + } + end + + subject(:html_response) do + get path + response + end + + before do + stub_licensed_features(logs_observability: true) + stub_feature_flags(observability_logs: observability_logs_ff) + sign_in(user) + end + + shared_examples 'logs route request' do + it_behaves_like 'observability csp policy' do + before_all do + project.add_reporter(user) + end + + let(:tested_path) { path } + end + + context 'when user does not have permissions' do + before_all do + project.add_guest(user) + end + + it 'returns 404' do + expect(html_response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user has permissions' do + before_all do + project.add_reporter(user) + end + + it 'returns 200' do + expect(html_response).to have_gitlab_http_status(:ok) + end + + context 'when feature is disabled' do + let(:observability_logs_ff) { false } + + it 'returns 404' do + expect(html_response).to have_gitlab_http_status(:not_found) + end + end + end + end + + describe 'GET #index' do + let(:path) { project_logs_path(project) } + + it_behaves_like 'logs route request' + + describe 'html response' do + before_all do + project.add_reporter(user) + end + + it 'renders the js-logs element correctly' do + element = Nokogiri::HTML.parse(html_response.body).at_css('#js-observability-logs') + + expected_view_model = { + apiConfig: expected_api_config + }.to_json + expect(element.attributes['data-view-model'].value).to eq(expected_view_model) + end + end + end +end diff --git a/ee/spec/requests/projects/metrics_controller_spec.rb b/ee/spec/requests/projects/metrics_controller_spec.rb index 6e92433085bb93b341a58e921dca53751618d36a..44a6d8338a689d6726f3a6903804bb2f29e0c350 100644 --- a/ee/spec/requests/projects/metrics_controller_spec.rb +++ b/ee/spec/requests/projects/metrics_controller_spec.rb @@ -18,7 +18,8 @@ operationsUrl: Gitlab::Observability.operations_url(project), metricsUrl: Gitlab::Observability.metrics_url(project), metricsSearchUrl: Gitlab::Observability.metrics_search_url(project), - metricsSearchMetadataUrl: Gitlab::Observability.metrics_search_metadata_url(project) + metricsSearchMetadataUrl: Gitlab::Observability.metrics_search_metadata_url(project), + logsSearchUrl: Gitlab::Observability.logs_search_url(project) } end diff --git a/ee/spec/requests/projects/tracing_controller_spec.rb b/ee/spec/requests/projects/tracing_controller_spec.rb index 383d4794516ab7a595fb71b4aa4100915d94f1da..33a12250325b3a66ce9d2d830af3f08403b5e1b2 100644 --- a/ee/spec/requests/projects/tracing_controller_spec.rb +++ b/ee/spec/requests/projects/tracing_controller_spec.rb @@ -18,7 +18,8 @@ operationsUrl: Gitlab::Observability.operations_url(project), metricsUrl: Gitlab::Observability.metrics_url(project), metricsSearchUrl: Gitlab::Observability.metrics_search_url(project), - metricsSearchMetadataUrl: Gitlab::Observability.metrics_search_metadata_url(project) + metricsSearchMetadataUrl: Gitlab::Observability.metrics_search_metadata_url(project), + logsSearchUrl: Gitlab::Observability.logs_search_url(project) } end diff --git a/lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb b/lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb index d4ecf132c4444b07eae44bd8d137ff3c203f9a88..6cdfa7d9c376849fd39d1259a23ae04b1f4e5f21 100644 --- a/lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb +++ b/lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb @@ -19,6 +19,7 @@ def configure_menu_items [ :tracing, :metrics, + :logs, :error_tracking, :alert_management, :incidents, diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb index 3f8a146f040ee5d8dfa92add372806b2814ccca9..5cf9353363994ba55b91df2b8c59cce75f4ad0ed 100644 --- a/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb +++ b/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb @@ -17,6 +17,7 @@ expect(items.map(&:item_id)).to eq([ :tracing, :metrics, + :logs, :error_tracking, :alert_management, :incidents, diff --git a/spec/support/helpers/user_with_namespace_shim.yml b/spec/support/helpers/user_with_namespace_shim.yml index 9b546c5709d0fb1e25e153528b616e9896d7cb8d..f91f8492c8fd819c451b3397a693606d03c49551 100644 --- a/spec/support/helpers/user_with_namespace_shim.yml +++ b/spec/support/helpers/user_with_namespace_shim.yml @@ -233,6 +233,7 @@ - ee/spec/requests/projects/analytics/code_reviews_controller_spec.rb - ee/spec/requests/projects/issues_controller_spec.rb - ee/spec/requests/projects/metrics_controller_spec.rb +- ee/spec/requests/projects/logs_controller_spec.rb - ee/spec/requests/projects/security/policies_controller_spec.rb - ee/spec/requests/projects/tracing_controller_spec.rb - ee/spec/requests/subscriptions/hand_raise_leads_spec.rb