From d1c67bcb87e09ad21770fe439f3c6b832993b1fd Mon Sep 17 00:00:00 2001 From: michaelangeloio Date: Sat, 6 Sep 2025 14:15:46 -0400 Subject: [PATCH 1/9] feat: gkg poc --- .../entrypoints/admin_knowledge_graph.js | 3 + .../pages/admin/knowledge_graph/show.js | 3 + config/routes/admin.rb | 8 + db/structure.sql | 359 +++++++++++++- .../admin/knowledge_graph/components/app.vue | 436 ++++++++++++++++++ .../admin/knowledge_graph/index.js | 25 + .../knowledge_graph/knowledge_graph_app.vue | 200 ++++++++ .../pages/admin/knowledge_graph/index.js | 3 + .../pages/admin/knowledge_graph/show/index.js | 5 + .../admin/knowledge_graph_controller.rb | 135 ++++++ .../knowledge_graph/indexing_task_service.rb | 3 - ee/app/services/ee/git/branch_push_service.rb | 3 - .../admin/knowledge_graph/index.html.haml | 91 ++++ .../admin/knowledge_graph/show.html.haml | 7 + lib/api/mcp/base.rb | 10 +- 15 files changed, 1281 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/entrypoints/admin_knowledge_graph.js create mode 100644 app/assets/javascripts/pages/admin/knowledge_graph/show.js create mode 100644 ee/app/assets/javascripts/admin/knowledge_graph/components/app.vue create mode 100644 ee/app/assets/javascripts/admin/knowledge_graph/index.js create mode 100644 ee/app/assets/javascripts/admin/knowledge_graph/knowledge_graph_app.vue create mode 100644 ee/app/assets/javascripts/pages/admin/knowledge_graph/index.js create mode 100644 ee/app/assets/javascripts/pages/admin/knowledge_graph/show/index.js create mode 100644 ee/app/controllers/admin/knowledge_graph_controller.rb create mode 100644 ee/app/views/admin/knowledge_graph/index.html.haml create mode 100644 ee/app/views/admin/knowledge_graph/show.html.haml diff --git a/app/assets/javascripts/entrypoints/admin_knowledge_graph.js b/app/assets/javascripts/entrypoints/admin_knowledge_graph.js new file mode 100644 index 00000000000000..d33116b4d3dd7b --- /dev/null +++ b/app/assets/javascripts/entrypoints/admin_knowledge_graph.js @@ -0,0 +1,3 @@ +import 'ee/admin/knowledge_graph'; + + diff --git a/app/assets/javascripts/pages/admin/knowledge_graph/show.js b/app/assets/javascripts/pages/admin/knowledge_graph/show.js new file mode 100644 index 00000000000000..d33116b4d3dd7b --- /dev/null +++ b/app/assets/javascripts/pages/admin/knowledge_graph/show.js @@ -0,0 +1,3 @@ +import 'ee/admin/knowledge_graph'; + + diff --git a/config/routes/admin.rb b/config/routes/admin.rb index a810279dfc664e..de54055427281e 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -142,6 +142,14 @@ get 'dev_ops_report', to: redirect('admin/dev_ops_reports') resources :cohorts, only: :index + # Knowledge Graph admin UI (EE) + resource :knowledge_graph, only: [:show], controller: 'knowledge_graph' do + post :provision + post :trigger + get :tasks + post :toggle + end + scope( path: 'projects/*namespace_id', as: :namespace, diff --git a/db/structure.sql b/db/structure.sql index ccb6fb98cbb24f..038dd0526cb581 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -5337,7 +5337,7 @@ PARTITION BY LIST (partition_id); CREATE TABLE p_ci_finished_build_ch_sync_events ( build_id bigint NOT NULL, - partition bigint DEFAULT 1 NOT NULL, + partition bigint DEFAULT 2 NOT NULL, build_finished_at timestamp without time zone NOT NULL, processed boolean DEFAULT false NOT NULL, project_id bigint NOT NULL @@ -5347,7 +5347,7 @@ PARTITION BY LIST (partition); CREATE TABLE p_ci_finished_pipeline_ch_sync_events ( pipeline_id bigint NOT NULL, project_namespace_id bigint NOT NULL, - partition bigint DEFAULT 1 NOT NULL, + partition bigint DEFAULT 2 NOT NULL, pipeline_finished_at timestamp without time zone NOT NULL, processed boolean DEFAULT false NOT NULL ) @@ -5417,6 +5417,7 @@ CREATE TABLE p_knowledge_graph_tasks ( state smallint DEFAULT 0 NOT NULL, task_type smallint NOT NULL, retries_left smallint NOT NULL, + metadata jsonb DEFAULT '"{}"'::jsonb NOT NULL, CONSTRAINT c_p_knowledge_graph_tasks_on_retries_left CHECK (((retries_left > 0) OR ((retries_left = 0) AND (state = 255)))) ) PARTITION BY LIST (partition_id); @@ -25808,8 +25809,8 @@ CREATE TABLE user_preferences ( work_items_display_settings jsonb DEFAULT '{}'::jsonb NOT NULL, default_duo_add_on_assignment_id bigint, markdown_maintain_indentation boolean DEFAULT false NOT NULL, - project_studio_enabled boolean DEFAULT false NOT NULL, merge_request_dashboard_show_drafts boolean DEFAULT true NOT NULL, + project_studio_enabled boolean DEFAULT false NOT NULL, CONSTRAINT check_1d670edc68 CHECK ((time_display_relative IS NOT NULL)), CONSTRAINT check_89bf269f41 CHECK ((char_length(diffs_deletion_color) <= 7)), CONSTRAINT check_9b50d9f942 CHECK ((char_length(extensions_marketplace_opt_in_url) <= 512)), @@ -43295,6 +43296,358 @@ ALTER INDEX index_uploads_9ba88c4165_on_uploaded_by_user_id ATTACH PARTITION vul ALTER INDEX index_uploads_9ba88c4165_on_uploader_and_path ATTACH PARTITION vulnerability_remediation_uploads_uploader_path_idx; +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_00 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_01 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_02 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_03 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_04 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_05 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_06 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_07 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_08 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_09 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_10 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_11 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_12 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_13 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_14 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_15 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_16 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_17 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_18 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_19 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_20 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_21 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_22 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_23 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_24 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_25 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_26 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_27 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_28 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_29 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_30 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_31 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_32 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_32 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_33 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_33 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_34 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_34 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_35 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_35 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_36 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_36 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_37 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_37 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_38 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_38 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_39 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_39 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_40 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_40 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_41 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_41 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_42 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_42 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_43 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_43 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_44 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_44 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_45 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_45 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_46 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_46 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_47 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_47 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_48 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_48 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_49 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_49 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_50 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_50 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_51 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_51 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_52 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_52 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_53 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_53 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_54 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_54 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_55 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_55 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_56 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_56 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_57 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_57 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_58 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_58 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_59 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_59 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_60 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_60 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_61 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_61 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_62 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_62 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_63 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_63 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_00 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_01 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_02 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_03 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_04 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_05 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_06 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_07 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_08 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_09 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_10 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_11 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_12 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_13 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_14 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_15 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_16 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_17 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_18 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_19 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_20 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_21 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_22 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_23 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_24 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_25 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_26 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_27 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_28 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_29 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_30 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_31 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + CREATE TRIGGER ai_active_context_connections_loose_fk_trigger AFTER DELETE ON ai_active_context_connections REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); CREATE TRIGGER ai_conversation_threads_loose_fk_trigger AFTER DELETE ON ai_conversation_threads REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); diff --git a/ee/app/assets/javascripts/admin/knowledge_graph/components/app.vue b/ee/app/assets/javascripts/admin/knowledge_graph/components/app.vue new file mode 100644 index 00000000000000..a3c369c51f402f --- /dev/null +++ b/ee/app/assets/javascripts/admin/knowledge_graph/components/app.vue @@ -0,0 +1,436 @@ + + + + + diff --git a/ee/app/assets/javascripts/admin/knowledge_graph/index.js b/ee/app/assets/javascripts/admin/knowledge_graph/index.js new file mode 100644 index 00000000000000..b76ccf0a4a10f0 --- /dev/null +++ b/ee/app/assets/javascripts/admin/knowledge_graph/index.js @@ -0,0 +1,25 @@ +import Vue from 'vue'; +import AdminKnowledgeGraphApp from './components/app.vue'; + +export default function mountAdminKnowledgeGraph() { + const el = document.getElementById('js-admin-knowledge-graph'); + if (!el) return false; + + new Vue({ + render(h) { + return h(AdminKnowledgeGraphApp, { + props: { + endpoint: el.dataset.endpoint, + provisionPath: el.dataset.provisionPath, + triggerPath: el.dataset.triggerPath, + tasksPath: el.dataset.tasksPath, + togglePath: el.dataset.togglePath, + }, + }); + }, + }).$mount(el); + + return true; +} + +mountAdminKnowledgeGraph(); diff --git a/ee/app/assets/javascripts/admin/knowledge_graph/knowledge_graph_app.vue b/ee/app/assets/javascripts/admin/knowledge_graph/knowledge_graph_app.vue new file mode 100644 index 00000000000000..19a0064053b0a0 --- /dev/null +++ b/ee/app/assets/javascripts/admin/knowledge_graph/knowledge_graph_app.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/ee/app/assets/javascripts/pages/admin/knowledge_graph/index.js b/ee/app/assets/javascripts/pages/admin/knowledge_graph/index.js new file mode 100644 index 00000000000000..d33116b4d3dd7b --- /dev/null +++ b/ee/app/assets/javascripts/pages/admin/knowledge_graph/index.js @@ -0,0 +1,3 @@ +import 'ee/admin/knowledge_graph'; + + diff --git a/ee/app/assets/javascripts/pages/admin/knowledge_graph/show/index.js b/ee/app/assets/javascripts/pages/admin/knowledge_graph/show/index.js new file mode 100644 index 00000000000000..82eaed7d39e173 --- /dev/null +++ b/ee/app/assets/javascripts/pages/admin/knowledge_graph/show/index.js @@ -0,0 +1,5 @@ +import mountAdminKnowledgeGraph from 'ee/admin/knowledge_graph'; + +export default () => { + mountAdminKnowledgeGraph(); +}; diff --git a/ee/app/controllers/admin/knowledge_graph_controller.rb b/ee/app/controllers/admin/knowledge_graph_controller.rb new file mode 100644 index 00000000000000..d552ea929aa364 --- /dev/null +++ b/ee/app/controllers/admin/knowledge_graph_controller.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module Admin + class KnowledgeGraphController < Admin::ApplicationController + feature_category :knowledge_graph + + def show + respond_to do |format| + format.html + format.json { render json: show_payload } + end + end + + def tasks + render json: { tasks: serialized_tasks } + end + + def toggle + enabled = ::Feature.enabled?(:knowledge_graph_indexing) + if enabled + ::Feature.disable(:knowledge_graph_indexing) + else + ::Feature.enable(:knowledge_graph_indexing) + end + + render json: { success: true, feature_enabled: ::Feature.enabled?(:knowledge_graph_indexing) } + end + + def provision + namespace = find_namespace_param! + unless namespace.is_a?(Namespaces::ProjectNamespace) + return render json: { success: false, message: 'Provide a project namespace (ID or full path like group/project).' }, + status: :unprocessable_entity + end + + count_param = params[:replica_count].to_i + replica_count = count_param.positive? ? count_param : 1 + result = ::Ai::KnowledgeGraph::ReplicasProvisionService.new(namespace, replica_count: replica_count).execute + status = result.success? + message = status ? 'Knowledge Graph replica provisioned.' : (result.message.presence || 'Provisioning failed.') + render json: { success: status, message: message } + rescue ActiveRecord::RecordNotFound + render json: { success: false, message: 'Namespace not found. Use an ID or full path.' }, status: :not_found + end + + def trigger + namespace = find_namespace_param! + + projects = if namespace.respond_to?(:project_namespace?) && namespace.project_namespace? + [namespace.project].compact + elsif namespace.respond_to?(:group_namespace?) && namespace.group_namespace? + namespace.all_projects + else + [] + end + + if projects.empty? + return render json: { success: false, message: 'No projects found for the given namespace.' }, + status: :unprocessable_entity + end + + enqueued = 0 + skipped = 0 + total = projects.respond_to?(:count) ? projects.count : projects.size + + if projects.respond_to?(:find_each) + projects.find_each do |project| + result = ::Ai::KnowledgeGraph::IndexingTaskService.new(project.project_namespace_id, + :index_graph_repo).execute + enqueued += 1 if result.success? + skipped += 1 if result.error? + end + else + projects.each do |project| + result = ::Ai::KnowledgeGraph::IndexingTaskService.new(project.project_namespace_id, + :index_graph_repo).execute + enqueued += 1 if result.success? + skipped += 1 if result.error? + end + end + + render json: { success: enqueued.positive?, message: "Indexing tasks processed.", enqueued: enqueued, + skipped: skipped, total: total } + rescue ActiveRecord::RecordNotFound + render json: { success: false, message: 'Namespace not found. Use an ID or full path.' }, status: :not_found + end + + private + + def show_payload + { + feature_enabled: ::Feature.enabled?(:knowledge_graph_indexing), + nodes: ::Search::Zoekt::Node.with_service(:knowledge_graph).order(id: :asc).limit(100).as_json(only: [:id, + :services, :schema_version, :knowledge_graph_schema_version, :index_base_url]), + enabled_namespaces: ::Ai::KnowledgeGraph::EnabledNamespace.includes(:namespace).order(id: :asc).limit(100).map do |en| + { id: en.id, namespace: en.namespace&.full_path } + end, + replicas: ::Ai::KnowledgeGraph::Replica.includes(:zoekt_node, + knowledge_graph_enabled_namespace: :namespace).order(id: :asc).limit(200).map do |r| + { id: r.id, namespace: r.knowledge_graph_enabled_namespace&.namespace&.full_path, node_id: r.zoekt_node&.id, + state: r.state, retries_left: r.retries_left, reserved_storage_bytes: r.reserved_storage_bytes } + end, + tasks: serialized_tasks + } + end + + def serialized_tasks + ::Ai::KnowledgeGraph::Task.with_namespace.order(id: :desc).limit(200).map do |t| + { id: t.id, namespace: t.knowledge_graph_replica&.knowledge_graph_enabled_namespace&.namespace&.full_path, + zoekt_node_id: t.zoekt_node_id, task_type: t.task_type, state: t.state, perform_at: t.perform_at } + end + end + + def find_namespace_param! + raw = params.require(:namespace_id).to_s + if raw.match?(/\A\d+\z/) + ::Namespace.find(raw) + else + ns = ::Namespace.respond_to?(:find_by_full_path) ? ::Namespace.find_by_full_path(raw) : nil + ns ||= ::Namespace.respond_to?(:find_by_full_path) ? ::Namespace.find_by_full_path(raw.delete_prefix('/')) : nil + + # If not a namespace full path, try resolving a project full path to its project namespace + unless ns + proj = ::Project.respond_to?(:find_by_full_path) ? ::Project.find_by_full_path(raw) : nil + proj ||= ::Project.respond_to?(:find_by_full_path) ? ::Project.find_by_full_path(raw.delete_prefix('/')) : nil + ns = proj&.project_namespace + end + + raise ActiveRecord::RecordNotFound, 'Namespace not found' unless ns + + ns + end + end + end +end diff --git a/ee/app/services/ai/knowledge_graph/indexing_task_service.rb b/ee/app/services/ai/knowledge_graph/indexing_task_service.rb index c4de3c81fb74a1..07666825f83418 100644 --- a/ee/app/services/ai/knowledge_graph/indexing_task_service.rb +++ b/ee/app/services/ai/knowledge_graph/indexing_task_service.rb @@ -31,9 +31,6 @@ def before_indexing_checks return "project has empty repo" if namespace.project.empty_repo? - return "project doesn't have duo features available" unless ::GitlabSubscriptions::AddOnPurchase - .for_active_add_ons(['duo_core'], resource: namespace).present? - nil end diff --git a/ee/app/services/ee/git/branch_push_service.rb b/ee/app/services/ee/git/branch_push_service.rb index a5b116511797f5..8bca446326eb81 100644 --- a/ee/app/services/ee/git/branch_push_service.rb +++ b/ee/app/services/ee/git/branch_push_service.rb @@ -64,9 +64,6 @@ def enqueue_knowledge_graph_indexing return false unless ::Feature.enabled?(:knowledge_graph_indexing, project) return false unless default_branch? - return false unless ::GitlabSubscriptions::AddOnPurchase - .for_active_add_ons(['duo_core'], resource: project.project_namespace).present? - ::Ai::KnowledgeGraph::IndexingTaskWorker.perform_async(project.project_namespace.id, :index_graph_repo) end diff --git a/ee/app/views/admin/knowledge_graph/index.html.haml b/ee/app/views/admin/knowledge_graph/index.html.haml new file mode 100644 index 00000000000000..80798f9a0c6dad --- /dev/null +++ b/ee/app/views/admin/knowledge_graph/index.html.haml @@ -0,0 +1,91 @@ +%h1 Knowledge Graph + +%p + Feature flag enabled: + %strong= @feature_enabled + +%h2 Nodes +%table.gl-table + %thead + %tr + %th ID + %th Index URL + %th Services + %th Schema + %th KG Schema + %tbody + - @nodes.each do |n| + %tr + %td= n.id + %td= n.index_base_url + %td= n.services + %td= n.schema_version + %td= n.knowledge_graph_schema_version + +%h2 Enabled Namespaces +%table.gl-table + %thead + %tr + %th ID + %th Namespace + %tbody + - @enabled_namespaces.each do |en| + %tr + %td= en.id + %td= en.namespace&.full_path + +%h2 Replicas +%table.gl-table + %thead + %tr + %th ID + %th Namespace + %th Node + %th State + %th Retries + %th Reserved Bytes + %tbody + - @replicas.each do |r| + %tr + %td= r.id + %td= r.knowledge_graph_enabled_namespace&.namespace&.full_path + %td= r.zoekt_node&.id + %td= r.state + %td= r.retries_left + %td= r.reserved_storage_bytes + +%h2 Tasks (latest) +%table.gl-table + %thead + %tr + %th ID + %th Namespace + %th Node + %th Type + %th State + %th Perform At + %tbody + - @tasks.each do |t| + - ns = t.knowledge_graph_replica&.knowledge_graph_enabled_namespace&.namespace + %tr + %td= t.id + %td= ns&.full_path + %td= t.zoekt_node_id + %td= t.task_type + %td= t.state + %td= t.perform_at + +%h2 Actions +%div + = form_with url: admin_knowledge_graph_provision_path, method: :post, local: true do |f| + .gl-form-group + = label_tag :namespace_id, 'Namespace ID' + = text_field_tag :namespace_id, nil, class: 'gl-form-input', placeholder: 'e.g., project namespace ID' + = submit_tag 'Provision Replica', class: 'gl-button btn btn-confirm' + +%div{ style: 'margin-top: 12px;' } + = form_with url: admin_knowledge_graph_trigger_path, method: :post, local: true do |f| + .gl-form-group + = label_tag :namespace_id, 'Namespace ID' + = text_field_tag :namespace_id, nil, class: 'gl-form-input', placeholder: 'e.g., project namespace ID' + = submit_tag 'Trigger Indexing', class: 'gl-button btn' diff --git a/ee/app/views/admin/knowledge_graph/show.html.haml b/ee/app/views/admin/knowledge_graph/show.html.haml new file mode 100644 index 00000000000000..c56db79ba1c3bb --- /dev/null +++ b/ee/app/views/admin/knowledge_graph/show.html.haml @@ -0,0 +1,7 @@ +%div#js-admin-knowledge-graph{ data: { endpoint: admin_knowledge_graph_path(format: :json), provision_path: provision_admin_knowledge_graph_path, trigger_path: trigger_admin_knowledge_graph_path, tasks_path: tasks_admin_knowledge_graph_path, toggle_path: toggle_admin_knowledge_graph_path } } + +- content_for :page_specific_javascripts do + - if vite_enabled? + = vite_javascript_tag 'pages.admin.knowledge_graph.show.js' + - else + = webpack_controller_bundle_tags diff --git a/lib/api/mcp/base.rb b/lib/api/mcp/base.rb index 9b75608f3acf60..dc28cf3022d417 100644 --- a/lib/api/mcp/base.rb +++ b/lib/api/mcp/base.rb @@ -44,7 +44,15 @@ class Base < ::API::Base before do authenticate! not_found! unless Feature.enabled?(:mcp_server, current_user) - forbidden! unless access_token&.scopes == [Gitlab::Auth::MCP_SCOPE] + + # In development, allow any valid token (including PATs with 'api' scope) + # to simplify local testing. In other environments, require 'mcp' scope. + if Rails.env.development? + forbidden! unless access_token.present? + else + allowed_scopes = Array(access_token&.scopes).map(&:to_s) + forbidden! unless allowed_scopes.include?(Gitlab::Auth::MCP_SCOPE.to_s) + end end helpers do -- GitLab From 2babf077b7c5476693cb44822c688081768fda95 Mon Sep 17 00:00:00 2001 From: michaelangeloio Date: Mon, 8 Sep 2025 09:59:17 -0400 Subject: [PATCH 2/9] save progress --- app/services/git/branch_push_service.rb | 1 + .../repositories/post_receive_worker.rb | 1 + .../components/solution_card.vue | 33 ++++++++++++++ .../vulnerabilities/components/footer.vue | 1 + ee/app/helpers/vulnerabilities_helper.rb | 10 +++++ .../ai/knowledge_graph/query_service.rb | 43 +++++++++++++++++++ ee/app/services/ee/git/branch_push_service.rb | 1 + 7 files changed, 90 insertions(+) create mode 100644 ee/app/services/ai/knowledge_graph/query_service.rb diff --git a/app/services/git/branch_push_service.rb b/app/services/git/branch_push_service.rb index 464d79d99911ca..8d6b9f877c6fe9 100644 --- a/app/services/git/branch_push_service.rb +++ b/app/services/git/branch_push_service.rb @@ -22,6 +22,7 @@ class BranchPushService < ::BaseService def execute return unless Gitlab::Git.branch_ref?(ref) + puts "DEBUG: BranchPushService#execute called for ref #{ref}" enqueue_update_mrs enqueue_detect_repository_languages enqueue_record_project_target_platforms diff --git a/app/workers/repositories/post_receive_worker.rb b/app/workers/repositories/post_receive_worker.rb index f39d170b24fdae..20854c9e1668f6 100644 --- a/app/workers/repositories/post_receive_worker.rb +++ b/app/workers/repositories/post_receive_worker.rb @@ -218,6 +218,7 @@ def enqueue_project_cache_update(post_received, project) def process_ref_changes(project, user, params = {}) return unless params[:changes].any? + puts "DEBUG: process_ref_changes called for project #{project.id}" Git::ProcessRefChangesService.new(project, user, params).execute end diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue b/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue index 4a65a6a3c0750b..7b9ad30530d46b 100644 --- a/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue +++ b/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue @@ -16,6 +16,11 @@ export default { type: Boolean, required: true, }, + knowledgeGraph: { + type: Object, + required: false, + default: null, + }, }, i18n: { createMergeRequestMsg: s__( @@ -34,5 +39,33 @@ export default {

{{ $options.i18n.createMergeRequestMsg }}

+ diff --git a/ee/app/assets/javascripts/vulnerabilities/components/footer.vue b/ee/app/assets/javascripts/vulnerabilities/components/footer.vue index ffdf07eb345b80..4dbc118e949f82 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/footer.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/footer.vue @@ -214,6 +214,7 @@ export default { class="md gl-my-6" :solution-text="solutionText" :can-download-patch="canDownloadPatch" + :knowledge-graph="vulnerability.knowledgeGraph" /> 'application/json' }, + body: Gitlab::Json.dump(payload), + allow_local_requests: allow_local + ) + + body = Gitlab::Json.parse(response.body) + puts "SHAYON DEBUG: Dependency analysis response: #{body}" + { + package_name: body['package_name'], + version: body['version'], + imports: Array(body['imports']), + definitions: Array(body['definitions']), + references: Array(body['references']) + } + rescue StandardError => e + puts "SHAYON DEBUG: Error getting dependency analysis: #{e}" + { package_name: package_name, version: version, imports: [], definitions: [], references: [] } + end + end + end +end diff --git a/ee/app/services/ee/git/branch_push_service.rb b/ee/app/services/ee/git/branch_push_service.rb index 8bca446326eb81..49b06f7b2d716c 100644 --- a/ee/app/services/ee/git/branch_push_service.rb +++ b/ee/app/services/ee/git/branch_push_service.rb @@ -64,6 +64,7 @@ def enqueue_knowledge_graph_indexing return false unless ::Feature.enabled?(:knowledge_graph_indexing, project) return false unless default_branch? + puts "enqueue_knowledge_graph_indexing" ::Ai::KnowledgeGraph::IndexingTaskWorker.perform_async(project.project_namespace.id, :index_graph_repo) end -- GitLab From 35e66f99081ae85c63e466ab5bfad48bf7d8b391 Mon Sep 17 00:00:00 2001 From: michaelangeloio Date: Tue, 9 Sep 2025 09:50:45 -0400 Subject: [PATCH 3/9] dependency query update --- .../components/solution_card.vue | 247 ++++++++++++++++-- .../ai/knowledge_graph/query_service.rb | 28 +- 2 files changed, 245 insertions(+), 30 deletions(-) diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue b/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue index 7b9ad30530d46b..d3ba2714559238 100644 --- a/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue +++ b/ee/app/assets/javascripts/vue_shared/security_reports/components/solution_card.vue @@ -1,12 +1,18 @@ diff --git a/ee/app/assets/javascripts/repository/components/knowledge_graph_sidebar.vue b/ee/app/assets/javascripts/repository/components/knowledge_graph_sidebar.vue new file mode 100644 index 00000000000000..bc47d50f68223e --- /dev/null +++ b/ee/app/assets/javascripts/repository/components/knowledge_graph_sidebar.vue @@ -0,0 +1,288 @@ + + + + + + + diff --git a/ee/app/controllers/projects/knowledge_graph/knowledge_graph_controller.rb b/ee/app/controllers/projects/knowledge_graph/knowledge_graph_controller.rb new file mode 100644 index 00000000000000..4b554b73b0a59f --- /dev/null +++ b/ee/app/controllers/projects/knowledge_graph/knowledge_graph_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Projects + module KnowledgeGraph + class KnowledgeGraphController < ::Projects::ApplicationController + feature_category :knowledge_graph + + before_action :authorize_read_code! + + def symbols + file_path = params.require(:file_path) + render json: ::Ai::KnowledgeGraph::QueryService.symbols( + project: @project, + file_path: file_path, + ref: params[:ref].presence || @project.default_branch + ) + end + + def references + file_path = params.require(:file_path) + definition_id = params.require(:definition_id).to_i + render json: ::Ai::KnowledgeGraph::QueryService.references( + project: @project, + file_path: file_path, + definition_id: definition_id, + ref: params[:ref].presence || @project.default_branch + ) + end + + end + end +end + + diff --git a/ee/app/services/ai/knowledge_graph/query_service.rb b/ee/app/services/ai/knowledge_graph/query_service.rb index 164d8927d82fbb..1b83ddb0adeda9 100644 --- a/ee/app/services/ai/knowledge_graph/query_service.rb +++ b/ee/app/services/ai/knowledge_graph/query_service.rb @@ -64,6 +64,54 @@ def self.gitaly_payload(project) } } end + + def self.base_url + ENV['GKG_DEPLOYED_BASE_URL'].presence || DEFAULT_BASE_URL + end + + def self.allow_local?(base_url) + Gitlab.dev_or_test_env? || base_url.start_with?('http://localhost', 'http://127.0.0.1', + 'https://localhost', 'https://127.0.0.1') + end + + def self.symbols(project:, file_path:, ref: nil) + url = File.join(base_url, '/api/v1/symbols') + payload = { + repo_id: project.id, + file_path: file_path, + gitaly: gitaly_payload(project)[:GitalyConnectionInfo] + } + response = Gitlab::HTTP.post( + url, + headers: { 'Content-Type' => 'application/json' }, + body: Gitlab::Json.dump(payload), + allow_local_requests: allow_local?(base_url) + ) + Gitlab::Json.parse(response.body) + rescue StandardError => e + Gitlab::ErrorTracking.track_exception(e) + { file_path: file_path, symbols: [] } + end + + def self.references(project:, file_path:, definition_id:, ref: nil) + url = File.join(base_url, '/api/v1/references') + payload = { + repo_id: project.id, + file_path: file_path, + definition_id: definition_id, + gitaly: gitaly_payload(project)[:GitalyConnectionInfo] + } + response = Gitlab::HTTP.post( + url, + headers: { 'Content-Type' => 'application/json' }, + body: Gitlab::Json.dump(payload), + allow_local_requests: allow_local?(base_url) + ) + Gitlab::Json.parse(response.body) + rescue StandardError => e + Gitlab::ErrorTracking.track_exception(e) + { file_path: file_path, definition_id: definition_id, references: [] } + end end end end diff --git a/ee/config/routes/project.rb b/ee/config/routes/project.rb index 238b867da9f82f..4761f22536514d 100644 --- a/ee/config/routes/project.rb +++ b/ee/config/routes/project.rb @@ -181,6 +181,12 @@ end resources :merge_trains, only: [:index] + + # Knowledge Graph project-scoped endpoints + namespace :knowledge_graph do + post :symbols, to: 'knowledge_graph#symbols' + post :references, to: 'knowledge_graph#references' + end end # End of the /-/ scope. diff --git a/log/.gitkeep b/log/.gitkeep deleted file mode 100644 index e69de29bb2d1d6..00000000000000 -- GitLab From 4d9ed21169f789f3b905390e7a64766932bbae1b Mon Sep 17 00:00:00 2001 From: michaelangeloio Date: Thu, 11 Sep 2025 21:17:13 -0400 Subject: [PATCH 5/9] dependeny mcp --- .../mcp/tools/dependency_analysis_service.rb | 299 ++++++++++++++++++ lib/api/mcp/base.rb | 26 +- lib/api/mcp/handlers/list_tools_request.rb | 3 +- 3 files changed, 314 insertions(+), 14 deletions(-) create mode 100644 app/services/mcp/tools/dependency_analysis_service.rb diff --git a/app/services/mcp/tools/dependency_analysis_service.rb b/app/services/mcp/tools/dependency_analysis_service.rb new file mode 100644 index 00000000000000..6aa7efc042c4a8 --- /dev/null +++ b/app/services/mcp/tools/dependency_analysis_service.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +# rubocop:disable Mcp/UseApiService -- Tool aggregates server-side data and permissions without REST calls +module Mcp + module Tools + class DependencyAnalysisService < BaseService + extend ::Gitlab::Utils::Override + + override :description + def description + 'List vulnerabilities for a project or group (recursive) or fetch details for a specific vulnerability with related dependency analysis.' + end + + override :input_schema + def input_schema + { + type: 'object', + properties: { + full_path: { + type: 'string', + description: 'Group or project full path', + minLength: 1 + }, + type: { + type: 'string', + description: 'Operation to perform', + enum: %w[get_vulnerabilities vulnerability_details] + }, + vulnerability: { + type: 'array', + description: 'Array of vulnerability IDs (required for vulnerability_details)', + items: { type: 'integer' }, + minItems: 1 + }, + per_page: { + type: 'integer', + description: 'Max items to return (applies to lists)', + minimum: 1 + }, + page: { + type: 'integer', + description: 'Page number (applies to lists)', + minimum: 1 + } + }, + required: %w[full_path type], + additionalProperties: false + } + end + + protected + + override :perform + def perform(_oauth_token, arguments = {}, _query = {}) + target = find_target_by_full_path!(arguments[:full_path]) + + case arguments[:type] + when 'get_vulnerabilities' + data = list_vulnerabilities(target, per_page: arguments[:per_page], page: arguments[:page]) + lines = format_vulnerability_list_text(data) + formatted_content = lines.map { |t| { type: 'text', text: t } } + ::Mcp::Tools::Response.success(formatted_content, data) + when 'vulnerability_details' + vuln_ids = Array(arguments[:vulnerability]).compact + raise ArgumentError, 'vulnerability is missing' if vuln_ids.empty? + + details_list = vuln_ids.map { |vid| vulnerability_details(target, vid) } + lines = details_list.flat_map { |d| format_vulnerability_details_text(d) + [''] } + formatted_content = lines.map { |t| { type: 'text', text: t } } + ::Mcp::Tools::Response.success(formatted_content, { items: details_list }) + else + raise ArgumentError, 'type is invalid' + end + end + + private + + def find_target_by_full_path!(full_path) + # Try project first for faster resolution + project = ::Project.find_by_full_path(full_path) + return project if project + + namespace = ::Namespace.find_by_full_path(full_path) + raise ArgumentError, 'full_path not found' unless namespace + + namespace + end + + def projects_for(target) + case target + when ::Project + ::Project.id_in([target.id]) + when ::Group, ::Namespace + # For groups and user/organization namespaces, include their projects (and subprojects for groups) + target.all_projects + else + ::Project.none + end + end + + def list_vulnerabilities(target, per_page:, page:) + items = [] + total = 0 + + projects_for(target).find_in_batches(batch_size: 100) do |batch| + batch.each do |project| + relation = ::Security::VulnerabilityReadsFinder.new(project).execute.as_vulnerabilities + relation = relation.where(report_type: ::Enums::Vulnerability.report_types[:dependency_scanning]) + total += relation.size + + relation.limit(limit_for(per_page)).offset(offset_for(per_page, page)).each do |vuln| + items << serialize_vulnerability_brief(vuln, project) + end + end + end + puts "SHAYON DEBUG: Items: #{items}" + + { + items: items, + metadata: { + count: items.length, + total_estimated: total, + has_more: false + } + } + end + + def vulnerability_details(target, vulnerability_id) + vulnerability = ::Vulnerability.find_by_id(vulnerability_id) + raise ArgumentError, 'vulnerability not found' unless vulnerability + + project = vulnerability.project + + # Ensure vulnerability belongs to the requested scope + case target + when ::Project + raise ArgumentError, 'vulnerability not in project' unless target.id == project.id + else + # Namespace or Group + raise ArgumentError, 'vulnerability not in scope' unless projects_for(target).where(id: project.id).exists? + end + + details = serialize_vulnerability_full(vulnerability, project) + + pkg = extract_dependency_from_finding(vulnerability.finding) + kg = if pkg[:package_name] + Ai::KnowledgeGraph::QueryService.dependency( + project: project, + package_name: pkg[:package_name], + version: pkg[:version] + ) + else + { package_name: nil, version: nil, imports: [], definitions: [], references: [] } + end + + { + vulnerability: details, + dependency_analysis: kg + } + end + + def dependency_analysis_for_project(project) + deps = ::Sbom::DependenciesFinder.new(project, params: { preload_vulnerabilities: true }).execute + deps = deps.with_component.with_version + + items = deps.limit(200).map do |dep| + serialize_dependency(dep) + end + + { + items: items, + metadata: { + count: items.length, + has_more: deps.count > items.length + } + } + end + + def limit_for(per_page) + per_page.to_i > 0 ? per_page.to_i : 20 + end + + def offset_for(per_page, page) + p = page.to_i > 0 ? page.to_i : 1 + (p - 1) * limit_for(per_page) + end + + def serialize_vulnerability_brief(vuln, project) + pkg = extract_dependency_from_finding(vuln.finding) + identifiers = Array(vuln.try(:identifiers)).map { |ident| serialize_identifier(ident) }.compact + scanner_name = vuln.try(:scanner)&.name + finding_uuid = vuln.try(:finding)&.uuid + location = vuln.try(:finding)&.location + { + id: vuln.id, + name: vuln.title, + severity: vuln.severity, + state: vuln.state, + report_type: vuln.report_type, + project_full_path: project.full_path, + created_at: vuln.created_at, + updated_at: vuln.updated_at, + short_description: truncate_text(vuln.description, 200), + package_name: pkg[:package_name], + package_version: pkg[:version], + scanner: scanner_name, + identifiers: identifiers, + finding_uuid: finding_uuid, + location: location + } + end + + def serialize_vulnerability_full(vuln, project) + { + id: vuln.id, + title: vuln.title, + description: vuln.description, + severity: vuln.severity, + state: vuln.state, + report_type: vuln.report_type, + project_full_path: project.full_path + } + end + + def serialize_dependency(dep) + component = dep.try(:component) + version = dep.try(:version) + + { + package_name: component&.name, + version: version&.name, + purl: component&.purl_type, + licenses: Array(dep.try(:licenses)).map { |l| l.try(:name) }.compact + } + end + + def serialize_identifier(identifier) + return unless identifier + + { name: identifier.try(:name), url: identifier.try(:url), external_type: identifier.try(:external_type) } + end + + def extract_dependency_from_finding(finding) + return { package_name: nil, version: nil } unless finding + + loc = finding.try(:location) + name = loc&.dig('dependency', 'package', 'name') + ver = loc&.dig('dependency', 'version') + { package_name: name, version: ver } + end + + def truncate_text(text, max_length) + return unless text.is_a?(String) + return text if text.length <= max_length + + text[0, max_length] + "\u2026" + end + + def format_vulnerability_line(item) + parts = [] + parts << "##{item[:id]}" + parts << item[:name].to_s + parts << "(#{item[:severity]})" + parts << "pkg=#{item[:package_name]}@#{item[:package_version]}" if item[:package_name] + parts << "project=#{item[:project_full_path]}" + parts.join(' ') + end + + def format_vulnerability_list_text(data) + header = "Vulnerabilities: #{data[:items].length}" + rows = data[:items].map { |i| format_vulnerability_line(i) } + [header, *rows] + end + + def format_vulnerability_details_text(data) + v = data[:vulnerability] + da = data[:dependency_analysis] + lines = [] + lines << "##{v[:id]} #{v[:title]}" + lines << "Severity: #{v[:severity]} State: #{v[:state]} Type: #{v[:report_type]}" + lines << "Project: #{v[:project_full_path]}" + lines << "Description: #{truncate_text(v[:description], 500)}" if v[:description] + + if da && (da[:imports].present? || da[:definitions].present? || da[:references].present?) + lines << "Dependency Analysis: #{da[:package_name]}@#{da[:version]}" + import_lines = Array(da[:imports]).first(5).map do |imp| + "import: #{imp['file']}:#{imp['start_line']} #{imp['content']}" + end + ref_lines = Array(da[:references]).first(5).map { |ref| "ref: #{ref['file']}:#{ref['line']} #{ref['text']}" } + lines.concat(import_lines) + lines.concat(ref_lines) + end + + lines + end + end + end +end +# rubocop:enable Mcp/UseApiService diff --git a/lib/api/mcp/base.rb b/lib/api/mcp/base.rb index dc28cf3022d417..69f14aa19fceb8 100644 --- a/lib/api/mcp/base.rb +++ b/lib/api/mcp/base.rb @@ -41,19 +41,19 @@ class Base < ::API::Base allow_access_with_scope :mcp urgency :low - before do - authenticate! - not_found! unless Feature.enabled?(:mcp_server, current_user) - - # In development, allow any valid token (including PATs with 'api' scope) - # to simplify local testing. In other environments, require 'mcp' scope. - if Rails.env.development? - forbidden! unless access_token.present? - else - allowed_scopes = Array(access_token&.scopes).map(&:to_s) - forbidden! unless allowed_scopes.include?(Gitlab::Auth::MCP_SCOPE.to_s) - end - end + # before do + # authenticate! + # not_found! unless Feature.enabled?(:mcp_server, current_user) + + # # In development, allow any valid token (including PATs with 'api' scope) + # # to simplify local testing. In other environments, require 'mcp' scope. + # if Rails.env.development? + # forbidden! unless access_token.present? + # else + # allowed_scopes = Array(access_token&.scopes).map(&:to_s) + # forbidden! unless allowed_scopes.include?(Gitlab::Auth::MCP_SCOPE.to_s) + # end + # end helpers do def find_handler_class(method_name) diff --git a/lib/api/mcp/handlers/list_tools_request.rb b/lib/api/mcp/handlers/list_tools_request.rb index 59c4bd70716bf6..a41ba4e9ebc69b 100644 --- a/lib/api/mcp/handlers/list_tools_request.rb +++ b/lib/api/mcp/handlers/list_tools_request.rb @@ -17,7 +17,8 @@ class ListToolsRequest < Base 'get_pipeline_jobs' => ::Mcp::Tools::GetPipelineJobsService }.freeze CUSTOM_TOOLS = { - 'get_mcp_server_version' => ::Mcp::Tools::GetServerVersionService + 'get_mcp_server_version' => ::Mcp::Tools::GetServerVersionService, + 'dependency_analysis' => ::Mcp::Tools::DependencyAnalysisService }.freeze TOOLS = CUSTOM_TOOLS.merge(API_TOOLS) -- GitLab From 9c715d244261fad60ec12f708377136dc39c5aad Mon Sep 17 00:00:00 2001 From: michaelangeloio Date: Sun, 28 Sep 2025 14:39:03 -0400 Subject: [PATCH 6/9] save --- .../mcp/tools/dependency_analysis_service.rb | 2 +- .../components/knowledge_graph_sidebar.vue | 6 +++--- instructions.md | 14 ++++++++++++++ lib/api/mcp/base.rb | 6 ++++-- lib/api/mcp/handlers/list_tools_request.rb | 2 +- lib/api/mcp/server.rb | 10 +++++----- 6 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 instructions.md diff --git a/app/services/mcp/tools/dependency_analysis_service.rb b/app/services/mcp/tools/dependency_analysis_service.rb index 6aa7efc042c4a8..1493b8a863e73c 100644 --- a/app/services/mcp/tools/dependency_analysis_service.rb +++ b/app/services/mcp/tools/dependency_analysis_service.rb @@ -8,7 +8,7 @@ class DependencyAnalysisService < BaseService override :description def description - 'List vulnerabilities for a project or group (recursive) or fetch details for a specific vulnerability with related dependency analysis.' + 'Using the knowledge graph, list vulnerabilities for a project or group (recursive) or fetch details for a specific vulnerability with related dependency analysis.' end override :input_schema diff --git a/ee/app/assets/javascripts/repository/components/knowledge_graph_sidebar.vue b/ee/app/assets/javascripts/repository/components/knowledge_graph_sidebar.vue index bc47d50f68223e..9ae27e92762233 100644 --- a/ee/app/assets/javascripts/repository/components/knowledge_graph_sidebar.vue +++ b/ee/app/assets/javascripts/repository/components/knowledge_graph_sidebar.vue @@ -200,7 +200,7 @@ export default {
- {{ t('Symbols') }} + Knowledge Graph Symbols
@@ -236,10 +236,10 @@ export default {
  • diff --git a/instructions.md b/instructions.md new file mode 100644 index 00000000000000..425e934d388e54 --- /dev/null +++ b/instructions.md @@ -0,0 +1,14 @@ + + You have MCP knowledge-graph tools: list_projects, repo_map, search_codebase_definitions, get_definition, get_references, read_definitions. + + Project absolute path is "/Users/angelo.rivera/gitlab/knowledge-graph-workspace/gdk/gitlab" + + Goal: solve tasks while minimizing tool calls and token usage. + + Operating rules: + - If unknown, call list_projects once and pick the relevant project_path; never call again this session. + - Use project-relative file paths when allowed to shorten arguments; otherwise, pass absolute paths. + - If a limit and size param is available, start with a small integer (like 10). + - Avoid pagination unless strictly required to proceed. Only fetch next-page if the first page lacks what you need. + + \ No newline at end of file diff --git a/lib/api/mcp/base.rb b/lib/api/mcp/base.rb index 69f14aa19fceb8..acc6c7c76a447c 100644 --- a/lib/api/mcp/base.rb +++ b/lib/api/mcp/base.rb @@ -74,8 +74,10 @@ def oauth_access_token current_request, *Doorkeeper.configuration.access_token_methods ) - unauthorized! unless token - token + return token if token + return if Gitlab.dev_or_test_env? + + unauthorized! end def create_handler(handler_class, handler_params) diff --git a/lib/api/mcp/handlers/list_tools_request.rb b/lib/api/mcp/handlers/list_tools_request.rb index a41ba4e9ebc69b..3b4bf1e6bc038d 100644 --- a/lib/api/mcp/handlers/list_tools_request.rb +++ b/lib/api/mcp/handlers/list_tools_request.rb @@ -18,7 +18,7 @@ class ListToolsRequest < Base }.freeze CUSTOM_TOOLS = { 'get_mcp_server_version' => ::Mcp::Tools::GetServerVersionService, - 'dependency_analysis' => ::Mcp::Tools::DependencyAnalysisService + 'knowledge_graph_dependency_analysis' => ::Mcp::Tools::DependencyAnalysisService }.freeze TOOLS = CUSTOM_TOOLS.merge(API_TOOLS) diff --git a/lib/api/mcp/server.rb b/lib/api/mcp/server.rb index ba9d5a91988636..3b549bf22adab9 100644 --- a/lib/api/mcp/server.rb +++ b/lib/api/mcp/server.rb @@ -39,11 +39,11 @@ class Server < ::API::Base allow_access_with_scope :mcp urgency :low - before do - authenticate! - not_found! unless Feature.enabled?(:mcp_server, current_user) - forbidden! unless access_token&.scopes&.include?(Gitlab::Auth::MCP_SCOPE.to_s) - end + # before do + # authenticate! + # not_found! unless Feature.enabled?(:mcp_server, current_user) + # forbidden! unless access_token&.scopes&.include?(Gitlab::Auth::MCP_SCOPE.to_s) + # end helpers do def invoke_basic_handler -- GitLab From f4bde9147f6ce1055799c536cf73af6dd115e8cd Mon Sep 17 00:00:00 2001 From: michaelangeloio Date: Fri, 10 Oct 2025 09:07:56 -0400 Subject: [PATCH 7/9] add gitaly endpoint --- ee/lib/api/internal/knowledge_graph.rb | 59 +++ lib/api/api.rb | 1 + steps-executed.md | 532 +++++++++++++++++++++++++ 3 files changed, 592 insertions(+) create mode 100644 ee/lib/api/internal/knowledge_graph.rb create mode 100644 steps-executed.md diff --git a/ee/lib/api/internal/knowledge_graph.rb b/ee/lib/api/internal/knowledge_graph.rb new file mode 100644 index 00000000000000..f3c823b9e5e94f --- /dev/null +++ b/ee/lib/api/internal/knowledge_graph.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module API + module Internal + class KnowledgeGraph < ::API::Base + feature_category :code_suggestions + + before { authenticate_by_gitlab_shell_token! } + + namespace 'internal' do + namespace 'knowledge_graph' do + desc 'Get Gitaly connection info for a project' do + detail 'Returns Gitaly connection information needed to fetch and index a repository' + end + params do + requires :project_id, type: Integer, desc: 'Project ID' + end + get 'gitaly_connection_info' do + project = Project.find_by_id(params[:project_id]) + + not_found!('Project') unless project + + gitaly_info = build_gitaly_connection_info(project) + + error!('Repository not found or not accessible', 404) if gitaly_info[:GitalyConnectionInfo].blank? + + gitaly_info + end + end + end + + helpers do + def build_gitaly_connection_info(project) + return {} if ::Search::Zoekt.missing_repo?(project) + + repository_storage = project.repository_storage + connection_info = Gitlab::GitalyClient.connection_data(repository_storage) + repository_path = "#{project.repository.disk_path}.git" + address = connection_info['address'] + + # Support relative unix: connection strings (same as task_serializer_service.rb) + if address.match?(%r{\Aunix:[^/.]}) + path = address.split('unix:').last + address = "unix:#{Rails.root.join(path)}" + end + + { + GitalyConnectionInfo: { + Address: address, + Token: connection_info['token'], + Storage: repository_storage, + Path: repository_path + } + } + end + end + end + end +end diff --git a/lib/api/api.rb b/lib/api/api.rb index 34cb11501226b9..22ff6c0ee08398 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -416,6 +416,7 @@ def initialize(location_url) mount ::API::Internal::MailRoom mount ::API::Internal::Workhorse mount ::API::Internal::Shellhorse + mount ::API::Internal::KnowledgeGraph route :any, '*path', feature_category: :not_owned do error!('404 Not Found', 404) diff --git a/steps-executed.md b/steps-executed.md new file mode 100644 index 00000000000000..55261ad999311a --- /dev/null +++ b/steps-executed.md @@ -0,0 +1,532 @@ +# GitLab Package Installation Steps for GCP Instance + +## Package Details +- **Package Name**: `gitlab-ee_18.4.1+rfbranch.2075437800.a11b2c5b-0_amd64.deb` +- **Local Path**: `/Users/angelo.rivera/gitlab/knowledge-graph-workspace/gdk/gitlab/ubuntu-noble/` +- **OS Target**: Ubuntu Noble (24.04) +- **GCP Project**: `arivera-d2b6e433` +- **Zone**: `us-central1-a` +- **Instance Name**: `gitlab-instance` +- **External IP**: `34.70.37.175` + +## Commands Executed + +### Step 1: Verified GCloud Configuration ✅ + +```bash +gcloud config list +``` + +Result: Active project is `arivera-d2b6e433` + +### Step 2: Created GCP Compute Instance ✅ + +```bash +gcloud compute instances create gitlab-instance \ + --project=arivera-d2b6e433 \ + --zone=us-central1-a \ + --machine-type=e2-standard-4 \ + --image-family=ubuntu-2404-lts-amd64 \ + --image-project=ubuntu-os-cloud \ + --boot-disk-size=100GB \ + --boot-disk-type=pd-standard +``` + +Result: Instance created successfully +- Internal IP: `10.128.0.2` +- External IP: `34.70.37.175` +- Status: `RUNNING` + +### Step 3: Configured Firewall Rules ✅ + +```bash +# HTTPS (port 443) +gcloud compute firewall-rules create default-allow-https \ + --project=arivera-d2b6e433 \ + --direction=INGRESS \ + --priority=1000 \ + --network=default \ + --action=ALLOW \ + --rules=tcp:443 \ + --source-ranges=0.0.0.0/0 + +# HTTP (port 80) +gcloud compute firewall-rules create default-allow-http \ + --project=arivera-d2b6e433 \ + --direction=INGRESS \ + --priority=1000 \ + --network=default \ + --action=ALLOW \ + --rules=tcp:80 \ + --source-ranges=0.0.0.0/0 +``` + +Result: Both firewall rules created successfully + +### Step 4: Transfer GitLab Package to VM ✅ + +```bash +gcloud compute scp \ + /Users/angelo.rivera/gitlab/knowledge-graph-workspace/gdk/gitlab/ubuntu-noble/gitlab-ee_18.4.1+rfbranch.2075437800.a11b2c5b-0_amd64.deb \ + gitlab-instance:~ \ + --zone us-central1-a \ + --project arivera-d2b6e433 +``` + +Result: File transferred successfully (1415MB in 44 seconds) + +### Step 5: Verify Ubuntu Version ✅ + +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="cat /etc/os-release" +``` + +Result: Ubuntu 24.04.3 LTS (Noble Numbat) confirmed + +### Step 6: Install Dependencies ✅ + +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo apt-get update && sudo apt-get install -y curl openssh-server ca-certificates perl" +``` + +Result: All dependencies already installed + +### Step 7: Install GitLab Package ✅ + +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo dpkg -i gitlab-ee_18.4.1+rfbranch.2075437800.a11b2c5b-0_amd64.deb" +``` + +Result: GitLab EE 18.4.1 installed successfully + +### Step 8: Configure External URL ✅ + +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="echo \"external_url 'https://gitlab-knowledge-graph.michaelangelo.io'\" | sudo tee -a /etc/gitlab/gitlab.rb" +``` + +Result: External URL configured + +### Step 9: Run GitLab Reconfigure ⚠️ + +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo gitlab-ctl reconfigure" +``` + +Result: GitLab configured successfully with self-signed SSL certificate. Let's Encrypt SSL failed (expected) because DNS is not configured yet. + +### Step 10: Verify Services ✅ + +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo gitlab-ctl status" +``` + +Result: All services running successfully: +- crond +- gitaly +- gitlab-kas +- gitlab-workhorse +- logrotate +- nginx +- postgresql +- puma +- redis +- registry +- sidekiq + +### Step 11: Retrieved Initial Root Password ✅ + +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo cat /etc/gitlab/initial_root_password" +``` + +Note: This password is automatically deleted after 24 hours. + +--- + +## DNS Configuration for Squarespace + +To complete the setup, you need to add a DNS A record in Squarespace: + +**Login to Squarespace:** +1. Go to https://account.squarespace.com +2. Click on your domain `michaelangelo.io` +3. Navigate to "DNS Settings" or "Advanced Settings" + +**Add A Record:** + +| Record Type | Host | Value | TTL | +|-------------|------|-------|-----| +| A | gitlab-knowledge-graph | 34.70.37.175 | Automatic (or 3600) | + +**Details:** +- **Record Type:** A +- **Host/Name:** `gitlab-knowledge-graph` (Squarespace will automatically add `.michaelangelo.io`) +- **Points to / Value:** `34.70.37.175` +- **TTL:** Leave as default (usually automatic or 3600 seconds) + +**After Adding the DNS Record:** + +1. Wait 5-15 minutes for DNS propagation +2. Verify DNS is working: + ```bash + nslookup gitlab-knowledge-graph.michaelangelo.io + # or + dig gitlab-knowledge-graph.michaelangelo.io + ``` +3. Once DNS resolves, run reconfigure again to get a proper SSL certificate: + ```bash + gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo gitlab-ctl reconfigure" + ``` +4. Access GitLab at: https://gitlab-knowledge-graph.michaelangelo.io + +### Step 12: DNS Configured and SSL Certificate Obtained ✅ + +DNS record successfully added in Squarespace: +- Record Type: A +- Host: gitlab-knowledge-graph +- Points to: 34.70.37.175 + +Verified DNS resolution: +```bash +nslookup gitlab-knowledge-graph.michaelangelo.io +# Resolved to: 34.70.37.175 +``` + +Re-ran GitLab reconfigure to obtain SSL certificate: +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo gitlab-ctl reconfigure" +``` + +Result: GitLab now has a valid SSL certificate from Let's Encrypt! + +### Step 13: Enabled Let's Encrypt and Obtained Production Certificate ✅ + +Added Let's Encrypt configuration: +```bash +echo "letsencrypt['enable'] = true" | sudo tee -a /etc/gitlab/gitlab.rb +echo "letsencrypt['contact_emails'] = ['arivera@gitlab.com']" | sudo tee -a /etc/gitlab/gitlab.rb +``` + +Re-ran reconfigure to obtain production certificate: +```bash +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo gitlab-ctl reconfigure" +``` + +Verified certificate: +```bash +sudo openssl x509 -in /etc/gitlab/ssl/gitlab-knowledge-graph.michaelangelo.io.crt -text -noout | grep Issuer +# Issuer: C = US, O = Let's Encrypt, CN = R12 +``` + +Result: Valid Let's Encrypt SSL certificate installed! +- Certificate auto-renews every 4 days (configured via cron) +- Valid until: December 31, 2025 + +## Installation Complete! 🎉 + +**Access your GitLab instance at:** +https://gitlab-knowledge-graph.michaelangelo.io + + +**Important:** Change the root password immediately after first login! + +### Step 14: Configure Staging Customers Portal for License Validation ✅ + +To use a staging license key, configured the instance to validate against staging customers portal with the required environment variables: + +```bash +# Add environment variables to gitlab.rb +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="echo \"gitlab_rails['env'] = { 'CUSTOMER_PORTAL_URL' => 'https://customers.staging.gitlab.com', 'GITLAB_LICENSE_MODE' => 'test' }\" | sudo tee -a /etc/gitlab/gitlab.rb" + +# Reconfigure GitLab +gcloud compute ssh gitlab-instance --zone us-central1-a --project arivera-d2b6e433 --command="sudo gitlab-ctl reconfigure && sudo gitlab-ctl restart puma && sudo gitlab-ctl restart sidekiq" +``` + +Verified the environment variables: +```bash +sudo ls /opt/gitlab/etc/gitlab-rails/env/ | grep -E 'CUSTOMER_PORTAL_URL|GITLAB_LICENSE_MODE' +# Output: +# CUSTOMER_PORTAL_URL +# GITLAB_LICENSE_MODE +``` + +**Environment Variables Set:** +- `CUSTOMER_PORTAL_URL`: `https://customers.staging.gitlab.com` +- `GITLAB_LICENSE_MODE`: `test` + +Result: GitLab is now configured to validate licenses against staging customers portal! + +**To activate your staging license:** +1. Go to https://gitlab-knowledge-graph.michaelangelo.io/admin/license +2. Upload or paste your staging license key +3. The license will be validated against `customers.staging.gitlab.com` + +--- + +## Step 1: Create GCP Project + +Follow the [instructions](https://handbook.gitlab.com/handbook/company/infrastructure-standards/realms/sandbox/#individual-aws-account-or-gcp-project) to create a GCP project: +- Select the `gcp-669306fb(organizations/769164969568)` Cloud Provider while creating the Cloud Account + +## Step 2: Create GCP Compute Instance + +Run the following command to create a VM instance: + +```bash +gcloud compute instances create gitlab-instance \ + --project= \ + --zone= \ + --machine-type=e2-standard-4 \ + --image-family=ubuntu-2404-lts-amd64 \ + --image-project=ubuntu-os-cloud \ + --boot-disk-size=100GB \ + --boot-disk-type=pd-standard +``` + +**Machine Configuration:** +- Machine type: `e2-standard-4` (4 vCPUs, 16 GB Memory) +- OS: Ubuntu 24.04 LTS (Noble) +- Disk size: 100 GB + +## Step 3: Configure Firewall Rules + +Allow access to ports 443 and 80: + +```bash +# HTTPS (port 443) +gcloud compute firewall-rules create default-allow-https \ + --project= \ + --direction=INGRESS \ + --priority=1000 \ + --network=default \ + --action=ALLOW \ + --rules=tcp:443 \ + --source-ranges=0.0.0.0/0 + +# HTTP (port 80) +gcloud compute firewall-rules create default-allow-http \ + --project= \ + --direction=INGRESS \ + --priority=1000 \ + --network=default \ + --action=ALLOW \ + --rules=tcp:80 \ + --source-ranges=0.0.0.0/0 +``` + +## Step 4: SSH into the Instance + +```bash +gcloud compute ssh --zone gitlab-instance --project +``` + +Confirm the OS version: + +```bash +cat /etc/os-release +``` + +Expected output should show Ubuntu 24.04 (Noble Numbat). + +## Step 5: Get VM Public IP + +```bash +gcloud compute instances describe gitlab-instance \ + --zone= \ + --project= \ + --format='get(networkInterfaces[0].accessConfigs[0].natIP)' +``` + +Save this IP address for DNS configuration. + +## Step 6: Configure DNS + +### Option A: Purchase a Non-Trademark Domain + +Follow [instructions](https://internal.gitlab.com/handbook/it/it-self-service/it-guides/domains-dns/#non-trademark-domain-names) to register a [Cloud Domain](https://console.cloud.google.com/net-services/domains/registrations/list). + +### Option B: Use existing domain + +If you already have a domain, create DNS records pointing to the VM's public IP. + +### Create DNS A Records + +```bash +# For root domain +gcloud dns record-sets create \ + --zone= \ + --project= \ + --type="A" \ + --ttl="300" \ + --rrdatas= + +# For www subdomain (optional) +gcloud dns record-sets create www. \ + --zone= \ + --project= \ + --type="A" \ + --ttl="300" \ + --rrdatas= +``` + +### Verify DNS Setup + +```bash +nslookup +# or +dig +``` + +## Step 7: Transfer Package to VM + +From your local machine, transfer the GitLab package: + +```bash +gcloud compute scp \ + /Users/angelo.rivera/gitlab/knowledge-graph-workspace/gdk/gitlab/ubuntu-noble/gitlab-ee_18.4.1+rfbranch.2075437800.a11b2c5b-0_amd64.deb \ + gitlab-instance:/home/$USER/ \ + --zone \ + --project +``` + +## Step 8: Install Required Dependencies on VM + +SSH into the VM and run: + +```bash +sudo apt-get update +sudo apt-get install -y curl openssh-server ca-certificates perl +``` + +## Step 9: Install GitLab Package + +```bash +sudo dpkg -i gitlab-ee_18.4.1+rfbranch.2075437800.a11b2c5b-0_amd64.deb +``` + +## Step 10: Configure GitLab + +Edit the GitLab configuration file: + +```bash +sudo nano /etc/gitlab/gitlab.rb +``` + +Set the `external_url` to your domain (must include `https://`): + +```ruby +external_url 'https://' +``` + +Save the file and reconfigure GitLab: + +```bash +sudo gitlab-ctl reconfigure +``` + +This process may take several minutes. + +## Step 11: Retrieve Initial Root Password + +```bash +sudo cat /etc/gitlab/initial_root_password +``` + +**Important:** Save this password securely. The file is automatically deleted after 24 hours. + +## Step 12: Verify GitLab Services + +Check that all services are running: + +```bash +sudo gitlab-ctl status +``` + +All services should show as `run`. + +## Step 13: Access GitLab + +Open your browser and navigate to: + +``` +https:// +``` + +Login with: +- **Username**: `root` +- **Password**: (from Step 11) + +## Step 14: Activate Ultimate License (Optional) + +Follow the [license activation instructions](https://docs.gitlab.com/ee/administration/license.html) to activate an Ultimate EE license. + +## Troubleshooting + +### Check GitLab Logs + +```bash +sudo gitlab-ctl tail +``` + +### Restart GitLab Services + +```bash +sudo gitlab-ctl restart +``` + +### Check Service Status + +```bash +sudo gitlab-ctl status +``` + +## Updating the Package + +If you need to update to a newer package version: + +1. Transfer the new package to the VM: + ```bash + gcloud compute scp gitlab-instance:/home/$USER/ --zone --project + ``` + +2. SSH into the VM and run: + ```bash + sudo touch /etc/gitlab/skip-auto-backup + sudo apt update + sudo dpkg -i + sudo rm /etc/gitlab/skip-auto-backup + ``` + +## Cleanup + +To delete the instance when done: + +```bash +gcloud compute instances delete gitlab-instance \ + --zone= \ + --project= +``` + +## Notes + +- Replace ``, ``, and `` with your actual values +- Default zone suggestions: `us-central1-a`, `us-east1-b`, `europe-west1-b` +- The package is for Ubuntu Noble (24.04), ensure your VM uses this OS version +- Initial setup and configuration takes approximately 10-15 minutes +- Monitor costs in GCP Console to avoid unexpected charges + +## Package Verification + +To verify the package integrity before installation, you can check the SHA256 hash: + +```bash +# On local machine +cat /Users/angelo.rivera/gitlab/knowledge-graph-workspace/gdk/gitlab/ubuntu-noble/gitlab-ee_18.4.1+rfbranch.2075437800.a11b2c5b-0_amd64.deb.sha256 + +# On VM after transfer +sha256sum gitlab-ee_18.4.1+rfbranch.2075437800.a11b2c5b-0_amd64.deb +``` + +The checksums should match. + -- GitLab From dc4b043d9d571766daf6c46c253e80308cbefa2c Mon Sep 17 00:00:00 2001 From: michaelangeloio Date: Sun, 12 Oct 2025 13:30:07 +0200 Subject: [PATCH 8/9] new endpoints, button at top --- app/views/layouts/nav/_top_bar.html.haml | 15 ++ ee/lib/api/internal/knowledge_graph.rb | 203 +++++++++++++++++++++++ lib/gitlab/application_rate_limiter.rb | 2 + 3 files changed, 220 insertions(+) diff --git a/app/views/layouts/nav/_top_bar.html.haml b/app/views/layouts/nav/_top_bar.html.haml index 927d112e53d3c9..d7777001a45482 100644 --- a/app/views/layouts/nav/_top_bar.html.haml +++ b/app/views/layouts/nav/_top_bar.html.haml @@ -8,3 +8,18 @@ - if duo_button_enabled .gl-flex-none.gl-flex.gl-items-center.gl-justify-center.gl-gap-3 = render "layouts/nav/ask_duo_button" + .gl-flex-none.gl-flex.gl-items-center.gl-justify-end.gl-gap-3.gl-ml-auto + %a.gl-button.gl-button-medium.gl-button-emphasized-variant-confirm{ href: 'https://kg-api.gitlab-knowledge-graph.michaelangelo.io', target: '_blank', rel: 'noopener noreferrer', title: 'Launch GitLab Knowledge Graph', style: 'animation: pulse-glow 1.5s ease-in-out 3;' } + %span.gl-button-text + Launch GitLab Knowledge Graph + :css + @keyframes pulse-glow { + 0%, 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(31, 117, 203, 0.7); + } + 50% { + transform: scale(1.05); + box-shadow: 0 0 20px 5px rgba(31, 117, 203, 0.4); + } + } diff --git a/ee/lib/api/internal/knowledge_graph.rb b/ee/lib/api/internal/knowledge_graph.rb index f3c823b9e5e94f..ee123bc8a86c53 100644 --- a/ee/lib/api/internal/knowledge_graph.rb +++ b/ee/lib/api/internal/knowledge_graph.rb @@ -26,6 +26,86 @@ class KnowledgeGraph < ::API::Base gitaly_info end + + desc 'Get merge request diff' do + detail 'Returns diff for a merge request with proper rate limiting for internal API usage' + end + params do + requires :project_id, type: Integer, desc: 'Project ID' + requires :merge_request_iid, type: Integer, desc: 'Merge Request IID' + optional :max_files, type: Integer, desc: 'Maximum number of files to return (default: 100)' + optional :max_lines, type: Integer, desc: 'Maximum number of lines to return (default: 5000)' + optional :max_patch_bytes, type: Integer, desc: 'Maximum bytes per file patch (default: 100KB)' + optional :ignore_whitespace_change, type: Boolean, desc: 'Ignore whitespace changes (default: false)' + end + get 'merge_request_diff' do + project = Project.find_by_id(params[:project_id]) + not_found!('Project') unless project + + # Rate limit this endpoint to prevent abuse (scope by project) + check_rate_limit!(:knowledge_graph_merge_request_diff, scope: [project]) + + merge_request = project.merge_requests.find_by_iid(params[:merge_request_iid]) + not_found!('Merge Request') unless merge_request + + # Build diff options with sensible defaults and limits + diff_options = build_diff_options(params) + + # Get diffs with the configured options (using merge_request.diffs which handles compare object) + diffs = merge_request.diffs(diff_options) + + { + merge_request: serialize_merge_request_info(merge_request), + diffs: serialize_diffs(diffs), + stats: calculate_diff_stats(diffs) + } + end + + desc 'Get repository files' do + detail 'Returns contents of multiple files from a repository at a specific ref with proper rate limiting' + end + params do + requires :project_id, type: Integer, desc: 'Project ID' + requires :file_paths, type: Array[String], desc: 'Array of file paths to fetch' + optional :ref, type: String, desc: 'Branch, tag, or commit SHA (default: HEAD)' + optional :max_file_size, type: Integer, desc: 'Maximum file size in bytes (default: 1MB)' + end + post 'repository_files' do + project = Project.find_by_id(params[:project_id]) + not_found!('Project') unless project + + # Rate limit this endpoint to prevent abuse (scope by project) + check_rate_limit!(:knowledge_graph_repository_files, scope: [project]) + + # Validate file_paths array + file_paths = params[:file_paths] + error!('file_paths must be an array', 400) unless file_paths.is_a?(Array) + error!('file_paths cannot be empty', 400) if file_paths.empty? + error!('Maximum 100 files per request', 400) if file_paths.length > 100 + + # Get ref (default to HEAD) + ref = params[:ref] || 'HEAD' + max_file_size = [params[:max_file_size] || 1_048_576, 10_485_760].min # Cap at 10MB + + repository = project.repository + error!('Repository not found', 404) unless repository + + # Resolve ref to commit + commit = repository.commit(ref) + error!("Ref '#{ref}' not found", 404) unless commit + + # Fetch files + files_data = file_paths.map do |file_path| + fetch_repository_file(repository, commit, file_path, max_file_size) + end + + { + project_id: project.id, + ref: ref, + commit_sha: commit.sha, + files: files_data.compact + } + end end end @@ -53,6 +133,129 @@ def build_gitaly_connection_info(project) } } end + + def build_diff_options(params) + { + max_files: [params[:max_files] || 100, 1000].min, # Cap at 1000 files + max_lines: [params[:max_lines] || 5000, 50000].min, # Cap at 50k lines + max_patch_bytes: [params[:max_patch_bytes] || 102400, 1048576].min, # Cap at 1MB + ignore_whitespace_change: params[:ignore_whitespace_change] || false, + expanded: true # Get full diffs, not collapsed + # Don't include paths: [] as it causes issues with the SQL query + } + end + + def serialize_merge_request_info(merge_request) + { + iid: merge_request.iid, + title: merge_request.title, + description: merge_request.description, + source_branch: merge_request.source_branch, + target_branch: merge_request.target_branch, + source_project_id: merge_request.source_project_id, + target_project_id: merge_request.target_project_id, + diff_head_sha: merge_request.diff_head_sha, + diff_base_sha: merge_request.diff_base_sha, + state: merge_request.state, + created_at: merge_request.created_at, + updated_at: merge_request.updated_at + } + end + + def serialize_diffs(diffs) + # Get diff files from the FileCollection + result = [] + diffs.diff_files.each do |diff| + result << { + old_path: diff.old_path, + new_path: diff.new_path, + a_mode: diff.a_mode, + b_mode: diff.b_mode, + new_file: diff.new_file?, + renamed_file: diff.renamed_file?, + deleted_file: diff.deleted_file?, + diff: diff.raw_diff, # Use raw_diff to get the actual diff string + binary: diff.binary?, + too_large: diff.too_large?, + collapsed: diff.collapsed? + } + end + result + end + + def calculate_diff_stats(diffs) + additions = 0 + deletions = 0 + total_files = 0 + binary_files = 0 + + diffs.diff_files.each do |diff| + additions += diff.added_lines + deletions += diff.removed_lines + total_files += 1 + binary_files += 1 if diff.binary? + end + + { + additions: additions, + deletions: deletions, + total_files: total_files, + binary_files: binary_files + } + end + + def fetch_repository_file(repository, commit, file_path, max_file_size) + # Check if file exists at this commit + blob = repository.blob_at(commit.sha, file_path) + + if blob.nil? + return { + path: file_path, + exists: false, + error: 'File not found' + } + end + + # Check file size + if blob.size > max_file_size + return { + path: file_path, + exists: true, + too_large: true, + size: blob.size, + max_size: max_file_size, + error: "File size #{blob.size} bytes exceeds maximum #{max_file_size} bytes" + } + end + + # Check if binary + if blob.binary? + return { + path: file_path, + exists: true, + binary: true, + size: blob.size, + error: 'Binary files are not returned' + } + end + + # Return file content + { + path: file_path, + exists: true, + binary: false, + size: blob.size, + content: blob.data, + mime_type: blob.mime_type, + encoding: blob.encoding + } + rescue StandardError => e + { + path: file_path, + exists: false, + error: "Failed to fetch file: #{e.message}" + } + end end end end diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index 2ee7b821c1b1c5..5fbbda3bd16723 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -51,6 +51,8 @@ def rate_limits # rubocop:disable Metrics/AbcSize group_shared_groups_api: { threshold: -> { application_settings.group_shared_groups_api_limit }, interval: 1.minute }, groups_api: { threshold: -> { application_settings.groups_api_limit }, interval: 1.minute }, import_source_user_notification: { threshold: 1, interval: 8.hours }, + knowledge_graph_merge_request_diff: { threshold: 30, interval: 1.minute }, + knowledge_graph_repository_files: { threshold: 60, interval: 1.minute }, issues_create: { threshold: -> { application_settings.issues_create_limit }, interval: 1.minute }, jobs_index: { threshold: -> { application_settings.project_jobs_api_rate_limit }, interval: 1.minute }, large_blob_download: { threshold: 5, interval: 1.minute }, -- GitLab From 0f3ca73196f28534bc087139893cd4d7c9307fde Mon Sep 17 00:00:00 2001 From: michaelangeloio Date: Wed, 22 Oct 2025 11:31:20 -0400 Subject: [PATCH 9/9] update --- .cursorindexingignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .cursorindexingignore diff --git a/.cursorindexingignore b/.cursorindexingignore deleted file mode 100644 index 953908e7300307..00000000000000 --- a/.cursorindexingignore +++ /dev/null @@ -1,3 +0,0 @@ - -# Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references -.specstory/** -- GitLab