diff --git a/Gemfile b/Gemfile index 7851769e477..0a96ff0c8ec 100644 --- a/Gemfile +++ b/Gemfile @@ -175,7 +175,7 @@ gem 'faraday_middleware-aws-sigv4', '~>0.3.0' gem 'typhoeus', '~> 1.4.0' # Used with Elasticsearch to support http keep-alive connections # Markdown and HTML processing -gem 'html-pipeline', '~> 2.13.2' +gem 'html-pipeline', '~> 2.14.3' gem 'deckar01-task_list', '2.3.2' gem 'gitlab-markup', '~> 1.8.0' gem 'github-markup', '~> 1.7.0', require: 'github/markup' diff --git a/Gemfile.checksum b/Gemfile.checksum index 8d20aa8cd28..a9d2e4d59bc 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -263,7 +263,7 @@ {"name":"hashie-forbidden_attributes","version":"0.1.1","platform":"ruby","checksum":"3a6ed37f3a314e4fb1dd1e2df6eb7721bcadd023a30bc0b951b2b5285a790fb2"}, {"name":"health_check","version":"3.1.0","platform":"ruby","checksum":"10146508237dc54ed7e24c292d8ba7fb8f9590cf26c66e325b947438c4103b57"}, {"name":"heapy","version":"0.2.0","platform":"ruby","checksum":"74141e845d61ffc7c1e8bf8b127c8cf94544ec7a1181aec613288682543585ea"}, -{"name":"html-pipeline","version":"2.13.2","platform":"ruby","checksum":"a1de83f7bd2d3464f3a068e391b661983fc6099d194c8d9ceb91ace02dadb803"}, +{"name":"html-pipeline","version":"2.14.3","platform":"ruby","checksum":"8a1d4d7128b2141913387cac0f8ba898bb6812557001acc0c2b46910f59413a0"}, {"name":"html2text","version":"0.2.0","platform":"ruby","checksum":"31c2f0be9ab7aa4fc780b07d5f84882ebc22a9024c29a45f4f5adfe42e92ad4f"}, {"name":"htmlbeautifier","version":"1.4.2","platform":"ruby","checksum":"9de0c98480fe80d795ed5734a11f183563cd969686f25a04609c0f5a446fa5f8"}, {"name":"htmlentities","version":"4.3.4","platform":"ruby","checksum":"125a73c6c9f2d1b62100b7c3c401e3624441b663762afa7fe428476435a673da"}, diff --git a/Gemfile.lock b/Gemfile.lock index d63719e07a7..cd1da4b7c76 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -716,7 +716,7 @@ GEM railties (>= 5.0) heapy (0.2.0) thor - html-pipeline (2.13.2) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) html2text (0.2.0) @@ -1667,7 +1667,7 @@ DEPENDENCIES hashie (~> 5.0.0) hashie-forbidden_attributes health_check (~> 3.0) - html-pipeline (~> 2.13.2) + html-pipeline (~> 2.14.3) html2text httparty (~> 0.20.0) icalendar diff --git a/app/assets/javascripts/import_entities/import_projects/components/advanced_settings.vue b/app/assets/javascripts/import_entities/import_projects/components/advanced_settings.vue index a8fdf9b9ec5..cf1a4de68ed 100644 --- a/app/assets/javascripts/import_entities/import_projects/components/advanced_settings.vue +++ b/app/assets/javascripts/import_entities/import_projects/components/advanced_settings.vue @@ -40,6 +40,8 @@ export default { v-for="{ name, label, details } in stages" :key="name" :checked="value[name]" + :data-qa-option-name="name" + data-qa-selector="advanced_settings_checkbox" @change="$emit('input', { ...value, [name]: $event })" > {{ label }} diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 76fac831199..dd2ad26ce49 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -123,7 +123,15 @@ module Types field :alert_management_alert, Types::AlertManagement::AlertType, null: true, - description: 'Alert associated to this issue.' + description: 'Alert associated to this issue.', + deprecated: { reason: 'Use `alert_management_alerts`', milestone: '15.6' } + + field :alert_management_alerts, + Types::AlertManagement::AlertType.connection_type, + null: true, + description: 'Alert Management alerts associated to this issue.', + extras: [:lookahead], + resolver: Resolvers::AlertManagement::AlertResolver field :severity, Types::IssuableSeverityEnum, null: true, description: 'Severity level of the incident.' diff --git a/app/models/issue.rb b/app/models/issue.rb index ea7acf9a5d1..61fc0264360 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -90,6 +90,7 @@ class Issue < ApplicationRecord has_one :incident_management_issuable_escalation_status, class_name: 'IncidentManagement::IssuableEscalationStatus' has_and_belongs_to_many :self_managed_prometheus_alert_events, join_table: :issues_self_managed_prometheus_alert_events # rubocop: disable Rails/HasAndBelongsToMany has_and_belongs_to_many :prometheus_alert_events, join_table: :issues_prometheus_alert_events # rubocop: disable Rails/HasAndBelongsToMany + has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :issue has_many :prometheus_alerts, through: :prometheus_alert_events has_many :issue_customer_relations_contacts, class_name: 'CustomerRelations::IssueContact', inverse_of: :issue has_many :customer_relations_contacts, through: :issue_customer_relations_contacts, source: :contact, class_name: 'CustomerRelations::Contact', inverse_of: :issues diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index e3b198d1012..66d4a740b27 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -155,6 +155,14 @@ We note in the instructions below where these secrets are required. NOTE: Omnibus GitLab installations can use `gitlab-secrets.json` for `GITLAB_SHELL_SECRET_TOKEN`. +### Customize time server setting + +By default, Gitaly and Praefect nodes use the time server at `pool.ntp.org` for time synchronization checks. You can customize this setting by adding the +following to `gitlab.rb` on each node: + +- `gitaly['env'] = { "NTP_HOST" => "ntp.example.com" }`, for Gitaly nodes. +- `praefect['env'] = { "NTP_HOST" => "ntp.example.com" }`, for Praefect nodes. + ### PostgreSQL NOTE: diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md index 0209f97bd31..c9b5784fa68 100644 --- a/doc/administration/housekeeping.md +++ b/doc/administration/housekeeping.md @@ -20,6 +20,65 @@ Do not manually execute Git commands to perform housekeeping in Git repositories that are controlled by GitLab. Doing so may lead to corrupt repositories and data loss. +## Housekeeping strategy + +Gitaly can perform housekeeping tasks in a Git repository in two ways: + +- [Eager housekeeping](#eager-housekeeping) executes specific housekeeping tasks + independent of the state a repository is in. +- [Heuristical housekeeping](#heuristical-housekeeping) executes housekeeping + tasks based on a set of heuristics that determine what housekeeping tasks need + to be executed based on the repository state. + +### Eager housekeeping + +The "eager" housekeeping strategy executes housekeeping tasks in a repository +independent of the repository state. This is the default strategy as used by the +[manual trigger](#manual-trigger) and the [push-based trigger](#push-based-trigger). + +The eager housekeeping strategy is controlled by the GitLab application. +Depending on the trigger that caused the housekeeping job to run, GitLab asks +Gitaly to perform specific housekeeping tasks. Gitaly performs these tasks even +if the repository is in an optimized state. As a result, this strategy can be +inefficient in large repositories where performing the housekeeping tasks may +be slow. + +### Heuristical housekeeping + +> - [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/2634) in GitLab 14.9 for the [manual trigger](#manual-trigger) and the [push-based trigger](#push-based-trigger) [with a flag](feature_flags.md) named `optimized_housekeeping`. Disabled by default. +> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/353607) in GitLab 14.10. + +FLAG: +On self-managed GitLab, by default this feature is not available for the [manual trigger](#manual-trigger) and the [push-based trigger](#push-based-trigger). +To make it available, ask an administrator to [enable the feature flag](feature_flags.md) named `optimized_housekeeping`. + +The heuristical (or "opportunistic") housekeeping strategy analyzes the +repository's state and executes housekeeping tasks only when it finds one or +more data structures are insufficiently optimized. This is the strategy used by +[scheduled housekeeping](#scheduled-housekeeping). It can optionally be enabled +for the [manual trigger](#manual-trigger) and the [push-based trigger](#push-based-trigger) +by enabling the `optimized_housekeeping` feature flag. + +Heuristical housekeeping uses the following information to decide on the tasks +it needs to run: + +- The number of loose and stale objects. +- The number of packfiles that contain already-compressed objects. +- The number of loose references. +- The presence of a commit-graph. + +The decision whether any of the analyzed data structures need to be optimized is +based on the size of the repository: + +- Objects are repacked frequently the bigger the total size of all objects. +- References are repacked less frequently the more references there are in + total. + +Gitaly does this to offset the fact that optimizing those data structures takes +more time the bigger they get. It is especially important in large +monorepositories (which receive a lot of traffic) to avoid optimizing them too +frequently. + ## Running housekeeping tasks There are different ways in which GitLab runs housekeeping tasks: diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 1a2ddc6eea8..46b23e4394e 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -12278,7 +12278,7 @@ Relationship between an epic and an issue. | Name | Type | Description | | ---- | ---- | ----------- | -| `alertManagementAlert` | [`AlertManagementAlert`](#alertmanagementalert) | Alert associated to this issue. | +| `alertManagementAlert` **{warning-solid}** | [`AlertManagementAlert`](#alertmanagementalert) | **Deprecated** in 15.6. Use `alert_management_alerts`. | | `assignees` | [`UserCoreConnection`](#usercoreconnection) | Assignees of the issue. (see [Connections](#connections)) | | `author` | [`UserCore!`](#usercore) | User that created the issue. | | `blocked` | [`Boolean!`](#boolean) | Indicates the issue is blocked. | @@ -12346,6 +12346,27 @@ Relationship between an epic and an issue. #### Fields with arguments +##### `EpicIssue.alertManagementAlerts` + +Alert Management alerts associated to this issue. + +Returns [`AlertManagementAlertConnection`](#alertmanagementalertconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `assigneeUsername` | [`String`](#string) | Username of a user assigned to the issue. | +| `domain` | [`AlertManagementDomainFilter!`](#alertmanagementdomainfilter) | Filter query for given domain. | +| `iid` | [`String`](#string) | IID of the alert. For example, "1". | +| `search` | [`String`](#string) | Search query for title, description, service, or monitoring_tool. | +| `sort` | [`AlertManagementAlertSort`](#alertmanagementalertsort) | Sort alerts by this criteria. | +| `statuses` | [`[AlertManagementStatus!]`](#alertmanagementstatus) | Alerts with the specified statues. For example, `[TRIGGERED]`. | + ##### `EpicIssue.currentUserTodos` To-do items for the current user. @@ -13845,7 +13866,7 @@ Describes an issuable resource link for incident issues. | Name | Type | Description | | ---- | ---- | ----------- | -| `alertManagementAlert` | [`AlertManagementAlert`](#alertmanagementalert) | Alert associated to this issue. | +| `alertManagementAlert` **{warning-solid}** | [`AlertManagementAlert`](#alertmanagementalert) | **Deprecated** in 15.6. Use `alert_management_alerts`. | | `assignees` | [`UserCoreConnection`](#usercoreconnection) | Assignees of the issue. (see [Connections](#connections)) | | `author` | [`UserCore!`](#usercore) | User that created the issue. | | `blocked` | [`Boolean!`](#boolean) | Indicates the issue is blocked. | @@ -13911,6 +13932,27 @@ Describes an issuable resource link for incident issues. #### Fields with arguments +##### `Issue.alertManagementAlerts` + +Alert Management alerts associated to this issue. + +Returns [`AlertManagementAlertConnection`](#alertmanagementalertconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `assigneeUsername` | [`String`](#string) | Username of a user assigned to the issue. | +| `domain` | [`AlertManagementDomainFilter!`](#alertmanagementdomainfilter) | Filter query for given domain. | +| `iid` | [`String`](#string) | IID of the alert. For example, "1". | +| `search` | [`String`](#string) | Search query for title, description, service, or monitoring_tool. | +| `sort` | [`AlertManagementAlertSort`](#alertmanagementalertsort) | Sort alerts by this criteria. | +| `statuses` | [`[AlertManagementStatus!]`](#alertmanagementstatus) | Alerts with the specified statues. For example, `[TRIGGERED]`. | + ##### `Issue.currentUserTodos` To-do items for the current user. diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb index 89d044bac8d..75468c74814 100644 --- a/qa/qa/page/project/import/github.rb +++ b/qa/qa/page/project/import/github.rb @@ -85,8 +85,15 @@ module QA end end end - alias_method :wait_for_success, :has_imported_project? + + # Select advanced github import option + # + # @param [Symbol] option_name + # @return [void] + def select_advanced_option(option_name) + check_element(:advanced_settings_checkbox, true, option_name: option_name) + end end end end diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb index 9ba9723f0cc..1e6b2ff620e 100644 --- a/qa/qa/resource/project_imported_from_github.rb +++ b/qa/qa/resource/project_imported_from_github.rb @@ -18,6 +18,11 @@ module QA Page::Project::Import::Github.perform do |import_page| import_page.add_personal_access_token(github_personal_access_token) + + import_page.select_advanced_option(:single_endpoint_issue_events_import) if issue_events_import + import_page.select_advanced_option(:single_endpoint_notes_import) if full_notes_import + import_page.select_advanced_option(:attachments_import) if attachments_import + import_page.import!(github_repository_path, group.full_path, name) import_page.wait_for_success(github_repository_path, wait: 240) end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index 6ac11fea7e1..4f0f54c1a15 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -25,6 +25,18 @@ module QA end end + let(:imported_issue) do + Resource::Issue.init do |resource| + resource.project = imported_project + resource.iid = imported_project.issues.first[:iid] + resource.api_client = api_client + end.reload! + end + + let(:imported_issue_events) do + imported_issue.label_events.map { |e| { name: "#{e[:action]}_label", label: e.dig(:label, :name) } } + end + before do group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) @@ -43,6 +55,11 @@ module QA it 'imports a GitHub repo', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347877' do Page::Project::Import::Github.perform do |import_page| import_page.add_personal_access_token(Runtime::Env.github_access_token) + + import_page.select_advanced_option(:single_endpoint_issue_events_import) + import_page.select_advanced_option(:single_endpoint_notes_import) + import_page.select_advanced_option(:attachments_import) + import_page.import!(github_repo, group.full_path, imported_project.name) aggregate_failures do @@ -60,6 +77,15 @@ module QA expect(project).to have_content('Project for github import test') end end + + # Validate :single_endpoint_issue_events_import option was triggered correctly and imported the events + expect(imported_issue_events).to match_array( + [ + { name: "add_label", label: "question" }, + { name: "add_label", label: "good first issue" }, + { name: "add_label", label: "help wanted" } + ] + ) end end end diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index 2a0ae79b2c4..dc444f90627 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -17,7 +17,7 @@ RSpec.describe GitlabSchema.types['Issue'] do fields = %i[id iid title description state reference author assignees updated_by participants labels milestone due_date confidential hidden discussion_locked upvotes downvotes merge_requests_count user_notes_count user_discussions_count web_path web_url relative_position emails_disabled subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status - design_collection alert_management_alert severity current_user_todos moved moved_to + design_collection alert_management_alert alert_management_alerts severity current_user_todos moved moved_to closed_as_duplicate_of create_note_email timelogs project_id customer_relations_contacts escalation_status] fields.each do |field_name| diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index a130438ce99..7a0d2596049 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -52,6 +52,7 @@ issues: - user_mentions - system_note_metadata - alert_management_alert +- alert_management_alerts - status_page_published_incident - namespace - note_authors diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 08c05b614f6..f8c731b0d10 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -25,6 +25,7 @@ RSpec.describe Issue do it { is_expected.to have_many(:design_versions) } it { is_expected.to have_one(:sentry_issue) } it { is_expected.to have_one(:alert_management_alert) } + it { is_expected.to have_many(:alert_management_alerts) } it { is_expected.to have_many(:resource_milestone_events) } it { is_expected.to have_many(:resource_state_events) } it { is_expected.to have_and_belong_to_many(:prometheus_alert_events) } diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index 3b8beb4f798..514fb6ff733 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -409,6 +409,11 @@ RSpec.describe 'getting an issue list for a project' do alertManagementAlert { title } + alertManagementAlerts { + nodes { + title + } + } } } QUERY @@ -435,6 +440,17 @@ RSpec.describe 'getting an issue list for a project' do expect(alert_titles).to contain_exactly(*expected_titles) end + + it 'returns the alerts data' do + post_graphql(query, current_user: current_user) + + alert_titles = issues_data.map { |issue| issue.dig('node', 'alertManagementAlerts', 'nodes') } + expected_titles = issues.map do |issue| + issue.alert_management_alerts.map { |alert| { 'title' => alert.title } } + end + + expect(alert_titles).to contain_exactly(*expected_titles) + end end context 'when fetching customer_relations_contacts' do