From aad3ac9e5e59d47e389ff387e9fc2ae3a008de33 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 26 Apr 2021 15:10:20 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/rails.gitlab-ci.yml | 22 +- .gitlab/ci/rules.gitlab-ci.yml | 6 +- .rubocop_manual_todo.yml | 14 - GITALY_SERVER_VERSION | 2 +- .../components/content_editor.vue | 8 +- .../content_editor/services/create_editor.js | 15 +- .../shared/wikis/components/wiki_form.vue | 137 +++- .../components/file_icon/file_icon_map.js | 3 + .../page_bundles/oncall_schedules.scss | 2 +- app/controllers/projects/wikis_controller.rb | 4 + .../resolvers/group_packages_resolver.rb | 22 +- .../resolvers/project_packages_resolver.rb | 20 +- app/graphql/types/merge_request_type.rb | 5 +- .../types/merge_requests/assignee_type.rb | 14 + .../interacts_with_merge_request.rb | 24 + .../types/merge_requests/reviewer_type.rb | 16 +- .../types/packages/package_group_sort_enum.rb | 15 + .../types/packages/package_sort_enum.rb | 19 + app/graphql/types/query_type.rb | 17 +- app/graphql/types/user_interface.rb | 112 +++ app/graphql/types/user_type.rb | 97 +-- app/models/packages/package.rb | 16 +- .../alert_management/alert_presenter.rb | 2 + app/services/merge_requests/merge_service.rb | 16 + .../merge_requests/post_merge_service.rb | 16 +- app/workers/all_queues.yml | 2 +- app/workers/merge_worker.rb | 12 +- .../285467-package-registry-graphql-api-2.yml | 5 + .../ajk-add-mr-interaction-for-assignees.yml | 5 + .../unreleased/feature-odt-ods-odp-icons.yml | 5 + .../graphql-expose-merge-request-at-root.yml | 5 + .../make-mergeservice-idempotent.yml | 5 + ...tage-remove-artifact-expiry-temp-index.yml | 5 + .../development/wiki_content_editor.yml | 7 + .../puma_client_tempfile_patch.rb | 18 +- .../counts_28d/20210216175542_ci_builds.yml | 9 +- .../20210216175544_ci_external_pipelines.yml | 9 +- .../20210216175546_ci_internal_pipelines.yml | 8 +- ...6175548_ci_pipeline_config_auto_devops.yml | 12 +- ...16175550_ci_pipeline_config_repository.yml | 8 +- .../20210216175552_ci_pipeline_schedules.yml | 8 +- .../20210216175554_ci_pipelines.yml | 8 +- .../counts_28d/20210216175556_ci_triggers.yml | 3 +- .../counts_all/20210216175510_ci_builds.yml | 4 +- .../20210216175512_ci_internal_pipelines.yml | 4 +- .../20210216175514_ci_external_pipelines.yml | 1 - ...6175516_ci_pipeline_config_auto_devops.yml | 10 +- ...16175518_ci_pipeline_config_repository.yml | 4 +- .../counts_all/20210216175520_ci_runners.yml | 4 +- .../counts_all/20210216175521_ci_triggers.yml | 4 +- .../20210216175523_ci_pipeline_schedules.yml | 4 +- .../counts_all/20210216175525_ci_builds.yml | 8 +- .../20210216175527_ci_external_pipelines.yml | 6 +- .../20210216175529_ci_internal_pipelines.yml | 6 +- ...6175531_ci_pipeline_config_auto_devops.yml | 12 +- ...16175533_ci_pipeline_config_repository.yml | 7 +- .../20210216175535_ci_pipeline_schedules.yml | 6 +- .../20210216175537_ci_pipelines.yml | 5 +- .../counts_all/20210216175539_ci_triggers.yml | 6 +- ...72449_remove_artifact_expiry_temp_index.rb | 18 + db/schema_migrations/20210215172449 | 1 + db/structure.sql | 2 - doc/administration/encrypted_configuration.md | 4 +- doc/api/graphql/reference/index.md | 752 ++++++++++++++---- .../ci_builds_cumulative_forecast.png | Bin 0 -> 36221 bytes .../ci_scale/ci_builds_daily_forecast.png | Bin 0 -> 29472 bytes doc/architecture/blueprints/ci_scale/index.md | 205 +++++ doc/ci/directed_acyclic_graph/index.md | 2 + doc/ci/jobs/index.md | 2 +- ...nes_graph_dependency_view_hover_v13_12.png | Bin 0 -> 42573 bytes ...nes_graph_dependency_view_links_v13_12.png | Bin 0 -> 37749 bytes ...pipelines_graph_dependency_view_v13_12.png | Bin 0 -> 36039 bytes .../img/pipelines_graph_stage_view_v13_12.png | Bin 0 -> 25969 bytes doc/ci/pipelines/index.md | 72 +- doc/development/usage_ping/dictionary.md | 66 +- doc/install/azure/index.md | 50 +- .../application_security/api_fuzzing/index.md | 13 - lib/gitlab/ci/ansi2html.rb | 6 +- .../Security/API-Fuzzing.latest.gitlab-ci.yml | 68 +- .../graphql/docs/templates/default.md.haml | 4 +- lib/gitlab/graphql/present/field_extension.rb | 3 +- locale/gitlab.pot | 35 +- spec/factories/alert_management/alerts.rb | 4 + .../packages/package_conan_metadata.json | 13 +- .../components/content_editor_spec.js | 22 +- .../services/create_editor_spec.js | 4 +- .../shared/wikis/components/wiki_form_spec.js | 210 ++++- .../resolvers/group_packages_resolver_spec.rb | 41 +- .../project_packages_resolver_spec.rb | 39 +- spec/graphql/types/query_type_spec.rb | 13 +- spec/graphql/types/user_type_spec.rb | 6 +- .../graphql/present/field_extension_spec.rb | 32 + .../api/graphql/group/packages_spec.rb | 44 +- spec/requests/api/graphql/issue/issue_spec.rb | 6 +- .../merge_request/merge_request_spec.rb | 111 +++ .../api/graphql/packages/composer_spec.rb | 64 ++ .../api/graphql/packages/conan_spec.rb | 90 +++ .../api/graphql/packages/package_spec.rb | 78 +- .../api/graphql/project/merge_request_spec.rb | 46 +- .../api/graphql/project/packages_spec.rb | 45 +- .../merge_requests/merge_service_spec.rb | 34 + .../merge_requests/post_merge_service_spec.rb | 1 - .../user_creates_wiki_page_shared_examples.rb | 2 +- .../user_updates_wiki_page_shared_examples.rb | 2 +- spec/workers/merge_worker_spec.rb | 18 + 105 files changed, 2371 insertions(+), 706 deletions(-) create mode 100644 app/graphql/types/merge_requests/assignee_type.rb create mode 100644 app/graphql/types/merge_requests/interacts_with_merge_request.rb create mode 100644 app/graphql/types/packages/package_group_sort_enum.rb create mode 100644 app/graphql/types/packages/package_sort_enum.rb create mode 100644 app/graphql/types/user_interface.rb create mode 100644 changelogs/unreleased/285467-package-registry-graphql-api-2.yml create mode 100644 changelogs/unreleased/ajk-add-mr-interaction-for-assignees.yml create mode 100644 changelogs/unreleased/feature-odt-ods-odp-icons.yml create mode 100644 changelogs/unreleased/graphql-expose-merge-request-at-root.yml create mode 100644 changelogs/unreleased/make-mergeservice-idempotent.yml create mode 100644 changelogs/unreleased/mc-backstage-remove-artifact-expiry-temp-index.yml create mode 100644 config/feature_flags/development/wiki_content_editor.yml create mode 100644 db/migrate/20210215172449_remove_artifact_expiry_temp_index.rb create mode 100644 db/schema_migrations/20210215172449 create mode 100644 doc/architecture/blueprints/ci_scale/ci_builds_cumulative_forecast.png create mode 100644 doc/architecture/blueprints/ci_scale/ci_builds_daily_forecast.png create mode 100644 doc/architecture/blueprints/ci_scale/index.md create mode 100644 doc/ci/pipelines/img/pipelines_graph_dependency_view_hover_v13_12.png create mode 100644 doc/ci/pipelines/img/pipelines_graph_dependency_view_links_v13_12.png create mode 100644 doc/ci/pipelines/img/pipelines_graph_dependency_view_v13_12.png create mode 100644 doc/ci/pipelines/img/pipelines_graph_stage_view_v13_12.png create mode 100644 spec/requests/api/graphql/merge_request/merge_request_spec.rb create mode 100644 spec/requests/api/graphql/packages/composer_spec.rb create mode 100644 spec/requests/api/graphql/packages/conan_spec.rb diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 6e86f92ab26..074a006dc1b 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -680,25 +680,25 @@ rspec migration pg12: extends: - .rspec-base-pg12 - .rspec-base-migration - - .rails:rules:default-branch-schedule-nightly--code-backstage + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage - .rspec-migration-parallel rspec unit pg12: extends: - .rspec-base-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage - .rspec-unit-parallel rspec integration pg12: extends: - .rspec-base-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage - .rspec-integration-parallel rspec system pg12: extends: - .rspec-base-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage - .rspec-system-parallel # EE/FOSS: default branch nightly scheduled jobs # ########################################## @@ -709,42 +709,42 @@ rspec-ee migration pg12: extends: - .rspec-ee-base-pg12 - .rspec-base-migration - - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage-ee-only - .rspec-ee-migration-parallel rspec-ee unit pg12: extends: - .rspec-ee-base-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage-ee-only - .rspec-ee-unit-parallel rspec-ee integration pg12: extends: - .rspec-ee-base-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage-ee-only - .rspec-ee-integration-parallel rspec-ee system pg12: extends: - .rspec-ee-base-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage-ee-only - .rspec-ee-system-parallel rspec-ee unit pg12 geo: extends: - .rspec-ee-base-geo-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage-ee-only - .rspec-ee-unit-geo-parallel rspec-ee integration pg12 geo: extends: - .rspec-ee-base-geo-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage-ee-only rspec-ee system pg12 geo: extends: - .rspec-ee-base-geo-pg12 - - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only + - .rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage-ee-only # EE: default branch nightly scheduled jobs # ##################################### diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index f1c918d8c00..a967d77441b 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -915,16 +915,18 @@ allow_failure: true - <<: *if-merge-request-title-run-all-rspec -.rails:rules:default-branch-schedule-nightly--code-backstage: +.rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage: rules: + - <<: *if-default-branch-schedule-2-hourly - <<: *if-default-branch-schedule-nightly - <<: *if-merge-request changes: [".gitlab/ci/rails.gitlab-ci.yml"] -.rails:rules:default-branch-schedule-nightly--code-backstage-ee-only: +.rails:rules:default-branch-schedule-2-hourly-nightly--code-backstage-ee-only: rules: - <<: *if-not-ee when: never + - <<: *if-default-branch-schedule-2-hourly - <<: *if-default-branch-schedule-nightly - <<: *if-merge-request changes: [".gitlab/ci/rails.gitlab-ci.yml"] diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index ec15dc7fde6..9a1ed73616c 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -126,12 +126,6 @@ Rails/SaveBang: - 'ee/spec/services/status_page/trigger_publish_service_spec.rb' - 'ee/spec/services/todo_service_spec.rb' - 'ee/spec/services/vulnerability_feedback/create_service_spec.rb' - - 'ee/spec/support/protected_tags/access_control_shared_examples.rb' - - 'ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb' - - 'ee/spec/support/shared_examples/finders/geo/framework_registry_finder_shared_examples.rb' - - 'ee/spec/support/shared_examples/graphql/geo/geo_registries_resolver_shared_examples.rb' - - 'ee/spec/support/shared_examples/lib/analytics/common_merge_request_metrics_refresh_shared_examples.rb' - - 'ee/spec/support/shared_examples/policies/protected_environments_shared_examples.rb' - 'qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb' - 'qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb' @@ -392,15 +386,7 @@ Rails/TimeZone: # WIP: https://gitlab.com/gitlab-org/gitlab/-/issues/325836 RSpec/EmptyLineAfterFinalLetItBe: Exclude: - - ee/spec/controllers/groups/analytics/cycle_analytics/stages_controller_spec.rb - - ee/spec/controllers/groups/analytics/cycle_analytics/summary_controller_spec.rb - - ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb - - ee/spec/controllers/groups/analytics/tasks_by_type_controller_spec.rb - - ee/spec/controllers/groups/autocomplete_sources_controller_spec.rb - - ee/spec/controllers/groups/insights_controller_spec.rb - - ee/spec/controllers/groups/todos_controller_spec.rb - ee/spec/controllers/subscriptions_controller_spec.rb - - ee/spec/features/boards/group_boards/multiple_boards_spec.rb - ee/spec/features/ci_shared_runner_warnings_spec.rb - ee/spec/features/groups/groups_security_credentials_spec.rb - ee/spec/features/groups/hooks/user_edits_hooks_spec.rb diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 58b1d361417..79d8b278a5f 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -7b3856441543419869962d34439efbda2fc00024 +585cfd46d6be12237b640d19cbd730f3065e0ecc diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index 13ddd327c7c..98acf2f26b4 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -17,10 +17,8 @@ export default { }; diff --git a/app/assets/javascripts/content_editor/services/create_editor.js b/app/assets/javascripts/content_editor/services/create_editor.js index 06b8b2b4c45..58e10e1dd5a 100644 --- a/app/assets/javascripts/content_editor/services/create_editor.js +++ b/app/assets/javascripts/content_editor/services/create_editor.js @@ -18,7 +18,12 @@ import { PROVIDE_SERIALIZER_OR_RENDERER_ERROR } from '../constants'; import CodeBlockHighlight from '../extensions/code_block_highlight'; import createMarkdownSerializer from './markdown_serializer'; -const createEditor = async ({ content, renderMarkdown, serializer: customSerializer } = {}) => { +const createEditor = async ({ + content, + renderMarkdown, + serializer: customSerializer, + ...options +} = {}) => { if (!customSerializer && !isFunction(renderMarkdown)) { throw new Error(PROVIDE_SERIALIZER_OR_RENDERER_ERROR); } @@ -41,14 +46,10 @@ const createEditor = async ({ content, renderMarkdown, serializer: customSeriali ], editorProps: { attributes: { - /* - * Adds some padding to the contenteditable element where the user types. - * Otherwise, the text cursor is not visible when its position is at the - * beginning of a line. - */ - class: 'gl-py-4 gl-px-5', + class: 'gl-outline-0!', }, }, + ...options, }); const serializer = customSerializer || createMarkdownSerializer({ render: renderMarkdown }); diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue index 6afc33ec8a5..7eff429b679 100644 --- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue +++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue @@ -1,9 +1,11 @@ @@ -99,6 +164,30 @@ export default { class="wiki-form common-note-form gl-mt-3 js-quick-submit" @submit="handleFormSubmit" > + +

+ {{ + s__( + "WikiPage|You are editing this page with Content Editor. This editor is in beta and may not display the page's contents properly.", + ) + }} +

+

+ {{ + s__( + "WikiPage|Switching to the old editor will discard any changes you've made in the new editor.", + ) + }} +

+
+ {{ __('More Information.') }} {{ s__('WikiPage|More Information.') }} @@ -147,12 +236,26 @@ export default { s__('WikiPage|Format') }} -
- + {{ s__('WikiPage|Use new editor') }}
@@ -163,6 +266,7 @@ export default {
+ +
+ + + +
+
-
+
{{ submitButtonText }} - {{ - __('Cancel') - }} + {{ s__('WikiPage|Cancel') }}
diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js index e622b505570..e1e71639115 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js +++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js @@ -93,6 +93,7 @@ const fileExtensionIcons = { pdf: 'pdf', xlsx: 'table', xls: 'table', + ods: 'table', csv: 'table', tsv: 'table', vscodeignore: 'vscode', @@ -154,6 +155,7 @@ const fileExtensionIcons = { gradle: 'gradle', doc: 'word', docx: 'word', + odt: 'word', rtf: 'word', cer: 'certificate', cert: 'certificate', @@ -204,6 +206,7 @@ const fileExtensionIcons = { pps: 'powerpoint', ppam: 'powerpoint', ppa: 'powerpoint', + odp: 'powerpoint', webm: 'movie', mkv: 'movie', flv: 'movie', diff --git a/app/assets/stylesheets/page_bundles/oncall_schedules.scss b/app/assets/stylesheets/page_bundles/oncall_schedules.scss index 5eaf91c3017..ddc638197ca 100644 --- a/app/assets/stylesheets/page_bundles/oncall_schedules.scss +++ b/app/assets/stylesheets/page_bundles/oncall_schedules.scss @@ -95,7 +95,7 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi @include gl-font-weight-normal; &.label-dark { - @include gl-text-gray-900; + color: var(--gray-900, $gray-900); } &.label-bold { diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index d1486f765e4..a1493a25a1a 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -6,4 +6,8 @@ class Projects::WikisController < Projects::ApplicationController alias_method :container, :project feature_category :wiki + + before_action do + push_frontend_feature_flag(:wiki_content_editor, project, default_enabled: :yaml) + end end diff --git a/app/graphql/resolvers/group_packages_resolver.rb b/app/graphql/resolvers/group_packages_resolver.rb index d441cd80249..3849b870b40 100644 --- a/app/graphql/resolvers/group_packages_resolver.rb +++ b/app/graphql/resolvers/group_packages_resolver.rb @@ -4,6 +4,24 @@ module Resolvers class GroupPackagesResolver < BaseResolver type Types::Packages::PackageType.connection_type, null: true + argument :sort, Types::Packages::PackageGroupSortEnum, + description: 'Sort packages by this criteria.', + required: false, + default_value: :created_desc + + SORT_TO_PARAMS_MAP = { + created_desc: { order_by: 'created', sort: 'desc' }, + created_asc: { order_by: 'created', sort: 'asc' }, + name_desc: { order_by: 'name', sort: 'desc' }, + name_asc: { order_by: 'name', sort: 'asc' }, + version_desc: { order_by: 'version', sort: 'desc' }, + version_asc: { order_by: 'version', sort: 'asc' }, + type_desc: { order_by: 'type', sort: 'desc' }, + type_asc: { order_by: 'type', sort: 'asc' }, + project_path_desc: { order_by: 'project_path', sort: 'desc' }, + project_path_asc: { order_by: 'project_path', sort: 'asc' } + }.freeze + def ready?(**args) context[self.class] ||= { executions: 0 } context[self.class][:executions] += 1 @@ -12,10 +30,10 @@ module Resolvers super end - def resolve(**args) + def resolve(sort:) return unless packages_available? - ::Packages::GroupPackagesFinder.new(current_user, object).execute + ::Packages::GroupPackagesFinder.new(current_user, object, SORT_TO_PARAMS_MAP[sort]).execute end private diff --git a/app/graphql/resolvers/project_packages_resolver.rb b/app/graphql/resolvers/project_packages_resolver.rb index 288e14b41d0..2f90a2fda1f 100644 --- a/app/graphql/resolvers/project_packages_resolver.rb +++ b/app/graphql/resolvers/project_packages_resolver.rb @@ -4,10 +4,26 @@ module Resolvers class ProjectPackagesResolver < BaseResolver type Types::Packages::PackageType.connection_type, null: true - def resolve(**args) + argument :sort, Types::Packages::PackageSortEnum, + description: 'Sort packages by this criteria.', + required: false, + default_value: :created_desc + + SORT_TO_PARAMS_MAP = { + created_desc: { order_by: 'created', sort: 'desc' }, + created_asc: { order_by: 'created', sort: 'asc' }, + name_desc: { order_by: 'name', sort: 'desc' }, + name_asc: { order_by: 'name', sort: 'asc' }, + version_desc: { order_by: 'version', sort: 'desc' }, + version_asc: { order_by: 'version', sort: 'asc' }, + type_desc: { order_by: 'type', sort: 'desc' }, + type_asc: { order_by: 'type', sort: 'asc' } + }.freeze + + def resolve(sort:) return unless packages_available? - ::Packages::PackagesFinder.new(object).execute + ::Packages::PackagesFinder.new(object, SORT_TO_PARAMS_MAP.fetch(sort)).execute end private diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index c8ccf9d8aff..f808986dcaa 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -130,7 +130,10 @@ module Types field :milestone, Types::MilestoneType, null: true, description: 'The milestone of the merge request.' - field :assignees, Types::UserType.connection_type, null: true, complexity: 5, + field :assignees, + type: Types::MergeRequests::AssigneeType.connection_type, + null: true, + complexity: 5, description: 'Assignees of the merge request.' field :reviewers, type: Types::MergeRequests::ReviewerType.connection_type, diff --git a/app/graphql/types/merge_requests/assignee_type.rb b/app/graphql/types/merge_requests/assignee_type.rb new file mode 100644 index 00000000000..8448477370e --- /dev/null +++ b/app/graphql/types/merge_requests/assignee_type.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + module MergeRequests + class AssigneeType < ::Types::UserType + include FindClosest + include ::Types::MergeRequests::InteractsWithMergeRequest + + graphql_name 'MergeRequestAssignee' + description 'A user assigned to a merge request.' + authorize :read_user + end + end +end diff --git a/app/graphql/types/merge_requests/interacts_with_merge_request.rb b/app/graphql/types/merge_requests/interacts_with_merge_request.rb new file mode 100644 index 00000000000..d685ac4d3c9 --- /dev/null +++ b/app/graphql/types/merge_requests/interacts_with_merge_request.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module MergeRequests + module InteractsWithMergeRequest + extend ActiveSupport::Concern + + included do + field :merge_request_interaction, + type: ::Types::UserMergeRequestInteractionType, + null: true, + extras: [:parent], + description: "Details of this user's interactions with the merge request." + end + + def merge_request_interaction(parent:) + merge_request = closest_parent(::Types::MergeRequestType, parent) + return unless merge_request + + Users::MergeRequestInteraction.new(user: object, merge_request: merge_request) + end + end + end +end diff --git a/app/graphql/types/merge_requests/reviewer_type.rb b/app/graphql/types/merge_requests/reviewer_type.rb index 09ced39844a..1ced821c839 100644 --- a/app/graphql/types/merge_requests/reviewer_type.rb +++ b/app/graphql/types/merge_requests/reviewer_type.rb @@ -4,23 +4,11 @@ module Types module MergeRequests class ReviewerType < ::Types::UserType include FindClosest + include ::Types::MergeRequests::InteractsWithMergeRequest graphql_name 'MergeRequestReviewer' - description 'A user from whom a merge request review has been requested.' + description 'A user assigned to a merge request as a reviewer.' authorize :read_user - - field :merge_request_interaction, - type: ::Types::UserMergeRequestInteractionType, - null: true, - extras: [:parent], - description: "Details of this user's interactions with the merge request." - - def merge_request_interaction(parent:) - merge_request = closest_parent(::Types::MergeRequestType, parent) - return unless merge_request - - Users::MergeRequestInteraction.new(user: object, merge_request: merge_request) - end end end end diff --git a/app/graphql/types/packages/package_group_sort_enum.rb b/app/graphql/types/packages/package_group_sort_enum.rb new file mode 100644 index 00000000000..70fb27ec0db --- /dev/null +++ b/app/graphql/types/packages/package_group_sort_enum.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module Packages + class PackageGroupSortEnum < PackageSortEnum + graphql_name 'PackageGroupSort' + description 'Values for sorting group packages' + + # The following enums are not available till we enable the new Arel node: + # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58657#note_552632305 + # value 'PROJECT_PATH_DESC', 'Project by descending order.', value: :project_path_desc + # value 'PROJECT_PATH_ASC', 'Project by ascending order.', value: :project_path_asc + end + end +end diff --git a/app/graphql/types/packages/package_sort_enum.rb b/app/graphql/types/packages/package_sort_enum.rb new file mode 100644 index 00000000000..ee14cf7a9e6 --- /dev/null +++ b/app/graphql/types/packages/package_sort_enum.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module Packages + class PackageSortEnum < BaseEnum + graphql_name 'PackageSort' + description 'Values for sorting package' + + value 'CREATED_DESC', 'Ordered by created_at in descending order.', value: :created_desc + value 'CREATED_ASC', 'Ordered by created_at in ascending order.', value: :created_asc + value 'NAME_DESC', 'Ordered by name in descending order.', value: :name_desc + value 'NAME_ASC', 'Ordered by name in ascending order.', value: :name_asc + value 'VERSION_DESC', 'Ordered by version in descending order.', value: :version_desc + value 'VERSION_ASC', 'Ordered by version in ascending order.', value: :version_asc + value 'TYPE_DESC', 'Ordered by type in descending order.', value: :type_desc + value 'TYPE_ASC', 'Ordered by type in ascending order.', value: :type_asc + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 8af0db644dd..545d82b8f36 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -79,8 +79,14 @@ module Types field :issue, Types::IssueType, null: true, - description: 'Find an Issue.' do - argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.' + description: 'Find an issue.' do + argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the issue.' + end + + field :merge_request, Types::MergeRequestType, + null: true, + description: 'Find a merge request.' do + argument :id, ::Types::GlobalIDType[::MergeRequest], required: true, description: 'The global ID of the merge request.' end field :instance_statistics_measurements, @@ -119,6 +125,13 @@ module Types GitlabSchema.find_by_gid(id) end + def merge_request(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::MergeRequest].coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + def milestone(id:) # TODO: remove this line when the compatibility layer is removed # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb new file mode 100644 index 00000000000..bc6e11d3ab9 --- /dev/null +++ b/app/graphql/types/user_interface.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +module Types + module UserInterface + include Types::BaseInterface + + graphql_name 'User' + description 'Representation of a GitLab user.' + + field :user_permissions, + type: Types::PermissionTypes::User, + description: 'Permissions for the current user on the resource.', + null: false, + method: :itself + + field :id, + type: GraphQL::ID_TYPE, + null: false, + description: 'ID of the user.' + field :bot, + type: GraphQL::BOOLEAN_TYPE, + null: false, + description: 'Indicates if the user is a bot.', + method: :bot? + field :username, + type: GraphQL::STRING_TYPE, + null: false, + description: 'Username of the user. Unique within this instance of GitLab.' + field :name, + type: GraphQL::STRING_TYPE, + null: false, + description: 'Human-readable name of the user.' + field :state, + type: Types::UserStateEnum, + null: false, + description: 'State of the user.' + field :email, + type: GraphQL::STRING_TYPE, + null: true, + description: 'User email.', method: :public_email, + deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' } + field :public_email, + type: GraphQL::STRING_TYPE, + null: true, + description: "User's public email." + field :avatar_url, + type: GraphQL::STRING_TYPE, + null: true, + description: "URL of the user's avatar." + field :web_url, + type: GraphQL::STRING_TYPE, + null: false, + description: 'Web URL of the user.' + field :web_path, + type: GraphQL::STRING_TYPE, + null: false, + description: 'Web path of the user.' + field :todos, + resolver: Resolvers::TodoResolver, + description: 'To-do items of the user.' + field :group_memberships, + type: Types::GroupMemberType.connection_type, + null: true, + description: 'Group memberships of the user.' + field :group_count, + resolver: Resolvers::Users::GroupCountResolver, + description: 'Group count for the user.', + feature_flag: :user_group_counts + field :status, + type: Types::UserStatusType, + null: true, + description: 'User status.' + field :location, + type: ::GraphQL::STRING_TYPE, + null: true, + description: 'The location of the user.' + field :project_memberships, + type: Types::ProjectMemberType.connection_type, + null: true, + description: 'Project memberships of the user.' + field :starred_projects, + description: 'Projects starred by the user.', + resolver: Resolvers::UserStarredProjectsResolver + + # Merge request field: MRs can be authored, assigned, or assigned-for-review: + field :authored_merge_requests, + resolver: Resolvers::AuthoredMergeRequestsResolver, + description: 'Merge requests authored by the user.' + field :assigned_merge_requests, + resolver: Resolvers::AssignedMergeRequestsResolver, + description: 'Merge requests assigned to the user.' + field :review_requested_merge_requests, + resolver: Resolvers::ReviewRequestedMergeRequestsResolver, + description: 'Merge requests assigned to the user for review.' + + field :snippets, + description: 'Snippets authored by the user.', + resolver: Resolvers::Users::SnippetsResolver + field :callouts, + Types::UserCalloutType.connection_type, + null: true, + description: 'User callouts that belong to the user.' + + definition_methods do + def resolve_type(object, context) + # in the absense of other information, we cannot tell - just default to + # the core user type. + ::Types::UserType + end + end + end +end diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb index 3d7db80ae11..a6f5b7e7456 100644 --- a/app/graphql/types/user_type.rb +++ b/app/graphql/types/user_type.rb @@ -1,102 +1,13 @@ # frozen_string_literal: true module Types - class UserType < BaseObject - graphql_name 'User' - description 'Representation of a GitLab user.' + class UserType < ::Types::BaseObject + graphql_name 'UserCore' + description 'Core represention of a GitLab user.' + implements ::Types::UserInterface authorize :read_user present_using UserPresenter - - expose_permissions Types::PermissionTypes::User - - field :id, - type: GraphQL::ID_TYPE, - null: false, - description: 'ID of the user.' - field :bot, - type: GraphQL::BOOLEAN_TYPE, - null: false, - description: 'Indicates if the user is a bot.', - method: :bot? - field :username, - type: GraphQL::STRING_TYPE, - null: false, - description: 'Username of the user. Unique within this instance of GitLab.' - field :name, - type: GraphQL::STRING_TYPE, - null: false, - description: 'Human-readable name of the user.' - field :state, - type: Types::UserStateEnum, - null: false, - description: 'State of the user.' - field :email, - type: GraphQL::STRING_TYPE, - null: true, - description: 'User email.', method: :public_email, - deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' } - field :public_email, - type: GraphQL::STRING_TYPE, - null: true, - description: "User's public email." - field :avatar_url, - type: GraphQL::STRING_TYPE, - null: true, - description: "URL of the user's avatar." - field :web_url, - type: GraphQL::STRING_TYPE, - null: false, - description: 'Web URL of the user.' - field :web_path, - type: GraphQL::STRING_TYPE, - null: false, - description: 'Web path of the user.' - field :todos, - resolver: Resolvers::TodoResolver, - description: 'To-do items of the user.' - field :group_memberships, - type: Types::GroupMemberType.connection_type, - null: true, - description: 'Group memberships of the user.' - field :group_count, - resolver: Resolvers::Users::GroupCountResolver, - description: 'Group count for the user.', - feature_flag: :user_group_counts - field :status, - type: Types::UserStatusType, - null: true, - description: 'User status.' - field :location, - type: ::GraphQL::STRING_TYPE, - null: true, - description: 'The location of the user.' - field :project_memberships, - type: Types::ProjectMemberType.connection_type, - null: true, - description: 'Project memberships of the user.' - field :starred_projects, - description: 'Projects starred by the user.', - resolver: Resolvers::UserStarredProjectsResolver - - # Merge request field: MRs can be authored, assigned, or assigned-for-review: - field :authored_merge_requests, - resolver: Resolvers::AuthoredMergeRequestsResolver, - description: 'Merge requests authored by the user.' - field :assigned_merge_requests, - resolver: Resolvers::AssignedMergeRequestsResolver, - description: 'Merge requests assigned to the user.' - field :review_requested_merge_requests, - resolver: Resolvers::ReviewRequestedMergeRequestsResolver, - description: 'Merge requests assigned to the user for review.' - - field :snippets, - description: 'Snippets authored by the user.', - resolver: Resolvers::Users::SnippetsResolver - field :callouts, - Types::UserCalloutType.connection_type, - null: true, - description: 'User callouts that belong to the user.' end end diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index 98c5db99b12..8acd4a121ca 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -122,14 +122,14 @@ class Packages::Package < ApplicationRecord scope :select_distinct_name, -> { select(:name).distinct } # Sorting - scope :order_created, -> { reorder('created_at ASC') } - scope :order_created_desc, -> { reorder('created_at DESC') } - scope :order_name, -> { reorder('name ASC') } - scope :order_name_desc, -> { reorder('name DESC') } - scope :order_version, -> { reorder('version ASC') } - scope :order_version_desc, -> { reorder('version DESC') } - scope :order_type, -> { reorder('package_type ASC') } - scope :order_type_desc, -> { reorder('package_type DESC') } + scope :order_created, -> { reorder(created_at: :asc) } + scope :order_created_desc, -> { reorder(created_at: :desc) } + scope :order_name, -> { reorder(name: :asc) } + scope :order_name_desc, -> { reorder(name: :desc) } + scope :order_version, -> { reorder(version: :asc) } + scope :order_version_desc, -> { reorder(version: :desc) } + scope :order_type, -> { reorder(package_type: :asc) } + scope :order_type_desc, -> { reorder(package_type: :desc) } scope :order_project_name, -> { joins(:project).reorder('projects.name ASC') } scope :order_project_name_desc, -> { joins(:project).reorder('projects.name DESC') } scope :order_project_path, -> { joins(:project).reorder('projects.path ASC, id ASC') } diff --git a/app/presenters/alert_management/alert_presenter.rb b/app/presenters/alert_management/alert_presenter.rb index 1cebf5c561a..2cfba937f91 100644 --- a/app/presenters/alert_management/alert_presenter.rb +++ b/app/presenters/alert_management/alert_presenter.rb @@ -112,3 +112,5 @@ module AlertManagement end end end + +AlertManagement::AlertPresenter.prepend_if_ee('EE::AlertManagement::AlertPresenter') diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index b580ab9e66b..111975b76bc 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -8,7 +8,10 @@ module MergeRequests # Executed when you do merge via GitLab UI # class MergeService < MergeRequests::MergeBaseService + include Gitlab::Utils::StrongMemoize + GENERIC_ERROR_MESSAGE = 'An error occurred while merging' + LEASE_TIMEOUT = 15.minutes.to_i delegate :merge_jid, :state, to: :@merge_request @@ -18,6 +21,9 @@ module MergeRequests return end + return if merge_request.merged? + return unless exclusive_lease(merge_request.id).try_obtain + @merge_request = merge_request @options = options @@ -34,6 +40,8 @@ module MergeRequests log_info("Merge process finished on JID #{merge_jid} with state #{state}") rescue MergeError => e handle_merge_error(log_message: e.message, save_message_on_model: true) + ensure + exclusive_lease(merge_request.id).cancel end private @@ -146,5 +154,13 @@ module MergeRequests # loaded from the database they're strings params.with_indifferent_access[:sha] == merge_request.diff_head_sha end + + def exclusive_lease(merge_request_id) + strong_memoize(:"exclusive_lease_#{merge_request_id}") do + lease_key = ['merge_requests_merge_service', merge_request_id].join(':') + + Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) + end + end end end diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index 4d7d632ee14..505625d2e00 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -12,20 +12,28 @@ module MergeRequests MAX_RETARGET_MERGE_REQUESTS = 4 def execute(merge_request) + return if merge_request.merged? + + # Mark the merge request as merged, everything that happens afterwards is + # executed once merge_request.mark_as_merged - close_issues(merge_request) - todo_service.merge_merge_request(merge_request, current_user) + create_event(merge_request) - create_note(merge_request) + todo_service.merge_merge_request(merge_request, current_user) + merge_request_activity_counter.track_merge_mr_action(user: current_user) + + create_note(merge_request) + close_issues(merge_request) notification_service.merge_mr(merge_request, current_user) - execute_hooks(merge_request, 'merge') invalidate_cache_counts(merge_request, users: merge_request.assignees | merge_request.reviewers) merge_request.update_project_counter_caches delete_non_latest_diffs(merge_request) cancel_review_app_jobs!(merge_request) cleanup_environments(merge_request) cleanup_refs(merge_request) + + execute_hooks(merge_request, 'merge') end private diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 0417d214231..b7c39e829fd 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1890,7 +1890,7 @@ :urgency: :high :resource_boundary: :unknown :weight: 5 - :idempotent: + :idempotent: true :tags: [] - :name: merge_request_cleanup_refs :feature_category: :code_review diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb index 270bd831f96..f77b1e27678 100644 --- a/app/workers/merge_worker.rb +++ b/app/workers/merge_worker.rb @@ -7,11 +7,19 @@ class MergeWorker # rubocop:disable Scalability/IdempotentWorker urgency :high weight 5 loggable_arguments 2 + idempotent! + + deduplicate :until_executed, including_scheduled: true def perform(merge_request_id, current_user_id, params) params = params.with_indifferent_access - current_user = User.find(current_user_id) - merge_request = MergeRequest.find(merge_request_id) + + begin + current_user = User.find(current_user_id) + merge_request = MergeRequest.find(merge_request_id) + rescue ActiveRecord::RecordNotFound + return + end MergeRequests::MergeService.new(merge_request.target_project, current_user, params) .execute(merge_request) diff --git a/changelogs/unreleased/285467-package-registry-graphql-api-2.yml b/changelogs/unreleased/285467-package-registry-graphql-api-2.yml new file mode 100644 index 00000000000..10353975713 --- /dev/null +++ b/changelogs/unreleased/285467-package-registry-graphql-api-2.yml @@ -0,0 +1,5 @@ +--- +title: Add sorting for group and project packages type +merge_request: 58657 +author: +type: added diff --git a/changelogs/unreleased/ajk-add-mr-interaction-for-assignees.yml b/changelogs/unreleased/ajk-add-mr-interaction-for-assignees.yml new file mode 100644 index 00000000000..a2199bfaf0d --- /dev/null +++ b/changelogs/unreleased/ajk-add-mr-interaction-for-assignees.yml @@ -0,0 +1,5 @@ +--- +title: Add merge request interaction details to MergeRequest.assignees +merge_request: 59770 +author: +type: changed diff --git a/changelogs/unreleased/feature-odt-ods-odp-icons.yml b/changelogs/unreleased/feature-odt-ods-odp-icons.yml new file mode 100644 index 00000000000..103facb229b --- /dev/null +++ b/changelogs/unreleased/feature-odt-ods-odp-icons.yml @@ -0,0 +1,5 @@ +--- +title: LibreOffice/OpenOffice file extensions in icon map +merge_request: 59159 +author: Holzfeind, Daniel Georg +type: changed diff --git a/changelogs/unreleased/graphql-expose-merge-request-at-root.yml b/changelogs/unreleased/graphql-expose-merge-request-at-root.yml new file mode 100644 index 00000000000..8bc84565c01 --- /dev/null +++ b/changelogs/unreleased/graphql-expose-merge-request-at-root.yml @@ -0,0 +1,5 @@ +--- +title: Allow merge request search via GraphQL +merge_request: 60190 +author: Lee Tickett @leetickett +type: added diff --git a/changelogs/unreleased/make-mergeservice-idempotent.yml b/changelogs/unreleased/make-mergeservice-idempotent.yml new file mode 100644 index 00000000000..1be81a1e267 --- /dev/null +++ b/changelogs/unreleased/make-mergeservice-idempotent.yml @@ -0,0 +1,5 @@ +--- +title: Make MergeService idempotent +merge_request: 55368 +author: +type: performance diff --git a/changelogs/unreleased/mc-backstage-remove-artifact-expiry-temp-index.yml b/changelogs/unreleased/mc-backstage-remove-artifact-expiry-temp-index.yml new file mode 100644 index 00000000000..7b8d93a558c --- /dev/null +++ b/changelogs/unreleased/mc-backstage-remove-artifact-expiry-temp-index.yml @@ -0,0 +1,5 @@ +--- +title: Remove artifact expiry backfill temp index. +merge_request: 54252 +author: +type: changed diff --git a/config/feature_flags/development/wiki_content_editor.yml b/config/feature_flags/development/wiki_content_editor.yml new file mode 100644 index 00000000000..f4d86f01465 --- /dev/null +++ b/config/feature_flags/development/wiki_content_editor.yml @@ -0,0 +1,7 @@ +--- +name: wiki_content_editor +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57370 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/255919 +group: group::editor +type: development +default_enabled: false diff --git a/config/initializers/puma_client_tempfile_patch.rb b/config/initializers/puma_client_tempfile_patch.rb index 33e7085944c..972eeaf0c83 100644 --- a/config/initializers/puma_client_tempfile_patch.rb +++ b/config/initializers/puma_client_tempfile_patch.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true if Gitlab::Runtime.puma? + # This patch represents https://github.com/puma/puma/pull/2613. If + # this PR is accepted in the next Puma release, we can remove this + # entire file. + # + # The patch itself is quite large because the tempfile creation in + # Puma is inside these fairly long methods. The actual changes are + # just two lines, commented with 'GitLab' to make them easier to find. raise "Remove this monkey patch: #{__FILE__}" unless Puma::Const::VERSION == '5.1.1' - # This is copied from https://github.com/puma/puma/blob/v5.1.1/lib/puma/client.rb, - # with two additions: both times we create a temporary file, we immediately - # call `#unlink`. This means that if the process gets terminated without being - # able to clean up itself, the temporary file will not linger on the file - # system. We will try to get this patch accepted upstream if it works for us - # (we just need to check if the temporary file responds to `#unlink` as that - # won't work on Windows, for instance). module Puma class Client private @@ -65,7 +65,7 @@ if Gitlab::Runtime.puma? if remain > MAX_BODY @body = Tempfile.new(Const::PUMA_TMP_BASE) @body.binmode - @body.unlink # This is the changed part + @body.unlink # GitLab: this is the changed part @tempfile = @body else # The body[0,0] trick is to get an empty string in the same @@ -87,7 +87,7 @@ if Gitlab::Runtime.puma? @body = Tempfile.new(Const::PUMA_TMP_BASE) @body.binmode - @body.unlink # This is the changed part + @body.unlink # GitLab: this is the changed part @tempfile = @body @chunked_content_length = 0 diff --git a/config/metrics/counts_28d/20210216175542_ci_builds.yml b/config/metrics/counts_28d/20210216175542_ci_builds.yml index 016d6c75cce..0f3d384937c 100644 --- a/config/metrics/counts_28d/20210216175542_ci_builds.yml +++ b/config/metrics/counts_28d/20210216175542_ci_builds.yml @@ -1,6 +1,6 @@ --- key_path: usage_activity_by_stage_monthly.verify.ci_builds -description: Unique builds in project +description: Unique monthly builds in project product_section: ops product_stage: verify product_group: group::continuous integration @@ -8,9 +8,12 @@ product_category: continuous_integration value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate + diff --git a/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml b/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml index f67fb96b9ee..bc0a2051097 100644 --- a/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml +++ b/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml @@ -1,6 +1,6 @@ --- key_path: usage_activity_by_stage_monthly.verify.ci_external_pipelines -description: Total pipelines in external repositories +description: Total pipelines in external repositories in a month product_section: ops product_stage: verify product_group: group::continuous integration @@ -8,9 +8,12 @@ product_category: continuous_integration value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate + diff --git a/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml b/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml index 66456d42ffe..d04419aebfc 100644 --- a/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml +++ b/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml @@ -1,6 +1,6 @@ --- key_path: usage_activity_by_stage_monthly.verify.ci_internal_pipelines -description: Total pipelines in GitLab repositories +description: Total pipelines in GitLab repositories in a month product_section: ops product_stage: verify product_group: group::continuous integration @@ -8,9 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216175548_ci_pipeline_config_auto_devops.yml b/config/metrics/counts_28d/20210216175548_ci_pipeline_config_auto_devops.yml index 7720ca5d26a..7cfbe4df320 100644 --- a/config/metrics/counts_28d/20210216175548_ci_pipeline_config_auto_devops.yml +++ b/config/metrics/counts_28d/20210216175548_ci_pipeline_config_auto_devops.yml @@ -2,15 +2,17 @@ key_path: usage_activity_by_stage_monthly.verify.ci_pipeline_config_auto_devops description: Total pipelines from an Auto DevOps template product_section: ops -product_stage: verify -product_group: group::continuous integration -product_category: continuous_integration +product_stage: configure +product_group: group::configure +product_category: auto_devops value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml b/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml index 8c125a51f89..c89d4804607 100644 --- a/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml +++ b/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml @@ -1,6 +1,6 @@ --- key_path: usage_activity_by_stage_monthly.verify.ci_pipeline_config_repository -description: Total Pipelines from templates in repository +description: Total Monthly Pipelines from templates in repository product_section: ops product_stage: verify product_group: group::continuous integration @@ -8,9 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml b/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml index 7464f28fc68..cfea6f49eb5 100644 --- a/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml +++ b/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml @@ -1,6 +1,6 @@ --- key_path: usage_activity_by_stage_monthly.verify.ci_pipeline_schedules -description: Pipeline schedules in GitLab +description: Total monthly Pipeline schedules in GitLab product_section: ops product_stage: verify product_group: group::continuous integration @@ -8,9 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216175554_ci_pipelines.yml b/config/metrics/counts_28d/20210216175554_ci_pipelines.yml index b818e52ecb5..55b0250bbbd 100644 --- a/config/metrics/counts_28d/20210216175554_ci_pipelines.yml +++ b/config/metrics/counts_28d/20210216175554_ci_pipelines.yml @@ -1,6 +1,6 @@ --- key_path: usage_activity_by_stage_monthly.verify.ci_pipelines -description: " Distinct users triggering pipelines in a month" +description: "Distinct users triggering pipelines in a month" product_section: ops product_stage: verify product_group: group::continuous integration @@ -8,10 +8,12 @@ product_category: continuous_integration value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce - ee tier: - free -skip_validation: true +- premium +- ultimate +- free diff --git a/config/metrics/counts_28d/20210216175556_ci_triggers.yml b/config/metrics/counts_28d/20210216175556_ci_triggers.yml index f409434feb0..7a06e45b4e4 100644 --- a/config/metrics/counts_28d/20210216175556_ci_triggers.yml +++ b/config/metrics/counts_28d/20210216175556_ci_triggers.yml @@ -8,7 +8,7 @@ product_category: continuous_integration value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce - ee @@ -16,4 +16,3 @@ tier: - free - premium - ultimate -skip_validation: true diff --git a/config/metrics/counts_all/20210216175510_ci_builds.yml b/config/metrics/counts_all/20210216175510_ci_builds.yml index a70878ce352..5efe8e30dfd 100644 --- a/config/metrics/counts_all/20210216175510_ci_builds.yml +++ b/config/metrics/counts_all/20210216175510_ci_builds.yml @@ -11,6 +11,8 @@ time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml b/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml index 425da65a0d5..3c924b71ed5 100644 --- a/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml +++ b/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml @@ -11,6 +11,8 @@ time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml b/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml index bf62f8096df..49846e9521d 100644 --- a/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml +++ b/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml @@ -13,4 +13,3 @@ distribution: - ce tier: - free -skip_validation: true diff --git a/config/metrics/counts_all/20210216175516_ci_pipeline_config_auto_devops.yml b/config/metrics/counts_all/20210216175516_ci_pipeline_config_auto_devops.yml index ac9ce910867..39fa444351f 100644 --- a/config/metrics/counts_all/20210216175516_ci_pipeline_config_auto_devops.yml +++ b/config/metrics/counts_all/20210216175516_ci_pipeline_config_auto_devops.yml @@ -2,15 +2,17 @@ key_path: counts.ci_pipeline_config_auto_devops description: Total pipelines from an Auto DevOps template product_section: ops -product_stage: verify -product_group: group::continuous integration -product_category: continuous_integration +product_stage: configure +product_group: group::configure +product_category: auto_devops value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml b/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml index dc796d25bb0..95f160bac0e 100644 --- a/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml +++ b/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml @@ -11,6 +11,8 @@ time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175520_ci_runners.yml b/config/metrics/counts_all/20210216175520_ci_runners.yml index 4bac8d39737..991e966b05e 100644 --- a/config/metrics/counts_all/20210216175520_ci_runners.yml +++ b/config/metrics/counts_all/20210216175520_ci_runners.yml @@ -11,6 +11,8 @@ time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175521_ci_triggers.yml b/config/metrics/counts_all/20210216175521_ci_triggers.yml index 5a2015d6fdf..38d5b621d0f 100644 --- a/config/metrics/counts_all/20210216175521_ci_triggers.yml +++ b/config/metrics/counts_all/20210216175521_ci_triggers.yml @@ -11,6 +11,8 @@ time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml b/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml index 330b0b64e26..7c8aeac67b4 100644 --- a/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml +++ b/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml @@ -11,6 +11,8 @@ time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175525_ci_builds.yml b/config/metrics/counts_all/20210216175525_ci_builds.yml index a9a5d9f6a88..16f1e46c94f 100644 --- a/config/metrics/counts_all/20210216175525_ci_builds.yml +++ b/config/metrics/counts_all/20210216175525_ci_builds.yml @@ -1,6 +1,6 @@ --- key_path: usage_activity_by_stage.verify.ci_builds -description: Unique builds in project +description: Unique count of builds in project product_section: ops product_stage: verify product_group: group::continuous integration @@ -8,9 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml b/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml index bc29f8fd33c..f97d7f31b67 100644 --- a/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml +++ b/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml @@ -8,9 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml b/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml index 9eb1541828f..700152cc710 100644 --- a/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml +++ b/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml @@ -8,9 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175531_ci_pipeline_config_auto_devops.yml b/config/metrics/counts_all/20210216175531_ci_pipeline_config_auto_devops.yml index 046f94261e1..af5ecbf5a63 100644 --- a/config/metrics/counts_all/20210216175531_ci_pipeline_config_auto_devops.yml +++ b/config/metrics/counts_all/20210216175531_ci_pipeline_config_auto_devops.yml @@ -2,15 +2,17 @@ key_path: usage_activity_by_stage.verify.ci_pipeline_config_auto_devops description: Total pipelines from an Auto DevOps template product_section: ops -product_stage: verify -product_group: group::continuous integration -product_category: continuous_integration +product_stage: configure +product_group: group::configure +product_category: auto_devops value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml b/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml index 6daf1a8fbd6..a7d45a74e72 100644 --- a/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml +++ b/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml @@ -8,9 +8,12 @@ product_category: continuous_integration value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate + diff --git a/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml b/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml index be012a5ff3e..dc34481c495 100644 --- a/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml +++ b/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml @@ -8,9 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175537_ci_pipelines.yml b/config/metrics/counts_all/20210216175537_ci_pipelines.yml index e9394e70fd1..9447661e19c 100644 --- a/config/metrics/counts_all/20210216175537_ci_pipelines.yml +++ b/config/metrics/counts_all/20210216175537_ci_pipelines.yml @@ -8,10 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce - ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216175539_ci_triggers.yml b/config/metrics/counts_all/20210216175539_ci_triggers.yml index d284687894a..85a7f097574 100644 --- a/config/metrics/counts_all/20210216175539_ci_triggers.yml +++ b/config/metrics/counts_all/20210216175539_ci_triggers.yml @@ -8,9 +8,11 @@ product_category: continuous_integration value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/db/migrate/20210215172449_remove_artifact_expiry_temp_index.rb b/db/migrate/20210215172449_remove_artifact_expiry_temp_index.rb new file mode 100644 index 00000000000..1e6619731a2 --- /dev/null +++ b/db/migrate/20210215172449_remove_artifact_expiry_temp_index.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class RemoveArtifactExpiryTempIndex < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + INDEX_NAME = 'expired_artifacts_temp_index' + INDEX_CONDITION = "expire_at IS NULL AND date(created_at AT TIME ZONE 'UTC') < '2020-06-22'::date" + + def up + remove_concurrent_index_by_name :ci_job_artifacts, INDEX_NAME + end + + def down + add_concurrent_index(:ci_job_artifacts, %i(id created_at), where: INDEX_CONDITION, name: INDEX_NAME) + end +end diff --git a/db/schema_migrations/20210215172449 b/db/schema_migrations/20210215172449 new file mode 100644 index 00000000000..5b310d80d6a --- /dev/null +++ b/db/schema_migrations/20210215172449 @@ -0,0 +1 @@ +f72f0a31bca545d2528030019695b03e0858d7ae9a0fb32d407c25580731fa6b \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 1ff0d72cb24..877b1cbbf38 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -21800,8 +21800,6 @@ CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_and_note_id_index ON epic_user CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_index ON epic_user_mentions USING btree (epic_id) WHERE (note_id IS NULL); -CREATE INDEX expired_artifacts_temp_index ON ci_job_artifacts USING btree (id, created_at) WHERE ((expire_at IS NULL) AND (date(timezone('UTC'::text, created_at)) < '2020-06-22'::date)); - CREATE INDEX finding_evidence_requests_on_finding_evidence_id ON vulnerability_finding_evidence_requests USING btree (vulnerability_finding_evidence_id); CREATE INDEX finding_evidence_responses_on_finding_evidences_id ON vulnerability_finding_evidence_responses USING btree (vulnerability_finding_evidence_id); diff --git a/doc/administration/encrypted_configuration.md b/doc/administration/encrypted_configuration.md index 508f4314694..ff48005e427 100644 --- a/doc/administration/encrypted_configuration.md +++ b/doc/administration/encrypted_configuration.md @@ -18,7 +18,7 @@ In order to enable the encrypted configuration settings, a new base key needs to **Omnibus Installation** -Starting with 13.7 the new secret is automatically generated for you, but you will need to ensure your +Starting with 13.7 the new secret is automatically generated for you, but you need to ensure your `/etc/gitlab/gitlab-secrets.json` contains the same values on all nodes. **GitLab Cloud Native Helm Chart** @@ -34,4 +34,4 @@ The new secret can be generated by running: bundle exec rake gitlab:env:info RAILS_ENV=production GITLAB_GENERATE_ENCRYPTED_SETTINGS_KEY_BASE=true ``` -This will print general information on the GitLab instance, but will also cause the key to be generated in `/config/secrets.yml` +This prints general information on the GitLab instance, but also causes the key to be generated in `/config/secrets.yml` diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e8b581b68a7..2a7bb7d2039 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -73,7 +73,7 @@ Returns [`CurrentLicense`](#currentlicense). Get information about current user. -Returns [`User`](#user). +Returns [`UserCore`](#usercore). ### `Query.designManagement` @@ -165,7 +165,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ### `Query.issue` -Find an Issue. +Find an issue. Returns [`Issue`](#issue). @@ -173,7 +173,7 @@ Returns [`Issue`](#issue). | Name | Type | Description | | ---- | ---- | ----------- | -| `id` | [`IssueID!`](#issueid) | The global ID of the Issue. | +| `id` | [`IssueID!`](#issueid) | The global ID of the issue. | ### `Query.iteration` @@ -197,6 +197,18 @@ This field returns a [connection](#connections). It accepts the four standard [pagination arguments](#connection-pagination-arguments): `before: String`, `after: String`, `first: Int`, `last: Int`. +### `Query.mergeRequest` + +Find a merge request. + +Returns [`MergeRequest`](#mergerequest). + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | [`MergeRequestID!`](#mergerequestid) | The global ID of the merge request. | + ### `Query.metadata` Metadata about GitLab. @@ -339,7 +351,7 @@ four standard [pagination arguments](#connection-pagination-arguments): Find a user. -Returns [`User`](#user). +Returns [`UserCore`](#usercore). #### Arguments @@ -352,7 +364,7 @@ Returns [`User`](#user). Find users. -Returns [`UserConnection`](#userconnection). +Returns [`UserCoreConnection`](#usercoreconnection). This field returns a [connection](#connections). It accepts the four standard [pagination arguments](#connection-pagination-arguments): @@ -4049,8 +4061,8 @@ All connections have at least the following fields: | `nodes` | `[item!]` | The items in the current page. | The precise type of `Edge` and `Item` depends on the kind of connection. A -[`UserConnection`](#userconnection) will have nodes that have the type -[`[User!]`](#user), and edges that have the type [`UserEdge`](#useredge). +[`ProjectConnection`](#projectconnection) will have nodes that have the type +[`[Project!]`](#project), and edges that have the type [`ProjectEdge`](#projectedge). ### Connection types @@ -5308,6 +5320,29 @@ The edge type for [`MemberInterface`](#memberinterface). | `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `node` | [`MemberInterface`](#memberinterface) | The item at the end of the edge. | +#### `MergeRequestAssigneeConnection` + +The connection type for [`MergeRequestAssignee`](#mergerequestassignee). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `edges` | [`[MergeRequestAssigneeEdge]`](#mergerequestassigneeedge) | A list of edges. | +| `nodes` | [`[MergeRequestAssignee]`](#mergerequestassignee) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `MergeRequestAssigneeEdge` + +The edge type for [`MergeRequestAssignee`](#mergerequestassignee). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`MergeRequestAssignee`](#mergerequestassignee) | The item at the end of the edge. | + #### `MergeRequestConnection` The connection type for [`MergeRequest`](#mergerequest). @@ -6373,28 +6408,28 @@ The edge type for [`UserCallout`](#usercallout). | `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `node` | [`UserCallout`](#usercallout) | The item at the end of the edge. | -#### `UserConnection` +#### `UserCoreConnection` -The connection type for [`User`](#user). +The connection type for [`UserCore`](#usercore). ##### Fields | Name | Type | Description | | ---- | ---- | ----------- | -| `edges` | [`[UserEdge]`](#useredge) | A list of edges. | -| `nodes` | [`[User]`](#user) | A list of nodes. | -| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | +| `edges` | [`[UserCoreEdge]`](#usercoreedge) | A list of edges. | +| `nodes` | [`[UserCore]`](#usercore) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | -#### `UserEdge` +#### `UserCoreEdge` -The edge type for [`User`](#user). +The edge type for [`UserCore`](#usercore). ##### Fields | Name | Type | Description | | ---- | ---- | ----------- | -| `cursor` | [`String!`](#string) | A cursor for use in pagination. | -| `node` | [`User`](#user) | The item at the end of the edge. | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`UserCore`](#usercore) | The item at the end of the edge. | #### `VulnerabilitiesCountByDayAndSeverityConnection` @@ -6566,7 +6601,7 @@ Describes an alert from the project's Alert Management. | Name | Type | Description | | ---- | ---- | ----------- | -| `assignees` | [`UserConnection`](#userconnection) | Assignees of the alert. | +| `assignees` | [`UserCoreConnection`](#usercoreconnection) | Assignees of the alert. | | `createdAt` | [`Time`](#time) | Timestamp the alert was created. | | `description` | [`String`](#string) | Description of the alert. | | `details` | [`JSON`](#json) | Alert details. | @@ -6737,7 +6772,7 @@ An emoji awarded by a user. | `name` | [`String!`](#string) | The emoji name. | | `unicode` | [`String!`](#string) | The emoji in Unicode. | | `unicodeVersion` | [`String!`](#string) | The Unicode version for this emoji. | -| `user` | [`User!`](#user) | The user who awarded the emoji. | +| `user` | [`UserCore!`](#usercore) | The user who awarded the emoji. | ### `BaseService` @@ -6789,7 +6824,7 @@ Represents a project or group issue board. | Name | Type | Description | | ---- | ---- | ----------- | -| `assignee` | [`User`](#user) | The board assignee. | +| `assignee` | [`UserCore`](#usercore) | The board assignee. | | `createdAt` | [`Time!`](#time) | Timestamp of when the board was created. | | `hideBacklogList` | [`Boolean`](#boolean) | Whether or not backlog list is hidden. | | `hideClosedList` | [`Boolean`](#boolean) | Whether or not closed list is hidden. | @@ -6846,7 +6881,7 @@ Represents an epic on an issue board. | Name | Type | Description | | ---- | ---- | ----------- | -| `author` | [`User!`](#user) | Author of the epic. | +| `author` | [`UserCore!`](#usercore) | Author of the epic. | | `awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | A list of award emojis associated with the epic. | | `closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. | | `confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. | @@ -6873,7 +6908,7 @@ Represents an epic on an issue board. | `labels` | [`LabelConnection`](#labelconnection) | Labels assigned to the epic. | | `notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. | | `parent` | [`Epic`](#epic) | Parent epic of the epic. | -| `participants` | [`UserConnection`](#userconnection) | List of participants for the epic. | +| `participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants for the epic. | | `relationPath` | [`String`](#string) | URI path of the epic-issue relationship. | | `relativePosition` | [`Int`](#int) | The relative position of the epic in the epic tree. | | `startDate` | [`Time`](#time) | Start date of the epic. | @@ -6971,7 +7006,7 @@ Represents a list for an issue board. | Name | Type | Description | | ---- | ---- | ----------- | -| `assignee` | [`User`](#user) | Assignee in the list. | +| `assignee` | [`UserCore`](#usercore) | Assignee in the list. | | `collapsed` | [`Boolean`](#boolean) | Indicates if the list is collapsed for this user. | | `id` | [`ID!`](#id) | ID (global ID) of the list. | | `issuesCount` | [`Int`](#int) | Count of issues in the list. | @@ -7183,7 +7218,7 @@ Represents the total number of issues and their weights for a particular day. | Name | Type | Description | | ---- | ---- | ----------- | | `createdAt` | [`Time`](#time) | Timestamp the cluster agent was created. | -| `createdByUser` | [`User`](#user) | User object, containing information about the person who created the agent. | +| `createdByUser` | [`UserCore`](#usercore) | User object, containing information about the person who created the agent. | | `id` | [`ID!`](#id) | ID of the cluster agent. | | `name` | [`String`](#string) | Name of the cluster agent. | | `project` | [`Project`](#project) | The project this cluster agent is associated with. | @@ -7199,7 +7234,7 @@ Represents the total number of issues and their weights for a particular day. | ---- | ---- | ----------- | | `clusterAgent` | [`ClusterAgent`](#clusteragent) | Cluster agent this token is associated with. | | `createdAt` | [`Time`](#time) | Timestamp the token was created. | -| `createdByUser` | [`User`](#user) | The user who created the token. | +| `createdByUser` | [`UserCore`](#usercore) | The user who created the token. | | `description` | [`String`](#string) | Description of the token. | | `id` | [`ClustersAgentTokenID!`](#clustersagenttokenid) | Global ID of the token. | | `lastUsedAt` | [`Time`](#time) | Timestamp the token was last used. | @@ -7236,7 +7271,7 @@ Represents the code coverage summary for a project. | Name | Type | Description | | ---- | ---- | ----------- | -| `author` | [`User`](#user) | Author of the commit. | +| `author` | [`UserCore`](#usercore) | Author of the commit. | | `authorGravatar` | [`String`](#string) | Commit authors gravatar. | | `authorName` | [`String`](#string) | Commit authors name. | | `authoredDate` | [`Time`](#time) | Timestamp of when the commit was authored. | @@ -7910,7 +7945,7 @@ Aggregated summary of changes. | `resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. | | `resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. | | `resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. | -| `resolvedBy` | [`User`](#user) | User who resolved the object. | +| `resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. | ### `Environment` @@ -7948,7 +7983,7 @@ Represents an epic. | Name | Type | Description | | ---- | ---- | ----------- | -| `author` | [`User!`](#user) | Author of the epic. | +| `author` | [`UserCore!`](#usercore) | Author of the epic. | | `awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | A list of award emojis associated with the epic. | | `closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. | | `confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. | @@ -7975,7 +8010,7 @@ Represents an epic. | `labels` | [`LabelConnection`](#labelconnection) | Labels assigned to the epic. | | `notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. | | `parent` | [`Epic`](#epic) | Parent epic of the epic. | -| `participants` | [`UserConnection`](#userconnection) | List of participants for the epic. | +| `participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants for the epic. | | `relationPath` | [`String`](#string) | URI path of the epic-issue relationship. | | `relativePosition` | [`Int`](#int) | The relative position of the epic in the epic tree. | | `startDate` | [`Time`](#time) | Start date of the epic. | @@ -8133,8 +8168,8 @@ Relationship between an epic and an issue. | Name | Type | Description | | ---- | ---- | ----------- | | `alertManagementAlert` | [`AlertManagementAlert`](#alertmanagementalert) | Alert associated to this issue. | -| `assignees` | [`UserConnection`](#userconnection) | Assignees of the issue. | -| `author` | [`User!`](#user) | User that created the issue. | +| `assignees` | [`UserCoreConnection`](#usercoreconnection) | Assignees of the issue. | +| `author` | [`UserCore!`](#usercore) | User that created the issue. | | `blocked` | [`Boolean!`](#boolean) | Indicates the issue is blocked. | | `blockedByCount` | [`Int`](#int) | Count of issues blocking this issue. | | `blockedByIssues` | [`IssueConnection`](#issueconnection) | Issues blocking this issue. | @@ -8164,7 +8199,7 @@ Relationship between an epic and an issue. | `moved` | [`Boolean`](#boolean) | Indicates if issue got moved from other project. | | `movedTo` | [`Issue`](#issue) | Updated Issue after it got moved to another project. | | `notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. | -| `participants` | [`UserConnection`](#userconnection) | List of participants in the issue. | +| `participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants in the issue. | | `relationPath` | [`String`](#string) | URI path of the epic-issue relation. | | `relativePosition` | [`Int`](#int) | Relative position of the issue (used for positioning in epic tree and issue boards). | | `severity` | [`IssuableSeverity`](#issuableseverity) | Severity level of the incident. | @@ -8180,7 +8215,7 @@ Relationship between an epic and an issue. | `totalTimeSpent` | [`Int!`](#int) | Total time reported as spent on the issue. | | `type` | [`IssueType`](#issuetype) | Type of the issue. | | `updatedAt` | [`Time!`](#time) | Timestamp of when the issue was last updated. | -| `updatedBy` | [`User`](#user) | User that last updated the issue. | +| `updatedBy` | [`UserCore`](#usercore) | User that last updated the issue. | | `upvotes` | [`Int!`](#int) | Number of upvotes the issue has received. | | `userDiscussionsCount` | [`Int!`](#int) | Number of user discussions in the issue. | | `userNotesCount` | [`Int!`](#int) | Number of user notes of the issue. | @@ -8279,7 +8314,7 @@ Representing an event. | Name | Type | Description | | ---- | ---- | ----------- | | `action` | [`EventAction!`](#eventaction) | Action of the event. | -| `author` | [`User!`](#user) | Author of this event. | +| `author` | [`UserCore!`](#usercore) | Author of this event. | | `createdAt` | [`Time!`](#time) | When this event was created. | | `id` | [`ID!`](#id) | ID of the event. | | `updatedAt` | [`Time!`](#time) | When this event was updated. | @@ -8475,7 +8510,6 @@ four standard [pagination arguments](#connection-pagination-arguments): | `mentionsDisabled` | [`Boolean`](#boolean) | Indicates if a group is disabled from getting mentioned. | | `name` | [`String!`](#string) | Name of the namespace. | | `packageSettings` | [`PackageSettings`](#packagesettings) | The package settings for the namespace. | -| `packages` | [`PackageConnection`](#packageconnection) | Packages of the group. | | `parent` | [`Group`](#group) | Parent group. | | `path` | [`String!`](#string) | Path of the namespace. | | `projectCreationLevel` | [`String`](#string) | The permission level required to create projects in the group. | @@ -8828,6 +8862,22 @@ four standard [pagination arguments](#connection-pagination-arguments): | `timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. | | `title` | [`String`](#string) | The title of the milestone. | +##### `Group.packages` + +Packages of the group. + +Returns [`PackageConnection`](#packageconnection). + +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 | +| ---- | ---- | ----------- | +| `sort` | [`PackageGroupSort`](#packagegroupsort) | Sort packages by this criteria. | + ##### `Group.projects` Projects within this namespace. @@ -8968,12 +9018,12 @@ Represents a Group Membership. | ---- | ---- | ----------- | | `accessLevel` | [`AccessLevel`](#accesslevel) | GitLab::Access level. | | `createdAt` | [`Time`](#time) | Date and time the membership was created. | -| `createdBy` | [`User`](#user) | User that authorized membership. | +| `createdBy` | [`UserCore`](#usercore) | User that authorized membership. | | `expiresAt` | [`Time`](#time) | Date and time the membership expires. | | `group` | [`Group`](#group) | Group that a User is a member of. | | `id` | [`ID!`](#id) | ID of the member. | | `updatedAt` | [`Time`](#time) | Date and time the membership was last updated. | -| `user` | [`User!`](#user) | User that is associated with the member object. | +| `user` | [`UserCore!`](#usercore) | User that is associated with the member object. | | `userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. | ### `GroupPermissions` @@ -9133,8 +9183,8 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). | Name | Type | Description | | ---- | ---- | ----------- | | `alertManagementAlert` | [`AlertManagementAlert`](#alertmanagementalert) | Alert associated to this issue. | -| `assignees` | [`UserConnection`](#userconnection) | Assignees of the issue. | -| `author` | [`User!`](#user) | User that created the issue. | +| `assignees` | [`UserCoreConnection`](#usercoreconnection) | Assignees of the issue. | +| `author` | [`UserCore!`](#usercore) | User that created the issue. | | `blocked` | [`Boolean!`](#boolean) | Indicates the issue is blocked. | | `blockedByCount` | [`Int`](#int) | Count of issues blocking this issue. | | `blockedByIssues` | [`IssueConnection`](#issueconnection) | Issues blocking this issue. | @@ -9163,7 +9213,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). | `moved` | [`Boolean`](#boolean) | Indicates if issue got moved from other project. | | `movedTo` | [`Issue`](#issue) | Updated Issue after it got moved to another project. | | `notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. | -| `participants` | [`UserConnection`](#userconnection) | List of participants in the issue. | +| `participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants in the issue. | | `relativePosition` | [`Int`](#int) | Relative position of the issue (used for positioning in epic tree and issue boards). | | `severity` | [`IssuableSeverity`](#issuableseverity) | Severity level of the incident. | | `slaDueAt` | [`Time`](#time) | Timestamp of when the issue SLA expires. | @@ -9178,7 +9228,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). | `totalTimeSpent` | [`Int!`](#int) | Total time reported as spent on the issue. | | `type` | [`IssueType`](#issuetype) | Type of the issue. | | `updatedAt` | [`Time!`](#time) | Timestamp of when the issue was last updated. | -| `updatedBy` | [`User`](#user) | User that last updated the issue. | +| `updatedBy` | [`UserCore`](#usercore) | User that last updated the issue. | | `upvotes` | [`Int!`](#int) | Number of upvotes the issue has received. | | `userDiscussionsCount` | [`Int!`](#int) | Number of user discussions in the issue. | | `userNotesCount` | [`Int!`](#int) | Number of user notes of the issue. | @@ -9298,7 +9348,7 @@ Represents an iteration cadence. | `importedIssuesCount` | [`Int!`](#int) | Count of issues that were successfully imported. | | `jiraProjectKey` | [`String!`](#string) | Project key for the imported Jira project. | | `scheduledAt` | [`Time`](#time) | Timestamp of when the Jira import was scheduled. | -| `scheduledBy` | [`User`](#user) | User that started the Jira import. | +| `scheduledBy` | [`UserCore`](#usercore) | User that started the Jira import. | | `totalIssueCount` | [`Int!`](#int) | Total count of issues that were attempted to import. | ### `JiraProject` @@ -9412,9 +9462,9 @@ Represents an entry from the Cloud License history. | `approvalsLeft` | [`Int`](#int) | Number of approvals left. | | `approvalsRequired` | [`Int`](#int) | Number of approvals required. | | `approved` | [`Boolean!`](#boolean) | Indicates if the merge request has all the required approvals. Returns true if no required approvals are configured. | -| `approvedBy` | [`UserConnection`](#userconnection) | Users who approved the merge request. | -| `assignees` | [`UserConnection`](#userconnection) | Assignees of the merge request. | -| `author` | [`User`](#user) | User who created this merge request. | +| `approvedBy` | [`UserCoreConnection`](#usercoreconnection) | Users who approved the merge request. | +| `assignees` | [`MergeRequestAssigneeConnection`](#mergerequestassigneeconnection) | Assignees of the merge request. | +| `author` | [`UserCore`](#usercore) | User who created this merge request. | | `autoMergeEnabled` | [`Boolean!`](#boolean) | Indicates if auto merge is enabled for the merge request. | | `autoMergeStrategy` | [`String`](#string) | Selected auto merge strategy. | | `availableAutoMergeStrategies` | [`[String!]`](#string) | Array of available auto merge strategies. | @@ -9447,14 +9497,14 @@ Represents an entry from the Cloud License history. | `mergeOngoing` | [`Boolean!`](#boolean) | Indicates if a merge is currently occurring. | | `mergeStatus` | [`String`](#string) | Status of the merge request. | | `mergeTrainsCount` | [`Int`](#int) | Number of merge requests in the merge train. | -| `mergeUser` | [`User`](#user) | User who merged this merge request. | +| `mergeUser` | [`UserCore`](#usercore) | User who merged this merge request. | | `mergeWhenPipelineSucceeds` | [`Boolean`](#boolean) | Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS). | | `mergeable` | [`Boolean!`](#boolean) | Indicates if the merge request is mergeable. | | `mergeableDiscussionsState` | [`Boolean`](#boolean) | Indicates if all discussions in the merge request have been resolved, allowing the merge request to be merged. | | `mergedAt` | [`Time`](#time) | Timestamp of when the merge request was merged, null if not merged. | | `milestone` | [`Milestone`](#milestone) | The milestone of the merge request. | | `notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. | -| `participants` | [`UserConnection`](#userconnection) | Participants in the merge request. This includes the author, assignees, reviewers, and users mentioned in notes. | +| `participants` | [`UserCoreConnection`](#usercoreconnection) | Participants in the merge request. This includes the author, assignees, reviewers, and users mentioned in notes. | | `project` | [`Project!`](#project) | Alias for target_project. | | `projectId` | [`Int!`](#int) | ID of the merge request project. | | `rebaseCommitSha` | [`String`](#string) | Rebase commit SHA of the merge request. | @@ -9551,6 +9601,177 @@ Returns [`String!`](#string). | ---- | ---- | ----------- | | `full` | [`Boolean`](#boolean) | Boolean option specifying whether the reference should be returned in full. | +### `MergeRequestAssignee` + +A user assigned to a merge request. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `avatarUrl` | [`String`](#string) | URL of the user's avatar. | +| `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | +| `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. | +| `email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | +| `groupCount` | [`Int`](#int) | Group count for the user. Available only when feature flag `user_group_counts` is enabled. | +| `groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. | +| `id` | [`ID!`](#id) | ID of the user. | +| `location` | [`String`](#string) | The location of the user. | +| `mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. | +| `name` | [`String!`](#string) | Human-readable name of the user. | +| `projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. | +| `publicEmail` | [`String`](#string) | User's public email. | +| `state` | [`UserState!`](#userstate) | State of the user. | +| `status` | [`UserStatus`](#userstatus) | User status. | +| `userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. | +| `username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. | +| `webPath` | [`String!`](#string) | Web path of the user. | +| `webUrl` | [`String!`](#string) | Web URL of the user. | + +#### Fields with arguments + +##### `MergeRequestAssignee.assignedMergeRequests` + +Merge requests assigned to the user. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +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 | +| ---- | ---- | ----------- | +| `authorUsername` | [`String`](#string) | Username of the author. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `reviewerUsername` | [`String`](#string) | Username of the reviewer. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | + +##### `MergeRequestAssignee.authoredMergeRequests` + +Merge requests authored by the user. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +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 the assignee. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `reviewerUsername` | [`String`](#string) | Username of the reviewer. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | + +##### `MergeRequestAssignee.reviewRequestedMergeRequests` + +Merge requests assigned to the user for review. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +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 the assignee. | +| `authorUsername` | [`String`](#string) | Username of the author. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | + +##### `MergeRequestAssignee.snippets` + +Snippets authored by the user. + +Returns [`SnippetConnection`](#snippetconnection). + +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 | +| ---- | ---- | ----------- | +| `ids` | [`[SnippetID!]`](#snippetid) | Array of global snippet IDs. For example, `gid://gitlab/ProjectSnippet/1`. | +| `type` | [`TypeEnum`](#typeenum) | The type of snippet. | +| `visibility` | [`VisibilityScopesEnum`](#visibilityscopesenum) | The visibility of the snippet. | + +##### `MergeRequestAssignee.starredProjects` + +Projects starred by the user. + +Returns [`ProjectConnection`](#projectconnection). + +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 | +| ---- | ---- | ----------- | +| `search` | [`String`](#string) | Search query. | + +##### `MergeRequestAssignee.todos` + +To-do items of the user. + +Returns [`TodoConnection`](#todoconnection). + +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 | +| ---- | ---- | ----------- | +| `action` | [`[TodoActionEnum!]`](#todoactionenum) | The action to be filtered. | +| `authorId` | [`[ID!]`](#id) | The ID of an author. | +| `groupId` | [`[ID!]`](#id) | The ID of a group. | +| `projectId` | [`[ID!]`](#id) | The ID of a project. | +| `state` | [`[TodoStateEnum!]`](#todostateenum) | The state of the todo. | +| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | The type of the todo. | + ### `MergeRequestDiffRegistry` Represents the Geo sync and verification state of a Merge Request diff. @@ -9588,7 +9809,7 @@ Check permissions for the current user on a merge request. ### `MergeRequestReviewer` -A user from whom a merge request review has been requested. +A user assigned to a merge request as a reviewer. #### Fields @@ -9928,7 +10149,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | -| `author` | [`User!`](#user) | User who wrote this note. | +| `author` | [`UserCore!`](#usercore) | User who wrote this note. | | `body` | [`String!`](#string) | Content of the note. | | `bodyHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `note`. | | `confidential` | [`Boolean`](#boolean) | Indicates if this note is confidential. | @@ -9940,7 +10161,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | `resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. | | `resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. | | `resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. | -| `resolvedBy` | [`User`](#user) | User who resolved the object. | +| `resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. | | `system` | [`Boolean!`](#boolean) | Indicates whether this note was created by the system or by a user. | | `systemNoteIconName` | [`String`](#string) | Name of the icon corresponding to a system note. | | `updatedAt` | [`Time!`](#time) | Timestamp of the note's last activity. | @@ -9971,7 +10192,7 @@ The rotation participant and color palette. | `colorPalette` | [`String`](#string) | The color palette to assign to the on-call user. For example "blue". | | `colorWeight` | [`String`](#string) | The color weight to assign to for the on-call user, for example "500". Max 4 chars. For easy identification of the user. | | `id` | [`IncidentManagementOncallParticipantID!`](#incidentmanagementoncallparticipantid) | ID of the on-call participant. | -| `user` | [`User!`](#user) | The user who is participating. | +| `user` | [`UserCore!`](#usercore) | The user who is participating. | ### `OncallRotationActivePeriodType` @@ -10143,7 +10364,7 @@ Information about pagination in a connection. | `testReportSummary` | [`TestReportSummary!`](#testreportsummary) | Summary of the test report generated by the pipeline. | | `updatedAt` | [`Time!`](#time) | Timestamp of the pipeline's last activity. | | `upstream` | [`Pipeline`](#pipeline) | Pipeline that triggered the pipeline. | -| `user` | [`User`](#user) | Pipeline user. | +| `user` | [`UserCore`](#usercore) | Pipeline user. | | `userPermissions` | [`PipelinePermissions!`](#pipelinepermissions) | Permissions for the current user on the resource. | | `usesNeeds` | [`Boolean`](#boolean) | Indicates if the pipeline has jobs with `needs` dependencies. | | `warnings` | [`Boolean!`](#boolean) | Indicates if a pipeline has warnings. | @@ -10323,7 +10544,6 @@ Represents vulnerability finding of a security report on the pipeline. | `onlyAllowMergeIfAllDiscussionsAreResolved` | [`Boolean`](#boolean) | Indicates if merge requests of the project can only be merged when all the discussions are resolved. | | `onlyAllowMergeIfPipelineSucceeds` | [`Boolean`](#boolean) | Indicates if merge requests of the project can only be merged with successful jobs. | | `openIssuesCount` | [`Int`](#int) | Number of open issues for the project. | -| `packages` | [`PackageConnection`](#packageconnection) | Packages of the project. | | `path` | [`String!`](#string) | Path of the project. | | `pipelineAnalytics` | [`PipelineAnalytics`](#pipelineanalytics) | Pipeline analytics. | | `printingMergeRequestLinkEnabled` | [`Boolean`](#boolean) | Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line. | @@ -10823,6 +11043,22 @@ four standard [pagination arguments](#connection-pagination-arguments): | `timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. | | `title` | [`String`](#string) | The title of the milestone. | +##### `Project.packages` + +Packages of the project. + +Returns [`PackageConnection`](#packageconnection). + +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 | +| ---- | ---- | ----------- | +| `sort` | [`PackageSort`](#packagesort) | Sort packages by this criteria. | + ##### `Project.pipeline` Build pipeline of the project. @@ -11075,12 +11311,12 @@ Represents a Project Membership. | ---- | ---- | ----------- | | `accessLevel` | [`AccessLevel`](#accesslevel) | GitLab::Access level. | | `createdAt` | [`Time`](#time) | Date and time the membership was created. | -| `createdBy` | [`User`](#user) | User that authorized membership. | +| `createdBy` | [`UserCore`](#usercore) | User that authorized membership. | | `expiresAt` | [`Time`](#time) | Date and time the membership expires. | | `id` | [`ID!`](#id) | ID of the member. | | `project` | [`Project`](#project) | Project that User is a member of. | | `updatedAt` | [`Time`](#time) | Date and time the membership was last updated. | -| `user` | [`User!`](#user) | User that is associated with the member object. | +| `user` | [`UserCore!`](#usercore) | User that is associated with the member object. | | `userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. | ### `ProjectPermissions` @@ -11189,7 +11425,7 @@ Represents a release. | Name | Type | Description | | ---- | ---- | ----------- | | `assets` | [`ReleaseAssets`](#releaseassets) | Assets of the release. | -| `author` | [`User`](#user) | User that created the release. | +| `author` | [`UserCore`](#usercore) | User that created the release. | | `commit` | [`Commit`](#commit) | The commit associated with the release. | | `createdAt` | [`Time`](#time) | Timestamp of when the release was created. | | `description` | [`String`](#string) | Description (also known as "release notes") of the release. | @@ -11356,7 +11592,7 @@ Represents a requirement. | Name | Type | Description | | ---- | ---- | ----------- | -| `author` | [`User!`](#user) | Author of the requirement. | +| `author` | [`UserCore!`](#usercore) | Author of the requirement. | | `createdAt` | [`Time!`](#time) | Timestamp of when the requirement was created. | | `description` | [`String`](#string) | Description of the requirement. | | `descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. | @@ -11769,7 +12005,7 @@ Represents a snippet entry. | Name | Type | Description | | ---- | ---- | ----------- | -| `author` | [`User`](#user) | The owner of the snippet. | +| `author` | [`UserCore`](#usercore) | The owner of the snippet. | | `blob` **{warning-solid}** | [`SnippetBlob!`](#snippetblob) | **Deprecated** in 13.3. Use `blobs`. | | `createdAt` | [`Time!`](#time) | Timestamp this snippet was created. | | `description` | [`String`](#string) | Description of the snippet. | @@ -11921,7 +12157,7 @@ Completion status of tasks. | `id` | [`ID!`](#id) | ID of the Terraform state. | | `latestVersion` | [`TerraformStateVersion`](#terraformstateversion) | The latest version of the Terraform state. | | `lockedAt` | [`Time`](#time) | Timestamp the Terraform state was locked. | -| `lockedByUser` | [`User`](#user) | The user currently holding a lock on the Terraform state. | +| `lockedByUser` | [`UserCore`](#usercore) | The user currently holding a lock on the Terraform state. | | `name` | [`String!`](#string) | Name of the Terraform state. | | `updatedAt` | [`Time!`](#time) | Timestamp the Terraform state was updated. | @@ -11932,7 +12168,7 @@ Completion status of tasks. | Name | Type | Description | | ---- | ---- | ----------- | | `createdAt` | [`Time!`](#time) | Timestamp the version was created. | -| `createdByUser` | [`User`](#user) | The user that created this version. | +| `createdByUser` | [`UserCore`](#usercore) | The user that created this version. | | `downloadPath` | [`String`](#string) | URL for downloading the version's JSON file. | | `id` | [`ID!`](#id) | ID of the Terraform state version. | | `job` | [`CiJob`](#cijob) | The job that created this version. | @@ -11982,7 +12218,7 @@ Represents a requirement test report. | Name | Type | Description | | ---- | ---- | ----------- | -| `author` | [`User`](#user) | Author of the test report. | +| `author` | [`UserCore`](#usercore) | Author of the test report. | | `createdAt` | [`Time!`](#time) | Timestamp of when the test report was created. | | `id` | [`ID!`](#id) | ID of the test report. | | `state` | [`TestReportState!`](#testreportstate) | State of the test report. | @@ -12094,7 +12330,7 @@ Represents a historically accurate report about the timebox. | `note` | [`Note`](#note) | The note where the quick action to add the logged time was executed. | | `spentAt` | [`Time`](#time) | Timestamp of when the time tracked was spent at. | | `timeSpent` | [`Int!`](#int) | The time spent displayed in seconds. | -| `user` | [`User!`](#user) | The user that logged the time. | +| `user` | [`UserCore!`](#usercore) | The user that logged the time. | ### `Todo` @@ -12105,7 +12341,7 @@ Representing a to-do entry. | Name | Type | Description | | ---- | ---- | ----------- | | `action` | [`TodoActionEnum!`](#todoactionenum) | Action of the to-do item. | -| `author` | [`User!`](#user) | The author of this to-do item. | +| `author` | [`UserCore!`](#usercore) | The author of this to-do item. | | `body` | [`String!`](#string) | Body of the to-do item. | | `createdAt` | [`Time!`](#time) | Timestamp this to-do item was created. | | `group` | [`Group`](#group) | Group this to-do item is associated with. | @@ -12154,35 +12390,44 @@ Represents a recorded measurement (object count) for the Admins. | `identifier` | [`MeasurementIdentifier!`](#measurementidentifier) | The type of objects being measured. | | `recordedAt` | [`Time`](#time) | The time the measurement was recorded. | -### `User` - -Representation of a GitLab user. +### `UserCallout` #### Fields | Name | Type | Description | | ---- | ---- | ----------- | -| `avatarUrl` | [`String`](#string) | URL of the user's avatar. | -| `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | -| `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. | -| `email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | -| `groupCount` | [`Int`](#int) | Group count for the user. Available only when feature flag `user_group_counts` is enabled. | -| `groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. | -| `id` | [`ID!`](#id) | ID of the user. | -| `location` | [`String`](#string) | The location of the user. | -| `name` | [`String!`](#string) | Human-readable name of the user. | -| `projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. | -| `publicEmail` | [`String`](#string) | User's public email. | -| `state` | [`UserState!`](#userstate) | State of the user. | -| `status` | [`UserStatus`](#userstatus) | User status. | -| `userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. | -| `username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. | -| `webPath` | [`String!`](#string) | Web path of the user. | -| `webUrl` | [`String!`](#string) | Web URL of the user. | +| `dismissedAt` | [`Time`](#time) | Date when the callout was dismissed. | +| `featureName` | [`UserCalloutFeatureNameEnum!`](#usercalloutfeaturenameenum) | Name of the feature that the callout is for. | + +### `UserCore` + +Core represention of a GitLab user. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `avatarUrl` | [`String`](#string) | URL of the user's avatar. | +| `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | +| `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. | +| `email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | +| `groupCount` | [`Int`](#int) | Group count for the user. Available only when feature flag `user_group_counts` is enabled. | +| `groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. | +| `id` | [`ID!`](#id) | ID of the user. | +| `location` | [`String`](#string) | The location of the user. | +| `name` | [`String!`](#string) | Human-readable name of the user. | +| `projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. | +| `publicEmail` | [`String`](#string) | User's public email. | +| `state` | [`UserState!`](#userstate) | State of the user. | +| `status` | [`UserStatus`](#userstatus) | User status. | +| `userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. | +| `username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. | +| `webPath` | [`String!`](#string) | Web path of the user. | +| `webUrl` | [`String!`](#string) | Web URL of the user. | #### Fields with arguments -##### `User.assignedMergeRequests` +##### `UserCore.assignedMergeRequests` Merge requests assigned to the user. @@ -12196,22 +12441,22 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | -| `authorUsername` | [`String`](#string) | Username of the author. | -| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | -| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | -| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | -| `milestoneTitle` | [`String`](#string) | Title of the milestone. | -| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | -| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | -| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | -| `reviewerUsername` | [`String`](#string) | Username of the reviewer. | -| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | -| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | -| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | -| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | +| `authorUsername` | [`String`](#string) | Username of the author. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `reviewerUsername` | [`String`](#string) | Username of the reviewer. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | -##### `User.authoredMergeRequests` +##### `UserCore.authoredMergeRequests` Merge requests authored by the user. @@ -12225,22 +12470,22 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | -| `assigneeUsername` | [`String`](#string) | Username of the assignee. | -| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | -| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | -| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | -| `milestoneTitle` | [`String`](#string) | Title of the milestone. | -| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | -| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | -| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | -| `reviewerUsername` | [`String`](#string) | Username of the reviewer. | -| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | -| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | -| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | -| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | +| `assigneeUsername` | [`String`](#string) | Username of the assignee. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `reviewerUsername` | [`String`](#string) | Username of the reviewer. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | -##### `User.reviewRequestedMergeRequests` +##### `UserCore.reviewRequestedMergeRequests` Merge requests assigned to the user for review. @@ -12254,22 +12499,22 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | -| `assigneeUsername` | [`String`](#string) | Username of the assignee. | -| `authorUsername` | [`String`](#string) | Username of the author. | -| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | -| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | -| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | -| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | -| `milestoneTitle` | [`String`](#string) | Title of the milestone. | -| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | -| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | -| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | -| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | -| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | -| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | -| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | +| `assigneeUsername` | [`String`](#string) | Username of the assignee. | +| `authorUsername` | [`String`](#string) | Username of the author. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | -##### `User.snippets` +##### `UserCore.snippets` Snippets authored by the user. @@ -12283,11 +12528,11 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | -| `ids` | [`[SnippetID!]`](#snippetid) | Array of global snippet IDs. For example, `gid://gitlab/ProjectSnippet/1`. | -| `type` | [`TypeEnum`](#typeenum) | The type of snippet. | -| `visibility` | [`VisibilityScopesEnum`](#visibilityscopesenum) | The visibility of the snippet. | +| `ids` | [`[SnippetID!]`](#snippetid) | Array of global snippet IDs. For example, `gid://gitlab/ProjectSnippet/1`. | +| `type` | [`TypeEnum`](#typeenum) | The type of snippet. | +| `visibility` | [`VisibilityScopesEnum`](#visibilityscopesenum) | The visibility of the snippet. | -##### `User.starredProjects` +##### `UserCore.starredProjects` Projects starred by the user. @@ -12301,9 +12546,9 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | -| `search` | [`String`](#string) | Search query. | +| `search` | [`String`](#string) | Search query. | -##### `User.todos` +##### `UserCore.todos` To-do items of the user. @@ -12317,21 +12562,12 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | -| `action` | [`[TodoActionEnum!]`](#todoactionenum) | The action to be filtered. | -| `authorId` | [`[ID!]`](#id) | The ID of an author. | -| `groupId` | [`[ID!]`](#id) | The ID of a group. | -| `projectId` | [`[ID!]`](#id) | The ID of a project. | -| `state` | [`[TodoStateEnum!]`](#todostateenum) | The state of the todo. | -| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | The type of the todo. | - -### `UserCallout` - -#### Fields - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `dismissedAt` | [`Time`](#time) | Date when the callout was dismissed. | -| `featureName` | [`UserCalloutFeatureNameEnum!`](#usercalloutfeaturenameenum) | Name of the feature that the callout is for. | +| `action` | [`[TodoActionEnum!]`](#todoactionenum) | The action to be filtered. | +| `authorId` | [`[ID!]`](#id) | The ID of an author. | +| `groupId` | [`[ID!]`](#id) | The ID of a group. | +| `projectId` | [`[ID!]`](#id) | The ID of a project. | +| `state` | [`[TodoStateEnum!]`](#todostateenum) | The state of the todo. | +| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | The type of the todo. | ### `UserMergeRequestInteraction` @@ -12408,13 +12644,13 @@ Represents a vulnerability. | Name | Type | Description | | ---- | ---- | ----------- | | `confirmedAt` | [`Time`](#time) | Timestamp of when the vulnerability state was changed to confirmed. | -| `confirmedBy` | [`User`](#user) | The user that confirmed the vulnerability. | +| `confirmedBy` | [`UserCore`](#usercore) | The user that confirmed the vulnerability. | | `description` | [`String`](#string) | Description of the vulnerability. | | `details` | [`[VulnerabilityDetail!]!`](#vulnerabilitydetail) | Details of the vulnerability. | | `detectedAt` | [`Time!`](#time) | Timestamp of when the vulnerability was first detected. | | `discussions` | [`DiscussionConnection!`](#discussionconnection) | All discussions on this noteable. | | `dismissedAt` | [`Time`](#time) | Timestamp of when the vulnerability state was changed to dismissed. | -| `dismissedBy` | [`User`](#user) | The user that dismissed the vulnerability. | +| `dismissedBy` | [`UserCore`](#usercore) | The user that dismissed the vulnerability. | | `externalIssueLinks` | [`VulnerabilityExternalIssueLinkConnection!`](#vulnerabilityexternalissuelinkconnection) | List of external issue links related to the vulnerability. | | `hasSolutions` | [`Boolean`](#boolean) | Indicates whether there is a solution available for this vulnerability. | | `id` | [`ID!`](#id) | GraphQL ID of the vulnerability. | @@ -12426,7 +12662,7 @@ Represents a vulnerability. | `project` | [`Project`](#project) | The project on which the vulnerability was found. | | `reportType` | [`VulnerabilityReportType`](#vulnerabilityreporttype) | Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION, COVERAGE_FUZZING, API_FUZZING). `Scan Type` in the UI. | | `resolvedAt` | [`Time`](#time) | Timestamp of when the vulnerability state was changed to resolved. | -| `resolvedBy` | [`User`](#user) | The user that resolved the vulnerability. | +| `resolvedBy` | [`UserCore`](#usercore) | The user that resolved the vulnerability. | | `resolvedOnDefaultBranch` | [`Boolean!`](#boolean) | Indicates whether the vulnerability is fixed on the default branch or not. | | `scanner` | [`VulnerabilityScanner`](#vulnerabilityscanner) | Scanner metadata for the vulnerability. | | `severity` | [`VulnerabilitySeverity`](#vulnerabilityseverity) | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL). | @@ -13585,6 +13821,36 @@ Rotation length unit of an on-call rotation. | `HOURS` | Hours. | | `WEEKS` | Weeks. | +### `PackageGroupSort` + +Values for sorting group packages. + +| Value | Description | +| ----- | ----------- | +| `CREATED_ASC` | Ordered by created_at in ascending order. | +| `CREATED_DESC` | Ordered by created_at in descending order. | +| `NAME_ASC` | Ordered by name in ascending order. | +| `NAME_DESC` | Ordered by name in descending order. | +| `TYPE_ASC` | Ordered by type in ascending order. | +| `TYPE_DESC` | Ordered by type in descending order. | +| `VERSION_ASC` | Ordered by version in ascending order. | +| `VERSION_DESC` | Ordered by version in descending order. | + +### `PackageSort` + +Values for sorting package. + +| Value | Description | +| ----- | ----------- | +| `CREATED_ASC` | Ordered by created_at in ascending order. | +| `CREATED_DESC` | Ordered by created_at in descending order. | +| `NAME_ASC` | Ordered by name in ascending order. | +| `NAME_DESC` | Ordered by name in descending order. | +| `TYPE_ASC` | Ordered by type in ascending order. | +| `TYPE_DESC` | Ordered by type in descending order. | +| `VERSION_ASC` | Ordered by version in ascending order. | +| `VERSION_DESC` | Ordered by version in descending order. | + ### `PackageTypeEnum` | Value | Description | @@ -14649,11 +14915,11 @@ Implementations: | ---- | ---- | ----------- | | `accessLevel` | [`AccessLevel`](#accesslevel) | GitLab::Access level. | | `createdAt` | [`Time`](#time) | Date and time the membership was created. | -| `createdBy` | [`User`](#user) | User that authorized membership. | +| `createdBy` | [`UserCore`](#usercore) | User that authorized membership. | | `expiresAt` | [`Time`](#time) | Date and time the membership expires. | | `id` | [`ID!`](#id) | ID of the member. | | `updatedAt` | [`Time`](#time) | Date and time the membership was last updated. | -| `user` | [`User!`](#user) | User that is associated with the member object. | +| `user` | [`UserCore!`](#usercore) | User that is associated with the member object. | #### `Noteable` @@ -14705,7 +14971,7 @@ Implementations: | `resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. | | `resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. | | `resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. | -| `resolvedBy` | [`User`](#user) | User who resolved the object. | +| `resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. | #### `Service` @@ -14734,6 +15000,182 @@ Implementations: | ---- | ---- | ----------- | | `report` | [`TimeboxReport`](#timeboxreport) | Historically accurate report about the timebox. | +#### `User` + +Representation of a GitLab user. + +Implementations: + +- [`MergeRequestAssignee`](#mergerequestassignee) +- [`MergeRequestReviewer`](#mergerequestreviewer) +- [`UserCore`](#usercore) + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `avatarUrl` | [`String`](#string) | URL of the user's avatar. | +| `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | +| `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. | +| `email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | +| `groupCount` | [`Int`](#int) | Group count for the user. Available only when feature flag `user_group_counts` is enabled. | +| `groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. | +| `id` | [`ID!`](#id) | ID of the user. | +| `location` | [`String`](#string) | The location of the user. | +| `name` | [`String!`](#string) | Human-readable name of the user. | +| `projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. | +| `publicEmail` | [`String`](#string) | User's public email. | +| `state` | [`UserState!`](#userstate) | State of the user. | +| `status` | [`UserStatus`](#userstatus) | User status. | +| `userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. | +| `username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. | +| `webPath` | [`String!`](#string) | Web path of the user. | +| `webUrl` | [`String!`](#string) | Web URL of the user. | + +##### Fields with arguments + +###### `User.assignedMergeRequests` + +Merge requests assigned to the user. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +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 | +| ---- | ---- | ----------- | +| `authorUsername` | [`String`](#string) | Username of the author. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `reviewerUsername` | [`String`](#string) | Username of the reviewer. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | + +###### `User.authoredMergeRequests` + +Merge requests authored by the user. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +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 the assignee. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `reviewerUsername` | [`String`](#string) | Username of the reviewer. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | + +###### `User.reviewRequestedMergeRequests` + +Merge requests assigned to the user for review. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +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 the assignee. | +| `authorUsername` | [`String`](#string) | Username of the author. | +| `iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| `labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| `mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| `mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| `milestoneTitle` | [`String`](#string) | Title of the milestone. | +| `not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| `projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| `projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| `sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| `sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| `state` | [`MergeRequestState`](#mergerequeststate) | A merge request state. If provided, all resolved merge requests will have this state. | +| `targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | + +###### `User.snippets` + +Snippets authored by the user. + +Returns [`SnippetConnection`](#snippetconnection). + +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 | +| ---- | ---- | ----------- | +| `ids` | [`[SnippetID!]`](#snippetid) | Array of global snippet IDs. For example, `gid://gitlab/ProjectSnippet/1`. | +| `type` | [`TypeEnum`](#typeenum) | The type of snippet. | +| `visibility` | [`VisibilityScopesEnum`](#visibilityscopesenum) | The visibility of the snippet. | + +###### `User.starredProjects` + +Projects starred by the user. + +Returns [`ProjectConnection`](#projectconnection). + +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 | +| ---- | ---- | ----------- | +| `search` | [`String`](#string) | Search query. | + +###### `User.todos` + +To-do items of the user. + +Returns [`TodoConnection`](#todoconnection). + +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 | +| ---- | ---- | ----------- | +| `action` | [`[TodoActionEnum!]`](#todoactionenum) | The action to be filtered. | +| `authorId` | [`[ID!]`](#id) | The ID of an author. | +| `groupId` | [`[ID!]`](#id) | The ID of a group. | +| `projectId` | [`[ID!]`](#id) | The ID of a project. | +| `state` | [`[TodoStateEnum!]`](#todostateenum) | The state of the todo. | +| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | The type of the todo. | + ## Input types Types that may be used as arguments (all scalar types may also diff --git a/doc/architecture/blueprints/ci_scale/ci_builds_cumulative_forecast.png b/doc/architecture/blueprints/ci_scale/ci_builds_cumulative_forecast.png new file mode 100644 index 0000000000000000000000000000000000000000..fa34c7d1c3614b9aced5b719bd52040c076ac2db GIT binary patch literal 36221 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%0DTl@$mhuj7)U*T>b{vlX}Xb`NdU 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 literal 0 HcmV?d00001 diff --git a/doc/architecture/blueprints/ci_scale/ci_builds_daily_forecast.png b/doc/architecture/blueprints/ci_scale/ci_builds_daily_forecast.png new file mode 100644 index 0000000000000000000000000000000000000000..b73a592fa6bfb88039b50d037f18cfb22daea951 GIT binary patch literal 29472 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 literal 0 HcmV?d00001 diff --git a/doc/architecture/blueprints/ci_scale/index.md b/doc/architecture/blueprints/ci_scale/index.md new file mode 100644 index 00000000000..99997e7b19b --- /dev/null +++ b/doc/architecture/blueprints/ci_scale/index.md @@ -0,0 +1,205 @@ +--- +stage: none +group: unassigned +comments: false +description: 'Improve scalability of GitLab CI/CD' +--- + +# Next CI/CD scale target: 20M builds per day by 2024 + +## Summary + +GitLab CI/CD is one of the most data and compute intensive components of GitLab. +Since its [initial release in November 2012](https://about.gitlab.com/blog/2012/11/13/continuous-integration-server-from-gitlab/), +the CI/CD subsystem has evolved significantly. It was [integrated into GitLab in September 2015](https://about.gitlab.com/releases/2015/09/22/gitlab-8-0-released/) +and has become [one of the most beloved CI/CD solutions](https://about.gitlab.com/blog/2017/09/27/gitlab-leader-continuous-integration-forrester-wave/). + +GitLab CI/CD has come a long way since the initial release, but the design of +the data storage for pipeline builds remains almost the same since 2012. We +store all the builds in PostgreSQL in `ci_builds` table, and because we are +creating more than [2 million builds each day on GitLab.com](https://docs.google.com/spreadsheets/d/17ZdTWQMnTHWbyERlvj1GA7qhw_uIfCoI5Zfrrsh95zU), +we are reaching database limits that are slowing our development velocity down. + +On February 1st, 2021, a billionth CI/CD job was created and the number of +builds is growing exponentially. We will run out of the available primary keys +for builds before December 2021 unless we improve the database model used to +store CI/CD data. + +We expect to see 20M builds created daily on GitLab.com in the first half of +2024. + +![ci_builds cumulative with forecast](ci_builds_cumulative_forecast.png) + +## Goals + +**Enable future growth by making processing 20M builds in a day possible.** + +## Challenges + +The current state of CI/CD product architecture needs to be updated if we want +to sustain future growth. + +### We are running out of the capacity to store primary keys + +The primary key in `ci_builds` table is an integer generated in a sequence. +Historically, Rails used to use [integer](https://www.postgresql.org/docs/9.1/datatype-numeric.html) +type when creating primary keys for a table. We did use the default when we +[created the `ci_builds` table in 2012](https://gitlab.com/gitlab-org/gitlab/-/blob/046b28312704f3131e72dcd2dbdacc5264d4aa62/db/ci/migrate/20121004165038_create_builds.rb). +[The behavior of Rails has changed](https://github.com/rails/rails/pull/26266) +since the release of Rails 5. The framework is now using bigint type that is 8 +bytes long, however we have not migrated primary keys for `ci_builds` table to +bigint yet. + +We will run out of the capacity of the integer type to store primary keys in +`ci_builds` table before December 2021. When it happens without a viable +workaround or an emergency plan, GitLab.com will go down. + +`ci_builds` is just one of the tables that are running out of the primary keys +available in Int4 sequence. There are multiple other tables storing CI/CD data +that have the same problem. + +Primary keys problem will be tackled by our Database Team. + +### The table is too large + +There is more than a billion rows in `ci_builds` table. We store more than 2 +terabytes of data in that table, and the total size of indexes is more than 1 +terabyte (as of February 2021). + +This amount of data contributes to a significant performance problems we +experience on our primary PostgreSQL database. + +Most of the problem are related to how PostgreSQL database works internally, +and how it is making use of resources on a node the database runs on. We are at +the limits of vertical scaling of the primary database nodes and we frequently +see a negative impact of the `ci_builds` table on the overall performance, +stability, scalability and predictability of the database GitLab.com depends +on. + +The size of the table also hinders development velocity because queries that +seem fine in the development environment may not work on GitLab.com. The +difference in the dataset size between the environments makes it difficult to +predict the performance of even the most simple queries. + +We also expect a significant, exponential growth in the upcoming years. + +One of the forecasts done using [Facebook's +Prophet](https://facebook.github.io/prophet/) shows that in the first half of +2024 we expect seeing 20M builds created on GitLab.com each day. In comparison +to around 2M we see created today, this is 10x growth our product might need to +sustain in upcoming years. + +![ci_builds daily forecast](ci_builds_daily_forecast.png) + +### Queuing mechanisms are using the large table + +Because of how large the table is, mechanisms that we use to build queues of +pending builds (there is more than one queue), are not very efficient. Pending +builds represent a small fraction of what we store in the `ci_builds` table, +yet we need to find them in this big dataset to determine an order in which we +want to process them. + +This mechanism is very inefficient, and it has been causing problems on the +production environment frequently. This usually results in a significant drop +of the CI/CD apdex score, and sometimes even causes a significant performance +degradation in the production environment. + +There are multiple other strategies that can improve performance and +reliability. We can use [Redis +queuing](https://gitlab.com/gitlab-org/gitlab/-/issues/322972), or [a separate +table that will accelerate SQL queries used to build +queues](https://gitlab.com/gitlab-org/gitlab/-/issues/322766) and we want to +explore them. + +### Moving big amounts of data is challenging + +We store a significant amount of data in `ci_builds` table. Some of the columns +in that table store a serialized user-provided data. Column `ci_builds.options` +stores more than 600 gigabytes of data, and `ci_builds.yaml_variables` more +than 300 gigabytes (as of February 2021). + +It is a lot of data that needs to be reliably moved to a different place. +Unfortunately, right now, our [background +migrations](https://docs.gitlab.com/ee/development/background_migrations.html) +are not reliable enough to migrate this amount of data at scale. We need to +build mechanisms that will give us confidence in moving this data between +columns, tables, partitions or database shards. + +Effort to improve background migrations will be owned by our Database Team. + +### Development velocity is negatively affected + +Team members and the wider community members are struggling to contribute the +Verify area, because we restricted the possibility of extending `ci_builds` +even further. Our static analysis tools prevent adding more columns to this +table. Adding new queries is unpredictable because of the size of the dataset +and the amount of queries executed using the table. This significantly hinders +the development velocity and contributes to incidents on the production +environment. + +## Proposal + +Making GitLab CI/CD product ready for the scale we expect to see in the +upcoming years is a multi-phase effort. + +First, we want to focus on things that are urgently needed right now. We need +to fix primary keys overflow risk and unblock other teams that are working on +database partitioning and sharding. + +We want to improve situation around bottlenecks that are known already, like +queuing mechanisms using the large table and things that are holding other +teams back. + +Extending CI/CD metrics is important to get a better sense of how the system +performs and to what growth should we expect. This will make it easier for us +to identify bottlenecks and perform more advanced capacity planning. + +As we work on first iterations we expect our Database Sharding team and +Database Scalability Working Group to make progress on patterns we will be able +to use to partition the large CI/CD dataset. We consider the strong time-decay +effect, related to the diminishing importance of pipelines with time, as an +opportunity we might want to seize. + +## Iterations + +Work required to achieve our next CI/CD scaling target is tracked in the +[GitLab CI/CD 20M builds per day scaling +target](https://gitlab.com/groups/gitlab-org/-/epics/5745) epic. + +## Status + +In progress. + +## Who + +Proposal: + + + +| Role | Who +|------------------------------|-------------------------| +| Author | Grzegorz Bizon | +| Architecture Evolution Coach | Kamil Trzciński | +| Engineering Leader | Darby Frey | +| Product Manager | Jackie Porter | +| Domain Expert / Verify | Fabio Pitino | +| Domain Expert / Database | Jose Finotto | +| Domain Expert / PostgreSQL | Nikolay Samokhvalov | + +DRIs: + +| Role | Who +|------------------------------|------------------------| +| Leadership | Darby Frey | +| Product | Jackie Porter | +| Engineering | Grzegorz Bizon | + +Domain experts: + +| Area | Who +|------------------------------|------------------------| +| Domain Expert / Verify | Fabio Pitino | +| Domain Expert / Database | Jose Finotto | +| Domain Expert / PostgreSQL | Nikolay Samokhvalov | + + diff --git a/doc/ci/directed_acyclic_graph/index.md b/doc/ci/directed_acyclic_graph/index.md index 5089aa6c9a5..dab9d8b78ae 100644 --- a/doc/ci/directed_acyclic_graph/index.md +++ b/doc/ci/directed_acyclic_graph/index.md @@ -94,3 +94,5 @@ To see the needs visualization, click on the **Needs** tab when viewing a pipeli Clicking a node highlights all the job paths it depends on. ![Needs visualization with path highlight](img/dag_graph_example_clicked_v13_1.png) + +You can also see `needs` relationships in [full pipeline graphs](../pipelines/index.md#view-full-pipeline-graph). diff --git a/doc/ci/jobs/index.md b/doc/ci/jobs/index.md index 1a31db09c92..a20fa1f8aa9 100644 --- a/doc/ci/jobs/index.md +++ b/doc/ci/jobs/index.md @@ -65,7 +65,7 @@ you can also see the reason it failed on the Job detail page. The order of jobs in a pipeline depends on the type of pipeline graph. -- For [regular pipeline graphs](../pipelines/index.md#regular-pipeline-graphs), jobs are sorted by name. +- For [full pipeline graphs](../pipelines/index.md#view-full-pipeline-graph), jobs are sorted by name. - For [pipeline mini graphs](../pipelines/index.md#pipeline-mini-graphs), jobs are sorted by severity and then by name. The order of severity is: diff --git a/doc/ci/pipelines/img/pipelines_graph_dependency_view_hover_v13_12.png b/doc/ci/pipelines/img/pipelines_graph_dependency_view_hover_v13_12.png new file mode 100644 index 0000000000000000000000000000000000000000..e73845b39b0d0f59dc9a12987890f1f1f80b72ab GIT binary patch literal 42573 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%0DU0qL5UXsDBuQp|Ie` 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 INh9e00Ge6C*#H0l literal 0 HcmV?d00001 diff --git a/doc/ci/pipelines/index.md b/doc/ci/pipelines/index.md index 6d013a43583..05413ff658b 100644 --- a/doc/ci/pipelines/index.md +++ b/doc/ci/pipelines/index.md @@ -325,23 +325,81 @@ Pipelines can be complex structures with many sequential and parallel jobs. To make it easier to understand the flow of a pipeline, GitLab has pipeline graphs for viewing pipelines and their statuses. -Pipeline graphs can be displayed in two different ways, depending on the page you +Pipeline graphs can be displayed as a large graph or a miniature representation, depending on the page you access the graph from. GitLab capitalizes the stages' names in the pipeline graphs. -### Regular pipeline graphs +### View full pipeline graph -> - [Visualization improved](https://gitlab.com/gitlab-org/gitlab/-/issues/276949) in GitLab 13.11. +> - [Visualization improvements introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276949) in GitLab 13.11. -Regular pipeline graphs show the names of the jobs in each stage. Regular pipeline graphs can -be found when you are on a [single pipeline page](#view-pipelines). For example: +The [pipeline details page](#view-pipelines) displays the full pipeline graph of +all the jobs in the pipeline. -![Pipelines example](img/pipelines_v13_11.png) +You can group the jobs by: + +- Stage, which arranges jobs in the same stage together in the same column. + + ![jobs grouped by stage](img/pipelines_graph_stage_view_v13_12.png) + +- [Job dependencies](#view-job-dependencies-in-the-pipeline-graph), which arranges + jobs based on their [`needs`](../yaml/README.md#needs) dependencies. [Multi-project pipeline graphs](../multi_project_pipelines.md#multi-project-pipeline-visualization) help you visualize the entire pipeline, including all cross-project inter-dependencies. **(PREMIUM)** +### View job dependencies in the pipeline graph + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298973) in GitLab 13.12. +> - [Deployed behind a feature flag](../../user/feature_flags.md), disabled by default. +> - Disabled on GitLab.com. +> - Not recommended for production use. +> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-job-dependency-view). **(FREE SELF)** + +This in-development feature might not be available for your use. There can be +[risks when enabling features still in development](../../user/feature_flags.md#risks-when-enabling-features-still-in-development). +Refer to this feature's version history for more details. + +You can arrange jobs in the pipeline graph based on their [`needs`](../yaml/README.md#needs) +dependencies. + +Jobs in the leftmost column run first, and jobs that depend on them are grouped in the next columns. + +For example, `build-job2` depends only on jobs in the first column, so it displays +in the second column from the left. `deploy-job2` depends on jobs in both the first +and second column and displays in the third column: + +![jobs grouped by needs dependency](img/pipelines_graph_dependency_view_v13_12.png) + +To add lines that show the `needs` relationships between jobs, select the **Show dependencies** toggle. +These lines are similar to the [needs visualization](../directed_acyclic_graph/index.md#needs-visualization): + +![jobs grouped by needs dependency with lines displayed](img/pipelines_graph_dependency_view_links_v13_12.png) + +To see the full `needs` dependency tree for a job, hover over it: + +![single job dependency tree highlighted](img/pipelines_graph_dependency_view_hover_v13_12.png) + +#### Enable or disable job dependency view **(FREE SELF)** + +The job dependency view is under development and not ready for production use. It is +deployed behind a feature flag that is **disabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) +can enable it. + +To enable it: + +```ruby +Feature.enable(:pipeline_graph_layers_view) +``` + +To disable it: + +```ruby +Feature.disable(:pipeline_graph_layers_view) +``` + ### Pipeline mini graphs Pipeline mini graphs take less space and can tell you at a @@ -356,6 +414,8 @@ Pipeline mini graphs allow you to see all related jobs for a single commit and t of each stage of your pipeline. This allows you to quickly see what failed and fix it. +Pipeline mini graphs only display jobs by stage. + Stages in pipeline mini graphs are collapsible. Hover your mouse over them and click to expand their jobs. | Mini graph | Mini graph expanded | diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index 2cd4ac1a17a..49181bebe75 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -486,7 +486,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.ci_external_pipelines` @@ -510,7 +510,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.ci_pipeline_config_auto_devops` @@ -518,11 +518,11 @@ Total pipelines from an Auto DevOps template [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175516_ci_pipeline_config_auto_devops.yml) -Group: `group::continuous integration` +Group: `group::configure` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.ci_pipeline_config_repository` @@ -534,7 +534,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.ci_pipeline_schedules` @@ -546,7 +546,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.ci_runners` @@ -558,7 +558,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.ci_triggers` @@ -570,7 +570,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.clusters` @@ -16128,7 +16128,7 @@ Tiers: `free` ### `usage_activity_by_stage.verify.ci_builds` -Unique builds in project +Unique count of builds in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175525_ci_builds.yml) @@ -16136,7 +16136,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.verify.ci_external_pipelines` @@ -16148,7 +16148,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.verify.ci_internal_pipelines` @@ -16160,7 +16160,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.verify.ci_pipeline_config_auto_devops` @@ -16168,11 +16168,11 @@ Total pipelines from an Auto DevOps template [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175531_ci_pipeline_config_auto_devops.yml) -Group: `group::continuous integration` +Group: `group::configure` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.verify.ci_pipeline_config_repository` @@ -16184,7 +16184,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.verify.ci_pipeline_schedules` @@ -16196,7 +16196,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.verify.ci_pipelines` @@ -16208,7 +16208,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.verify.ci_triggers` @@ -16220,7 +16220,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.verify.clusters_applications_runner` @@ -18132,7 +18132,7 @@ Tiers: `free` ### `usage_activity_by_stage_monthly.verify.ci_builds` -Unique builds in project +Unique monthly builds in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175542_ci_builds.yml) @@ -18140,11 +18140,11 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.verify.ci_external_pipelines` -Total pipelines in external repositories +Total pipelines in external repositories in a month [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml) @@ -18152,11 +18152,11 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.verify.ci_internal_pipelines` -Total pipelines in GitLab repositories +Total pipelines in GitLab repositories in a month [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml) @@ -18164,7 +18164,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.verify.ci_pipeline_config_auto_devops` @@ -18172,15 +18172,15 @@ Total pipelines from an Auto DevOps template [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175548_ci_pipeline_config_auto_devops.yml) -Group: `group::continuous integration` +Group: `group::configure` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.verify.ci_pipeline_config_repository` -Total Pipelines from templates in repository +Total Monthly Pipelines from templates in repository [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml) @@ -18188,11 +18188,11 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.verify.ci_pipeline_schedules` -Pipeline schedules in GitLab +Total monthly Pipeline schedules in GitLab [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml) @@ -18200,11 +18200,11 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.verify.ci_pipelines` - Distinct users triggering pipelines in a month +Distinct users triggering pipelines in a month [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175554_ci_pipelines.yml) @@ -18212,7 +18212,7 @@ Group: `group::continuous integration` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate`, `free` ### `usage_activity_by_stage_monthly.verify.ci_triggers` @@ -18240,7 +18240,7 @@ Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.verify.projects_reporting_ci_cd_back_to_github` -Projects with a GitHub service pipeline enabled +Projects with a GitHub repository mirror pipeline enabled [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175558_projects_reporting_ci_cd_back_to_github.yml) diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md index 8fb400c796e..100bb71cc8c 100644 --- a/doc/install/azure/index.md +++ b/doc/install/azure/index.md @@ -31,20 +31,20 @@ Because GitLab is already installed in a pre-configured image, all you have to d create a new VM: 1. [Visit the GitLab offering in the marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/gitlabinc1586447921813.gitlabee?tab=Overview) -1. Select **Get it now** and you will be presented with the **Create this app in Azure** window. +1. Select **Get it now** and the **Create this app in Azure** window opens. Select **Continue**. 1. Select one of the following options from the Azure portal: - Select **Create** to create a VM from scratch. - Select **Start with a pre-set configuration** to get started with some pre-configured options. You can modify these configurations at any time. -For the sake of this guide, we'll create the VM from scratch, so +For the sake of this guide, let's create the VM from scratch, so select **Create**. NOTE: -Be aware that while your VM is active (known as "allocated"), it incurs -compute charges for which you'll be billed. Even if you're using the -free trial credits, you'll want to know +Be aware that Azure incurs compute charges whenever your VM is +active (known as "allocated"), even if you're using free trial +credits. [how to properly shutdown an Azure VM to save money](https://build5nines.com/properly-shutdown-azure-vm-to-save-money/). See the [Azure pricing calculator](https://azure.microsoft.com/en-us/pricing/calculator/) to learn how much resources can cost. @@ -68,7 +68,7 @@ The first items you need to configure are the basic settings of the underlying v is covered by the `D4s_v3` size, select that option. 1. Set the authentication type to **SSH public key**. 1. Enter a user name or leave the one that is automatically created. This is - the user you'll use to connect to the VM through SSH. By default, the user + the user Azure uses to connect to the VM through SSH. By default, the user has root access. 1. Determine if you want to provide your own SSH key or let Azure create one for you. Read the [SSH documentation](../../ssh/README.md) to learn more about how to set up SSH @@ -103,7 +103,7 @@ The GitLab image in the marketplace has the following ports open by default: | 22 | Enable our VM to respond to SSH connection requests, allowing public access (with authentication) to remote terminal sessions. | If you want to change the ports or add any rules, you can do it -after the VM is created by going to the Networking settings in the left sidebar, +after the VM is created by selecting Networking settings in the left sidebar, while in the VM dashboard. ### Configure the Management tab @@ -126,13 +126,13 @@ resources. You don't need to change the default settings. The final tab presents you with all of your selected options, where you can review and modify your choices from the -previous steps. Azure will run validation tests in the background, +previous steps. Azure runs validation tests in the background, and if you provided all of the required settings, you can create the VM. After you select **Create**, if you had opted for Azure to create an SSH key pair -for you, you'll be asked to download the private SSH key. Download the key, as you'll -need it to SSH into the VM. +for you, a prompt appears to download the private SSH key. Download the key, as it's +needed to SSH into the VM. After you download the key, the deployment begins. @@ -153,11 +153,11 @@ to assign a descriptive DNS name to the VM: 1. From the VM dashboard, select **Configure** under **DNS name**. 1. Enter a descriptive DNS name for your instance in the **DNS name label** field, - for example `gitlab-prod`. This will make the VM accessible at + for example `gitlab-prod`. This makes the VM accessible at `gitlab-prod.eastus.cloudapp.azure.com`. 1. Select **Save** for the changes to take effect. -Eventually, you'll want to use your own domain name. To do this, you need to add a DNS `A` record +Eventually, most users want to use their own domain name. For you to do this, you need to add a DNS `A` record with your domain registrar that points to the public IP address of your Azure VM. You can use [Azure's DNS](https://docs.microsoft.com/en-us/azure/dns/dns-delegate-domain-azure-dns) or some [other registrar](https://docs.gitlab.com/omnibus/settings/dns.html). @@ -165,15 +165,15 @@ or some [other registrar](https://docs.gitlab.com/omnibus/settings/dns.html). ### Change the GitLab external URL GitLab uses `external_url` in its configuration file to set up the domain name. -If you don't set this up, when you visit the Azure friendly name, you'll -instead be redirected to the public IP. +If you don't set this up, when you visit the Azure friendly name, the browser will +redirect you to the public IP. To set up the GitLab external URL: 1. Connect to GitLab through SSH by going to **Settings > Connect** from the VM dashboard, and follow the instructions. Remember to sign in with the username and SSH key you specified when you [created the VM](#configure-the-basics-tab). - The Azure VM domain name will be the one you + The Azure VM domain name is the one you [set up previously](#set-up-a-domain-name). If you didn't set up a domain name for your VM, you can use the IP address in its place. @@ -189,10 +189,10 @@ To set up the GitLab external URL: 1. Open `/etc/gitlab/gitlab.rb` with your editor. 1. Find `external_url` and replace it with your own domain name. For the sake - of this example, we'll use the friendly domain name that Azure set up. - If you use `https` in the URL, Let's Encrypt will be - [automatically enabled](https://docs.gitlab.com/omnibus/settings/ssl.html#lets-encrypt-integration), - and you'll have HTTPS by default: + of this example, use the default domain name Azure sets up. + Using `https` in the URL + [automatically enables](https://docs.gitlab.com/omnibus/settings/ssl.html#lets-encrypt-integration), + Let's Encrypt, and sets HTTPS by default: ```ruby external_url 'https://gitlab-prod.eastus.cloudapp.azure.com' @@ -221,7 +221,7 @@ You can now visit GitLab with your browser at the new external URL. Use the domain name you set up earlier to visit your new GitLab instance in your browser. In this example, it's `https://gitlab-prod.eastus.cloudapp.azure.com`. -The first thing you'll see is the sign-in page. GitLab creates an admin user by default. +The first thing that appears is the sign-in page. GitLab creates an admin user by default. The credentials are: - Username: `root` @@ -239,7 +239,7 @@ in this section whenever you need to update GitLab. ### Check the current version To determine the version of GitLab you're currently running, -go to the **{admin}** **Admin Area**, and you will find the version +go to the **{admin}** **Admin Area**, and find the version under the **Components** table. If there's a newer available version of GitLab that contains one or more @@ -259,7 +259,7 @@ To update GitLab to the latest version: ``` This command updates GitLab and its associated components to the latest versions, - and can take time to complete. You'll see various update tasks being + and can take time to complete. During this time, the terminal shows various update tasks being completed in your terminal. NOTE: @@ -267,8 +267,8 @@ To update GitLab to the latest version: `E: The repository 'https://packages.gitlab.com/gitlab/gitlab-ee/debian buster InRelease' is not signed.`, see the [troubleshooting section](#update-the-gpg-key-for-the-gitlab-repositories). -1. After the update process is complete, you'll see a message like the - following: +1. After the update process is complete, a message like the + following appears: ```plaintext Upgrade complete! If your GitLab server is misbehaving try running @@ -300,7 +300,7 @@ GPG key. The pre-configured GitLab image in Azure (provided by Bitnami) uses a GPG key [deprecated in April 2020](https://about.gitlab.com/blog/2020/03/30/gpg-key-for-gitlab-package-repositories-metadata-changing/). -If you try to update the repositories, you'll get the following error: +If you try to update the repositories, the system returns the following error: diff --git a/doc/user/application_security/api_fuzzing/index.md b/doc/user/application_security/api_fuzzing/index.md index f8e861500a4..ecd185221d9 100644 --- a/doc/user/application_security/api_fuzzing/index.md +++ b/doc/user/application_security/api_fuzzing/index.md @@ -599,7 +599,6 @@ repository's root as `.gitlab-api-fuzzing.yml`. | `FUZZAPI_TARGET_URL` | Base URL of API testing target. | |[`FUZZAPI_CONFIG`](#configuration-files) | API Fuzzing configuration file. Defaults to `.gitlab-apifuzzer.yml`. | |[`FUZZAPI_PROFILE`](#configuration-files) | Configuration profile to use during testing. Defaults to `Quick`. | -| `FUZZAPI_REPORT` | Scan report filename. Defaults to `gl-api_fuzzing-report.xml`. | |[`FUZZAPI_OPENAPI`](#openapi-specification) | OpenAPI specification file or URL. | |[`FUZZAPI_HAR`](#http-archive-har) | HTTP Archive (HAR) file. | |[`FUZZAPI_POSTMAN_COLLECTION`](#postman-collection) | Postman Collection file. | @@ -611,18 +610,6 @@ repository's root as `.gitlab-api-fuzzing.yml`. |[`FUZZAPI_HTTP_USERNAME`](#http-basic-authentication) | Username for HTTP authentication. | |[`FUZZAPI_HTTP_PASSWORD`](#http-basic-authentication) | Password for HTTP authentication. | - - ### Overrides API Fuzzing provides a method to add or override specific items in your request, for example: diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb index 1fac00337a3..97988d8aa13 100644 --- a/lib/gitlab/ci/ansi2html.rb +++ b/lib/gitlab/ci/ansi2html.rb @@ -30,6 +30,8 @@ module Gitlab Converter.new.convert(ansi, state) end + Result = Struct.new(:html, :state, :append, :truncated, :offset, :size, :total, keyword_init: true) # rubocop:disable Lint/StructNewOverride + class Converter def on_0(_) reset @@ -278,9 +280,7 @@ module Gitlab close_open_tags - # TODO: replace OpenStruct with a better type - # https://gitlab.com/gitlab-org/gitlab/issues/34305 - OpenStruct.new( + Ansi2html::Result.new( html: @out.force_encoding(Encoding.default_external), state: state, append: append, diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml index 95aab128ec6..8fa33026011 100644 --- a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml @@ -5,41 +5,13 @@ # How to set: https://docs.gitlab.com/ee/ci/yaml/#variables variables: - FUZZAPI_PROFILE: Quick FUZZAPI_VERSION: "1" - FUZZAPI_CONFIG: .gitlab-api-fuzzing.yml - FUZZAPI_TIMEOUT: 30 - FUZZAPI_REPORT: gl-api-fuzzing-report.json - FUZZAPI_REPORT_ASSET_PATH: assets - # - # Wait up to 5 minutes for API Fuzzer and target url to become - # available (non 500 response to HTTP(s)) - FUZZAPI_SERVICE_START_TIMEOUT: "300" - # SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers" FUZZAPI_IMAGE: ${SECURE_ANALYZERS_PREFIX}/api-fuzzing:${FUZZAPI_VERSION} - # - -apifuzzer_fuzz_unlicensed: - stage: fuzz - allow_failure: true - rules: - - if: '$GITLAB_FEATURES !~ /\bapi_fuzzing\b/ && $API_FUZZING_DISABLED == null' - - when: never - script: - - | - echo "Error: Your GitLab project is not licensed for API Fuzzing." - - exit 1 apifuzzer_fuzz: stage: fuzz image: $FUZZAPI_IMAGE - variables: - FUZZAPI_PROJECT: $CI_PROJECT_PATH - FUZZAPI_API: http://localhost:80 - FUZZAPI_NEW_REPORT: 1 - FUZZAPI_LOG_SCANNER: gl-apifuzzing-api-scanner.log - TZ: America/Los_Angeles allow_failure: true rules: - if: $API_FUZZING_DISABLED @@ -47,44 +19,16 @@ apifuzzer_fuzz: - if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH && $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME when: never - - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bapi_fuzzing\b/ + - if: $CI_COMMIT_BRANCH script: - # - # Validate options - - | - if [ "$FUZZAPI_HAR$FUZZAPI_OPENAPI$FUZZAPI_POSTMAN_COLLECTION" == "" ]; then \ - echo "Error: One of FUZZAPI_HAR, FUZZAPI_OPENAPI, or FUZZAPI_POSTMAN_COLLECTION must be provided."; \ - echo "See https://docs.gitlab.com/ee/user/application_security/api_fuzzing/ for information on how to configure API Fuzzing."; \ - exit 1; \ - fi - # - # Run user provided pre-script - - sh -c "$FUZZAPI_PRE_SCRIPT" - # - # Make sure asset path exists - - mkdir -p $FUZZAPI_REPORT_ASSET_PATH - # - # Start API Security background process - - dotnet /peach/Peach.Web.dll &> $FUZZAPI_LOG_SCANNER & - - APISEC_PID=$! - # - # Start scanning - - worker-entry - # - # Run user provided post-script - - sh -c "$FUZZAPI_POST_SCRIPT" - # - # Shutdown API Security - - kill $APISEC_PID - - wait $APISEC_PID - # + - /peach/analyzer-fuzz-api artifacts: when: always paths: - - $FUZZAPI_REPORT_ASSET_PATH - - $FUZZAPI_REPORT - - $FUZZAPI_LOG_SCANNER + - gl-assets + - gl-api-fuzzing-report.json + - gl-*.log reports: - api_fuzzing: $FUZZAPI_REPORT + api_fuzzing: gl-api-fuzzing-report.json # end diff --git a/lib/gitlab/graphql/docs/templates/default.md.haml b/lib/gitlab/graphql/docs/templates/default.md.haml index 57f4409d76f..ef1de1a3e5c 100644 --- a/lib/gitlab/graphql/docs/templates/default.md.haml +++ b/lib/gitlab/graphql/docs/templates/default.md.haml @@ -86,8 +86,8 @@ | `nodes` | `[item!]` | The items in the current page. | The precise type of `Edge` and `Item` depends on the kind of connection. A - [`UserConnection`](#userconnection) will have nodes that have the type - [`[User!]`](#user), and edges that have the type [`UserEdge`](#useredge). + [`ProjectConnection`](#projectconnection) will have nodes that have the type + [`[Project!]`](#project), and edges that have the type [`ProjectEdge`](#projectedge). ### Connection types diff --git a/lib/gitlab/graphql/present/field_extension.rb b/lib/gitlab/graphql/present/field_extension.rb index 2e211b70d35..050a3a276ea 100644 --- a/lib/gitlab/graphql/present/field_extension.rb +++ b/lib/gitlab/graphql/present/field_extension.rb @@ -13,7 +13,8 @@ module Gitlab # inner Schema::Object#object. This depends on whether the field # has a @resolver_proc or not. if object.is_a?(::Types::BaseObject) - object.present(field.owner, attrs) + type = field.owner.kind.abstract? ? object.class : field.owner + object.present(type, attrs) yield(object, arguments) else # This is the legacy code-path, hit if the field has a @resolver_proc diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5b0efc70209..99906409900 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4047,11 +4047,6 @@ msgid_plural "ApprovalRuleRemove|%d members" msgstr[0] "" msgstr[1] "" -msgid "ApprovalRuleRemove|Approvals from this member are not revoked." -msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked." -msgstr[0] "" -msgstr[1] "" - msgid "ApprovalRuleRemove|Remove approval gate" msgstr "" @@ -4061,8 +4056,10 @@ msgstr "" msgid "ApprovalRuleRemove|You are about to remove the %{name} approval gate. Approval from this service is not revoked." msgstr "" -msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}." -msgstr "" +msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{strongStart}%{count} member%{strongEnd}. Approvals from this member are not revoked." +msgid_plural "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{strongStart}%{count} members%{strongEnd}. Approvals from these members are not revoked." +msgstr[0] "" +msgstr[1] "" msgid "ApprovalRuleSummary|%d member" msgid_plural "ApprovalRuleSummary|%d members" @@ -21041,9 +21038,6 @@ msgstr "" msgid "More Information" msgstr "" -msgid "More Information." -msgstr "" - msgid "More Slack commands" msgstr "" @@ -36086,6 +36080,9 @@ msgstr "" msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{wikiLinkStart}the page%{wikiLinkEnd} and make sure your changes will not unintentionally remove theirs." msgstr "" +msgid "WikiPage|Cancel" +msgstr "" + msgid "WikiPage|Commit message" msgstr "" @@ -36101,9 +36098,21 @@ msgstr "" msgid "WikiPage|Format" msgstr "" +msgid "WikiPage|More Information." +msgstr "" + msgid "WikiPage|Page title" msgstr "" +msgid "WikiPage|Save changes" +msgstr "" + +msgid "WikiPage|Switch to old editor" +msgstr "" + +msgid "WikiPage|Switching to the old editor will discard any changes you've made in the new editor." +msgstr "" + msgid "WikiPage|Tip: You can move this page by adding the path to the beginning of the title." msgstr "" @@ -36119,9 +36128,15 @@ msgstr "" msgid "WikiPage|Update %{pageTitle}" msgstr "" +msgid "WikiPage|Use new editor" +msgstr "" + msgid "WikiPage|Write your content or drag files here…" msgstr "" +msgid "WikiPage|You are editing this page with Content Editor. This editor is in beta and may not display the page's contents properly." +msgstr "" + msgid "Wikis" msgstr "" diff --git a/spec/factories/alert_management/alerts.rb b/spec/factories/alert_management/alerts.rb index 0131eca68a1..f63a3c9f7f5 100644 --- a/spec/factories/alert_management/alerts.rb +++ b/spec/factories/alert_management/alerts.rb @@ -95,6 +95,10 @@ FactoryBot.define do severity { 'unknown' } end + trait :threat_monitoring do + domain { :threat_monitoring } + end + trait :prometheus do monitoring_tool { Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus] } payload do diff --git a/spec/fixtures/api/schemas/graphql/packages/package_conan_metadata.json b/spec/fixtures/api/schemas/graphql/packages/package_conan_metadata.json index 31bb861ced5..acfef595b08 100644 --- a/spec/fixtures/api/schemas/graphql/packages/package_conan_metadata.json +++ b/spec/fixtures/api/schemas/graphql/packages/package_conan_metadata.json @@ -8,29 +8,28 @@ "packageUsername", "packageChannel", "recipe", - "recipePath", - "packageName" + "recipePath" ], "properties": { "id": { "type": "string" }, - "created_at": { + "createdAt": { "type": "string" }, - "updated_at": { + "updatedAt": { "type": "string" }, - "package_username": { + "packageUsername": { "type": "string" }, - "package_channel": { + "packageChannel": { "type": "string" }, "recipe": { "type": "string" }, - "recipe_path": { + "recipePath": { "type": "string" } } diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js index c3c8654ce3d..020a6f4c37d 100644 --- a/spec/frontend/content_editor/components/content_editor_spec.js +++ b/spec/frontend/content_editor/components/content_editor_spec.js @@ -1,36 +1,38 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { EditorContent } from 'tiptap'; +import waitForPromises from 'helpers/wait_for_promises'; import ContentEditor from '~/content_editor/components/content_editor.vue'; import TopToolbar from '~/content_editor/components/top_toolbar.vue'; import createEditor from '~/content_editor/services/create_editor'; -import createMarkdownSerializer from '~/content_editor/services/markdown_serializer'; describe('ContentEditor', () => { let wrapper; let editor; - const buildWrapper = async () => { - editor = await createEditor({ serializer: createMarkdownSerializer({ toHTML: () => '' }) }); - wrapper = shallowMount(ContentEditor, { + const createWrapper = async (_editor) => { + wrapper = mount(ContentEditor, { propsData: { - editor, + editor: _editor, }, }); }; + beforeEach(async () => { + editor = await createEditor({ renderMarkdown: () => 'sample text' }); + createWrapper(editor); + + await waitForPromises(); + }); + afterEach(() => { wrapper.destroy(); }); it('renders editor content component and attaches editor instance', async () => { - await buildWrapper(); - expect(wrapper.findComponent(EditorContent).props().editor).toBe(editor); }); it('renders top toolbar component and attaches editor instance', async () => { - await buildWrapper(); - expect(wrapper.findComponent(TopToolbar).props().editor).toBe(editor); }); }); diff --git a/spec/frontend/content_editor/services/create_editor_spec.js b/spec/frontend/content_editor/services/create_editor_spec.js index a6a2327a11f..1f297cb5958 100644 --- a/spec/frontend/content_editor/services/create_editor_spec.js +++ b/spec/frontend/content_editor/services/create_editor_spec.js @@ -11,12 +11,12 @@ describe('content_editor/services/create_editor', () => { deserialize: jest.fn(), }); - it('sets gl-py-4 gl-px-5 class selectors to editor attributes', async () => { + it('sets gl-outline-0! class selector to editor attributes', async () => { const editor = await createEditor({ renderMarkdown }); expect(editor.options.editorProps).toMatchObject({ attributes: { - class: 'gl-py-4 gl-px-5', + class: 'gl-outline-0!', }, }); }); diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js index 8ab0b87d2ee..bc32d3904b4 100644 --- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js +++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js @@ -1,6 +1,12 @@ +import { GlAlert, GlButton, GlLoadingIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import ContentEditor from '~/content_editor/components/content_editor.vue'; import WikiForm from '~/pages/shared/wikis/components/wiki_form.vue'; +import MarkdownField from '~/vue_shared/components/markdown/field.vue'; describe('WikiForm', () => { let wrapper; @@ -11,10 +17,26 @@ describe('WikiForm', () => { const findContent = () => wrapper.find('#wiki_content'); const findMessage = () => wrapper.find('#wiki_message'); const findSubmitButton = () => wrapper.findByTestId('wiki-submit-button'); - const findCancelButton = () => wrapper.findByTestId('wiki-cancel-button'); - const findTitleHelpLink = () => wrapper.findByTestId('wiki-title-help-link'); + const findCancelButton = () => wrapper.findByRole('link', { name: 'Cancel' }); + const findUseNewEditorButton = () => wrapper.findByRole('button', { name: 'Use new editor' }); + const findTitleHelpLink = () => wrapper.findByRole('link', { name: 'More Information.' }); const findMarkdownHelpLink = () => wrapper.findByTestId('wiki-markdown-help-link'); + const setFormat = (value) => { + const format = findFormat(); + format.find(`option[value=${value}]`).setSelected(); + format.element.dispatchEvent(new Event('change')); + }; + + const triggerFormSubmit = () => findForm().element.dispatchEvent(new Event('submit')); + + const dispatchBeforeUnload = () => { + const e = new Event('beforeunload'); + jest.spyOn(e, 'preventDefault'); + window.dispatchEvent(e); + return e; + }; + const pageInfoNew = { persisted: false, uploadsPath: '/project/path/-/wikis/attachments', @@ -35,7 +57,10 @@ describe('WikiForm', () => { path: '/project/path/-/wikis/home', }; - function createWrapper(persisted = false, pageInfo = {}) { + function createWrapper( + persisted = false, + { pageInfo, glFeatures } = { glFeatures: { wikiContentEditor: false } }, + ) { wrapper = extendedWrapper( mount( WikiForm, @@ -51,13 +76,12 @@ describe('WikiForm', () => { ...(persisted ? pageInfoPersisted : pageInfoNew), ...pageInfo, }, + glFeatures, }, }, { attachToDocument: true }, ), ); - - jest.spyOn(wrapper.vm, 'onBeforeUnload'); } afterEach(() => { @@ -101,7 +125,7 @@ describe('WikiForm', () => { `('updates the link help message when format=$value is selected', async ({ value, text }) => { createWrapper(); - findFormat().find(`option[value=${value}]`).setSelected(); + setFormat(value); await wrapper.vm.$nextTick(); @@ -113,9 +137,9 @@ describe('WikiForm', () => { await wrapper.vm.$nextTick(); - window.dispatchEvent(new Event('beforeunload')); - - expect(wrapper.vm.onBeforeUnload).not.toHaveBeenCalled(); + const e = dispatchBeforeUnload(); + expect(typeof e.returnValue).not.toBe('string'); + expect(e.preventDefault).not.toHaveBeenCalled(); }); it.each` @@ -156,19 +180,18 @@ describe('WikiForm', () => { }); it('sets before unload warning', () => { - window.dispatchEvent(new Event('beforeunload')); + const e = dispatchBeforeUnload(); - expect(wrapper.vm.onBeforeUnload).toHaveBeenCalled(); + expect(e.preventDefault).toHaveBeenCalledTimes(1); }); it('when form submitted, unsets before unload warning', async () => { - findForm().element.dispatchEvent(new Event('submit')); + triggerFormSubmit(); await wrapper.vm.$nextTick(); - window.dispatchEvent(new Event('beforeunload')); - - expect(wrapper.vm.onBeforeUnload).not.toHaveBeenCalled(); + const e = dispatchBeforeUnload(); + expect(e.preventDefault).not.toHaveBeenCalled(); }); }); @@ -219,4 +242,161 @@ describe('WikiForm', () => { }, ); }); + + describe('when feature flag wikiContentEditor is enabled', () => { + beforeEach(() => { + createWrapper(true, { glFeatures: { wikiContentEditor: true } }); + }); + + it.each` + format | buttonExists + ${'markdown'} | ${true} + ${'rdoc'} | ${false} + `( + 'switch to new editor button exists: $buttonExists if format is $format', + async ({ format, buttonExists }) => { + setFormat(format); + + await wrapper.vm.$nextTick(); + + expect(findUseNewEditorButton().exists()).toBe(buttonExists); + }, + ); + + const assertOldEditorIsVisible = () => { + expect(wrapper.findComponent(ContentEditor).exists()).toBe(false); + expect(wrapper.findComponent(MarkdownField).exists()).toBe(true); + expect(wrapper.findComponent(GlAlert).exists()).toBe(false); + }; + + it('shows old editor by default', assertOldEditorIsVisible); + + describe('switch format to rdoc', () => { + beforeEach(async () => { + setFormat('rdoc'); + + await wrapper.vm.$nextTick(); + }); + + it('continues to show the old editor', assertOldEditorIsVisible); + + describe('switch format back to markdown', () => { + beforeEach(async () => { + setFormat('rdoc'); + + await wrapper.vm.$nextTick(); + }); + + it( + 'still shows the old editor and does not automatically switch to the content editor ', + assertOldEditorIsVisible, + ); + }); + }); + + describe('clicking "use new editor"', () => { + let mock; + + beforeEach(async () => { + mock = new MockAdapter(axios); + mock.onPost(/preview-markdown/).reply(200, { body: '

hello world

' }); + + findUseNewEditorButton().trigger('click'); + + await wrapper.vm.$nextTick(); + }); + + afterEach(() => { + mock.restore(); + }); + + it('shows a loading indicator for the rich text editor', () => { + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + }); + + it('shows a warning alert that the rich text editor is in beta', () => { + expect(wrapper.findComponent(GlAlert).text()).toContain( + "You are editing this page with Content Editor. This editor is in beta and may not display the page's contents properly.", + ); + }); + + it('shows the rich text editor when loading finishes', async () => { + // wait for content editor to load + await waitForPromises(); + + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(ContentEditor).exists()).toBe(true); + }); + + it('disables the format dropdown', () => { + expect(findFormat().element.getAttribute('disabled')).toBeDefined(); + }); + + describe('when wiki content is updated', () => { + beforeEach(async () => { + // wait for content editor to load + await waitForPromises(); + + wrapper.vm.editor.setContent('

hello __world__ from content editor

', true); + + await waitForPromises(); + + return wrapper.vm.$nextTick(); + }); + + it('sets before unload warning', () => { + const e = dispatchBeforeUnload(); + expect(e.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('unsets before unload warning on form submit', async () => { + triggerFormSubmit(); + + await wrapper.vm.$nextTick(); + + const e = dispatchBeforeUnload(); + expect(e.preventDefault).not.toHaveBeenCalled(); + }); + }); + + it('updates content from content editor on form submit', async () => { + // old value + expect(findContent().element.value).toBe('My page content'); + + // wait for content editor to load + await waitForPromises(); + + triggerFormSubmit(); + + await wrapper.vm.$nextTick(); + + expect(findContent().element.value).toBe('hello **world**'); + }); + + describe('clicking "switch to old editor"', () => { + beforeEach(async () => { + // wait for content editor to load + await waitForPromises(); + + wrapper.vm.editor.setContent('

hello __world__ from content editor

', true); + wrapper.findComponent(GlAlert).findComponent(GlButton).trigger('click'); + + await wrapper.vm.$nextTick(); + }); + + it('switches to old editor', () => { + expect(wrapper.findComponent(ContentEditor).exists()).toBe(false); + expect(wrapper.findComponent(MarkdownField).exists()).toBe(true); + }); + + it('does not show a warning alert about content editor', () => { + expect(wrapper.findComponent(GlAlert).exists()).toBe(false); + }); + + it('the old editor retains its old value and does not use the content from the content editor', () => { + expect(findContent().element.value).toBe('My page content'); + }); + }); + }); + }); }); diff --git a/spec/graphql/resolvers/group_packages_resolver_spec.rb b/spec/graphql/resolvers/group_packages_resolver_spec.rb index 59438b8d5ad..d2695a3c43a 100644 --- a/spec/graphql/resolvers/group_packages_resolver_spec.rb +++ b/spec/graphql/resolvers/group_packages_resolver_spec.rb @@ -8,11 +8,46 @@ RSpec.describe Resolvers::GroupPackagesResolver do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public, group: group) } - let_it_be(:package) { create(:package, project: project) } + + let(:args) do + { sort: :created_desc } + end describe '#resolve' do - subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: group) } + subject { resolve(described_class, ctx: { current_user: user }, obj: group, args: args).to_a } - it { is_expected.to contain_exactly(package) } + context 'without sort' do + let_it_be(:package) { create(:package, project: project) } + + it { is_expected.to contain_exactly(package) } + end + + context 'with a sort argument' do + let_it_be(:project2) { create(:project, :public, group: group) } + + let_it_be(:sort_repository) do + create(:conan_package, name: 'bar', project: project, created_at: 1.day.ago, version: "1.0.0") + end + + let_it_be(:sort_repository2) do + create(:maven_package, name: 'foo', project: project2, created_at: 1.hour.ago, version: "2.0.0") + end + + [:created_desc, :name_desc, :version_desc, :type_asc, :project_path_desc].each do |order| + context "#{order}" do + let(:args) { { sort: order } } + + it { is_expected.to eq([sort_repository2, sort_repository]) } + end + end + + [:created_asc, :name_asc, :version_asc, :type_desc, :project_path_asc].each do |order| + context "#{order}" do + let(:args) { { sort: order } } + + it { is_expected.to eq([sort_repository, sort_repository2]) } + end + end + end end end diff --git a/spec/graphql/resolvers/project_packages_resolver_spec.rb b/spec/graphql/resolvers/project_packages_resolver_spec.rb index c8105ed2a38..695db64743d 100644 --- a/spec/graphql/resolvers/project_packages_resolver_spec.rb +++ b/spec/graphql/resolvers/project_packages_resolver_spec.rb @@ -7,11 +7,44 @@ RSpec.describe Resolvers::ProjectPackagesResolver do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public) } - let_it_be(:package) { create(:package, project: project) } + + let(:args) do + { sort: :created_desc } + end describe '#resolve' do - subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: project) } + subject { resolve(described_class, ctx: { current_user: user }, obj: project, args: args).to_a } - it { is_expected.to contain_exactly(package) } + context 'without sort' do + let_it_be(:package) { create(:package, project: project) } + + it { is_expected.to contain_exactly(package) } + end + + context 'with a sort argument' do + let_it_be(:sort_repository) do + create(:conan_package, name: 'bar', project: project, created_at: 1.day.ago, version: "1.0.0") + end + + let_it_be(:sort_repository2) do + create(:maven_package, name: 'foo', project: project, created_at: 1.hour.ago, version: "2.0.0") + end + + [:created_desc, :name_desc, :version_desc, :type_asc].each do |order| + context "#{order}" do + let(:args) { { sort: order } } + + it { is_expected.to eq([sort_repository2, sort_repository]) } + end + end + + [:created_asc, :name_asc, :version_asc, :type_desc].each do |order| + context "#{order}" do + let(:args) { { sort: order } } + + it { is_expected.to eq([sort_repository, sort_repository2]) } + end + end + end end end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index d3dcdd260b0..a877e19c069 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -21,6 +21,7 @@ RSpec.describe GitlabSchema.types['Query'] do user users issue + merge_request usage_trends_measurements runner_platforms ] @@ -60,11 +61,21 @@ RSpec.describe GitlabSchema.types['Query'] do describe 'issue field' do subject { described_class.fields['issue'] } - it 'returns issue' do + it "finds an issue by it's gid" do + is_expected.to have_graphql_arguments(:id) is_expected.to have_graphql_type(Types::IssueType) end end + describe 'merge_request field' do + subject { described_class.fields['mergeRequest'] } + + it "finds a merge_request by it's gid" do + is_expected.to have_graphql_arguments(:id) + is_expected.to have_graphql_type(Types::MergeRequestType) + end + end + describe 'usage_trends_measurements field' do subject { described_class.fields['usageTrendsMeasurements'] } diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb index d9e67ff348b..7d73727b041 100644 --- a/spec/graphql/types/user_type_spec.rb +++ b/spec/graphql/types/user_type_spec.rb @@ -5,7 +5,11 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['User'] do specify { expect(described_class.graphql_name).to eq('User') } - specify { expect(described_class).to require_graphql_authorizations(:read_user) } + specify do + runtime_type = described_class.resolve_type(build(:user), {}) + + expect(runtime_type).to require_graphql_authorizations(:read_user) + end it 'has the expected fields' do expected_fields = %w[ diff --git a/spec/lib/gitlab/graphql/present/field_extension_spec.rb b/spec/lib/gitlab/graphql/present/field_extension_spec.rb index 5e66e16d655..59e3bcbf418 100644 --- a/spec/lib/gitlab/graphql/present/field_extension_spec.rb +++ b/spec/lib/gitlab/graphql/present/field_extension_spec.rb @@ -33,6 +33,38 @@ RSpec.describe Gitlab::Graphql::Present::FieldExtension do end end + context 'when the field is declared on an interface, and implemented by a presenter' do + let(:interface) do + Module.new do + include ::Types::BaseInterface + + field :interface_field, GraphQL::STRING_TYPE, null: true + end + end + + let(:implementation) do + type = fresh_object_type('Concrete') + type.present_using(concrete_impl) + type.implements(interface) + type + end + + def concrete_impl + Class.new(base_presenter) do + def interface_field + 'made of concrete' + end + end + end + + it 'resolves the interface field using the implementation from the presenter' do + field = ::Types::BaseField.new(name: :interface_field, type: GraphQL::STRING_TYPE, null: true, owner: interface) + value = resolve_field(field, object, object_type: implementation) + + expect(value).to eq 'made of concrete' + end + end + describe 'interactions with inheritance' do def parent type = fresh_object_type('Parent') diff --git a/spec/requests/api/graphql/group/packages_spec.rb b/spec/requests/api/graphql/group/packages_spec.rb index 85775598b2e..409771b13ac 100644 --- a/spec/requests/api/graphql/group/packages_spec.rb +++ b/spec/requests/api/graphql/group/packages_spec.rb @@ -12,11 +12,11 @@ RSpec.describe 'getting a package list for a group' do let_it_be(:group_two_project) { create(:project, :repository, group: group_two) } let_it_be(:current_user) { create(:user) } - let_it_be(:package) { create(:package, project: project) } let_it_be(:npm_package) { create(:npm_package, project: group_two_project) } - let_it_be(:maven_package) { create(:maven_package, project: project) } - let_it_be(:debian_package) { create(:debian_package, project: another_project) } - let_it_be(:composer_package) { create(:composer_package, project: another_project) } + let_it_be(:maven_package) { create(:maven_package, project: project, name: 'tab', version: '4.0.0', created_at: 5.days.ago) } + let_it_be(:package) { create(:npm_package, project: project, name: 'uab', version: '5.0.0', created_at: 4.days.ago) } + let_it_be(:composer_package) { create(:composer_package, project: another_project, name: 'vab', version: '6.0.0', created_at: 3.days.ago) } + let_it_be(:debian_package) { create(:debian_package, project: another_project, name: 'zab', version: '7.0.0', created_at: 2.days.ago) } let_it_be(:composer_metadatum) do create(:composer_metadatum, package: composer_package, target_sha: 'afdeh', @@ -46,6 +46,42 @@ RSpec.describe 'getting a package list for a group' do it_behaves_like 'group and project packages query' + describe 'sorting and pagination' do + let_it_be(:ascending_packages) { [maven_package, package, composer_package, debian_package].map { |package| global_id_of(package)} } + + let(:data_path) { [:group, :packages] } + + before do + resource.add_reporter(current_user) + end + + [:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC].each do |order| + context "#{order}" do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { order } + let(:first_param) { 4 } + let(:expected_results) { ascending_packages } + end + end + end + + [:CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order| + context "#{order}" do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { order } + let(:first_param) { 4 } + let(:expected_results) { ascending_packages.reverse } + end + end + end + + def pagination_query(params) + graphql_query_for(:group, { 'fullPath' => resource.full_path }, + query_nodes(:packages, :id, include_pagination_info: true, args: params) + ) + end + end + context 'with a batched query' do let(:batch_query) do <<~QUERY diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb index e8b8caf6c2d..42ca3348384 100644 --- a/spec/requests/api/graphql/issue/issue_spec.rb +++ b/spec/requests/api/graphql/issue/issue_spec.rb @@ -76,7 +76,7 @@ RSpec.describe 'Query.issue(id)' do post_graphql(query, current_user: current_user) end - it "returns the Issue and field #{params['field']}" do + it "returns the issue and field #{params['field']}" do expect(issue_data.keys).to eq([field]) end end @@ -86,7 +86,7 @@ RSpec.describe 'Query.issue(id)' do context 'when selecting multiple fields' do let(:issue_fields) { ['title', 'description', 'updatedBy { username }'] } - it 'returns the Issue with the specified fields' do + it 'returns the issue with the specified fields' do post_graphql(query, current_user: current_user) expect(issue_data.keys).to eq %w[title description updatedBy] @@ -115,7 +115,7 @@ RSpec.describe 'Query.issue(id)' do end end - context 'when passed a non-Issue gid' do + context 'when passed a non-issue gid' do let(:mr) { create(:merge_request) } it 'returns an error' do diff --git a/spec/requests/api/graphql/merge_request/merge_request_spec.rb b/spec/requests/api/graphql/merge_request/merge_request_spec.rb new file mode 100644 index 00000000000..75dd01a0763 --- /dev/null +++ b/spec/requests/api/graphql/merge_request/merge_request_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.merge_request(id)' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :empty_repo) } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + let_it_be(:current_user) { create(:user) } + let_it_be(:merge_request_params) { { 'id' => merge_request.to_global_id.to_s } } + + let(:merge_request_data) { graphql_data['mergeRequest'] } + let(:merge_request_fields) { all_graphql_fields_for('MergeRequest'.classify) } + + let(:query) do + graphql_query_for('mergeRequest', merge_request_params, merge_request_fields) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end + + it_behaves_like 'a noteable graphql type we can query' do + let(:noteable) { merge_request } + let(:project) { merge_request.project } + let(:path_to_noteable) { [:merge_request] } + + before do + project.add_reporter(current_user) + end + + def query(fields) + graphql_query_for('mergeRequest', merge_request_params, fields) + end + end + + context 'when the user does not have access to the merge request' do + it 'returns nil' do + post_graphql(query) + + expect(merge_request_data).to be nil + end + end + + context 'when the user does have access' do + before do + project.add_reporter(current_user) + end + + it 'returns the merge request' do + post_graphql(query, current_user: current_user) + + expect(merge_request_data).to include( + 'title' => merge_request.title, + 'description' => merge_request.description + ) + end + + context 'when selecting any single field' do + where(:field) do + scalar_fields_of('MergeRequest').map { |name| [name] } + end + + with_them do + it_behaves_like 'a working graphql query' do + let(:merge_request_fields) do + field + end + + before do + post_graphql(query, current_user: current_user) + end + + it "returns the merge request and field #{params['field']}" do + expect(merge_request_data.keys).to eq([field]) + end + end + end + end + + context 'when selecting multiple fields' do + let(:merge_request_fields) { ['title', 'description', 'author { username }'] } + + it 'returns the merge request with the specified fields' do + post_graphql(query, current_user: current_user) + + expect(merge_request_data.keys).to eq %w[title description author] + expect(merge_request_data['title']).to eq(merge_request.title) + expect(merge_request_data['description']).to eq(merge_request.description) + expect(merge_request_data['author']['username']).to eq(merge_request.author.username) + end + end + + context 'when passed a non-merge request gid' do + let(:issue) { create(:issue) } + + it 'returns an error' do + gid = issue.to_global_id.to_s + merge_request_params['id'] = gid + + post_graphql(query, current_user: current_user) + + expect(graphql_errors).not_to be nil + expect(graphql_errors.first['message']).to eq("\"#{gid}\" does not represent an instance of MergeRequest") + end + end + end +end diff --git a/spec/requests/api/graphql/packages/composer_spec.rb b/spec/requests/api/graphql/packages/composer_spec.rb new file mode 100644 index 00000000000..34137a07c34 --- /dev/null +++ b/spec/requests/api/graphql/packages/composer_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'package details' do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:composer_package) { create(:composer_package, project: project) } + let_it_be(:composer_json) { { name: 'name', type: 'type', license: 'license', version: 1 } } + let_it_be(:composer_metadatum) do + # we are forced to manually create the metadatum, without using the factory to force the sha to be a string + # and avoid an error where gitaly can't find the repository + create(:composer_metadatum, package: composer_package, target_sha: 'foo_sha', composer_json: composer_json) + end + + let(:depth) { 3 } + let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] } + let(:metadata) { query_graphql_fragment('ComposerMetadata') } + let(:package_files) { all_graphql_fields_for('PackageFile') } + let(:user) { project.owner } + let(:package_global_id) { global_id_of(composer_package) } + let(:package_details) { graphql_data_at(:package) } + let(:metadata_response) { graphql_data_at(:package, :metadata) } + let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) } + + let(:query) do + graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) + #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)} + metadata { + #{metadata} + } + packageFiles { + nodes { + #{package_files} + } + } + FIELDS + end + + subject { post_graphql(query, current_user: user) } + + before do + subject + end + + it_behaves_like 'a working graphql query' do + it 'matches the JSON schema' do + expect(package_details).to match_schema('graphql/packages/package_details') + end + end + + describe 'Composer' do + it 'has the correct metadata' do + expect(metadata_response).to include( + 'targetSha' => 'foo_sha', + 'composerJson' => composer_json.transform_keys(&:to_s).transform_values(&:to_s) + ) + end + + it 'does not have files' do + expect(package_files_response).to be_empty + end + end +end diff --git a/spec/requests/api/graphql/packages/conan_spec.rb b/spec/requests/api/graphql/packages/conan_spec.rb new file mode 100644 index 00000000000..dc64c5057d5 --- /dev/null +++ b/spec/requests/api/graphql/packages/conan_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'conan package details' do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:conan_package) { create(:conan_package, project: project) } + + let(:package_global_id) { global_id_of(conan_package) } + let(:metadata) { query_graphql_fragment('ConanMetadata') } + let(:first_file) { conan_package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } } + + let(:depth) { 3 } + let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] } + let(:package_files) { all_graphql_fields_for('PackageFile') } + let(:package_files_metadata) {query_graphql_fragment('ConanFileMetadata')} + + let(:user) { project.owner } + let(:package_details) { graphql_data_at(:package) } + let(:metadata_response) { graphql_data_at(:package, :metadata) } + let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) } + let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)} + let(:first_file_response_metadata) { graphql_data_at(:package, :package_files, :nodes, 0, :file_metadata)} + + let(:query) do + graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) + #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)} + metadata { + #{metadata} + } + packageFiles { + nodes { + #{package_files} + fileMetadata { + #{package_files_metadata} + } + } + } + FIELDS + end + + subject { post_graphql(query, current_user: user) } + + before do + subject + end + + it_behaves_like 'a working graphql query' do + it 'matches the JSON schema' do + expect(package_details).to match_schema('graphql/packages/package_details') + end + end + + it 'has the correct metadata' do + expect(metadata_response).to include( + 'id' => global_id_of(conan_package.conan_metadatum), + 'recipe' => conan_package.conan_metadatum.recipe, + 'packageChannel' => conan_package.conan_metadatum.package_channel, + 'packageUsername' => conan_package.conan_metadatum.package_username, + 'recipePath' => conan_package.conan_metadatum.recipe_path + ) + end + + it 'has the right amount of files' do + expect(package_files_response.length).to be(conan_package.package_files.length) + end + + it 'has the basic package files data' do + expect(first_file_response).to include( + 'id' => global_id_of(first_file), + 'fileName' => first_file.file_name, + 'size' => first_file.size.to_s, + 'downloadPath' => first_file.download_path, + 'fileSha1' => first_file.file_sha1, + 'fileMd5' => first_file.file_md5, + 'fileSha256' => first_file.file_sha256 + ) + end + + it 'has the correct file metadata' do + expect(first_file_response_metadata).to include( + 'id' => global_id_of(first_file.conan_file_metadatum), + 'packageRevision' => first_file.conan_file_metadatum.package_revision, + 'conanPackageReference' => first_file.conan_file_metadatum.conan_package_reference, + 'recipeRevision' => first_file.conan_file_metadatum.recipe_revision, + 'conanFileType' => first_file.conan_file_metadatum.conan_file_type.upcase + ) + end +end diff --git a/spec/requests/api/graphql/packages/package_spec.rb b/spec/requests/api/graphql/packages/package_spec.rb index a0131c7733e..83ea9ff4dc8 100644 --- a/spec/requests/api/graphql/packages/package_spec.rb +++ b/spec/requests/api/graphql/packages/package_spec.rb @@ -17,7 +17,9 @@ RSpec.describe 'package details' do let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] } let(:metadata) { query_graphql_fragment('ComposerMetadata') } let(:package_files) {all_graphql_fields_for('PackageFile')} - let(:package_files_metadata) {query_graphql_fragment('ConanFileMetadata')} + let(:user) { project.owner } + let(:package_global_id) { global_id_of(composer_package) } + let(:package_details) { graphql_data_at(:package) } let(:query) do graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) @@ -28,22 +30,11 @@ RSpec.describe 'package details' do packageFiles { nodes { #{package_files} - fileMetadata { - #{package_files_metadata} - } } } FIELDS end - let(:user) { project.owner } - let(:package_global_id) { global_id_of(composer_package) } - let(:package_details) { graphql_data_at(:package) } - let(:metadata_response) { graphql_data_at(:package, :metadata) } - let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) } - let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)} - let(:first_file_response_metadata) { graphql_data_at(:package, :package_files, :nodes, 0, :file_metadata)} - subject { post_graphql(query, current_user: user) } it_behaves_like 'a working graphql query' do @@ -56,69 +47,6 @@ RSpec.describe 'package details' do end end - describe 'Packages Metadata' do - before do - subject - end - - describe 'Composer' do - it 'has the correct metadata' do - expect(metadata_response).to include( - 'targetSha' => 'foo_sha', - 'composerJson' => composer_json.transform_keys(&:to_s).transform_values(&:to_s) - ) - end - - it 'does not have files' do - expect(package_files_response).to be_empty - end - end - - describe 'Conan' do - let_it_be(:conan_package) { create(:conan_package, project: project) } - - let(:package_global_id) { global_id_of(conan_package) } - let(:metadata) { query_graphql_fragment('ConanMetadata') } - let(:first_file) { conan_package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } } - - it 'has the correct metadata' do - expect(metadata_response).to include( - 'id' => global_id_of(conan_package.conan_metadatum), - 'recipe' => conan_package.conan_metadatum.recipe, - 'packageChannel' => conan_package.conan_metadatum.package_channel, - 'packageUsername' => conan_package.conan_metadatum.package_username, - 'recipePath' => conan_package.conan_metadatum.recipe_path - ) - end - - it 'has the right amount of files' do - expect(package_files_response.length).to be(conan_package.package_files.length) - end - - it 'has the basic package files data' do - expect(first_file_response).to include( - 'id' => global_id_of(first_file), - 'fileName' => first_file.file_name, - 'size' => first_file.size.to_s, - 'downloadPath' => first_file.download_path, - 'fileSha1' => first_file.file_sha1, - 'fileMd5' => first_file.file_md5, - 'fileSha256' => first_file.file_sha256 - ) - end - - it 'has the correct file metadata' do - expect(first_file_response_metadata).to include( - 'id' => global_id_of(first_file.conan_file_metadatum), - 'packageRevision' => first_file.conan_file_metadatum.package_revision, - 'conanPackageReference' => first_file.conan_file_metadatum.conan_package_reference, - 'recipeRevision' => first_file.conan_file_metadatum.recipe_revision, - 'conanFileType' => first_file.conan_file_metadatum.conan_file_type.upcase - ) - end - end - end - context 'there are other versions of this package' do let(:depth) { 3 } let(:excluded) { %w[metadata project tags pipelines] } # to limit the query complexity diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index 15551005502..438ea9bb4c1 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -311,23 +311,23 @@ RSpec.describe 'getting merge request information nested in a project' do end end - context 'when requesting information about MR interactions' do + shared_examples 'when requesting information about MR interactions' do let_it_be(:user) { create(:user) } let(:selected_fields) { all_graphql_fields_for('UserMergeRequestInteraction') } let(:mr_fields) do query_nodes( - :reviewers, + field, query_graphql_field(:merge_request_interaction, nil, selected_fields) ) end def interaction_data - graphql_data_at(:project, :merge_request, :reviewers, :nodes, :merge_request_interaction) + graphql_data_at(:project, :merge_request, field, :nodes, :merge_request_interaction) end - context 'when the user does not have interactions' do + context 'when the user is not assigned' do it 'returns null data' do post_graphql(query) @@ -338,7 +338,7 @@ RSpec.describe 'getting merge request information nested in a project' do context 'when the user is a reviewer, but has not reviewed' do before do project.add_guest(user) - merge_request.merge_request_reviewers.create!(reviewer: user) + assign_user(user) end it 'returns falsey values' do @@ -346,8 +346,8 @@ RSpec.describe 'getting merge request information nested in a project' do expect(interaction_data).to contain_exactly a_hash_including( 'canMerge' => false, - 'canUpdate' => false, - 'reviewState' => 'UNREVIEWED', + 'canUpdate' => can_update, + 'reviewState' => unreviewed, 'reviewed' => false, 'approved' => false ) @@ -357,7 +357,9 @@ RSpec.describe 'getting merge request information nested in a project' do context 'when the user has interacted' do before do project.add_maintainer(user) - merge_request.merge_request_reviewers.create!(reviewer: user, state: 'reviewed') + assign_user(user) + r = merge_request.merge_request_reviewers.find_or_create_by!(reviewer: user) + r.update!(state: 'reviewed') merge_request.approved_by_users << user end @@ -392,7 +394,10 @@ RSpec.describe 'getting merge request information nested in a project' do end it 'does not suffer from N+1' do - merge_request.merge_request_reviewers.create!(reviewer: user, state: 'reviewed') + assign_user(user) + merge_request.merge_request_reviewers + .find_or_create_by!(reviewer: user) + .update!(state: 'reviewed') baseline = ActiveRecord::QueryRecorder.new do post_graphql(query) @@ -401,7 +406,8 @@ RSpec.describe 'getting merge request information nested in a project' do expect(interaction_data).to contain_exactly(include(reviewed)) other_users.each do |user| - merge_request.merge_request_reviewers.create!(reviewer: user) + assign_user(user) + merge_request.merge_request_reviewers.find_or_create_by!(reviewer: user) end expect { post_graphql(query) }.not_to exceed_query_limit(baseline) @@ -435,4 +441,24 @@ RSpec.describe 'getting merge request information nested in a project' do end end end + + it_behaves_like 'when requesting information about MR interactions' do + let(:field) { :reviewers } + let(:unreviewed) { 'UNREVIEWED' } + let(:can_update) { false } + + def assign_user(user) + merge_request.merge_request_reviewers.create!(reviewer: user) + end + end + + it_behaves_like 'when requesting information about MR interactions' do + let(:field) { :assignees } + let(:unreviewed) { nil } + let(:can_update) { true } # assignees can update MRs + + def assign_user(user) + merge_request.assignees << user + end + end end diff --git a/spec/requests/api/graphql/project/packages_spec.rb b/spec/requests/api/graphql/project/packages_spec.rb index 3c04e0caf61..e7c6a4c0018 100644 --- a/spec/requests/api/graphql/project/packages_spec.rb +++ b/spec/requests/api/graphql/project/packages_spec.rb @@ -7,11 +7,10 @@ RSpec.describe 'getting a package list for a project' do let_it_be(:resource) { create(:project, :repository) } let_it_be(:current_user) { create(:user) } - - let_it_be(:package) { create(:package, project: resource) } - let_it_be(:maven_package) { create(:maven_package, project: resource) } - let_it_be(:debian_package) { create(:debian_package, project: resource) } - let_it_be(:composer_package) { create(:composer_package, project: resource) } + let_it_be(:maven_package) { create(:maven_package, project: resource, name: 'tab', version: '4.0.0', created_at: 5.days.ago) } + let_it_be(:package) { create(:npm_package, project: resource, name: 'uab', version: '5.0.0', created_at: 4.days.ago) } + let_it_be(:composer_package) { create(:composer_package, project: resource, name: 'vab', version: '6.0.0', created_at: 3.days.ago) } + let_it_be(:debian_package) { create(:debian_package, project: resource, name: 'zab', version: '7.0.0', created_at: 2.days.ago) } let_it_be(:composer_metadatum) do create(:composer_metadatum, package: composer_package, target_sha: 'afdeh', @@ -40,4 +39,40 @@ RSpec.describe 'getting a package list for a project' do end it_behaves_like 'group and project packages query' + + describe 'sorting and pagination' do + let_it_be(:ascending_packages) { [maven_package, package, composer_package, debian_package].map { |package| global_id_of(package)} } + + let(:data_path) { [:project, :packages] } + + before do + resource.add_reporter(current_user) + end + + [:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC].each do |order| + context "#{order}" do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { order } + let(:first_param) { 4 } + let(:expected_results) { ascending_packages } + end + end + end + + [:CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order| + context "#{order}" do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { order } + let(:first_param) { 4 } + let(:expected_results) { ascending_packages.reverse } + end + end + end + + def pagination_query(params) + graphql_query_for(:project, { 'fullPath' => resource.full_path }, + query_nodes(:packages, :id, include_pagination_info: true, args: params) + ) + end + end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index c73cbad9d2f..df7d9bbf13d 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe MergeRequests::MergeService do + include ExclusiveLeaseHelpers + let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } @@ -20,6 +22,9 @@ RSpec.describe MergeRequests::MergeService do { commit_message: 'Awesome message', sha: merge_request.diff_head_sha } end + let(:lease_key) { "merge_requests_merge_service:#{merge_request.id}" } + let!(:lease) { stub_exclusive_lease(lease_key) } + context 'valid params' do before do allow(service).to receive(:execute_hooks) @@ -90,6 +95,20 @@ RSpec.describe MergeRequests::MergeService do end end + context 'running the service multiple time' do + it 'is idempotent' do + 2.times { service.execute(merge_request) } + + expect(merge_request.merge_error).to be_falsey + expect(merge_request).to be_valid + expect(merge_request).to be_merged + + commit_messages = project.repository.commits('master', limit: 2).map(&:message) + expect(commit_messages.uniq.size).to eq(2) + expect(merge_request.in_progress_merge_commit_sha).to be_nil + end + end + context 'when an invalid sha is passed' do let(:merge_request) do create(:merge_request, :simple, @@ -306,6 +325,8 @@ RSpec.describe MergeRequests::MergeService do end it 'logs and saves error if user is not authorized' do + stub_exclusive_lease + unauthorized_user = create(:user) project.add_reporter(unauthorized_user) @@ -423,6 +444,7 @@ RSpec.describe MergeRequests::MergeService do merge_request.project.update!(merge_method: merge_method) error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch' allow(service).to receive(:execute_hooks) + expect(lease).to receive(:cancel) service.execute(merge_request) @@ -473,5 +495,17 @@ RSpec.describe MergeRequests::MergeService do end end end + + context 'when the other sidekiq worker has already been running' do + before do + stub_exclusive_lease_taken(lease_key) + end + + it 'does not execute service' do + expect(service).not_to receive(:commit) + + service.execute(merge_request) + end + end end end diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index 247b053e729..cc347904f49 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -22,7 +22,6 @@ RSpec.describe MergeRequests::PostMergeService do it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do # Cache the counter before the MR changed state. project.open_merge_requests_count - merge_request.update!(state: 'merged') expect { subject }.to change { project.open_merge_requests_count }.from(1).to(0) end diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb index 8a6d5d88ca6..f2576931642 100644 --- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb @@ -24,8 +24,8 @@ RSpec.shared_examples 'User creates wiki page' do page.within(".wiki-form") do fill_in(:wiki_content, with: "") - page.execute_script("window.onbeforeunload = null") page.execute_script("document.querySelector('.wiki-form').submit()") + page.accept_alert # manually force form submit end expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank") diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb index d185e9dd81c..e5f05217ff0 100644 --- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb @@ -93,8 +93,8 @@ RSpec.shared_examples 'User updates wiki page' do it 'shows a validation error message if the form is force submitted', :js do fill_in(:wiki_content, with: '') - page.execute_script("window.onbeforeunload = null") page.execute_script("document.querySelector('.wiki-form').submit()") + page.accept_alert # manually force form submit expect(page).to have_selector('.wiki-form') expect(page).to have_content('Edit Page') diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index 417e6edce96..0268bc2388f 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -29,5 +29,23 @@ RSpec.describe MergeWorker do source_project.repository.expire_branches_cache expect(source_project.repository.branch_names).not_to include('markdown') end + + it_behaves_like 'an idempotent worker' do + let(:job_args) do + [ + merge_request.id, + merge_request.author_id, + commit_message: 'wow such merge', + sha: merge_request.diff_head_sha + ] + end + + it 'the merge request is still shown as merged' do + subject + + merge_request.reload + expect(merge_request).to be_merged + end + end end end