summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-11 15:10:04 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-11 15:10:04 +0000
commita8704bd33cb36b4e7e88bb10d61265b8ad8a058c (patch)
tree4a9eb565d016d789168657fadbc78214afb61725
parent54f170b69972d46a5bab2a0231510a41e610da31 (diff)
downloadgitlab-ce-a8704bd33cb36b4e7e88bb10d61265b8ad8a058c.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop.yml1
-rw-r--r--.rubocop_manual_todo.yml371
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--app/assets/javascripts/boards/boards_util.js11
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue71
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue1
-rw-r--r--app/assets/javascripts/boards/index.js5
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js5
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js50
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/form.vue21
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/details_header.vue36
-rw-r--r--app/assets/javascripts/registry/explorer/constants/details.js2
-rw-r--r--app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql4
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue2
-rw-r--r--app/graphql/types/base_field.rb2
-rw-r--r--app/graphql/types/concerns/gitlab_style_deprecations.rb4
-rw-r--r--app/views/groups/edit.html.haml1
-rw-r--r--changelogs/unreleased/216795-show-last-update-and-visibility.yml5
-rw-r--r--changelogs/unreleased/229310-migrate-bootstrap-button.yml5
-rw-r--r--changelogs/unreleased/244273-boards-migrate-createboard-board_store-function-to-vuex-action.yml5
-rw-r--r--changelogs/unreleased/id-bump-gitlab-shell-version.yml5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql140
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json118
-rw-r--r--doc/api/graphql/reference/index.md54
-rw-r--r--doc/api/notes.md17
-rw-r--r--doc/development/api_graphql_styleguide.md60
-rw-r--r--doc/development/fe_guide/performance.md26
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md95
-rw-r--r--lib/gitlab/setup_helper.rb18
-rw-r--r--lib/tasks/gitlab/workhorse.rake9
-rw-r--r--locale/gitlab.pot30
-rw-r--r--rubocop/cop/graphql/descriptions.rb60
-rw-r--r--spec/frontend/boards/boards_store_spec.js114
-rw-r--r--spec/frontend/boards/components/board_form_spec.js286
-rw-r--r--spec/frontend/registry/explorer/components/details_page/details_header_spec.js48
-rw-r--r--spec/frontend/registry/explorer/mock_data.js5
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js7
-rw-r--r--spec/graphql/types/base_field_spec.rb10
-rw-r--r--spec/lib/gitlab/graphql/docs/renderer_spec.rb26
-rw-r--r--spec/lib/gitlab/setup_helper/workhorse_spec.rb25
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb5
-rw-r--r--spec/rubocop/cop/graphql/descriptions_spec.rb102
-rw-r--r--spec/support/helpers/test_env.rb17
-rw-r--r--spec/support/matchers/exceed_query_limit.rb121
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb6
-rw-r--r--spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb226
-rw-r--r--spec/tasks/gitlab/workhorse_rake_spec.rb61
47 files changed, 1686 insertions, 609 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 2609b9a1ded..6d2ade9e1ed 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -427,6 +427,7 @@ Scalability/FileUploads:
Graphql/Descriptions:
Enabled: true
+ AutoCorrect: true
Include:
- 'app/graphql/**/*'
- 'ee/app/graphql/**/*'
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml
index d5627d740f4..ef316e8b08f 100644
--- a/.rubocop_manual_todo.yml
+++ b/.rubocop_manual_todo.yml
@@ -671,3 +671,374 @@ RSpec/TimecopTravel:
- 'spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb'
- 'spec/workers/concerns/reenqueuer_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb'
+
+Graphql/Descriptions:
+ Exclude:
+ - 'app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb'
+ - 'app/graphql/mutations/alert_management/base.rb'
+ - 'app/graphql/mutations/alert_management/http_integration/create.rb'
+ - 'app/graphql/mutations/alert_management/http_integration/destroy.rb'
+ - 'app/graphql/mutations/alert_management/http_integration/http_integration_base.rb'
+ - 'app/graphql/mutations/alert_management/http_integration/reset_token.rb'
+ - 'app/graphql/mutations/alert_management/http_integration/update.rb'
+ - 'app/graphql/mutations/alert_management/prometheus_integration/create.rb'
+ - 'app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb'
+ - 'app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb'
+ - 'app/graphql/mutations/alert_management/prometheus_integration/update.rb'
+ - 'app/graphql/mutations/alert_management/update_alert_status.rb'
+ - 'app/graphql/mutations/award_emojis/base.rb'
+ - 'app/graphql/mutations/boards/destroy.rb'
+ - 'app/graphql/mutations/boards/issues/issue_move_list.rb'
+ - 'app/graphql/mutations/boards/lists/base.rb'
+ - 'app/graphql/mutations/boards/lists/create.rb'
+ - 'app/graphql/mutations/boards/lists/update.rb'
+ - 'app/graphql/mutations/branches/create.rb'
+ - 'app/graphql/mutations/ci/base.rb'
+ - 'app/graphql/mutations/ci/pipeline_retry.rb'
+ - 'app/graphql/mutations/commits/create.rb'
+ - 'app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb'
+ - 'app/graphql/mutations/concerns/mutations/resolves_subscription.rb'
+ - 'app/graphql/mutations/concerns/mutations/spammable_mutation_fields.rb'
+ - 'app/graphql/mutations/container_expiration_policies/update.rb'
+ - 'app/graphql/mutations/container_repositories/destroy_tags.rb'
+ - 'app/graphql/mutations/custom_emoji/create.rb'
+ - 'app/graphql/mutations/design_management/base.rb'
+ - 'app/graphql/mutations/design_management/delete.rb'
+ - 'app/graphql/mutations/design_management/move.rb'
+ - 'app/graphql/mutations/design_management/upload.rb'
+ - 'app/graphql/mutations/discussions/toggle_resolve.rb'
+ - 'app/graphql/mutations/environments/canary_ingress/update.rb'
+ - 'app/graphql/mutations/issues/base.rb'
+ - 'app/graphql/mutations/issues/create.rb'
+ - 'app/graphql/mutations/issues/move.rb'
+ - 'app/graphql/mutations/issues/set_due_date.rb'
+ - 'app/graphql/mutations/issues/set_locked.rb'
+ - 'app/graphql/mutations/issues/update.rb'
+ - 'app/graphql/mutations/jira_import/import_users.rb'
+ - 'app/graphql/mutations/jira_import/start.rb'
+ - 'app/graphql/mutations/labels/create.rb'
+ - 'app/graphql/mutations/merge_requests/base.rb'
+ - 'app/graphql/mutations/merge_requests/create.rb'
+ - 'app/graphql/mutations/metrics/dashboard/annotations/create.rb'
+ - 'app/graphql/mutations/metrics/dashboard/annotations/delete.rb'
+ - 'app/graphql/mutations/notes/base.rb'
+ - 'app/graphql/mutations/notes/create/base.rb'
+ - 'app/graphql/mutations/notes/create/note.rb'
+ - 'app/graphql/mutations/notes/destroy.rb'
+ - 'app/graphql/mutations/notes/reposition_image_diff_note.rb'
+ - 'app/graphql/mutations/notes/update/base.rb'
+ - 'app/graphql/mutations/releases/base.rb'
+ - 'app/graphql/mutations/releases/create.rb'
+ - 'app/graphql/mutations/releases/update.rb'
+ - 'app/graphql/mutations/snippets/base.rb'
+ - 'app/graphql/mutations/snippets/create.rb'
+ - 'app/graphql/mutations/snippets/destroy.rb'
+ - 'app/graphql/mutations/snippets/mark_as_spam.rb'
+ - 'app/graphql/mutations/snippets/update.rb'
+ - 'app/graphql/mutations/terraform/state/base.rb'
+ - 'app/graphql/mutations/todos/create.rb'
+ - 'app/graphql/mutations/todos/mark_all_done.rb'
+ - 'app/graphql/mutations/todos/mark_done.rb'
+ - 'app/graphql/mutations/todos/restore.rb'
+ - 'app/graphql/mutations/todos/restore_many.rb'
+ - 'app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb'
+ - 'app/graphql/resolvers/alert_management/alert_resolver.rb'
+ - 'app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb'
+ - 'app/graphql/resolvers/board_list_issues_resolver.rb'
+ - 'app/graphql/resolvers/board_lists_resolver.rb'
+ - 'app/graphql/resolvers/board_resolver.rb'
+ - 'app/graphql/resolvers/boards_resolver.rb'
+ - 'app/graphql/resolvers/ci/config_resolver.rb'
+ - 'app/graphql/resolvers/ci/jobs_resolver.rb'
+ - 'app/graphql/resolvers/ci/runner_setup_resolver.rb'
+ - 'app/graphql/resolvers/concerns/issue_resolver_arguments.rb'
+ - 'app/graphql/resolvers/concerns/resolves_pipelines.rb'
+ - 'app/graphql/resolvers/concerns/resolves_snippets.rb'
+ - 'app/graphql/resolvers/concerns/time_frame_arguments.rb'
+ - 'app/graphql/resolvers/container_repositories_resolver.rb'
+ - 'app/graphql/resolvers/design_management/design_at_version_resolver.rb'
+ - 'app/graphql/resolvers/design_management/design_resolver.rb'
+ - 'app/graphql/resolvers/design_management/designs_resolver.rb'
+ - 'app/graphql/resolvers/design_management/version/design_at_version_resolver.rb'
+ - 'app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb'
+ - 'app/graphql/resolvers/design_management/version_in_collection_resolver.rb'
+ - 'app/graphql/resolvers/design_management/version_resolver.rb'
+ - 'app/graphql/resolvers/design_management/versions_resolver.rb'
+ - 'app/graphql/resolvers/echo_resolver.rb'
+ - 'app/graphql/resolvers/environments_resolver.rb'
+ - 'app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb'
+ - 'app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb'
+ - 'app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb'
+ - 'app/graphql/resolvers/full_path_resolver.rb'
+ - 'app/graphql/resolvers/group_members_resolver.rb'
+ - 'app/graphql/resolvers/group_milestones_resolver.rb'
+ - 'app/graphql/resolvers/issues_resolver.rb'
+ - 'app/graphql/resolvers/members_resolver.rb'
+ - 'app/graphql/resolvers/merge_request_resolver.rb'
+ - 'app/graphql/resolvers/merge_requests_resolver.rb'
+ - 'app/graphql/resolvers/metrics/dashboard_resolver.rb'
+ - 'app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb'
+ - 'app/graphql/resolvers/milestones_resolver.rb'
+ - 'app/graphql/resolvers/namespace_projects_resolver.rb'
+ - 'app/graphql/resolvers/project_members_resolver.rb'
+ - 'app/graphql/resolvers/project_milestones_resolver.rb'
+ - 'app/graphql/resolvers/project_pipeline_resolver.rb'
+ - 'app/graphql/resolvers/projects/jira_projects_resolver.rb'
+ - 'app/graphql/resolvers/projects/services_resolver.rb'
+ - 'app/graphql/resolvers/projects_resolver.rb'
+ - 'app/graphql/resolvers/release_resolver.rb'
+ - 'app/graphql/resolvers/releases_resolver.rb'
+ - 'app/graphql/resolvers/snippets/blobs_resolver.rb'
+ - 'app/graphql/resolvers/snippets_resolver.rb'
+ - 'app/graphql/resolvers/todo_resolver.rb'
+ - 'app/graphql/resolvers/tree_resolver.rb'
+ - 'app/graphql/resolvers/user_resolver.rb'
+ - 'app/graphql/resolvers/user_starred_projects_resolver.rb'
+ - 'app/graphql/resolvers/users/snippets_resolver.rb'
+ - 'app/graphql/resolvers/users_resolver.rb'
+ - 'app/graphql/types/access_level_type.rb'
+ - 'app/graphql/types/admin/analytics/instance_statistics/measurement_type.rb'
+ - 'app/graphql/types/admin/sidekiq_queues/delete_jobs_response_type.rb'
+ - 'app/graphql/types/alert_management/alert_status_counts_type.rb'
+ - 'app/graphql/types/alert_management/alert_type.rb'
+ - 'app/graphql/types/alert_management/integration_type.rb'
+ - 'app/graphql/types/award_emojis/award_emoji_type.rb'
+ - 'app/graphql/types/board_list_type.rb'
+ - 'app/graphql/types/board_type.rb'
+ - 'app/graphql/types/boards/board_issue_input_base_type.rb'
+ - 'app/graphql/types/boards/board_issue_input_type.rb'
+ - 'app/graphql/types/branch_type.rb'
+ - 'app/graphql/types/ci/analytics_type.rb'
+ - 'app/graphql/types/ci/config/config_type.rb'
+ - 'app/graphql/types/ci/config/group_type.rb'
+ - 'app/graphql/types/ci/config/job_type.rb'
+ - 'app/graphql/types/ci/config/need_type.rb'
+ - 'app/graphql/types/ci/config/stage_type.rb'
+ - 'app/graphql/types/ci/detailed_status_type.rb'
+ - 'app/graphql/types/ci/group_type.rb'
+ - 'app/graphql/types/ci/job_artifact_type.rb'
+ - 'app/graphql/types/ci/job_type.rb'
+ - 'app/graphql/types/ci/pipeline_type.rb'
+ - 'app/graphql/types/ci/runner_architecture_type.rb'
+ - 'app/graphql/types/ci/runner_platform_type.rb'
+ - 'app/graphql/types/ci/runner_setup_type.rb'
+ - 'app/graphql/types/ci/stage_type.rb'
+ - 'app/graphql/types/ci/status_action_type.rb'
+ - 'app/graphql/types/commit_action_type.rb'
+ - 'app/graphql/types/commit_type.rb'
+ - 'app/graphql/types/container_expiration_policy_type.rb'
+ - 'app/graphql/types/container_repository_details_type.rb'
+ - 'app/graphql/types/container_repository_type.rb'
+ - 'app/graphql/types/countable_connection_type.rb'
+ - 'app/graphql/types/current_user_todos.rb'
+ - 'app/graphql/types/custom_emoji_type.rb'
+ - 'app/graphql/types/design_management/design_at_version_type.rb'
+ - 'app/graphql/types/design_management/design_collection_type.rb'
+ - 'app/graphql/types/design_management/design_fields.rb'
+ - 'app/graphql/types/design_management/design_type.rb'
+ - 'app/graphql/types/design_management/version_type.rb'
+ - 'app/graphql/types/design_management_type.rb'
+ - 'app/graphql/types/diff_paths_input_type.rb'
+ - 'app/graphql/types/diff_refs_type.rb'
+ - 'app/graphql/types/diff_stats_summary_type.rb'
+ - 'app/graphql/types/diff_stats_type.rb'
+ - 'app/graphql/types/environment_type.rb'
+ - 'app/graphql/types/error_tracking/sentry_detailed_error_type.rb'
+ - 'app/graphql/types/error_tracking/sentry_error_collection_type.rb'
+ - 'app/graphql/types/error_tracking/sentry_error_frequency_type.rb'
+ - 'app/graphql/types/error_tracking/sentry_error_stack_trace_context_type.rb'
+ - 'app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb'
+ - 'app/graphql/types/error_tracking/sentry_error_stack_trace_type.rb'
+ - 'app/graphql/types/error_tracking/sentry_error_tags_type.rb'
+ - 'app/graphql/types/error_tracking/sentry_error_type.rb'
+ - 'app/graphql/types/evidence_type.rb'
+ - 'app/graphql/types/grafana_integration_type.rb'
+ - 'app/graphql/types/group_invitation_type.rb'
+ - 'app/graphql/types/group_member_type.rb'
+ - 'app/graphql/types/group_type.rb'
+ - 'app/graphql/types/invitation_interface.rb'
+ - 'app/graphql/types/issue_type.rb'
+ - 'app/graphql/types/jira_import_type.rb'
+ - 'app/graphql/types/jira_user_type.rb'
+ - 'app/graphql/types/jira_users_mapping_input_type.rb'
+ - 'app/graphql/types/label_type.rb'
+ - 'app/graphql/types/member_interface.rb'
+ - 'app/graphql/types/merge_request_connection_type.rb'
+ - 'app/graphql/types/merge_request_type.rb'
+ - 'app/graphql/types/metadata_type.rb'
+ - 'app/graphql/types/metrics/dashboard_type.rb'
+ - 'app/graphql/types/metrics/dashboards/annotation_type.rb'
+ - 'app/graphql/types/milestone_stats_type.rb'
+ - 'app/graphql/types/milestone_type.rb'
+ - 'app/graphql/types/namespace_type.rb'
+ - 'app/graphql/types/notes/diff_position_type.rb'
+ - 'app/graphql/types/notes/discussion_type.rb'
+ - 'app/graphql/types/notes/note_type.rb'
+ - 'app/graphql/types/notes/noteable_type.rb'
+ - 'app/graphql/types/package_type.rb'
+ - 'app/graphql/types/project_invitation_type.rb'
+ - 'app/graphql/types/project_member_type.rb'
+ - 'app/graphql/types/project_statistics_type.rb'
+ - 'app/graphql/types/project_type.rb'
+ - 'app/graphql/types/projects/service_type.rb'
+ - 'app/graphql/types/projects/services/jira_project_type.rb'
+ - 'app/graphql/types/projects/services/jira_service_type.rb'
+ - 'app/graphql/types/prometheus_alert_type.rb'
+ - 'app/graphql/types/query_type.rb'
+ - 'app/graphql/types/range_input_type.rb'
+ - 'app/graphql/types/release_asset_link_input_type.rb'
+ - 'app/graphql/types/release_asset_link_type.rb'
+ - 'app/graphql/types/release_assets_input_type.rb'
+ - 'app/graphql/types/release_assets_type.rb'
+ - 'app/graphql/types/release_links_type.rb'
+ - 'app/graphql/types/release_source_type.rb'
+ - 'app/graphql/types/release_type.rb'
+ - 'app/graphql/types/repository_type.rb'
+ - 'app/graphql/types/resolvable_interface.rb'
+ - 'app/graphql/types/root_storage_statistics_type.rb'
+ - 'app/graphql/types/snippet_type.rb'
+ - 'app/graphql/types/snippets/blob_action_input_type.rb'
+ - 'app/graphql/types/snippets/blob_type.rb'
+ - 'app/graphql/types/snippets/blob_viewer_type.rb'
+ - 'app/graphql/types/task_completion_status.rb'
+ - 'app/graphql/types/terraform/state_type.rb'
+ - 'app/graphql/types/terraform/state_version_type.rb'
+ - 'app/graphql/types/todo_type.rb'
+ - 'app/graphql/types/tree/blob_type.rb'
+ - 'app/graphql/types/tree/entry_type.rb'
+ - 'app/graphql/types/tree/submodule_type.rb'
+ - 'app/graphql/types/tree/tree_entry_type.rb'
+ - 'app/graphql/types/tree/tree_type.rb'
+ - 'app/graphql/types/user_status_type.rb'
+ - 'app/graphql/types/user_type.rb'
+ - 'ee/app/graphql/ee/mutations/boards/issues/issue_move_list.rb'
+ - 'ee/app/graphql/ee/mutations/boards/lists/create.rb'
+ - 'ee/app/graphql/ee/mutations/issues/create.rb'
+ - 'ee/app/graphql/ee/mutations/issues/update.rb'
+ - 'ee/app/graphql/ee/resolvers/issues_resolver.rb'
+ - 'ee/app/graphql/ee/resolvers/namespace_projects_resolver.rb'
+ - 'ee/app/graphql/ee/types/board_list_type.rb'
+ - 'ee/app/graphql/ee/types/board_type.rb'
+ - 'ee/app/graphql/ee/types/boards/board_issue_input_base_type.rb'
+ - 'ee/app/graphql/ee/types/boards/board_issue_input_type.rb'
+ - 'ee/app/graphql/ee/types/ci/pipeline_type.rb'
+ - 'ee/app/graphql/ee/types/group_type.rb'
+ - 'ee/app/graphql/ee/types/issue_connection_type.rb'
+ - 'ee/app/graphql/ee/types/merge_request_type.rb'
+ - 'ee/app/graphql/ee/types/namespace_type.rb'
+ - 'ee/app/graphql/ee/types/project_type.rb'
+ - 'ee/app/graphql/ee/types/query_type.rb'
+ - 'ee/app/graphql/mutations/admin/analytics/devops_adoption/segments/delete.rb'
+ - 'ee/app/graphql/mutations/admin/analytics/devops_adoption/segments/mixins.rb'
+ - 'ee/app/graphql/mutations/admin/analytics/devops_adoption/segments/update.rb'
+ - 'ee/app/graphql/mutations/boards/lists/update_limit_metrics.rb'
+ - 'ee/app/graphql/mutations/boards/update.rb'
+ - 'ee/app/graphql/mutations/boards/update_epic_user_preferences.rb'
+ - 'ee/app/graphql/mutations/compliance_management/frameworks/destroy.rb'
+ - 'ee/app/graphql/mutations/compliance_management/frameworks/update.rb'
+ - 'ee/app/graphql/mutations/clusters/agent_tokens/create.rb'
+ - 'ee/app/graphql/mutations/clusters/agent_tokens/delete.rb'
+ - 'ee/app/graphql/mutations/clusters/agents/create.rb'
+ - 'ee/app/graphql/mutations/clusters/agents/delete.rb'
+ - 'ee/app/graphql/mutations/concerns/mutations/shared_epic_arguments.rb'
+ - 'ee/app/graphql/mutations/epic_tree/reorder.rb'
+ - 'ee/app/graphql/mutations/epics/add_issue.rb'
+ - 'ee/app/graphql/mutations/epics/base.rb'
+ - 'ee/app/graphql/mutations/epics/create.rb'
+ - 'ee/app/graphql/mutations/epics/set_subscription.rb'
+ - 'ee/app/graphql/mutations/epics/update.rb'
+ - 'ee/app/graphql/mutations/incident_management/oncall_schedule/create.rb'
+ - 'ee/app/graphql/mutations/incident_management/oncall_schedule/destroy.rb'
+ - 'ee/app/graphql/mutations/incident_management/oncall_schedule/oncall_schedule_base.rb'
+ - 'ee/app/graphql/mutations/incident_management/oncall_schedule/update.rb'
+ - 'ee/app/graphql/mutations/instance_security_dashboard/add_project.rb'
+ - 'ee/app/graphql/mutations/instance_security_dashboard/remove_project.rb'
+ - 'ee/app/graphql/mutations/issues/common_ee_mutation_arguments.rb'
+ - 'ee/app/graphql/mutations/issues/promote_to_epic.rb'
+ - 'ee/app/graphql/mutations/issues/set_weight.rb'
+ - 'ee/app/graphql/mutations/iterations/create.rb'
+ - 'ee/app/graphql/mutations/namespaces/base.rb'
+ - 'ee/app/graphql/mutations/quality_management/test_cases/create.rb'
+ - 'ee/app/graphql/mutations/requirements_management/base_requirement.rb'
+ - 'ee/app/graphql/mutations/requirements_management/update_requirement.rb'
+ - 'ee/app/graphql/mutations/security/ci_configuration/configure_sast.rb'
+ - 'ee/app/graphql/mutations/vulnerabilities/confirm.rb'
+ - 'ee/app/graphql/mutations/vulnerabilities/dismiss.rb'
+ - 'ee/app/graphql/mutations/vulnerabilities/resolve.rb'
+ - 'ee/app/graphql/mutations/vulnerabilities/revert_to_detected.rb'
+ - 'ee/app/graphql/resolvers/board_groupings/epics_resolver.rb'
+ - 'ee/app/graphql/resolvers/boards/epic_boards_resolver.rb'
+ - 'ee/app/graphql/resolvers/ci/code_coverage_activities_resolver.rb'
+ - 'ee/app/graphql/resolvers/clusters/agents_resolver.rb'
+ - 'ee/app/graphql/resolvers/dast_site_profile_resolver.rb'
+ - 'ee/app/graphql/resolvers/dast_site_validation_resolver.rb'
+ - 'ee/app/graphql/resolvers/epics_resolver.rb'
+ - 'ee/app/graphql/resolvers/geo/registries_resolver.rb'
+ - 'ee/app/graphql/resolvers/requirements_management/requirements_resolver.rb'
+ - 'ee/app/graphql/resolvers/requirements_management/test_reports_resolver.rb'
+ - 'ee/app/graphql/resolvers/timelog_resolver.rb'
+ - 'ee/app/graphql/resolvers/vulnerabilities/issue_links_resolver.rb'
+ - 'ee/app/graphql/resolvers/vulnerabilities_count_per_day_resolver.rb'
+ - 'ee/app/graphql/resolvers/vulnerabilities_grade_resolver.rb'
+ - 'ee/app/graphql/resolvers/vulnerabilities_history_resolver.rb'
+ - 'ee/app/graphql/resolvers/vulnerabilities_resolver.rb'
+ - 'ee/app/graphql/resolvers/vulnerability_severities_count_resolver.rb'
+ - 'ee/app/graphql/types/admin/analytics/devops_adoption/segment_type.rb'
+ - 'ee/app/graphql/types/admin/analytics/devops_adoption/snapshot_type.rb'
+ - 'ee/app/graphql/types/boards/board_epic_type.rb'
+ - 'ee/app/graphql/types/boards/epic_board_type.rb'
+ - 'ee/app/graphql/types/boards/epic_user_preferences_type.rb'
+ - 'ee/app/graphql/types/burnup_chart_daily_totals_type.rb'
+ - 'ee/app/graphql/types/ci_configuration/sast/analyzers_entity_input_type.rb'
+ - 'ee/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb'
+ - 'ee/app/graphql/types/ci_configuration/sast/entity_input_type.rb'
+ - 'ee/app/graphql/types/ci_configuration/sast/input_type.rb'
+ - 'ee/app/graphql/types/clusters/agent_token_type.rb'
+ - 'ee/app/graphql/types/clusters/agent_type.rb'
+ - 'ee/app/graphql/types/compliance_management/compliance_framework_type.rb'
+ - 'ee/app/graphql/types/dast_scanner_profile_type.rb'
+ - 'ee/app/graphql/types/dast_site_profile_type.rb'
+ - 'ee/app/graphql/types/dast_site_validation_type.rb'
+ - 'ee/app/graphql/types/epic_descendant_count_type.rb'
+ - 'ee/app/graphql/types/epic_descendant_weight_sum_type.rb'
+ - 'ee/app/graphql/types/epic_health_status_type.rb'
+ - 'ee/app/graphql/types/epic_issue_type.rb'
+ - 'ee/app/graphql/types/epic_tree/epic_tree_node_input_type.rb'
+ - 'ee/app/graphql/types/epic_type.rb'
+ - 'ee/app/graphql/types/external_issue_type.rb'
+ - 'ee/app/graphql/types/geo/geo_node_type.rb'
+ - 'ee/app/graphql/types/geo/merge_request_diff_registry_type.rb'
+ - 'ee/app/graphql/types/geo/package_file_registry_type.rb'
+ - 'ee/app/graphql/types/geo/snippet_repository_registry_type.rb'
+ - 'ee/app/graphql/types/geo/terraform_state_version_registry_type.rb'
+ - 'ee/app/graphql/types/group_stats_type.rb'
+ - 'ee/app/graphql/types/incident_management/oncall_schedule_type.rb'
+ - 'ee/app/graphql/types/instance_security_dashboard_type.rb'
+ - 'ee/app/graphql/types/iteration_type.rb'
+ - 'ee/app/graphql/types/metric_image_type.rb'
+ - 'ee/app/graphql/types/requirements_management/requirement_states_count_type.rb'
+ - 'ee/app/graphql/types/requirements_management/requirement_type.rb'
+ - 'ee/app/graphql/types/requirements_management/test_report_type.rb'
+ - 'ee/app/graphql/types/scanned_resource_type.rb'
+ - 'ee/app/graphql/types/security_report_summary_section_type.rb'
+ - 'ee/app/graphql/types/time_report_stats_type.rb'
+ - 'ee/app/graphql/types/timebox_metrics_type.rb'
+ - 'ee/app/graphql/types/timebox_report_interface.rb'
+ - 'ee/app/graphql/types/timebox_report_type.rb'
+ - 'ee/app/graphql/types/timelog_type.rb'
+ - 'ee/app/graphql/types/vulnerabilities_count_by_day_and_severity_type.rb'
+ - 'ee/app/graphql/types/vulnerabilities_count_by_day_type.rb'
+ - 'ee/app/graphql/types/vulnerability/external_issue_link_type.rb'
+ - 'ee/app/graphql/types/vulnerability/issue_link_type.rb'
+ - 'ee/app/graphql/types/vulnerability_identifier_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/container_scanning_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/coverage_fuzzing_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/dast_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/dependency_scanning_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/sast_type.rb'
+ - 'ee/app/graphql/types/vulnerability_location/secret_detection_type.rb'
+ - 'ee/app/graphql/types/vulnerability_scanner_type.rb'
+ - 'ee/app/graphql/types/vulnerability_type.rb'
+ - 'ee/app/graphql/types/vulnerable_dependency_type.rb'
+ - 'ee/app/graphql/types/vulnerable_package_type.rb'
+ - 'ee/app/graphql/types/vulnerable_projects_by_grade_type.rb'
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 0b704f2a43a..d9a01d25342 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-13.13.0
+13.14.0
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
index 0142c95e773..4961017b6af 100644
--- a/app/assets/javascripts/boards/boards_util.js
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -1,4 +1,5 @@
import { sortBy } from 'lodash';
+import axios from '~/lib/utils/axios_utils';
import { ListType } from './constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import boardsStore from '~/boards/stores/boards_store';
@@ -108,10 +109,20 @@ export function moveIssueListHelper(issue, fromList, toList) {
return updatedIssue;
}
+export function getBoardsPath(endpoint, board) {
+ const path = `${endpoint}${board.id ? `/${board.id}` : ''}.json`;
+
+ if (board.id) {
+ return axios.put(path, { board });
+ }
+ return axios.post(path, { board });
+}
+
export default {
getMilestone,
formatIssue,
formatListIssues,
fullBoardId,
fullLabelId,
+ getBoardsPath,
};
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 74d538d68ed..ad6829c2e20 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -1,11 +1,14 @@
<script>
import { GlModal } from '@gitlab/ui';
+import { pick } from 'lodash';
import { __, s__ } from '~/locale';
import { deprecatedCreateFlash as Flash } from '~/flash';
import { visitUrl } from '~/lib/utils/url_utility';
import boardsStore from '~/boards/stores/boards_store';
+import { fullBoardId, getBoardsPath } from '../boards_util';
import BoardConfigurationOptions from './board_configuration_options.vue';
+import createBoardMutation from '../graphql/board.mutation.graphql';
const boardDefaults = {
id: false,
@@ -81,11 +84,19 @@ export default {
required: false,
default: false,
},
+ currentBoard: {
+ type: Object,
+ required: true,
+ },
+ },
+ inject: {
+ endpoints: {
+ default: {},
+ },
},
data() {
return {
board: { ...boardDefaults, ...this.currentBoard },
- currentBoard: boardsStore.state.currentBoard,
currentPage: boardsStore.state.currentPage,
isLoading: false,
};
@@ -143,6 +154,15 @@ export default {
text: this.$options.i18n.cancelButtonText,
};
},
+ boardPayload() {
+ const { assignee, milestone, labels } = this.board;
+ return {
+ ...this.board,
+ assignee_id: assignee?.id,
+ milestone_id: milestone?.id,
+ label_ids: labels.length ? labels.map(b => b.id) : [''],
+ };
+ },
},
mounted() {
this.resetFormState();
@@ -151,6 +171,31 @@ export default {
}
},
methods: {
+ callBoardMutation(id) {
+ return this.$apollo.mutate({
+ mutation: createBoardMutation,
+ variables: {
+ ...pick(this.boardPayload, ['hideClosedList', 'hideBacklogList']),
+ id,
+ },
+ });
+ },
+ async updateBoard() {
+ const responses = await Promise.all([
+ // Remove unnecessary REST API call when https://gitlab.com/gitlab-org/gitlab/-/issues/282299#note_462996301 is resolved
+ getBoardsPath(this.endpoints.boardsEndpoint, this.boardPayload),
+ this.callBoardMutation(fullBoardId(this.boardPayload.id)),
+ ]);
+
+ return responses[0].data;
+ },
+ async createBoard() {
+ // TODO: change this to use `createBoard` mutation https://gitlab.com/gitlab-org/gitlab/-/issues/292466 is resolved
+ const boardData = await getBoardsPath(this.endpoints.boardsEndpoint, this.boardPayload);
+ await this.callBoardMutation(fullBoardId(boardData.data.id));
+
+ return boardData.data || boardData;
+ },
submit() {
if (this.board.name.length === 0) return;
this.isLoading = true;
@@ -166,21 +211,9 @@ export default {
this.isLoading = false;
});
} else {
- boardsStore
- .createBoard(this.board)
- .then(resp => {
- // This handles 2 use cases
- // - In create call we only get one parameter, the new board
- // - In update call, due to Promise.all, we get REST response in
- // array index 0
-
- if (Array.isArray(resp)) {
- return resp[0].data;
- }
- return resp.data ? resp.data : resp;
- })
+ const boardAction = this.boardPayload.id ? this.updateBoard : this.createBoard;
+ boardAction()
.then(data => {
- this.isLoading = false;
visitUrl(data.board_path);
})
.catch(() => {
@@ -219,9 +252,11 @@ export default {
@close="cancel"
@hide.prevent
>
- <p v-if="isDeleteForm">{{ $options.i18n.deleteConfirmationMessage }}</p>
- <form v-else class="js-board-config-modal" @submit.prevent>
- <div v-if="!readonly" class="gl-mb-5">
+ <p v-if="isDeleteForm" data-testid="delete-confirmation-message">
+ {{ $options.i18n.deleteConfirmationMessage }}
+ </p>
+ <form v-else class="js-board-config-modal" data-testid="board-form-wrapper" @submit.prevent>
+ <div v-if="!readonly" class="gl-mb-5" data-testid="board-form">
<label class="gl-font-weight-bold gl-font-lg" for="board-new-name">
{{ $options.i18n.titleFieldLabel }}
</label>
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 7fc91bc4aee..b0521e69b93 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -345,6 +345,7 @@ export default {
:scoped-issue-board-feature-enabled="scopedIssueBoardFeatureEnabled"
:weights="weights"
:enable-scoped-labels="enabledScopedLabels"
+ :current-board="currentBoard"
/>
</span>
</div>
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index cb9782b08ed..a58afa759fb 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -349,5 +349,8 @@ export default () => {
toggleEpicsSwimlanes();
}
- mountMultipleBoardsSwitcher();
+ mountMultipleBoardsSwitcher({
+ boardsEndpoint: $boardApp.dataset.boardsEndpoint,
+ recentBoardsEndpoint: $boardApp.dataset.recentBoardsEndpoint,
+ });
};
diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
index 51bb72b7657..df65ebb7526 100644
--- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
+++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
@@ -10,7 +10,7 @@ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
-export default () => {
+export default (endpoints = {}) => {
const boardsSwitcherElement = document.getElementById('js-multiple-boards-switcher');
return new Vue({
el: boardsSwitcherElement,
@@ -35,6 +35,9 @@ export default () => {
return { boardsSelectorProps };
},
+ provide: {
+ endpoints,
+ },
render(createElement) {
return createElement(BoardsSelector, {
props: this.boardsSelectorProps,
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 4505155d7d6..90ca4851a4c 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -1,7 +1,7 @@
/* eslint-disable no-shadow, no-param-reassign,consistent-return */
/* global List */
/* global ListIssue */
-import { sortBy, pick } from 'lodash';
+import { sortBy } from 'lodash';
import Vue from 'vue';
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
import {
@@ -21,8 +21,6 @@ import ListLabel from '../models/label';
import ListAssignee from '../models/assignee';
import ListMilestone from '../models/milestone';
-import createBoardMutation from '../graphql/board.mutation.graphql';
-
const PER_PAGE = 20;
export const gqlClient = createDefaultClient();
@@ -759,52 +757,6 @@ const boardsStore = {
return axios.get(this.state.endpoints.recentBoardsEndpoint);
},
- createBoard(board) {
- const boardPayload = { ...board };
- boardPayload.label_ids = (board.labels || []).map(b => b.id);
-
- if (boardPayload.label_ids.length === 0) {
- boardPayload.label_ids = [''];
- }
-
- if (boardPayload.assignee) {
- boardPayload.assignee_id = boardPayload.assignee.id;
- }
-
- if (boardPayload.milestone) {
- boardPayload.milestone_id = boardPayload.milestone.id;
- }
-
- if (boardPayload.id) {
- const input = {
- ...pick(boardPayload, ['hideClosedList', 'hideBacklogList']),
- id: this.generateBoardGid(boardPayload.id),
- };
-
- return Promise.all([
- axios.put(this.generateBoardsPath(boardPayload.id), { board: boardPayload }),
- gqlClient.mutate({
- mutation: createBoardMutation,
- variables: input,
- }),
- ]);
- }
-
- return axios
- .post(this.generateBoardsPath(), { board: boardPayload })
- .then(resp => resp.data)
- .then(data => {
- gqlClient.mutate({
- mutation: createBoardMutation,
- variables: {
- ...pick(boardPayload, ['hideClosedList', 'hideBacklogList']),
- id: this.generateBoardGid(data.id),
- },
- });
- return data;
- });
- },
-
deleteBoard({ id }) {
return axios.delete(this.generateBoardsPath(id));
},
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index 9d2deb1d4d0..7c3e522a488 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -134,15 +134,17 @@ export default {
@after-enter="afterEndTransition"
>
<div v-if="isCompact" ref="compactEl" class="commit-form-compact">
- <button
+ <gl-button
:disabled="!someUncommittedChanges"
- type="button"
- class="btn btn-primary btn-sm btn-block qa-begin-commit-button"
+ category="primary"
+ variant="info"
+ block
+ class="qa-begin-commit-button"
data-testid="begin-commit-button"
@click="beginCommit"
>
{{ __('Commit…') }}
- </button>
+ </gl-button>
<p class="text-center bold">{{ overviewText }}</p>
</div>
<form v-else ref="formEl" @submit.prevent.stop="commit">
@@ -158,28 +160,21 @@ export default {
<gl-button
:loading="submitCommitLoading"
class="float-left qa-commit-button"
- size="small"
category="primary"
variant="success"
@click="commit"
>
{{ __('Commit') }}
</gl-button>
- <button
- v-if="!discardDraftButtonDisabled"
- type="button"
- class="btn btn-default btn-sm float-right"
- @click="discardDraft"
- >
+ <gl-button v-if="!discardDraftButtonDisabled" class="float-right" @click="discardDraft">
{{ __('Discard draft') }}
- </button>
+ </gl-button>
<gl-button
v-else
type="button"
class="float-right"
category="secondary"
variant="default"
- size="small"
@click="toggleIsCompact"
>
{{ __('Collapse') }}
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue b/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue
index ff613daf7fa..3eeb7b29386 100644
--- a/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue
+++ b/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue
@@ -1,15 +1,29 @@
<script>
import { GlSprintf } from '@gitlab/ui';
+import { sprintf } from '~/locale';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
-import { DETAILS_PAGE_TITLE } from '../../constants/index';
+import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { DETAILS_PAGE_TITLE, UPDATED_AT } from '../../constants/index';
export default {
- components: { GlSprintf, TitleArea },
+ components: { GlSprintf, TitleArea, MetadataItem },
+ mixins: [timeagoMixin],
props: {
- imageName: {
- type: String,
- required: false,
- default: '',
+ image: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ visibilityIcon() {
+ return this.image?.project?.visibility === 'public' ? 'eye' : 'eye-slash';
+ },
+ timeAgo() {
+ return this.timeFormatted(this.image.updatedAt);
+ },
+ updatedText() {
+ return sprintf(UPDATED_AT, { time: this.timeAgo });
},
},
i18n: {
@@ -23,9 +37,17 @@ export default {
<template #title>
<gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE">
<template #imageName>
- {{ imageName }}
+ {{ image.name }}
</template>
</gl-sprintf>
</template>
+ <template #metadata-updated>
+ <metadata-item
+ :icon="visibilityIcon"
+ :text="updatedText"
+ size="xl"
+ data-testid="updated-and-visibility"
+ />
+ </template>
</title-area>
</template>
diff --git a/app/assets/javascripts/registry/explorer/constants/details.js b/app/assets/javascripts/registry/explorer/constants/details.js
index 306e6903a4f..1babaaa93da 100644
--- a/app/assets/javascripts/registry/explorer/constants/details.js
+++ b/app/assets/javascripts/registry/explorer/constants/details.js
@@ -56,6 +56,8 @@ export const MISSING_MANIFEST_WARNING_TOOLTIP = s__(
'ContainerRegistry|Invalid tag: missing manifest digest',
);
+export const UPDATED_AT = s__('ContainerRegistry|Last updated %{time}');
+
export const NOT_AVAILABLE_TEXT = __('N/A');
export const NOT_AVAILABLE_SIZE = __('0 bytes');
// Parameters
diff --git a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql
index a54934fa681..b40200e020b 100644
--- a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql
+++ b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql
@@ -15,6 +15,7 @@ query getContainerRepositoryDetails(
location
canDelete
createdAt
+ updatedAt
tagsCount
expirationPolicyStartedAt
tags(after: $after, before: $before, first: $first, last: $last) {
@@ -33,5 +34,8 @@ query getContainerRepositoryDetails(
...PageInfo
}
}
+ project {
+ visibility
+ }
}
}
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index 7e93b05b43a..57c59c7dc42 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -183,7 +183,7 @@ export default {
@dismiss="dismissPartialCleanupWarning = true"
/>
- <details-header :image-name="image.name" />
+ <details-header :image="image" />
<tags-loader v-if="isLoading" />
<template v-else>
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index 4070496b436..c4ce2cecd8b 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -78,7 +78,7 @@ module Types
attr_reader :feature_flag
def feature_documentation_message(key, description)
- "#{description}. Available only when feature flag `#{key}` is enabled"
+ "#{description} Available only when feature flag `#{key}` is enabled."
end
def check_feature_flag(args)
diff --git a/app/graphql/types/concerns/gitlab_style_deprecations.rb b/app/graphql/types/concerns/gitlab_style_deprecations.rb
index 2c932f4214b..9f087f3812d 100644
--- a/app/graphql/types/concerns/gitlab_style_deprecations.rb
+++ b/app/graphql/types/concerns/gitlab_style_deprecations.rb
@@ -23,8 +23,8 @@ module GitlabStyleDeprecations
raise ArgumentError, '`milestone` must be a `String`' unless milestone.is_a?(String)
deprecated_in = "Deprecated in #{milestone}"
- kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}"
- kwargs[:description] += ". #{deprecated_in}: #{reason}" if kwargs[:description]
+ kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}."
+ kwargs[:description] += " #{deprecated_in}: #{reason}." if kwargs[:description]
kwargs
end
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index eafee325500..e32d9dcba92 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -40,6 +40,7 @@
.settings-content
= render 'shared/badges/badge_settings'
+= render_if_exists 'groups/compliance_frameworks', expanded: expanded
= render_if_exists 'groups/custom_project_templates_setting'
= render_if_exists 'groups/templates_setting', expanded: expanded
diff --git a/changelogs/unreleased/216795-show-last-update-and-visibility.yml b/changelogs/unreleased/216795-show-last-update-and-visibility.yml
new file mode 100644
index 00000000000..4ed9e3d25db
--- /dev/null
+++ b/changelogs/unreleased/216795-show-last-update-and-visibility.yml
@@ -0,0 +1,5 @@
+---
+title: Add visibility and last updated image repository details
+merge_request: 49703
+author:
+type: changed
diff --git a/changelogs/unreleased/229310-migrate-bootstrap-button.yml b/changelogs/unreleased/229310-migrate-bootstrap-button.yml
new file mode 100644
index 00000000000..5952fa463ae
--- /dev/null
+++ b/changelogs/unreleased/229310-migrate-bootstrap-button.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate Bootstrap button to GitLab UI GlButton in IDE
+merge_request: 39988
+author:
+type: changed
diff --git a/changelogs/unreleased/244273-boards-migrate-createboard-board_store-function-to-vuex-action.yml b/changelogs/unreleased/244273-boards-migrate-createboard-board_store-function-to-vuex-action.yml
new file mode 100644
index 00000000000..d64587a064a
--- /dev/null
+++ b/changelogs/unreleased/244273-boards-migrate-createboard-board_store-function-to-vuex-action.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate `createBoard` away from boardStore
+merge_request: 49450
+author:
+type: changed
diff --git a/changelogs/unreleased/id-bump-gitlab-shell-version.yml b/changelogs/unreleased/id-bump-gitlab-shell-version.yml
new file mode 100644
index 00000000000..d681b89809a
--- /dev/null
+++ b/changelogs/unreleased/id-bump-gitlab-shell-version.yml
@@ -0,0 +1,5 @@
+---
+title: Bump gitlab-shell version to v13.14.0
+merge_request: 49810
+author:
+type: other
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 75b586a902c..4ffe7c7598f 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -537,22 +537,22 @@ enum AlertManagementAlertSort {
"""
Created at ascending order
"""
- created_asc @deprecated(reason: "Use CREATED_ASC. Deprecated in 13.5")
+ created_asc @deprecated(reason: "Use CREATED_ASC. Deprecated in 13.5.")
"""
Created at descending order
"""
- created_desc @deprecated(reason: "Use CREATED_DESC. Deprecated in 13.5")
+ created_desc @deprecated(reason: "Use CREATED_DESC. Deprecated in 13.5.")
"""
Updated at ascending order
"""
- updated_asc @deprecated(reason: "Use UPDATED_ASC. Deprecated in 13.5")
+ updated_asc @deprecated(reason: "Use UPDATED_ASC. Deprecated in 13.5.")
"""
Updated at descending order
"""
- updated_desc @deprecated(reason: "Use UPDATED_DESC. Deprecated in 13.5")
+ updated_desc @deprecated(reason: "Use UPDATED_DESC. Deprecated in 13.5.")
}
"""
@@ -1435,7 +1435,7 @@ type BoardEpic implements CurrentUserTodos & Noteable {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use timeframe.end
+ date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.
"""
endDate: Time
@@ -1491,8 +1491,8 @@ type BoardEpic implements CurrentUserTodos & Noteable {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use
- timeframe.start
+ date is provided, both must be present) Deprecated in 13.5: Use
+ timeframe.start.
"""
startDate: Time
@@ -4925,9 +4925,9 @@ type DastScannerProfile {
editPath: String
"""
- ID of the DAST scanner profile. Deprecated in 13.6: Use `id`
+ ID of the DAST scanner profile Deprecated in 13.6: Use `id`.
"""
- globalId: DastScannerProfileID! @deprecated(reason: "Use `id`. Deprecated in 13.6")
+ globalId: DastScannerProfileID! @deprecated(reason: "Use `id`. Deprecated in 13.6.")
"""
ID of the DAST scanner profile
@@ -5049,9 +5049,9 @@ type DastScannerProfileCreatePayload {
errors: [String!]!
"""
- ID of the scanner profile.. Deprecated in 13.6: Use `id`
+ ID of the scanner profile. Deprecated in 13.6: Use `id`.
"""
- globalId: DastScannerProfileID @deprecated(reason: "Use `id`. Deprecated in 13.6")
+ globalId: DastScannerProfileID @deprecated(reason: "Use `id`. Deprecated in 13.6.")
"""
ID of the scanner profile.
@@ -7540,7 +7540,7 @@ type Epic implements CurrentUserTodos & Noteable {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use timeframe.end
+ date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.
"""
endDate: Time
@@ -7596,8 +7596,8 @@ type Epic implements CurrentUserTodos & Noteable {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use
- timeframe.start
+ date is provided, both must be present) Deprecated in 13.5: Use
+ timeframe.start.
"""
startDate: Time
@@ -9213,8 +9213,8 @@ type Group {
): CodeCoverageActivityConnection
"""
- Compliance frameworks available to projects in this namespace. Available only
- when feature flag `ff_custom_compliance_frameworks` is enabled
+ Compliance frameworks available to projects in this namespace Available only
+ when feature flag `ff_custom_compliance_frameworks` is enabled.
"""
complianceFrameworks(
"""
@@ -9279,7 +9279,7 @@ type Group {
containsLockedProjects: Boolean!
"""
- Custom emoji within this namespace. Available only when feature flag `custom_emoji` is enabled
+ Custom emoji within this namespace Available only when feature flag `custom_emoji` is enabled.
"""
customEmoji(
"""
@@ -9329,7 +9329,7 @@ type Group {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use timeframe.end
+ date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.
"""
endDate: Time
@@ -9375,8 +9375,8 @@ type Group {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use
- timeframe.start
+ date is provided, both must be present) Deprecated in 13.5: Use
+ timeframe.start.
"""
startDate: Time
@@ -9447,7 +9447,7 @@ type Group {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use timeframe.end
+ date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.
"""
endDate: Time
@@ -9503,8 +9503,8 @@ type Group {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use
- timeframe.start
+ date is provided, both must be present) Deprecated in 13.5: Use
+ timeframe.start.
"""
startDate: Time
@@ -9730,7 +9730,7 @@ type Group {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use timeframe.end
+ date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.
"""
endDate: Time
@@ -9761,8 +9761,8 @@ type Group {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use
- timeframe.start
+ date is provided, both must be present) Deprecated in 13.5: Use
+ timeframe.start.
"""
startDate: Time
@@ -9938,7 +9938,7 @@ type Group {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use timeframe.end
+ date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.
"""
endDate: Time
@@ -9969,8 +9969,8 @@ type Group {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use
- timeframe.start
+ date is provided, both must be present) Deprecated in 13.5: Use
+ timeframe.start.
"""
startDate: Time
@@ -10272,7 +10272,7 @@ type Group {
"""
Number of vulnerabilities per severity level, per day, for the projects in the
- group and its subgroups. Deprecated in 13.3: Use `vulnerabilitiesCountByDay`
+ group and its subgroups Deprecated in 13.3: Use `vulnerabilitiesCountByDay`.
"""
vulnerabilitiesCountByDayAndSeverity(
"""
@@ -10304,7 +10304,7 @@ type Group {
First day for which to fetch vulnerability history
"""
startDate: ISO8601Date!
- ): VulnerabilitiesCountByDayAndSeverityConnection @deprecated(reason: "Use `vulnerabilitiesCountByDay`. Deprecated in 13.3")
+ ): VulnerabilitiesCountByDayAndSeverityConnection @deprecated(reason: "Use `vulnerabilitiesCountByDay`. Deprecated in 13.3.")
"""
Represents vulnerable project counts for each grade
@@ -12066,22 +12066,22 @@ enum IssueSort {
"""
Created at ascending order
"""
- created_asc @deprecated(reason: "Use CREATED_ASC. Deprecated in 13.5")
+ created_asc @deprecated(reason: "Use CREATED_ASC. Deprecated in 13.5.")
"""
Created at descending order
"""
- created_desc @deprecated(reason: "Use CREATED_DESC. Deprecated in 13.5")
+ created_desc @deprecated(reason: "Use CREATED_DESC. Deprecated in 13.5.")
"""
Updated at ascending order
"""
- updated_asc @deprecated(reason: "Use UPDATED_ASC. Deprecated in 13.5")
+ updated_asc @deprecated(reason: "Use UPDATED_ASC. Deprecated in 13.5.")
"""
Updated at descending order
"""
- updated_desc @deprecated(reason: "Use UPDATED_DESC. Deprecated in 13.5")
+ updated_desc @deprecated(reason: "Use UPDATED_DESC. Deprecated in 13.5.")
}
"""
@@ -14124,22 +14124,22 @@ enum MergeRequestSort {
"""
Created at ascending order
"""
- created_asc @deprecated(reason: "Use CREATED_ASC. Deprecated in 13.5")
+ created_asc @deprecated(reason: "Use CREATED_ASC. Deprecated in 13.5.")
"""
Created at descending order
"""
- created_desc @deprecated(reason: "Use CREATED_DESC. Deprecated in 13.5")
+ created_desc @deprecated(reason: "Use CREATED_DESC. Deprecated in 13.5.")
"""
Updated at ascending order
"""
- updated_asc @deprecated(reason: "Use UPDATED_ASC. Deprecated in 13.5")
+ updated_asc @deprecated(reason: "Use UPDATED_ASC. Deprecated in 13.5.")
"""
Updated at descending order
"""
- updated_desc @deprecated(reason: "Use UPDATED_DESC. Deprecated in 13.5")
+ updated_desc @deprecated(reason: "Use UPDATED_DESC. Deprecated in 13.5.")
}
"""
@@ -14515,7 +14515,7 @@ enum MoveType {
}
type Mutation {
- addAwardEmoji(input: AddAwardEmojiInput!): AddAwardEmojiPayload @deprecated(reason: "Use awardEmojiAdd. Deprecated in 13.2")
+ addAwardEmoji(input: AddAwardEmojiInput!): AddAwardEmojiPayload @deprecated(reason: "Use awardEmojiAdd. Deprecated in 13.2.")
addProjectToSecurityDashboard(input: AddProjectToSecurityDashboardInput!): AddProjectToSecurityDashboardPayload
adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload
alertSetAssignees(input: AlertSetAssigneesInput!): AlertSetAssigneesPayload
@@ -14537,7 +14537,7 @@ type Mutation {
createClusterAgent(input: CreateClusterAgentInput!): CreateClusterAgentPayload
"""
- . Available only when feature flag `custom_emoji` is enabled
+ Available only when feature flag `custom_emoji` is enabled.
"""
createCustomEmoji(input: CreateCustomEmojiInput!): CreateCustomEmojiPayload
createDevopsAdoptionSegment(input: CreateDevopsAdoptionSegmentInput!): CreateDevopsAdoptionSegmentPayload
@@ -14576,7 +14576,7 @@ type Mutation {
Toggles the resolved state of a discussion
"""
discussionToggleResolve(input: DiscussionToggleResolveInput!): DiscussionToggleResolvePayload
- dismissVulnerability(input: DismissVulnerabilityInput!): DismissVulnerabilityPayload @deprecated(reason: "Use vulnerabilityDismiss. Deprecated in 13.5")
+ dismissVulnerability(input: DismissVulnerabilityInput!): DismissVulnerabilityPayload @deprecated(reason: "Use vulnerabilityDismiss. Deprecated in 13.5.")
environmentsCanaryIngressUpdate(input: EnvironmentsCanaryIngressUpdateInput!): EnvironmentsCanaryIngressUpdatePayload
epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
@@ -14626,15 +14626,15 @@ type Mutation {
releaseCreate(input: ReleaseCreateInput!): ReleaseCreatePayload
releaseDelete(input: ReleaseDeleteInput!): ReleaseDeletePayload
releaseUpdate(input: ReleaseUpdateInput!): ReleaseUpdatePayload
- removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload @deprecated(reason: "Use awardEmojiRemove. Deprecated in 13.2")
+ removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload @deprecated(reason: "Use awardEmojiRemove. Deprecated in 13.2.")
removeProjectFromSecurityDashboard(input: RemoveProjectFromSecurityDashboardInput!): RemoveProjectFromSecurityDashboardPayload
"""
Repositions a DiffNote on an image (a `Note` where the `position.positionType` is `"image"`)
"""
repositionImageDiffNote(input: RepositionImageDiffNoteInput!): RepositionImageDiffNotePayload
- revertVulnerabilityToDetected(input: RevertVulnerabilityToDetectedInput!): RevertVulnerabilityToDetectedPayload @deprecated(reason: "Use vulnerabilityRevertToDetected. Deprecated in 13.5")
- runDastScan(input: RunDASTScanInput!): RunDASTScanPayload @deprecated(reason: "Use DastOnDemandScanCreate. Deprecated in 13.4")
+ revertVulnerabilityToDetected(input: RevertVulnerabilityToDetectedInput!): RevertVulnerabilityToDetectedPayload @deprecated(reason: "Use vulnerabilityRevertToDetected. Deprecated in 13.5.")
+ runDastScan(input: RunDASTScanInput!): RunDASTScanPayload @deprecated(reason: "Use DastOnDemandScanCreate. Deprecated in 13.4.")
terraformStateDelete(input: TerraformStateDeleteInput!): TerraformStateDeletePayload
terraformStateLock(input: TerraformStateLockInput!): TerraformStateLockPayload
terraformStateUnlock(input: TerraformStateUnlockInput!): TerraformStateUnlockPayload
@@ -14643,7 +14643,7 @@ type Mutation {
todoRestore(input: TodoRestoreInput!): TodoRestorePayload
todoRestoreMany(input: TodoRestoreManyInput!): TodoRestoreManyPayload
todosMarkAllDone(input: TodosMarkAllDoneInput!): TodosMarkAllDonePayload
- toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload @deprecated(reason: "Use awardEmojiToggle. Deprecated in 13.2")
+ toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload @deprecated(reason: "Use awardEmojiToggle. Deprecated in 13.2.")
updateAlertStatus(input: UpdateAlertStatusInput!): UpdateAlertStatusPayload
updateBoard(input: UpdateBoardInput!): UpdateBoardPayload
updateBoardEpicUserPreferences(input: UpdateBoardEpicUserPreferencesInput!): UpdateBoardEpicUserPreferencesPayload
@@ -14707,8 +14707,8 @@ type Namespace {
additionalPurchasedStorageSize: Float
"""
- Compliance frameworks available to projects in this namespace. Available only
- when feature flag `ff_custom_compliance_frameworks` is enabled
+ Compliance frameworks available to projects in this namespace Available only
+ when feature flag `ff_custom_compliance_frameworks` is enabled.
"""
complianceFrameworks(
"""
@@ -16912,7 +16912,7 @@ type Project {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use timeframe.end
+ date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.
"""
endDate: Time
@@ -16943,8 +16943,8 @@ type Project {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use
- timeframe.start
+ date is provided, both must be present) Deprecated in 13.5: Use
+ timeframe.start.
"""
startDate: Time
@@ -17177,7 +17177,7 @@ type Project {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use timeframe.end
+ date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.
"""
endDate: Time
@@ -17208,8 +17208,8 @@ type Project {
"""
List items overlapping a time frame defined by startDate..endDate (if one
- date is provided, both must be present). Deprecated in 13.5: Use
- timeframe.start
+ date is provided, both must be present) Deprecated in 13.5: Use
+ timeframe.start.
"""
startDate: Time
@@ -19008,8 +19008,8 @@ type Query {
"""
Number of vulnerabilities per severity level, per day, for the projects on the
- current user's instance security dashboard. Deprecated in 13.3: Use
- `vulnerabilitiesCountByDay`
+ current user's instance security dashboard Deprecated in 13.3: Use
+ `vulnerabilitiesCountByDay`.
"""
vulnerabilitiesCountByDayAndSeverity(
"""
@@ -19041,7 +19041,7 @@ type Query {
First day for which to fetch vulnerability history
"""
startDate: ISO8601Date!
- ): VulnerabilitiesCountByDayAndSeverityConnection @deprecated(reason: "Use `vulnerabilitiesCountByDay`. Deprecated in 13.3")
+ ): VulnerabilitiesCountByDayAndSeverityConnection @deprecated(reason: "Use `vulnerabilitiesCountByDay`. Deprecated in 13.3.")
"""
Find a vulnerability
@@ -21529,9 +21529,9 @@ type Snippet implements Noteable {
author: User
"""
- Snippet blob. Deprecated in 13.3: Use `blobs`
+ Snippet blob Deprecated in 13.3: Use `blobs`.
"""
- blob: SnippetBlob! @deprecated(reason: "Use `blobs`. Deprecated in 13.3")
+ blob: SnippetBlob! @deprecated(reason: "Use `blobs`. Deprecated in 13.3.")
"""
Snippet blobs
@@ -22038,22 +22038,22 @@ enum Sort {
"""
Created at ascending order
"""
- created_asc @deprecated(reason: "Use CREATED_ASC. Deprecated in 13.5")
+ created_asc @deprecated(reason: "Use CREATED_ASC. Deprecated in 13.5.")
"""
Created at descending order
"""
- created_desc @deprecated(reason: "Use CREATED_DESC. Deprecated in 13.5")
+ created_desc @deprecated(reason: "Use CREATED_DESC. Deprecated in 13.5.")
"""
Updated at ascending order
"""
- updated_asc @deprecated(reason: "Use UPDATED_ASC. Deprecated in 13.5")
+ updated_asc @deprecated(reason: "Use UPDATED_ASC. Deprecated in 13.5.")
"""
Updated at descending order
"""
- updated_desc @deprecated(reason: "Use UPDATED_DESC. Deprecated in 13.5")
+ updated_desc @deprecated(reason: "Use UPDATED_DESC. Deprecated in 13.5.")
}
type StatusAction {
@@ -22891,9 +22891,9 @@ type TodoRestoreManyPayload {
todos: [Todo!]!
"""
- The IDs of the updated todo items. Deprecated in 13.2: Use todos
+ The IDs of the updated todo items Deprecated in 13.2: Use todos.
"""
- updatedIds: [TodoID!]! @deprecated(reason: "Use todos. Deprecated in 13.2")
+ updatedIds: [TodoID!]! @deprecated(reason: "Use todos. Deprecated in 13.2.")
}
"""
@@ -22988,9 +22988,9 @@ type TodosMarkAllDonePayload {
todos: [Todo!]!
"""
- Ids of the updated todos. Deprecated in 13.2: Use todos
+ Ids of the updated todos Deprecated in 13.2: Use todos.
"""
- updatedIds: [TodoID!]! @deprecated(reason: "Use todos. Deprecated in 13.2")
+ updatedIds: [TodoID!]! @deprecated(reason: "Use todos. Deprecated in 13.2.")
}
"""
@@ -24261,12 +24261,12 @@ type User {
avatarUrl: String
"""
- User email. Deprecated in 13.7: Use public_email
+ User email Deprecated in 13.7: Use public_email.
"""
- email: String @deprecated(reason: "Use public_email. Deprecated in 13.7")
+ email: String @deprecated(reason: "Use public_email. Deprecated in 13.7.")
"""
- Group count for the user. Available only when feature flag `user_group_counts` is enabled
+ Group count for the user Available only when feature flag `user_group_counts` is enabled.
"""
groupCount: Int
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index a69b67af66f..84f5c88e12c 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -1252,25 +1252,25 @@
"name": "updated_desc",
"description": "Updated at descending order",
"isDeprecated": true,
- "deprecationReason": "Use UPDATED_DESC. Deprecated in 13.5"
+ "deprecationReason": "Use UPDATED_DESC. Deprecated in 13.5."
},
{
"name": "updated_asc",
"description": "Updated at ascending order",
"isDeprecated": true,
- "deprecationReason": "Use UPDATED_ASC. Deprecated in 13.5"
+ "deprecationReason": "Use UPDATED_ASC. Deprecated in 13.5."
},
{
"name": "created_desc",
"description": "Created at descending order",
"isDeprecated": true,
- "deprecationReason": "Use CREATED_DESC. Deprecated in 13.5"
+ "deprecationReason": "Use CREATED_DESC. Deprecated in 13.5."
},
{
"name": "created_asc",
"description": "Created at ascending order",
"isDeprecated": true,
- "deprecationReason": "Use CREATED_ASC. Deprecated in 13.5"
+ "deprecationReason": "Use CREATED_ASC. Deprecated in 13.5."
},
{
"name": "UPDATED_DESC",
@@ -3766,7 +3766,7 @@
"args": [
{
"name": "startDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.start",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.start.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -3776,7 +3776,7 @@
},
{
"name": "endDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.end",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -13550,7 +13550,7 @@
},
{
"name": "globalId",
- "description": "ID of the DAST scanner profile. Deprecated in 13.6: Use `id`",
+ "description": "ID of the DAST scanner profile Deprecated in 13.6: Use `id`.",
"args": [
],
@@ -13564,7 +13564,7 @@
}
},
"isDeprecated": true,
- "deprecationReason": "Use `id`. Deprecated in 13.6"
+ "deprecationReason": "Use `id`. Deprecated in 13.6."
},
{
"name": "id",
@@ -13897,7 +13897,7 @@
},
{
"name": "globalId",
- "description": "ID of the scanner profile.. Deprecated in 13.6: Use `id`",
+ "description": "ID of the scanner profile. Deprecated in 13.6: Use `id`.",
"args": [
],
@@ -13907,7 +13907,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use `id`. Deprecated in 13.6"
+ "deprecationReason": "Use `id`. Deprecated in 13.6."
},
{
"name": "id",
@@ -21129,7 +21129,7 @@
"args": [
{
"name": "startDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.start",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.start.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -21139,7 +21139,7 @@
},
{
"name": "endDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.end",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -25739,7 +25739,7 @@
},
{
"name": "complianceFrameworks",
- "description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled",
+ "description": "Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled.",
"args": [
{
"name": "after",
@@ -25891,7 +25891,7 @@
},
{
"name": "customEmoji",
- "description": "Custom emoji within this namespace. Available only when feature flag `custom_emoji` is enabled",
+ "description": "Custom emoji within this namespace Available only when feature flag `custom_emoji` is enabled.",
"args": [
{
"name": "after",
@@ -25990,7 +25990,7 @@
"args": [
{
"name": "startDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.start",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.start.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -26000,7 +26000,7 @@
},
{
"name": "endDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.end",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -26229,7 +26229,7 @@
"args": [
{
"name": "startDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.start",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.start.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -26239,7 +26239,7 @@
},
{
"name": "endDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.end",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -26908,7 +26908,7 @@
"args": [
{
"name": "startDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.start",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.start.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -26918,7 +26918,7 @@
},
{
"name": "endDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.end",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -27364,7 +27364,7 @@
"args": [
{
"name": "startDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.start",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.start.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -27374,7 +27374,7 @@
},
{
"name": "endDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.end",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -28227,7 +28227,7 @@
},
{
"name": "vulnerabilitiesCountByDayAndSeverity",
- "description": "Number of vulnerabilities per severity level, per day, for the projects in the group and its subgroups. Deprecated in 13.3: Use `vulnerabilitiesCountByDay`",
+ "description": "Number of vulnerabilities per severity level, per day, for the projects in the group and its subgroups Deprecated in 13.3: Use `vulnerabilitiesCountByDay`.",
"args": [
{
"name": "startDate",
@@ -28304,7 +28304,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use `vulnerabilitiesCountByDay`. Deprecated in 13.3"
+ "deprecationReason": "Use `vulnerabilitiesCountByDay`. Deprecated in 13.3."
},
{
"name": "vulnerabilityGrades",
@@ -33085,25 +33085,25 @@
"name": "updated_desc",
"description": "Updated at descending order",
"isDeprecated": true,
- "deprecationReason": "Use UPDATED_DESC. Deprecated in 13.5"
+ "deprecationReason": "Use UPDATED_DESC. Deprecated in 13.5."
},
{
"name": "updated_asc",
"description": "Updated at ascending order",
"isDeprecated": true,
- "deprecationReason": "Use UPDATED_ASC. Deprecated in 13.5"
+ "deprecationReason": "Use UPDATED_ASC. Deprecated in 13.5."
},
{
"name": "created_desc",
"description": "Created at descending order",
"isDeprecated": true,
- "deprecationReason": "Use CREATED_DESC. Deprecated in 13.5"
+ "deprecationReason": "Use CREATED_DESC. Deprecated in 13.5."
},
{
"name": "created_asc",
"description": "Created at ascending order",
"isDeprecated": true,
- "deprecationReason": "Use CREATED_ASC. Deprecated in 13.5"
+ "deprecationReason": "Use CREATED_ASC. Deprecated in 13.5."
},
{
"name": "UPDATED_DESC",
@@ -39012,25 +39012,25 @@
"name": "updated_desc",
"description": "Updated at descending order",
"isDeprecated": true,
- "deprecationReason": "Use UPDATED_DESC. Deprecated in 13.5"
+ "deprecationReason": "Use UPDATED_DESC. Deprecated in 13.5."
},
{
"name": "updated_asc",
"description": "Updated at ascending order",
"isDeprecated": true,
- "deprecationReason": "Use UPDATED_ASC. Deprecated in 13.5"
+ "deprecationReason": "Use UPDATED_ASC. Deprecated in 13.5."
},
{
"name": "created_desc",
"description": "Created at descending order",
"isDeprecated": true,
- "deprecationReason": "Use CREATED_DESC. Deprecated in 13.5"
+ "deprecationReason": "Use CREATED_DESC. Deprecated in 13.5."
},
{
"name": "created_asc",
"description": "Created at ascending order",
"isDeprecated": true,
- "deprecationReason": "Use CREATED_ASC. Deprecated in 13.5"
+ "deprecationReason": "Use CREATED_ASC. Deprecated in 13.5."
},
{
"name": "UPDATED_DESC",
@@ -40261,7 +40261,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use awardEmojiAdd. Deprecated in 13.2"
+ "deprecationReason": "Use awardEmojiAdd. Deprecated in 13.2."
},
{
"name": "addProjectToSecurityDashboard",
@@ -40778,7 +40778,7 @@
},
{
"name": "createCustomEmoji",
- "description": ". Available only when feature flag `custom_emoji` is enabled",
+ "description": " Available only when feature flag `custom_emoji` is enabled.",
"args": [
{
"name": "input",
@@ -41692,7 +41692,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use vulnerabilityDismiss. Deprecated in 13.5"
+ "deprecationReason": "Use vulnerabilityDismiss. Deprecated in 13.5."
},
{
"name": "environmentsCanaryIngressUpdate",
@@ -42934,7 +42934,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use awardEmojiRemove. Deprecated in 13.2"
+ "deprecationReason": "Use awardEmojiRemove. Deprecated in 13.2."
},
{
"name": "removeProjectFromSecurityDashboard",
@@ -43015,7 +43015,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use vulnerabilityRevertToDetected. Deprecated in 13.5"
+ "deprecationReason": "Use vulnerabilityRevertToDetected. Deprecated in 13.5."
},
{
"name": "runDastScan",
@@ -43042,7 +43042,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use DastOnDemandScanCreate. Deprecated in 13.4"
+ "deprecationReason": "Use DastOnDemandScanCreate. Deprecated in 13.4."
},
{
"name": "terraformStateDelete",
@@ -43285,7 +43285,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use awardEmojiToggle. Deprecated in 13.2"
+ "deprecationReason": "Use awardEmojiToggle. Deprecated in 13.2."
},
{
"name": "updateAlertStatus",
@@ -43845,7 +43845,7 @@
},
{
"name": "complianceFrameworks",
- "description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled",
+ "description": "Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled.",
"args": [
{
"name": "after",
@@ -49886,7 +49886,7 @@
"args": [
{
"name": "startDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.start",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.start.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -49896,7 +49896,7 @@
},
{
"name": "endDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.end",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -50478,7 +50478,7 @@
"args": [
{
"name": "startDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.start",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.start.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -50488,7 +50488,7 @@
},
{
"name": "endDate",
- "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present). Deprecated in 13.5: Use timeframe.end",
+ "description": "List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present) Deprecated in 13.5: Use timeframe.end.",
"type": {
"kind": "SCALAR",
"name": "Time",
@@ -55455,7 +55455,7 @@
},
{
"name": "vulnerabilitiesCountByDayAndSeverity",
- "description": "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard. Deprecated in 13.3: Use `vulnerabilitiesCountByDay`",
+ "description": "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard Deprecated in 13.3: Use `vulnerabilitiesCountByDay`.",
"args": [
{
"name": "startDate",
@@ -55532,7 +55532,7 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use `vulnerabilitiesCountByDay`. Deprecated in 13.3"
+ "deprecationReason": "Use `vulnerabilitiesCountByDay`. Deprecated in 13.3."
},
{
"name": "vulnerability",
@@ -62800,7 +62800,7 @@
},
{
"name": "blob",
- "description": "Snippet blob. Deprecated in 13.3: Use `blobs`",
+ "description": "Snippet blob Deprecated in 13.3: Use `blobs`.",
"args": [
],
@@ -62814,7 +62814,7 @@
}
},
"isDeprecated": true,
- "deprecationReason": "Use `blobs`. Deprecated in 13.3"
+ "deprecationReason": "Use `blobs`. Deprecated in 13.3."
},
{
"name": "blobs",
@@ -64283,25 +64283,25 @@
"name": "updated_desc",
"description": "Updated at descending order",
"isDeprecated": true,
- "deprecationReason": "Use UPDATED_DESC. Deprecated in 13.5"
+ "deprecationReason": "Use UPDATED_DESC. Deprecated in 13.5."
},
{
"name": "updated_asc",
"description": "Updated at ascending order",
"isDeprecated": true,
- "deprecationReason": "Use UPDATED_ASC. Deprecated in 13.5"
+ "deprecationReason": "Use UPDATED_ASC. Deprecated in 13.5."
},
{
"name": "created_desc",
"description": "Created at descending order",
"isDeprecated": true,
- "deprecationReason": "Use CREATED_DESC. Deprecated in 13.5"
+ "deprecationReason": "Use CREATED_DESC. Deprecated in 13.5."
},
{
"name": "created_asc",
"description": "Created at ascending order",
"isDeprecated": true,
- "deprecationReason": "Use CREATED_ASC. Deprecated in 13.5"
+ "deprecationReason": "Use CREATED_ASC. Deprecated in 13.5."
},
{
"name": "UPDATED_DESC",
@@ -66994,7 +66994,7 @@
},
{
"name": "updatedIds",
- "description": "The IDs of the updated todo items. Deprecated in 13.2: Use todos",
+ "description": "The IDs of the updated todo items Deprecated in 13.2: Use todos.",
"args": [
],
@@ -67016,7 +67016,7 @@
}
},
"isDeprecated": true,
- "deprecationReason": "Use todos. Deprecated in 13.2"
+ "deprecationReason": "Use todos. Deprecated in 13.2."
}
],
"inputFields": null,
@@ -67271,7 +67271,7 @@
},
{
"name": "updatedIds",
- "description": "Ids of the updated todos. Deprecated in 13.2: Use todos",
+ "description": "Ids of the updated todos Deprecated in 13.2: Use todos.",
"args": [
],
@@ -67293,7 +67293,7 @@
}
},
"isDeprecated": true,
- "deprecationReason": "Use todos. Deprecated in 13.2"
+ "deprecationReason": "Use todos. Deprecated in 13.2."
}
],
"inputFields": null,
@@ -70685,7 +70685,7 @@
},
{
"name": "email",
- "description": "User email. Deprecated in 13.7: Use public_email",
+ "description": "User email Deprecated in 13.7: Use public_email.",
"args": [
],
@@ -70695,11 +70695,11 @@
"ofType": null
},
"isDeprecated": true,
- "deprecationReason": "Use public_email. Deprecated in 13.7"
+ "deprecationReason": "Use public_email. Deprecated in 13.7."
},
{
"name": "groupCount",
- "description": "Group count for the user. Available only when feature flag `user_group_counts` is enabled",
+ "description": "Group count for the user Available only when feature flag `user_group_counts` is enabled.",
"args": [
],
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index b539f55b06d..c2ac7d7b7c4 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -831,7 +831,7 @@ Represents a DAST scanner profile.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `editPath` | String | Relative web path to the edit page of a scanner profile |
-| `globalId` **{warning-solid}** | DastScannerProfileID! | **Deprecated:** Use `id`. Deprecated in 13.6 |
+| `globalId` **{warning-solid}** | DastScannerProfileID! | **Deprecated:** Use `id`. Deprecated in 13.6. |
| `id` | DastScannerProfileID! | ID of the DAST scanner profile |
| `profileName` | String | Name of the DAST scanner profile |
| `scanType` | DastScanTypeEnum | Indicates the type of DAST scan that will run. Either a Passive Scan or an Active Scan. |
@@ -848,7 +848,7 @@ Autogenerated return type of DastScannerProfileCreate.
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
-| `globalId` **{warning-solid}** | DastScannerProfileID | **Deprecated:** Use `id`. Deprecated in 13.6 |
+| `globalId` **{warning-solid}** | DastScannerProfileID | **Deprecated:** Use `id`. Deprecated in 13.6. |
| `id` | DastScannerProfileID | ID of the scanner profile. |
### DastScannerProfileDeletePayload
@@ -1554,11 +1554,11 @@ Represents an external issue.
| `board` | Board | A single board of the group |
| `boards` | BoardConnection | Boards of the group |
| `codeCoverageActivities` | CodeCoverageActivityConnection | Represents the code coverage activity for this group |
-| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled |
+| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled. |
| `containerRepositories` | ContainerRepositoryConnection | Container repositories of the group |
| `containerRepositoriesCount` | Int! | Number of container repositories in the group |
| `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
-| `customEmoji` | CustomEmojiConnection | Custom emoji within this namespace. Available only when feature flag `custom_emoji` is enabled |
+| `customEmoji` | CustomEmojiConnection | Custom emoji within this namespace Available only when feature flag `custom_emoji` is enabled. |
| `description` | String | Description of the namespace |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `emailsDisabled` | Boolean | Indicates if a group has email notifications disabled |
@@ -1603,7 +1603,7 @@ Represents an external issue.
| `visibility` | String | Visibility of the namespace |
| `vulnerabilities` | VulnerabilityConnection | Vulnerabilities reported on the projects in the group and its subgroups |
| `vulnerabilitiesCountByDay` | VulnerabilitiesCountByDayConnection | Number of vulnerabilities per day for the projects in the group and its subgroups |
-| `vulnerabilitiesCountByDayAndSeverity` **{warning-solid}** | VulnerabilitiesCountByDayAndSeverityConnection | **Deprecated:** Use `vulnerabilitiesCountByDay`. Deprecated in 13.3 |
+| `vulnerabilitiesCountByDayAndSeverity` **{warning-solid}** | VulnerabilitiesCountByDayAndSeverityConnection | **Deprecated:** Use `vulnerabilitiesCountByDay`. Deprecated in 13.3. |
| `vulnerabilityGrades` | VulnerableProjectsByGrade! => Array | Represents vulnerable project counts for each grade |
| `vulnerabilityScanners` | VulnerabilityScannerConnection | Vulnerability scanners reported on the project vulnerabilties of the group and its subgroups |
| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity in the group and its subgroups |
@@ -2288,7 +2288,7 @@ Contains statistics about a milestone.
| ----- | ---- | ----------- |
| `actualRepositorySizeLimit` | Float | Size limit for repositories in the namespace in bytes |
| `additionalPurchasedStorageSize` | Float | Additional storage purchased for the root namespace in bytes |
-| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled |
+| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled. |
| `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
| `description` | String | Description of the namespace |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
@@ -3198,7 +3198,7 @@ Represents a snippet entry.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `author` | User | The owner of the snippet |
-| `blob` **{warning-solid}** | SnippetBlob! | **Deprecated:** Use `blobs`. Deprecated in 13.3 |
+| `blob` **{warning-solid}** | SnippetBlob! | **Deprecated:** Use `blobs`. Deprecated in 13.3. |
| `blobs` | SnippetBlobConnection | Snippet blobs |
| `createdAt` | Time! | Timestamp this snippet was created |
| `description` | String | Description of the snippet |
@@ -3468,7 +3468,7 @@ Autogenerated return type of TodoRestoreMany.
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `todos` | Todo! => Array | Updated todos |
-| `updatedIds` **{warning-solid}** | TodoID! => Array | **Deprecated:** Use todos. Deprecated in 13.2 |
+| `updatedIds` **{warning-solid}** | TodoID! => Array | **Deprecated:** Use todos. Deprecated in 13.2. |
### TodoRestorePayload
@@ -3489,7 +3489,7 @@ Autogenerated return type of TodosMarkAllDone.
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `todos` | Todo! => Array | Updated todos |
-| `updatedIds` **{warning-solid}** | TodoID! => Array | **Deprecated:** Use todos. Deprecated in 13.2 |
+| `updatedIds` **{warning-solid}** | TodoID! => Array | **Deprecated:** Use todos. Deprecated in 13.2. |
### ToggleAwardEmojiPayload
@@ -3676,8 +3676,8 @@ Autogenerated return type of UpdateSnippet.
| `assignedMergeRequests` | MergeRequestConnection | Merge Requests assigned to the user |
| `authoredMergeRequests` | MergeRequestConnection | Merge Requests authored by the user |
| `avatarUrl` | String | URL of the user's avatar |
-| `email` **{warning-solid}** | String | **Deprecated:** Use public_email. Deprecated in 13.7 |
-| `groupCount` | Int | Group count for the user. Available only when feature flag `user_group_counts` is enabled |
+| `email` **{warning-solid}** | String | **Deprecated:** Use public_email. Deprecated in 13.7. |
+| `groupCount` | Int | Group count for the user Available only when feature flag `user_group_counts` is enabled. |
| `groupMemberships` | GroupMemberConnection | Group memberships of the user |
| `id` | ID! | ID of the user |
| `location` | String | The location of the user. |
@@ -4015,10 +4015,10 @@ Values for sorting alerts.
| `UPDATED_DESC` | Updated at descending order |
| `UPDATED_TIME_ASC` | Created time by ascending order |
| `UPDATED_TIME_DESC` | Created time by descending order |
-| `created_asc` **{warning-solid}** | **Deprecated:** Use CREATED_ASC. Deprecated in 13.5 |
-| `created_desc` **{warning-solid}** | **Deprecated:** Use CREATED_DESC. Deprecated in 13.5 |
-| `updated_asc` **{warning-solid}** | **Deprecated:** Use UPDATED_ASC. Deprecated in 13.5 |
-| `updated_desc` **{warning-solid}** | **Deprecated:** Use UPDATED_DESC. Deprecated in 13.5 |
+| `created_asc` **{warning-solid}** | **Deprecated:** Use CREATED_ASC. Deprecated in 13.5. |
+| `created_desc` **{warning-solid}** | **Deprecated:** Use CREATED_DESC. Deprecated in 13.5. |
+| `updated_asc` **{warning-solid}** | **Deprecated:** Use UPDATED_ASC. Deprecated in 13.5. |
+| `updated_desc` **{warning-solid}** | **Deprecated:** Use UPDATED_DESC. Deprecated in 13.5. |
### AlertManagementDomainFilter
@@ -4332,10 +4332,10 @@ Values for sorting issues.
| `UPDATED_DESC` | Updated at descending order |
| `WEIGHT_ASC` | Weight by ascending order |
| `WEIGHT_DESC` | Weight by descending order |
-| `created_asc` **{warning-solid}** | **Deprecated:** Use CREATED_ASC. Deprecated in 13.5 |
-| `created_desc` **{warning-solid}** | **Deprecated:** Use CREATED_DESC. Deprecated in 13.5 |
-| `updated_asc` **{warning-solid}** | **Deprecated:** Use UPDATED_ASC. Deprecated in 13.5 |
-| `updated_desc` **{warning-solid}** | **Deprecated:** Use UPDATED_DESC. Deprecated in 13.5 |
+| `created_asc` **{warning-solid}** | **Deprecated:** Use CREATED_ASC. Deprecated in 13.5. |
+| `created_desc` **{warning-solid}** | **Deprecated:** Use CREATED_DESC. Deprecated in 13.5. |
+| `updated_asc` **{warning-solid}** | **Deprecated:** Use UPDATED_ASC. Deprecated in 13.5. |
+| `updated_desc` **{warning-solid}** | **Deprecated:** Use UPDATED_DESC. Deprecated in 13.5. |
### IssueState
@@ -4466,10 +4466,10 @@ Values for sorting merge requests.
| `PRIORITY_DESC` | Priority by descending order |
| `UPDATED_ASC` | Updated at ascending order |
| `UPDATED_DESC` | Updated at descending order |
-| `created_asc` **{warning-solid}** | **Deprecated:** Use CREATED_ASC. Deprecated in 13.5 |
-| `created_desc` **{warning-solid}** | **Deprecated:** Use CREATED_DESC. Deprecated in 13.5 |
-| `updated_asc` **{warning-solid}** | **Deprecated:** Use UPDATED_ASC. Deprecated in 13.5 |
-| `updated_desc` **{warning-solid}** | **Deprecated:** Use UPDATED_DESC. Deprecated in 13.5 |
+| `created_asc` **{warning-solid}** | **Deprecated:** Use CREATED_ASC. Deprecated in 13.5. |
+| `created_desc` **{warning-solid}** | **Deprecated:** Use CREATED_DESC. Deprecated in 13.5. |
+| `updated_asc` **{warning-solid}** | **Deprecated:** Use UPDATED_ASC. Deprecated in 13.5. |
+| `updated_desc` **{warning-solid}** | **Deprecated:** Use UPDATED_DESC. Deprecated in 13.5. |
### MergeRequestState
@@ -4724,10 +4724,10 @@ Common sort values.
| `CREATED_DESC` | Created at descending order |
| `UPDATED_ASC` | Updated at ascending order |
| `UPDATED_DESC` | Updated at descending order |
-| `created_asc` **{warning-solid}** | **Deprecated:** Use CREATED_ASC. Deprecated in 13.5 |
-| `created_desc` **{warning-solid}** | **Deprecated:** Use CREATED_DESC. Deprecated in 13.5 |
-| `updated_asc` **{warning-solid}** | **Deprecated:** Use UPDATED_ASC. Deprecated in 13.5 |
-| `updated_desc` **{warning-solid}** | **Deprecated:** Use UPDATED_DESC. Deprecated in 13.5 |
+| `created_asc` **{warning-solid}** | **Deprecated:** Use CREATED_ASC. Deprecated in 13.5. |
+| `created_desc` **{warning-solid}** | **Deprecated:** Use CREATED_DESC. Deprecated in 13.5. |
+| `updated_asc` **{warning-solid}** | **Deprecated:** Use UPDATED_ASC. Deprecated in 13.5. |
+| `updated_desc` **{warning-solid}** | **Deprecated:** Use UPDATED_DESC. Deprecated in 13.5. |
### TestReportState
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 613a09cdbc4..621d8179d98 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -14,7 +14,7 @@ Notes are comments on:
- Epics **(ULTIMATE)**
This includes system notes, which are notes about changes to the object (for example, when an
-assignee changes, there will be a corresponding system note).
+assignee changes, GitLab posts a system note).
## Resource events
@@ -137,7 +137,7 @@ Parameters:
- `issue_iid` (required) - The IID of an issue
- `body` (required) - The content of a note. Limited to 1,000,000 characters.
- `confidential` (optional) - The confidential flag of a note. Default is false.
-- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights)
+- `created_at` (optional) - Date time string, ISO 8601 formatted. Example: `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights)
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note"
@@ -244,8 +244,8 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
### Create new snippet note
-Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet.
-If you create a note where the body only contains an Award Emoji, you'll receive this object back.
+Creates a new note for a single snippet. Snippet notes are user comments on snippets.
+If you create a note where the body only contains an Award Emoji, GitLab returns this object.
```plaintext
POST /projects/:id/snippets/:snippet_id/notes
@@ -256,7 +256,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `snippet_id` (required) - The ID of a snippet
- `body` (required) - The content of a note. Limited to 1,000,000 characters.
-- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
+- `created_at` (optional) - Date time string, ISO 8601 formatted. Example: `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights)
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note"
@@ -368,8 +368,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
### Create new merge request note
Creates a new note for a single merge request.
-If you create a note where the body only contains an Award Emoji, you'll receive
-this object back.
+If you create a note where the body only contains an Award Emoji, GitLab returns this object.
```plaintext
POST /projects/:id/merge_requests/:merge_request_iid/notes
@@ -380,7 +379,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `merge_request_iid` (required) - The IID of a merge request
- `body` (required) - The content of a note. Limited to 1,000,000 characters.
-- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
+- `created_at` (optional) - Date time string, ISO 8601 formatted. Example: `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights)
### Modify existing merge request note
@@ -486,7 +485,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
### Create new epic note
Creates a new note for a single epic. Epic notes are comments users can post to an epic.
-If you create a note where the body only contains an Award Emoji, you'll receive this object back.
+If you create a note where the body only contains an Award Emoji, GitLab returns this object.
```plaintext
POST /groups/:id/epics/:epic_id/notes
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 4e3b505d5d4..698fbd35a82 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -310,7 +310,7 @@ class MergeRequestPermissionsType < BasePermissionType
abilities :admin_merge_request, :update_merge_request, :create_note
ability_field :resolve_note,
- description: 'Indicates the user can resolve discussions on the merge request'
+ description: 'Indicates the user can resolve discussions on the merge request.'
permission_field :push_to_source_branch, method: :can_push_to_source_branch?
end
```
@@ -369,7 +369,7 @@ Example:
```ruby
field :test_field, type: GraphQL::STRING_TYPE,
null: true,
- description: 'Some test field',
+ description: 'Some test field.',
feature_flag: :my_feature_flag
```
@@ -394,7 +394,7 @@ Example:
field :foo, GraphQL::STRING_TYPE,
null: true,
description: 'Some test field. Will always return `null`' \
- 'if `my_feature_flag` feature flag is disabled'
+ 'if `my_feature_flag` feature flag is disabled.'
def foo
object.foo if Feature.enabled?(:my_feature_flag, object)
@@ -420,7 +420,7 @@ Example:
```ruby
field :token, GraphQL::STRING_TYPE, null: true,
deprecated: { reason: 'Login via token has been removed', milestone: '10.0' },
- description: 'Token for login'
+ description: 'Token for login.'
```
The original `description` of the things being deprecated should be maintained,
@@ -441,7 +441,7 @@ Example:
```ruby
field :designs, ::Types::DesignManagement::DesignCollectionType, null: true,
deprecated: { reason: 'Use `designCollection`', milestone: '10.0' },
- description: 'The designs associated with this issue',
+ description: 'The designs associated with this issue.',
```
```ruby
@@ -477,9 +477,9 @@ module Types
graphql_name 'TrafficLightState'
description 'State of a traffic light'
- value 'RED', description: 'Drivers must stop'
- value 'YELLOW', description: 'Drivers must stop when it is safe to'
- value 'GREEN', description: 'Drivers can start or keep driving'
+ value 'RED', description: 'Drivers must stop.'
+ value 'YELLOW', description: 'Drivers must stop when it is safe to.'
+ value 'GREEN', description: 'Drivers can start or keep driving.'
end
end
```
@@ -498,8 +498,8 @@ module Types
graphql_name 'EpicState'
description 'State of a GitLab epic'
- value 'OPENED', value: 'opened', description: 'An open Epic'
- value 'CLOSED', value: 'closed', description: 'An closed Epic'
+ value 'OPENED', value: 'opened', description: 'An open Epic.'
+ value 'CLOSED', value: 'closed', description: 'A closed Epic.'
end
end
```
@@ -523,7 +523,7 @@ module Types
description 'Incident severity'
::IssuableSeverity.severities.keys.each do |severity|
- value severity.upcase, value: severity, description: "#{severity.titleize} severity"
+ value severity.upcase, value: severity, description: "#{severity.titleize} severity."
end
end
end
@@ -562,15 +562,15 @@ We can use GraphQL types like this:
```ruby
module Types
class ChartType < BaseObject
- field :title, GraphQL::STRING_TYPE, null: true, description: 'Title of the chart'
- field :data, [Types::ChartDatumType], null: true, description: 'Data of the chart'
+ field :title, GraphQL::STRING_TYPE, null: true, description: 'Title of the chart.'
+ field :data, [Types::ChartDatumType], null: true, description: 'Data of the chart.'
end
end
module Types
class ChartDatumType < BaseObject
- field :x, GraphQL::INT_TYPE, null: true, description: 'X-axis value of the chart datum'
- field :y, GraphQL::INT_TYPE, null: true, description: 'Y-axis value of the chart datum'
+ field :x, GraphQL::INT_TYPE, null: true, description: 'X-axis value of the chart datum.'
+ field :y, GraphQL::INT_TYPE, null: true, description: 'Y-axis value of the chart datum.'
end
end
```
@@ -584,7 +584,7 @@ A description of a field or argument is given using the `description:`
keyword. For example:
```ruby
-field :id, GraphQL::ID_TYPE, description: 'ID of the resource'
+field :id, GraphQL::ID_TYPE, description: 'ID of the resource.'
```
Descriptions of fields and arguments are viewable to users through:
@@ -606,14 +606,14 @@ descriptions:
- Always include the word `"timestamp"` when describing an argument or
field of type `Types::TimeType`. This lets the reader know that the
format of the property is `Time`, rather than just `Date`.
-- No `.` at end of strings.
+- Must end with a period (`.`).
Example:
```ruby
-field :id, GraphQL::ID_TYPE, description: 'ID of the issue'
-field :confidential, GraphQL::BOOLEAN_TYPE, description: 'Indicates the issue is confidential'
-field :closed_at, Types::TimeType, description: 'Timestamp of when the issue was closed'
+field :id, GraphQL::ID_TYPE, description: 'ID of the issue.'
+field :confidential, GraphQL::BOOLEAN_TYPE, description: 'Indicates the issue is confidential.'
+field :closed_at, Types::TimeType, description: 'Timestamp of when the issue was closed.'
```
### `copy_field_description` helper
@@ -889,8 +889,8 @@ Then we can use these resolver on fields:
```ruby
# In PipelineType
-field :jobs, resolver: JobsResolver, description: 'All jobs'
-field :job, resolver: JobsResolver.single, description: 'A single job'
+field :jobs, resolver: JobsResolver, description: 'All jobs.'
+field :job, resolver: JobsResolver.single, description: 'A single job.'
```
### Correct use of `Resolver#ready?`
@@ -965,7 +965,7 @@ to advertise the need for lookahead:
field :my_things, MyThingType.connection_type, null: true,
extras: [:lookahead], # Necessary
resolver: MyThingResolver,
- description: 'My things'
+ description: 'My things.'
```
For an example of real world use, please
@@ -1034,7 +1034,7 @@ To find the parent object in your `Presenter` class:
field :computed_field, SomeType, null: true,
method: :my_computing_method,
extras: [:parent], # Necessary
- description: 'My field description'
+ description: 'My field description.'
field :resolver_field, resolver: SomeTypeResolver
@@ -1042,7 +1042,7 @@ To find the parent object in your `Presenter` class:
extras [:parent]
type SomeType, null: true
- description 'My field description'
+ description 'My field description.'
```
1. Declare your field's method in your Presenter class and have it accept the `parent` keyword argument.
@@ -1161,7 +1161,7 @@ Example:
```ruby
argument :my_arg, GraphQL::STRING_TYPE,
required: true,
- description: "A description of the argument"
+ description: "A description of the argument."
```
Each GraphQL `argument` defined is passed to the `#resolve` method
@@ -1186,11 +1186,11 @@ defines these arguments (some
```ruby
argument :project_path, GraphQL::ID_TYPE,
required: true,
- description: "The project the merge request to mutate is in"
+ description: "The project the merge request to mutate is in."
argument :iid, GraphQL::STRING_TYPE,
required: true,
- description: "The IID of the merge request to mutate"
+ description: "The IID of the merge request to mutate."
argument :wip,
GraphQL::BOOLEAN_TYPE,
@@ -1242,7 +1242,7 @@ field:
field :merge_request,
Types::MergeRequestType,
null: true,
- description: "The merge request after mutation"
+ description: "The merge request after mutation."
```
This means that the hash returned from `resolve` in this mutation
@@ -1527,7 +1527,7 @@ and handles time inputs.
Example:
```ruby
-field :created_at, Types::TimeType, null: true, description: 'Timestamp of when the issue was created'
+field :created_at, Types::TimeType, null: true, description: 'Timestamp of when the issue was created.'
```
## Testing
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index b5237e34f29..1d983dd8683 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -127,21 +127,21 @@ To use the Vue performance plugin:
1. Import the plugin:
- ```javascript
- import PerformancePlugin from '~/performance/vue_performance_plugin';
- ```
+ ```javascript
+ import PerformancePlugin from '~/performance/vue_performance_plugin';
+ ```
1. Use it before initializing your Vue application:
- ```javascript
- Vue.use(PerformancePlugin, {
- components: [
- 'IdeTreeList',
- 'FileTree',
- 'RepoEditor',
- ]
- });
- ```
+ ```javascript
+ Vue.use(PerformancePlugin, {
+ components: [
+ 'IdeTreeList',
+ 'FileTree',
+ 'RepoEditor',
+ ]
+ });
+ ```
The plugin accepts the list of components, performance of which should be measured. The components
should be specified by their `name` option.
@@ -182,7 +182,7 @@ To access stored measurements, you can use either:
performance.getEntriesByType('measure');
```
-## Naming convention
+### Naming convention
All the marks and measures should be instantiated with the constants from
`app/assets/javascripts/performance/constants.js`. When you’re ready to add a new mark’s or
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index 960d10b48d5..be2bdf06488 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -10,14 +10,16 @@ Please read through the [GitLab Issue Documentation](index.md) for an overview o
## Parts of an Issue
-The image below illustrates what an issue may look like. Note that certain parts will
-look slightly different or will be absent, depending on the version of GitLab being used
-and the permissions of the user viewing the issue.
+The image below illustrates what an issue may look like. Certain parts
+look slightly different or are absent, depending on the GitLab version
+and the user's permissions.
-You can find all the information for that issue on one screen.
+You can find all of an issue's information on one page.
![Issue view](img/issues_main_view_numbered.png)
+The numbers in the image correspond to the following features:
+
- **1.** [Issue actions](#issue-actions)
- **2.** [To Do](#to-do)
- **3.** [Assignee](#assignee)
@@ -47,10 +49,6 @@ You can find all the information for that issue on one screen.
- **25.** [Submit comment, start a thread, or comment and close](#submit-comment-start-a-thread-or-comment-and-close)
- **26.** [Zoom meetings](#zoom-meetings)
-An issue starts with its status (open or closed), followed by its author,
-and includes many other functionalities, numbered in the image above to
-explain what they mean, one by one.
-
Many of the elements of the issue screen refresh automatically, such as the title and
description, when they are changed by another user. Comments and system notes also
update automatically in response to various actions and content updates.
@@ -89,9 +87,9 @@ An issue can be assigned to:
- Another person.
- [Many people](#multiple-assignees). **(STARTER)**
-The assignee(s) can be changed as often as needed. The idea is that the assignees are
+The assignees can be changed as often as needed. The idea is that the assignees are
responsible for that issue until it's reassigned to someone else to take it from there.
-When assigned to someone, it will appear in their assigned issues list.
+When assigned to someone, it appears in their assigned issues list.
NOTE:
If a user is not member of that project, it can only be
@@ -99,7 +97,7 @@ assigned to them if they created the issue themselves.
#### Multiple Assignees **(STARTER)**
-Often multiple people work on the same issue together, which can be especially difficult
+Often, multiple people work on the same issue together. This can be difficult
to track in large teams where there is shared ownership of an issue.
In [GitLab Starter](https://about.gitlab.com/pricing/), you can
@@ -116,10 +114,10 @@ Select a [milestone](../milestones/index.md) to attribute that issue to.
### Time tracking
-Use [GitLab Quick Actions](../quick_actions.md) to [track estimates and time spent on issues](../time_tracking.md).
-You can add an [estimate of the time it will take](../time_tracking.md#estimates)
-to resolve the issue, and also add [the time spent](../time_tracking.md#time-spent)
-on the resolution of the issue.
+Use [GitLab Quick Actions](../quick_actions.md) to [track estimates and time
+spent on issues](../time_tracking.md). You can add a [time estimate](../time_tracking.md#estimates)
+for resolving the issue, and also add [the time spent](../time_tracking.md#time-spent)
+to resolve the issue.
### Due date
@@ -132,13 +130,12 @@ element. Due dates can be changed as many times as needed.
Categorize issues by giving them [labels](../labels.md). They help to organize workflows,
and they enable you to work with the [GitLab Issue Board](index.md#issue-boards).
-Group Labels, which allow you to use the same labels for all projects within the same
-group, can be also given to issues. They work exactly the same, but they are immediately
+Group Labels, which allow you to use the same labels for all projects in the same
+group, can also be given to issues. They work exactly the same, but are immediately
available to all projects in the group.
-NOTE:
-If a label doesn't exist yet, you can click **Edit**, and it opens a dropdown menu
-from which you can select **Create new label**.
+If a label doesn't exist yet, you can create one by clicking **Edit**
+followed by **Create new label** in the dropdown menu.
### Weight **(STARTER)**
@@ -148,9 +145,8 @@ positive values or zero are allowed.
### Confidentiality
-You can [set an issue to be confidential](confidential_issues.md). When set, unauthorized
-users will not be able to access the issue, and will not see it listed in project
-issue boards or the issue list.
+You can [set an issue to be confidential](confidential_issues.md). Unauthorized users
+cannot access the issue, and it is not listed in the project's issue boards nor list for them.
### Lock issue
@@ -165,7 +161,7 @@ or were mentioned in the description or threads.
### Notifications
Click on the icon to enable/disable [notifications](../../profile/notifications.md#issue--epics--merge-request-events)
-for the issue. This will automatically enable if you participate in the issue in any way.
+for the issue. Notifications are automatically enabled after you participate in the issue in any way.
- **Enable**: If you are not a participant in the discussion on that issue, but
want to receive notifications on each update, subscribe to it.
@@ -180,9 +176,9 @@ for the issue. This will automatically enable if you participate in the issue in
### Edit
-Clicking this icon opens the issue for editing, and you will have access to all the
-same fields as when the issue was created. This icon will not display if the user
-does not have permission to edit the issue.
+Clicking this icon opens the issue for editing. All the fields which
+were shown when the issue was created are displayed for editing.
+This icon is only displayed if the user has permission to edit the issue.
### Description
@@ -195,17 +191,15 @@ allowing many formatting options.
### Mentions
You can mention a user or a group present in your GitLab instance with `@username` or
-`@groupname` and they will be notified via to-dos and email, unless they have disabled
-all notifications in their profile settings. This is controlled in the
-[notification settings](../../profile/notifications.md).
+`@groupname`. All mentioned users are notified via to-do items and emails,
+unless they have disabled all notifications in their profile settings.
+This is controlled in the [notification settings](../../profile/notifications.md).
-Mentions for yourself (the current logged in user), will be highlighted in a different
-color, allowing you to easily see which comments involve you, helping you focus on
-them quickly.
+Mentions for yourself (the current logged in user) are highlighted
+in a different color, which allows you to quickly see which comments involve you.
-NOTE:
Avoid mentioning `@all` in issues and merge requests, as it sends an email notification
-to all the members of that project's group, which can be interpreted as spam.
+to all the members of that project's group. This might be interpreted as spam.
### Related Issues
@@ -217,18 +211,18 @@ You can also click the `+` to add more related issues.
Merge requests that were mentioned in that issue's description or in the issue thread
are listed as [related merge requests](crosslinking_issues.md#from-merge-requests) here.
Also, if the current issue was mentioned as related in another merge request, that
-merge request will be listed here.
+merge request is also listed here.
### Award emoji
-You can award an emoji to that issue. There are shortcuts to "thumbs_up" and "thumbs_down",
-or you can click on the light gray "face" to choose a different reaction from the
-dropdown list of available [GitLab Flavored Markdown Emoji](../../markdown.md#emoji).
+You can award emojis to issues. You can select the "thumbs up" and "thumbs down",
+or the gray "smiley-face" to choose from the list of available
+[GitLab Flavored Markdown Emoji](../../markdown.md#emoji).
NOTE:
Posting "+1" as a comment in a thread spams all subscribed participants of that issue,
clutters the threads, and is not recommended. Awarding an emoji is a way
-to let them know your reaction without spamming them.
+to let them know your reaction without notifying them.
### Show all activity
@@ -241,21 +235,20 @@ and selecting either:
Also:
- You can mention a user or a group present in your GitLab instance with
- `@username` or `@groupname` and they will be notified via to-do items
- and email, unless they have [disabled all notifications](#notifications)
+ `@username` or `@groupname` and they are notified via to-do items
+ and emails, unless they have [disabled all notifications](#notifications)
in their profile settings.
-- Mentions for yourself (the current logged in user), will be highlighted
- in a different color, allowing you to easily see which comments involve you,
- helping you focus on them quickly.
+- Mentions for yourself (the current logged-in user) are highlighted
+ in a different color, which allows you to quickly see which comments involve you.
![Show all activity](img/show-all-activity.png)
### Create Merge Request
Create a new branch and [**Draft** merge request](../merge_requests/work_in_progress_merge_requests.md)
-in one action. The branch will be named `issuenumber-title` by default, but you can
-choose any name, and GitLab will verify that it is not already in use. The merge request
-will automatically inherit the milestone and labels of the issue, and will be set to
+in one action. The branch is named `issuenumber-title` by default, but you can
+choose any name, and GitLab verifies that it is not already in use. The merge request
+inherits the milestone and labels of the issue, and is set to automatically
close the issue when it is merged.
![Create MR from issue](img/create_mr_from_issue.png)
@@ -288,11 +281,11 @@ supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-g
### Submit comment, start a thread, or comment and close
-Once you write a comment, you can:
+After you write a comment, you can:
-- Click **Comment** and your comment will be published.
+- Click **Comment** and to publish your comment.
- Choose **Start thread** from the dropdown list and start a new [thread](../../discussions/index.md#threaded-discussions)
- within that issue's main thread to discuss specific points. This invites other participants
+ in that issue's main thread to discuss specific points. This invites other participants
to reply directly to your thread, keeping related comments grouped together.
![Comment or thread](img/comment-or-discussion.png)
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index 259d3e300b6..f6e4c3bd584 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -44,6 +44,24 @@ module Gitlab
def get_config_path(dir)
File.join(dir, 'config.toml')
end
+
+ def compile_into(dir)
+ command = %W[#{make} -C #{Rails.root.join('workhorse')} install PREFIX=#{File.absolute_path(dir)}]
+
+ make_out, make_status = Gitlab::Popen.popen(command)
+ unless make_status == 0
+ warn make_out
+ raise 'workhorse make failed'
+ end
+
+ # 'make install' puts the binaries in #{dir}/bin but the init script expects them in dir
+ FileUtils.mv(Dir["#{dir}/bin/*"], dir)
+ end
+
+ def make
+ _, which_status = Gitlab::Popen.popen(%w[which gmake])
+ which_status == 0 ? 'gmake' : 'make'
+ end
end
end
diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake
index 129768712bb..2d72a01f66f 100644
--- a/lib/tasks/gitlab/workhorse.rake
+++ b/lib/tasks/gitlab/workhorse.rake
@@ -26,14 +26,7 @@ namespace :gitlab do
args.with_defaults(repo: 'https://gitlab.com/gitlab-org/gitlab-workhorse.git')
checkout_or_clone_version(version: 'workhorse-move-notice', repo: args.repo, target_dir: args.dir, clone_opts: %w[--depth 1])
- _, which_status = Gitlab::Popen.popen(%w[which gmake])
- make = which_status == 0 ? 'gmake' : 'make'
- command = %W[#{make} -C #{Rails.root.join('workhorse')} install PREFIX=#{File.absolute_path(args.dir)}]
-
- run_command!(command)
-
- # 'make install' puts the binaries in #{args.dir}/bin but the init script expects them in args.dir
- FileUtils.mv(Dir["#{args.dir}/bin/*"], args.dir)
+ Gitlab::SetupHelper::Workhorse.compile_into(args.dir)
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7ad120023df..3650f2c9d40 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7131,6 +7131,24 @@ msgstr ""
msgid "ComplianceDashboard|created by:"
msgstr ""
+msgid "ComplianceFrameworks|Add framework"
+msgstr ""
+
+msgid "ComplianceFrameworks|All"
+msgstr ""
+
+msgid "ComplianceFrameworks|Error fetching compliance frameworks data. Please refresh the page"
+msgstr ""
+
+msgid "ComplianceFrameworks|Once you have created a compliance framework it will appear here."
+msgstr ""
+
+msgid "ComplianceFrameworks|Regulated"
+msgstr ""
+
+msgid "ComplianceFrameworks|There are no compliance frameworks set up yet"
+msgstr ""
+
msgid "ComplianceFramework|GDPR"
msgstr ""
@@ -7432,6 +7450,9 @@ msgstr ""
msgid "ContainerRegistry|Keep these tags"
msgstr ""
+msgid "ContainerRegistry|Last updated %{time}"
+msgstr ""
+
msgid "ContainerRegistry|Login"
msgstr ""
@@ -9715,6 +9736,9 @@ msgstr ""
msgid "DevopsAdoption|Segment"
msgstr ""
+msgid "DevopsAdoption|Segment data pending until the start of next month"
+msgstr ""
+
msgid "DevopsAdoption|There was an error fetching Groups. Please refresh the page to try again."
msgstr ""
@@ -13679,6 +13703,12 @@ msgstr ""
msgid "GroupSettings|Changing group URL can have unintended side effects."
msgstr ""
+msgid "GroupSettings|Compliance frameworks"
+msgstr ""
+
+msgid "GroupSettings|Configure frameworks to apply enforceable rules to projects."
+msgstr ""
+
msgid "GroupSettings|Custom project templates"
msgstr ""
diff --git a/rubocop/cop/graphql/descriptions.rb b/rubocop/cop/graphql/descriptions.rb
index b4e00dfe336..1585e5c9788 100644
--- a/rubocop/cop/graphql/descriptions.rb
+++ b/rubocop/cop/graphql/descriptions.rb
@@ -13,42 +13,74 @@
# argument :some_argument, GraphQL::STRING_TYPE
# end
#
+# class UngoodClass
+# field :some_argument,
+# GraphQL::STRING_TYPE,
+# description: "A description that does not end in a period"
+# end
+#
# # good
# class GreatClass
# argument :some_field,
# GraphQL::STRING_TYPE,
-# description: "Well described - a superb description"
+# description: "Well described - a superb description."
#
# field :some_field,
# GraphQL::STRING_TYPE,
-# description: "A thorough and compelling description"
+# description: "A thorough and compelling description."
# end
module RuboCop
module Cop
module Graphql
class Descriptions < RuboCop::Cop::Cop
- MSG = 'Please add a `description` property.'
+ MSG_NO_DESCRIPTION = 'Please add a `description` property.'
+ MSG_NO_PERIOD = '`description` strings must end with a `.`.'
# ability_field and permission_field set a default description.
- def_node_matcher :fields, <<~PATTERN
- (send nil? :field $...)
- PATTERN
-
- def_node_matcher :arguments, <<~PATTERN
- (send nil? :argument $...)
+ def_node_matcher :field_or_argument?, <<~PATTERN
+ (send nil? {:field :argument} ...)
PATTERN
- def_node_matcher :has_description?, <<~PATTERN
- (hash <(pair (sym :description) _) ...>)
+ def_node_matcher :description, <<~PATTERN
+ (... (hash <(pair (sym :description) $_) ...>))
PATTERN
def on_send(node)
- matches = fields(node) || arguments(node)
+ return unless field_or_argument?(node)
+
+ description = description(node)
+
+ return add_offense(node, location: :expression, message: MSG_NO_DESCRIPTION) unless description
+
+ add_offense(node, location: :expression, message: MSG_NO_PERIOD) if no_period?(description)
+ end
+
+ # Autocorrect missing periods at end of description.
+ def autocorrect(node)
+ lambda do |corrector|
+ description = description(node)
+ next unless description
+
+ corrector.insert_after(before_end_quote(description), '.')
+ end
+ end
+
+ private
+
+ def no_period?(description)
+ # Test that the description node is a `:str` (as opposed to
+ # a `#copy_field_description` call) before checking.
+ description.type == :str && !description.value.strip.end_with?('.')
+ end
- return if matches.nil?
+ # Returns a Parser::Source::Range that ends just before the final String delimiter.
+ def before_end_quote(string)
+ return string.source_range.adjust(end_pos: -1) unless string.heredoc?
- add_offense(node, location: :expression) unless has_description?(matches.last)
+ heredoc_source = string.location.heredoc_body.source
+ adjust = heredoc_source.index(/\s+\Z/) - heredoc_source.length
+ string.location.heredoc_body.adjust(end_pos: adjust)
end
end
end
diff --git a/spec/frontend/boards/boards_store_spec.js b/spec/frontend/boards/boards_store_spec.js
index 84c8242a7b9..c89f6d22ef2 100644
--- a/spec/frontend/boards/boards_store_spec.js
+++ b/spec/frontend/boards/boards_store_spec.js
@@ -1,7 +1,7 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import boardsStore, { gqlClient } from '~/boards/stores/boards_store';
+import boardsStore from '~/boards/stores/boards_store';
import eventHub from '~/boards/eventhub';
import { listObj, listObjDuplicate } from './mock_data';
@@ -456,118 +456,6 @@ describe('boardsStore', () => {
});
});
- describe('createBoard', () => {
- const labelIds = ['first label', 'second label'];
- const assigneeId = 'as sign ee';
- const milestoneId = 'vegetable soup';
- const board = {
- labels: labelIds.map(id => ({ id })),
- assignee: { id: assigneeId },
- milestone: { id: milestoneId },
- };
-
- describe('for existing board', () => {
- const id = 'skate-board';
- const url = `${endpoints.boardsEndpoint}/${id}.json`;
- const expectedRequest = expect.objectContaining({
- data: JSON.stringify({
- board: {
- ...board,
- id,
- label_ids: labelIds,
- assignee_id: assigneeId,
- milestone_id: milestoneId,
- },
- }),
- });
-
- let requestSpy;
-
- beforeEach(() => {
- requestSpy = jest.fn();
- axiosMock.onPut(url).replyOnce(config => requestSpy(config));
- jest.spyOn(gqlClient, 'mutate').mockReturnValue(Promise.resolve({}));
- });
-
- it('makes a request to update the board', () => {
- requestSpy.mockReturnValue([200, dummyResponse]);
- const expectedResponse = [
- expect.objectContaining({ data: dummyResponse }),
- expect.objectContaining({}),
- ];
-
- return expect(
- boardsStore.createBoard({
- ...board,
- id,
- }),
- )
- .resolves.toEqual(expectedResponse)
- .then(() => {
- expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
- });
- });
-
- it('fails for error response', () => {
- requestSpy.mockReturnValue([500]);
-
- return expect(
- boardsStore.createBoard({
- ...board,
- id,
- }),
- )
- .rejects.toThrow()
- .then(() => {
- expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
- });
- });
- });
-
- describe('for new board', () => {
- const url = `${endpoints.boardsEndpoint}.json`;
- const expectedRequest = expect.objectContaining({
- data: JSON.stringify({
- board: {
- ...board,
- label_ids: labelIds,
- assignee_id: assigneeId,
- milestone_id: milestoneId,
- },
- }),
- });
-
- let requestSpy;
-
- beforeEach(() => {
- requestSpy = jest.fn();
- axiosMock.onPost(url).replyOnce(config => requestSpy(config));
- jest.spyOn(gqlClient, 'mutate').mockReturnValue(Promise.resolve({}));
- });
-
- it('makes a request to create a new board', () => {
- requestSpy.mockReturnValue([200, dummyResponse]);
- const expectedResponse = dummyResponse;
-
- return expect(boardsStore.createBoard(board))
- .resolves.toEqual(expectedResponse)
- .then(() => {
- expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
- });
- });
-
- it('fails for error response', () => {
- requestSpy.mockReturnValue([500]);
-
- return expect(boardsStore.createBoard(board))
- .rejects.toThrow()
- .then(() => {
- expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
- });
- });
- });
- });
-
describe('deleteBoard', () => {
const id = 'capsized';
const url = `${endpoints.boardsEndpoint}/${id}.json`;
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 447628d2663..89ec3159171 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -1,47 +1,289 @@
-import { mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'jest/helpers/test_constants';
import { GlModal } from '@gitlab/ui';
+import waitForPromises from 'helpers/wait_for_promises';
+import BoardScope from 'ee_component/boards/components/board_scope.vue';
+
+import axios from '~/lib/utils/axios_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
import boardsStore from '~/boards/stores/boards_store';
-import boardForm from '~/boards/components/board_form.vue';
+import BoardForm from '~/boards/components/board_form.vue';
+import BoardConfigurationOptions from '~/boards/components/board_configuration_options.vue';
+import createBoardMutation from '~/boards/graphql/board.mutation.graphql';
-describe('board_form.vue', () => {
- let wrapper;
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn().mockName('visitUrlMock'),
+}));
- const propsData = {
- canAdminBoard: false,
- labelsPath: `${TEST_HOST}/labels/path`,
- labelsWebUrl: `${TEST_HOST}/-/labels`,
- };
+const currentBoard = {
+ id: 1,
+ name: 'test',
+ labels: [],
+ milestone_id: undefined,
+ assignee: {},
+ assignee_id: undefined,
+ weight: null,
+ hide_backlog_list: false,
+ hide_closed_list: false,
+};
+
+const boardDefaults = {
+ id: false,
+ name: '',
+ labels: [],
+ milestone_id: undefined,
+ assignee: {},
+ assignee_id: undefined,
+ weight: null,
+ hide_backlog_list: false,
+ hide_closed_list: false,
+};
+
+const defaultProps = {
+ canAdminBoard: false,
+ labelsPath: `${TEST_HOST}/labels/path`,
+ labelsWebUrl: `${TEST_HOST}/-/labels`,
+ currentBoard,
+};
+
+const endpoints = {
+ boardsEndpoint: 'test-endpoint',
+};
+
+const mutate = jest.fn().mockResolvedValue({});
+
+describe('BoardForm', () => {
+ let wrapper;
+ let axiosMock;
const findModal = () => wrapper.find(GlModal);
+ const findModalActionPrimary = () => findModal().props('actionPrimary');
+ const findForm = () => wrapper.find('[data-testid="board-form"]');
+ const findFormWrapper = () => wrapper.find('[data-testid="board-form-wrapper"]');
+ const findDeleteConfirmation = () => wrapper.find('[data-testid="delete-confirmation-message"]');
+ const findConfigurationOptions = () => wrapper.find(BoardConfigurationOptions);
+ const findBoardScope = () => wrapper.find(BoardScope);
+ const findInput = () => wrapper.find('#board-new-name');
+
+ const createComponent = (props, data) => {
+ wrapper = shallowMount(BoardForm, {
+ propsData: { ...defaultProps, ...props },
+ data() {
+ return {
+ ...data,
+ };
+ },
+ provide: {
+ endpoints,
+ },
+ mocks: {
+ $apollo: {
+ mutate,
+ },
+ },
+ attachToDocument: true,
+ });
+ };
beforeEach(() => {
- boardsStore.state.currentPage = 'edit';
- wrapper = mount(boardForm, { propsData });
+ axiosMock = new AxiosMockAdapter(axios);
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ axiosMock.restore();
+ boardsStore.state.currentPage = null;
+ });
+
+ describe('when user can not admin the board', () => {
+ beforeEach(() => {
+ boardsStore.state.currentPage = 'new';
+ createComponent();
+ });
+
+ it('hides modal footer when user is not a board admin', () => {
+ expect(findModal().attributes('hide-footer')).toBeDefined();
+ });
+
+ it('displays board scope title', () => {
+ expect(findModal().attributes('title')).toBe('Board scope');
+ });
+
+ it('does not display a form', () => {
+ expect(findForm().exists()).toBe(false);
+ });
});
- describe('methods', () => {
- describe('cancel', () => {
- it('resets currentPage', () => {
- wrapper.vm.cancel();
- expect(boardsStore.state.currentPage).toBe('');
+ describe('when user can admin the board', () => {
+ beforeEach(() => {
+ boardsStore.state.currentPage = 'new';
+ createComponent({ canAdminBoard: true });
+ });
+
+ it('shows modal footer when user is a board admin', () => {
+ expect(findModal().attributes('hide-footer')).toBeUndefined();
+ });
+
+ it('displays a form', () => {
+ expect(findForm().exists()).toBe(true);
+ });
+
+ it('focuses an input field', async () => {
+ expect(document.activeElement).toBe(wrapper.vm.$refs.name);
+ });
+ });
+
+ describe('when creating a new board', () => {
+ beforeEach(() => {
+ boardsStore.state.currentPage = 'new';
+ });
+
+ describe('on non-scoped-board', () => {
+ beforeEach(() => {
+ createComponent({ canAdminBoard: true });
+ });
+
+ it('clears the form', () => {
+ expect(findConfigurationOptions().props('board')).toEqual(boardDefaults);
+ });
+
+ it('shows a correct title about creating a board', () => {
+ expect(findModal().attributes('title')).toBe('Create new board');
+ });
+
+ it('passes correct primary action text and variant', () => {
+ expect(findModalActionPrimary().text).toBe('Create board');
+ expect(findModalActionPrimary().attributes[0].variant).toBe('success');
+ });
+
+ it('does not render delete confirmation message', () => {
+ expect(findDeleteConfirmation().exists()).toBe(false);
+ });
+
+ it('renders form wrapper', () => {
+ expect(findFormWrapper().exists()).toBe(true);
+ });
+
+ it('passes a true isNewForm prop to BoardConfigurationOptions component', () => {
+ expect(findConfigurationOptions().props('isNewForm')).toBe(true);
+ });
+ });
+
+ it('passes a correct collapseScope property to BoardScope component on scoped board', async () => {
+ createComponent({ canAdminBoard: true, scopedIssueBoardFeatureEnabled: true });
+ await waitForPromises();
+ expect(findBoardScope().props('collapseScope')).toBe(true);
+ });
+
+ describe('when submitting a create event', () => {
+ beforeEach(() => {
+ const url = `${endpoints.boardsEndpoint}.json`;
+ axiosMock.onPost(url).reply(200, { id: '2', board_path: 'new path' });
+ });
+
+ it('does not call API if board name is empty', async () => {
+ createComponent({ canAdminBoard: true });
+ findInput().trigger('keyup.enter', { metaKey: true });
+
+ await waitForPromises();
+
+ expect(mutate).not.toHaveBeenCalled();
+ });
+
+ it('calls REST and GraphQL API and redirects to correct page', async () => {
+ createComponent({ canAdminBoard: true });
+
+ findInput().value = 'Test name';
+ findInput().trigger('input');
+ findInput().trigger('keyup.enter', { metaKey: true });
+
+ await waitForPromises();
+
+ expect(axiosMock.history.post[0].data).toBe(
+ JSON.stringify({ board: { ...boardDefaults, name: 'test', label_ids: [''] } }),
+ );
+
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: createBoardMutation,
+ variables: {
+ id: 'gid://gitlab/Board/2',
+ },
+ });
+
+ await waitForPromises();
+ expect(visitUrl).toHaveBeenCalledWith('new path');
});
});
});
- describe('buttons', () => {
- it('cancel button triggers cancel()', () => {
- wrapper.setMethods({ cancel: jest.fn() });
- findModal().vm.$emit('cancel');
+ describe('when editing a board', () => {
+ beforeEach(() => {
+ boardsStore.state.currentPage = 'edit';
+ });
+
+ describe('on non-scoped-board', () => {
+ beforeEach(() => {
+ createComponent({ canAdminBoard: true });
+ });
+
+ it('clears the form', () => {
+ expect(findConfigurationOptions().props('board')).toEqual(currentBoard);
+ });
+
+ it('shows a correct title about creating a board', () => {
+ expect(findModal().attributes('title')).toBe('Edit board');
+ });
+
+ it('passes correct primary action text and variant', () => {
+ expect(findModalActionPrimary().text).toBe('Save changes');
+ expect(findModalActionPrimary().attributes[0].variant).toBe('info');
+ });
+
+ it('does not render delete confirmation message', () => {
+ expect(findDeleteConfirmation().exists()).toBe(false);
+ });
+
+ it('renders form wrapper', () => {
+ expect(findFormWrapper().exists()).toBe(true);
+ });
+
+ it('passes a false isNewForm prop to BoardConfigurationOptions component', () => {
+ expect(findConfigurationOptions().props('isNewForm')).toBe(false);
+ });
+ });
+
+ it('passes a correct collapseScope property to BoardScope component on scoped board', async () => {
+ createComponent({ canAdminBoard: true, scopedIssueBoardFeatureEnabled: true });
+ await waitForPromises();
+ expect(findBoardScope().props('collapseScope')).toBe(false);
+ });
+
+ describe('when submitting an update event', () => {
+ beforeEach(() => {
+ const url = endpoints.boardsEndpoint;
+ axiosMock.onPut(url).reply(200, { board_path: 'new path' });
+ });
+
+ it('calls REST and GraphQL API with correct parameters', async () => {
+ createComponent({ canAdminBoard: true });
+
+ findInput().trigger('keyup.enter', { metaKey: true });
+
+ await waitForPromises();
+
+ expect(axiosMock.history.put[0].data).toBe(
+ JSON.stringify({ board: { ...currentBoard, label_ids: [''] } }),
+ );
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.cancel).toHaveBeenCalled();
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: createBoardMutation,
+ variables: {
+ id: `gid://gitlab/Board/${currentBoard.id}`,
+ },
+ });
});
});
});
diff --git a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
index fc93e9094c9..ec883886026 100644
--- a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
@@ -7,9 +7,27 @@ import { DETAILS_PAGE_TITLE } from '~/registry/explorer/constants';
describe('Details Header', () => {
let wrapper;
- const mountComponent = propsData => {
+ const defaultImage = {
+ name: 'foo',
+ updatedAt: '2020-11-03T13:29:21Z',
+ project: {
+ visibility: 'public',
+ },
+ };
+
+ const findLastUpdatedAndVisibility = () => wrapper.find('[data-testid="updated-and-visibility"]');
+
+ const waitForMetadataItems = async () => {
+ // Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available
+ await wrapper.vm.$nextTick();
+ await wrapper.vm.$nextTick();
+ };
+
+ const mountComponent = (image = defaultImage) => {
wrapper = shallowMount(component, {
- propsData,
+ propsData: {
+ image,
+ },
stubs: {
GlSprintf,
TitleArea,
@@ -23,12 +41,34 @@ describe('Details Header', () => {
});
it('has the correct title ', () => {
- mountComponent();
+ mountComponent({ ...defaultImage, name: '' });
expect(wrapper.text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE);
});
it('shows imageName in the title', () => {
- mountComponent({ imageName: 'foo' });
+ mountComponent();
expect(wrapper.text()).toContain('foo');
});
+
+ it('has a metadata item with last updated text', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('text')).toBe('Last updated 1 month ago');
+ });
+
+ describe('visibility icon', () => {
+ it('shows an eye when the project is public', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye');
+ });
+ it('shows an eye slashed when the project is not public', async () => {
+ mountComponent({ ...defaultImage, project: { visibility: 'private' } });
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye-slash');
+ });
+ });
});
diff --git a/spec/frontend/registry/explorer/mock_data.js b/spec/frontend/registry/explorer/mock_data.js
index 3ec558b1891..992d880581a 100644
--- a/spec/frontend/registry/explorer/mock_data.js
+++ b/spec/frontend/registry/explorer/mock_data.js
@@ -114,8 +114,13 @@ export const containerRepositoryMock = {
location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009',
canDelete: true,
createdAt: '2020-11-03T13:29:21Z',
+ updatedAt: '2020-11-03T13:29:21Z',
tagsCount: 13,
expirationPolicyStartedAt: null,
+ project: {
+ visibility: 'public',
+ __typename: 'Project',
+ },
};
export const tagsPageInfo = {
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index ccbb1127444..b4378acdff1 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -353,7 +353,12 @@ describe('Details Page', () => {
mountComponent();
await waitForApolloRequestRender();
- expect(findDetailsHeader().props()).toEqual({ imageName: containerRepositoryMock.name });
+ expect(findDetailsHeader().props('image')).toMatchObject({
+ name: containerRepositoryMock.name,
+ project: {
+ visibility: containerRepositoryMock.project.visibility,
+ },
+ });
});
});
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index d61ea6aa6e9..54b59317b55 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -145,11 +145,11 @@ RSpec.describe Types::BaseField do
describe '#description' do
context 'feature flag given' do
- let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false, description: 'Test description') }
+ let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false, description: 'Test description.') }
let(:flag) { :test_flag }
it 'prepends the description' do
- expect(field.description). to eq 'Test description. Available only when feature flag `test_flag` is enabled'
+ expect(field.description). to eq 'Test description. Available only when feature flag `test_flag` is enabled.'
end
context 'falsey feature_flag values' do
@@ -164,7 +164,7 @@ RSpec.describe Types::BaseField do
with_them do
it 'returns the correct description' do
- expect(field.description).to eq('Test description')
+ expect(field.description).to eq('Test description.')
end
end
end
@@ -181,11 +181,11 @@ RSpec.describe Types::BaseField do
it 'interacts well with the `feature_flag` property' do
field = subject(
deprecated: { milestone: '1.10', reason: 'Deprecation reason' },
- description: 'Field description',
+ description: 'Field description.',
feature_flag: 'foo_flag'
)
- expectation = 'Field description. Available only when feature flag `foo_flag` is enabled. Deprecated in 1.10: Deprecation reason'
+ expectation = 'Field description. Available only when feature flag `foo_flag` is enabled. Deprecated in 1.10: Deprecation reason.'
expect(field.description).to eq(expectation)
end
diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
index d1be962a4f8..064e0c6828b 100644
--- a/spec/lib/gitlab/graphql/docs/renderer_spec.rb
+++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
Class.new(Types::BaseObject) do
graphql_name 'ArrayTest'
- field :foo, [GraphQL::STRING_TYPE], null: false, description: 'A description'
+ field :foo, [GraphQL::STRING_TYPE], null: false, description: 'A description.'
end
end
@@ -40,7 +40,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
- | `foo` | String! => Array | A description |
+ | `foo` | String! => Array | A description. |
DOC
is_expected.to include(expectation)
@@ -52,8 +52,8 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
Class.new(Types::BaseObject) do
graphql_name 'OrderingTest'
- field :foo, GraphQL::STRING_TYPE, null: false, description: 'A description of foo field'
- field :bar, GraphQL::STRING_TYPE, null: false, description: 'A description of bar field'
+ field :foo, GraphQL::STRING_TYPE, null: false, description: 'A description of foo field.'
+ field :bar, GraphQL::STRING_TYPE, null: false, description: 'A description of bar field.'
end
end
@@ -63,8 +63,8 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
- | `bar` | String! | A description of bar field |
- | `foo` | String! | A description of foo field |
+ | `bar` | String! | A description of bar field. |
+ | `foo` | String! | A description of foo field. |
DOC
is_expected.to include(expectation)
@@ -76,7 +76,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
Class.new(Types::BaseObject) do
graphql_name 'DeprecatedTest'
- field :foo, GraphQL::STRING_TYPE, null: false, deprecated: { reason: 'This is deprecated', milestone: '1.10' }, description: 'A description'
+ field :foo, GraphQL::STRING_TYPE, null: false, deprecated: { reason: 'This is deprecated', milestone: '1.10' }, description: 'A description.'
end
end
@@ -86,7 +86,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
- | `foo` **{warning-solid}** | String! | **Deprecated:** This is deprecated. Deprecated in 1.10 |
+ | `foo` **{warning-solid}** | String! | **Deprecated:** This is deprecated. Deprecated in 1.10. |
DOC
is_expected.to include(expectation)
@@ -98,14 +98,14 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
enum_type = Class.new(Types::BaseEnum) do
graphql_name 'MyEnum'
- value 'BAZ', description: 'A description of BAZ'
- value 'BAR', description: 'A description of BAR', deprecated: { reason: 'This is deprecated', milestone: '1.10' }
+ value 'BAZ', description: 'A description of BAZ.'
+ value 'BAR', description: 'A description of BAR.', deprecated: { reason: 'This is deprecated', milestone: '1.10' }
end
Class.new(Types::BaseObject) do
graphql_name 'EnumTest'
- field :foo, enum_type, null: false, description: 'A description of foo field'
+ field :foo, enum_type, null: false, description: 'A description of foo field.'
end
end
@@ -115,8 +115,8 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Value | Description |
| ----- | ----------- |
- | `BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10 |
- | `BAZ` | A description of BAZ |
+ | `BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10. |
+ | `BAZ` | A description of BAZ. |
DOC
is_expected.to include(expectation)
diff --git a/spec/lib/gitlab/setup_helper/workhorse_spec.rb b/spec/lib/gitlab/setup_helper/workhorse_spec.rb
new file mode 100644
index 00000000000..aa9b4595799
--- /dev/null
+++ b/spec/lib/gitlab/setup_helper/workhorse_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SetupHelper::Workhorse do
+ describe '.make' do
+ subject { described_class.make }
+
+ context 'when there is a gmake' do
+ it 'returns gmake' do
+ expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
+
+ expect(subject).to eq 'gmake'
+ end
+ end
+
+ context 'when there is no gmake' do
+ it 'returns make' do
+ expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 1])
+
+ expect(subject).to eq 'make'
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
index 4b8ffb0675c..b0bf5bd4844 100644
--- a/spec/requests/api/graphql/project_query_spec.rb
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -151,8 +151,9 @@ RSpec.describe 'getting project information' do
)))
end
- it 'can lookahead to eliminate N+1 queries', :use_clean_rails_memory_store_caching, :request_store do
- expect { run_query(10) }.to issue_same_number_of_queries_as { run_query(1) }.or_fewer.ignoring_cached_queries
+ it 'can lookahead to eliminate N+1 queries' do
+ baseline = ActiveRecord::QueryRecorder.new { run_query(1) }
+ expect { run_query(10) }.not_to exceed_query_limit(baseline)
end
end
diff --git a/spec/rubocop/cop/graphql/descriptions_spec.rb b/spec/rubocop/cop/graphql/descriptions_spec.rb
index 3b29cd2fbee..f4693057bcb 100644
--- a/spec/rubocop/cop/graphql/descriptions_spec.rb
+++ b/spec/rubocop/cop/graphql/descriptions_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe RuboCop::Cop::Graphql::Descriptions, type: :rubocop do
subject(:cop) { described_class.new }
context 'fields' do
- it 'adds an offense when there is no field description' do
+ it 'adds an offense when there is no description' do
inspect_source(<<~TYPE)
module Types
class FakeType < BaseObject
@@ -24,24 +24,37 @@ RSpec.describe RuboCop::Cop::Graphql::Descriptions, type: :rubocop do
expect(cop.offenses.size).to eq 1
end
- it 'does not add an offense for fields with a description' do
- expect_no_offenses(<<~TYPE.strip)
+ it 'adds an offense when description does not end in a period' do
+ inspect_source(<<~TYPE)
module Types
class FakeType < BaseObject
- graphql_name 'FakeTypeName'
-
- argument :a_thing,
+ field :a_thing,
GraphQL::STRING_TYPE,
null: false,
description: 'A descriptive description'
end
end
TYPE
+
+ expect(cop.offenses.size).to eq 1
+ end
+
+ it 'does not add an offense when description is correct' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'A descriptive description.'
+ end
+ end
+ TYPE
end
end
context 'arguments' do
- it 'adds an offense when there is no argument description' do
+ it 'adds an offense when there is no description' do
inspect_source(<<~TYPE)
module Types
class FakeType < BaseObject
@@ -55,19 +68,88 @@ RSpec.describe RuboCop::Cop::Graphql::Descriptions, type: :rubocop do
expect(cop.offenses.size).to eq 1
end
- it 'does not add an offense for arguments with a description' do
- expect_no_offenses(<<~TYPE.strip)
+ it 'adds an offense when description does not end in a period' do
+ inspect_source(<<~TYPE)
module Types
class FakeType < BaseObject
- graphql_name 'FakeTypeName'
+ argument :a_thing,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'Behold! A description'
+ end
+ end
+ TYPE
+ expect(cop.offenses.size).to eq 1
+ end
+
+ it 'does not add an offense when description is correct' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class FakeType < BaseObject
argument :a_thing,
GraphQL::STRING_TYPE,
null: false,
+ description: 'Behold! A description.'
+ end
+ end
+ TYPE
+ end
+ end
+
+ describe 'autocorrecting descriptions without periods' do
+ it 'can autocorrect' do
+ expect_offense(<<~TYPE)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ ^^^^^^^^^^^^^^^ `description` strings must end with a `.`.
+ GraphQL::STRING_TYPE,
+ null: false,
description: 'Behold! A description'
end
end
TYPE
+
+ expect_correction(<<~TYPE)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'Behold! A description.'
+ end
+ end
+ TYPE
+ end
+
+ it 'can autocorrect a heredoc' do
+ expect_offense(<<~TYPE)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ ^^^^^^^^^^^^^^^ `description` strings must end with a `.`.
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: <<~DESC
+ Behold! A description
+ DESC
+ end
+ end
+ TYPE
+
+ expect_correction(<<~TYPE)
+ module Types
+ class FakeType < BaseObject
+ field :a_thing,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: <<~DESC
+ Behold! A description.
+ DESC
+ end
+ end
+ TYPE
end
end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 4c78ca0117c..531f561da91 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -241,15 +241,14 @@ module TestEnv
end
def setup_workhorse
- install_workhorse_args = [workhorse_dir, workhorse_url].compact.join(',')
-
- component_timed_setup(
- 'GitLab Workhorse',
- install_dir: workhorse_dir,
- version: Gitlab::Workhorse.version,
- task: "gitlab:workhorse:install[#{install_workhorse_args}]") do
- Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
- end
+ start = Time.now
+ puts "\n==> Setting up GitLab Workhorse..."
+
+ FileUtils.rm_rf(workhorse_dir)
+ Gitlab::SetupHelper::Workhorse.compile_into(workhorse_dir)
+ Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
+
+ puts " GitLab Workhorse set up in #{Time.now - start} seconds...\n"
end
def workhorse_dir
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
index 04482d3bfb8..7a66eff3a41 100644
--- a/spec/support/matchers/exceed_query_limit.rb
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -3,6 +3,13 @@
module ExceedQueryLimitHelpers
MARGINALIA_ANNOTATION_REGEX = %r{\s*\/\*.*\*\/}.freeze
+ DB_QUERY_RE = Regexp.union([
+ /^(?<prefix>SELECT .* FROM "?[a-z_]+"?) (?<suffix>.*)$/m,
+ /^(?<prefix>UPDATE "?[a-z_]+"?) (?<suffix>.*)$/m,
+ /^(?<prefix>INSERT INTO "[a-z_]+" \((?:"[a-z_]+",?\s?)+\)) (?<suffix>.*)$/m,
+ /^(?<prefix>DELETE FROM "[a-z_]+") (?<suffix>.*)$/m
+ ]).freeze
+
def with_threshold(threshold)
@threshold = threshold
self
@@ -13,41 +20,129 @@ module ExceedQueryLimitHelpers
self
end
+ def show_common_queries
+ @show_common_queries = true
+ self
+ end
+
+ def ignoring(pattern)
+ @ignoring_pattern = pattern
+ self
+ end
+
def threshold
@threshold.to_i
end
def expected_count
if expected.is_a?(ActiveRecord::QueryRecorder)
- expected.count
+ query_recorder_count(expected)
else
expected
end
end
def actual_count
- @actual_count ||= if @query
- recorder.log.select { |recorded| recorded =~ @query }.size
- else
- recorder.count
- end
+ @actual_count ||= query_recorder_count(recorder)
+ end
+
+ def query_recorder_count(query_recorder)
+ return query_recorder.count unless @query || @ignoring_pattern
+
+ query_log(query_recorder).size
+ end
+
+ def query_log(query_recorder)
+ filtered = query_recorder.log
+ filtered = filtered.select { |q| q =~ @query } if @query
+ filtered = filtered.reject { |q| q =~ @ignoring_pattern } if @ignoring_pattern
+ filtered
end
def recorder
@recorder ||= ActiveRecord::QueryRecorder.new(skip_cached: skip_cached, &@subject_block)
end
- def count_queries(queries)
- queries.each_with_object(Hash.new(0)) { |query, counts| counts[query] += 1 }
+ # Take a query recorder and tabulate the frequencies of suffixes for each prefix.
+ #
+ # @return Hash[String, Hash[String, Int]]
+ #
+ # Example:
+ #
+ # r = ActiveRecord::QueryRecorder.new do
+ # SomeTable.create(x: 1, y: 2, z: 3)
+ # SomeOtherTable.where(id: 1).first
+ # SomeTable.create(x: 4, y: 5, z: 6)
+ # SomeOtherTable.all
+ # end
+ # count_queries(r)
+ # #=>
+ # {
+ # 'INSERT INTO "some_table" VALUES' => {
+ # '(1,2,3)' => 1,
+ # '(4,5,6)' => 1
+ # },
+ # 'SELECT * FROM "some_other_table"' => {
+ # 'WHERE id = 1 LIMIT 1' => 1,
+ # '' => 2
+ # }
+ # }
+ def count_queries(query_recorder)
+ strip_marginalia_annotations(query_log(query_recorder))
+ .map { |q| query_group_key(q) }
+ .group_by { |k| k[:prefix] }
+ .transform_values { |keys| frequencies(:suffix, keys) }
+ end
+
+ def frequencies(key, things)
+ things.group_by { |x| x[key] }.transform_values(&:size)
+ end
+
+ def query_group_key(query)
+ DB_QUERY_RE.match(query) || { prefix: query, suffix: '' }
+ end
+
+ def diff_query_counts(expected, actual)
+ expected_counts = expected.transform_values do |suffixes|
+ suffixes.transform_values { |n| [n, 0] }
+ end
+ recorded_counts = actual.transform_values do |suffixes|
+ suffixes.transform_values { |n| [0, n] }
+ end
+
+ combined_counts = expected_counts.merge(recorded_counts) do |_k, exp, got|
+ exp.merge(got) do |_k, exp_counts, got_counts|
+ exp_counts.zip(got_counts).map { |a, b| a + b }
+ end
+ end
+
+ unless @show_common_queries
+ combined_counts = combined_counts.transform_values do |suffs|
+ suffs.reject { |_k, counts| counts.first == counts.second }
+ end
+ end
+
+ combined_counts.reject { |_prefix, suffs| suffs.empty? }
+ end
+
+ def diff_query_group_message(query, suffixes)
+ suffix_messages = suffixes.map do |s, counts|
+ "-- (expected: #{counts.first}, got: #{counts.second})\n #{s}"
+ end
+
+ "#{query}...\n#{suffix_messages.join("\n")}"
end
def log_message
if expected.is_a?(ActiveRecord::QueryRecorder)
- counts = count_queries(strip_marginalia_annotations(expected.log))
- extra_queries = strip_marginalia_annotations(@recorder.log).reject { |query| counts[query] -= 1 unless counts[query] == 0 }
- extra_queries_display = count_queries(extra_queries).map { |query, count| "[#{count}] #{query}" }
-
- (['Extra queries:'] + extra_queries_display).join("\n\n")
+ diff_counts = diff_query_counts(count_queries(expected), count_queries(@recorder))
+ sections = diff_counts.map { |q, suffixes| diff_query_group_message(q, suffixes) }
+
+ <<~MSG
+ Query Diff:
+ -----------
+ #{sections.join("\n\n")}
+ MSG
else
@recorder.log_message
end
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index ed139e638bf..269e9170906 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -32,16 +32,16 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
it 'adds a formatted `deprecated_reason` to the subject' do
deprecable = subject(deprecated: { milestone: '1.10', reason: 'Deprecation reason' })
- expect(deprecable.deprecation_reason).to eq('Deprecation reason. Deprecated in 1.10')
+ expect(deprecable.deprecation_reason).to eq('Deprecation reason. Deprecated in 1.10.')
end
it 'appends to the description if given' do
deprecable = subject(
deprecated: { milestone: '1.10', reason: 'Deprecation reason' },
- description: 'Deprecable description'
+ description: 'Deprecable description.'
)
- expect(deprecable.description).to eq('Deprecable description. Deprecated in 1.10: Deprecation reason')
+ expect(deprecable.description).to eq('Deprecable description. Deprecated in 1.10: Deprecation reason.')
end
it 'does not append to the description if it is absent' do
diff --git a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
index 15b846f28cb..6d8d9ba0754 100644
--- a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
+++ b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
@@ -22,6 +22,232 @@ RSpec.describe ExceedQueryLimitHelpers do
end
end
+ describe '#diff_query_group_message' do
+ it 'prints a group helpfully' do
+ test_matcher = TestMatcher.new
+ suffixes = {
+ 'WHERE x = z' => [1, 1],
+ 'WHERE x = y' => [1, 2],
+ 'LIMIT 1' => [1, 0]
+ }
+
+ message = test_matcher.diff_query_group_message('SELECT * FROM foo', suffixes)
+
+ expect(message).to eq(<<~MSG.chomp)
+ SELECT * FROM foo...
+ -- (expected: 1, got: 1)
+ WHERE x = z
+ -- (expected: 1, got: 2)
+ WHERE x = y
+ -- (expected: 1, got: 0)
+ LIMIT 1
+ MSG
+ end
+ end
+
+ describe '#diff_query_counts' do
+ let(:expected) do
+ ActiveRecord::QueryRecorder.new do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.count
+ TestQueries.first
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'x').update_all(version: 'y')
+ TestQueries.where(version: 'foobar').count
+ TestQueries.where(version: 'z').delete_all
+ end
+ end
+
+ let(:actual) do
+ ActiveRecord::QueryRecorder.new do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.count
+ TestQueries.create!(version: 'x')
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'x').update_all(version: 'y')
+ TestQueries.where(version: 'foobar').count
+ TestQueries.count
+ TestQueries.where(version: 'y').update_all(version: 'z')
+ TestQueries.where(version: 'z').delete_all
+ end
+ end
+
+ it 'merges two query counts' do
+ test_matcher = TestMatcher.new
+
+ diff = test_matcher.diff_query_counts(
+ test_matcher.count_queries(expected),
+ test_matcher.count_queries(actual)
+ )
+
+ expect(diff).to eq({
+ "SELECT \"schema_migrations\".* FROM \"schema_migrations\"" => {
+ "ORDER BY \"schema_migrations\".\"version\" ASC LIMIT 1" => [1, 0]
+ },
+ "SELECT COUNT(*) FROM \"schema_migrations\"" => { "" => [1, 2] },
+ "UPDATE \"schema_migrations\"" => {
+ "SET \"version\" = 'z' WHERE \"schema_migrations\".\"version\" = 'y'" => [0, 1]
+ },
+ "SAVEPOINT active_record_1" => { "" => [0, 1] },
+ "INSERT INTO \"schema_migrations\" (\"version\")" => {
+ "VALUES ('x') RETURNING \"version\"" => [0, 1]
+ },
+ "RELEASE SAVEPOINT active_record_1" => { "" => [0, 1] }
+ })
+ end
+
+ it 'can show common queries if so desired' do
+ test_matcher = TestMatcher.new.show_common_queries
+
+ diff = test_matcher.diff_query_counts(
+ test_matcher.count_queries(expected),
+ test_matcher.count_queries(actual)
+ )
+
+ expect(diff).to eq({
+ "SELECT \"schema_migrations\".* FROM \"schema_migrations\"" => {
+ "WHERE \"schema_migrations\".\"version\" = 'foobar'" => [2, 2],
+ "WHERE \"schema_migrations\".\"version\" = 'also foobar and baz'" => [1, 1],
+ "ORDER BY \"schema_migrations\".\"version\" ASC LIMIT 1" => [1, 0]
+ },
+ "SELECT COUNT(*) FROM \"schema_migrations\"" => {
+ "" => [1, 2],
+ "WHERE \"schema_migrations\".\"version\" = 'foobar'" => [1, 1]
+ },
+ "UPDATE \"schema_migrations\"" => {
+ "SET \"version\" = 'y' WHERE \"schema_migrations\".\"version\" = 'x'" => [1, 1],
+ "SET \"version\" = 'z' WHERE \"schema_migrations\".\"version\" = 'y'" => [0, 1]
+ },
+ "DELETE FROM \"schema_migrations\"" => {
+ "WHERE \"schema_migrations\".\"version\" = 'z'" => [1, 1]
+ },
+ "SAVEPOINT active_record_1" => {
+ "" => [0, 1]
+ },
+ "INSERT INTO \"schema_migrations\" (\"version\")" => {
+ "VALUES ('x') RETURNING \"version\"" => [0, 1]
+ },
+ "RELEASE SAVEPOINT active_record_1" => {
+ "" => [0, 1]
+ }
+ })
+ end
+ end
+
+ describe '#count_queries' do
+ it 'handles queries with suffixes over multiple lines' do
+ test_matcher = TestMatcher.new
+
+ recorder = ActiveRecord::QueryRecorder.new do
+ TestQueries.find_by(version: %w(foo bar baz).join("\n"))
+ TestQueries.find_by(version: %w(foo biz baz).join("\n"))
+ TestQueries.find_by(version: %w(foo bar baz).join("\n"))
+ end
+
+ recorder.count
+
+ expect(test_matcher.count_queries(recorder)).to eq({
+ 'SELECT "schema_migrations".* FROM "schema_migrations"' => {
+ %Q[WHERE "schema_migrations"."version" = 'foo\nbar\nbaz' LIMIT 1] => 2,
+ %Q[WHERE "schema_migrations"."version" = 'foo\nbiz\nbaz' LIMIT 1] => 1
+ }
+ })
+ end
+
+ it 'can aggregate queries' do
+ test_matcher = TestMatcher.new
+
+ recorder = ActiveRecord::QueryRecorder.new do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.count
+ TestQueries.create!(version: 'x')
+ TestQueries.first
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'x').update_all(version: 'y')
+ TestQueries.where(version: 'foobar').count
+ TestQueries.count
+ TestQueries.where(version: 'y').update_all(version: 'z')
+ TestQueries.where(version: 'z').delete_all
+ end
+
+ recorder.count
+
+ expect(test_matcher.count_queries(recorder)).to eq({
+ 'SELECT "schema_migrations".* FROM "schema_migrations"' => {
+ %q[WHERE "schema_migrations"."version" = 'foobar'] => 2,
+ %q[WHERE "schema_migrations"."version" = 'also foobar and baz'] => 1,
+ %q[ORDER BY "schema_migrations"."version" ASC LIMIT 1] => 1
+ },
+ 'SELECT COUNT(*) FROM "schema_migrations"' => {
+ "" => 2,
+ %q[WHERE "schema_migrations"."version" = 'foobar'] => 1
+ },
+ 'SAVEPOINT active_record_1' => { "" => 1 },
+ 'INSERT INTO "schema_migrations" ("version")' => {
+ %q[VALUES ('x') RETURNING "version"] => 1
+ },
+ 'RELEASE SAVEPOINT active_record_1' => { "" => 1 },
+ 'UPDATE "schema_migrations"' => {
+ %q[SET "version" = 'y' WHERE "schema_migrations"."version" = 'x'] => 1,
+ %q[SET "version" = 'z' WHERE "schema_migrations"."version" = 'y'] => 1
+ },
+ 'DELETE FROM "schema_migrations"' => {
+ %q[WHERE "schema_migrations"."version" = 'z'] => 1
+ }
+ })
+ end
+ end
+
+ it 'can count queries' do
+ test_matcher = TestMatcher.new
+ test_matcher.verify_count do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.first
+ TestQueries.count
+ end
+
+ expect(test_matcher.actual_count).to eq(4)
+ end
+
+ it 'can select specific queries' do
+ test_matcher = TestMatcher.new.for_query(/foobar/)
+ test_matcher.verify_count do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.first
+ TestQueries.count
+ end
+
+ expect(test_matcher.actual_count).to eq(2)
+ end
+
+ it 'can ignore specific queries' do
+ test_matcher = TestMatcher.new.ignoring(/foobar/)
+ test_matcher.verify_count do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.first
+ end
+
+ expect(test_matcher.actual_count).to eq(1)
+ end
+
+ it 'can perform inclusion and exclusion' do
+ test_matcher = TestMatcher.new.for_query(/foobar/).ignoring(/baz/)
+ test_matcher.verify_count do
+ TestQueries.where(version: 'foobar').to_a
+ TestQueries.where(version: 'also foobar and baz').to_a
+ TestQueries.first
+ TestQueries.count
+ end
+
+ expect(test_matcher.actual_count).to eq(1)
+ end
+
it 'does not contain marginalia annotations' do
test_matcher = TestMatcher.new
test_matcher.verify_count do
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
index 26a3c5e2b9e..0757f6ca015 100644
--- a/spec/tasks/gitlab/workhorse_rake_spec.rb
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -9,9 +9,13 @@ RSpec.describe 'gitlab:workhorse namespace rake task' do
describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' }
- let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s }
+ let(:clone_path) { Dir.mktmpdir('gitlab:workhorse:install-rake-test') }
let(:workhorse_source) { Rails.root.join('workhorse').to_s }
+ after do
+ FileUtils.rm_rf(clone_path)
+ end
+
context 'no dir given' do
it 'aborts and display a help message' do
# avoid writing task output to spec progress
@@ -20,7 +24,7 @@ RSpec.describe 'gitlab:workhorse namespace rake task' do
end
end
- context 'when an underlying Git command fail' do
+ context 'when an underlying Git command fails' do
it 'aborts and display a help message' do
expect(main_object)
.to receive(:checkout_or_clone_version).and_raise 'Git error'
@@ -29,47 +33,26 @@ RSpec.describe 'gitlab:workhorse namespace rake task' do
end
end
- describe 'checkout or clone' do
- it 'calls checkout_or_clone_version with the right arguments' do
- expect(main_object)
- .to receive(:checkout_or_clone_version).with(version: 'workhorse-move-notice', repo: repo, target_dir: clone_path, clone_opts: %w[--depth 1])
-
- run_rake_task('gitlab:workhorse:install', clone_path)
- end
- end
-
- describe 'gmake/make' do
- before do
- FileUtils.mkdir_p(clone_path)
- end
-
- context 'gmake is available' do
- before do
- expect(main_object).to receive(:checkout_or_clone_version)
- allow(Object).to receive(:run_command!).with(['gmake']).and_return(true)
+ it 'clones the origin and creates a gitlab-workhorse binary' do
+ FileUtils.rm_rf(clone_path)
+
+ Dir.mktmpdir('fake-workhorse-origin') do |workhorse_origin|
+ [
+ %W[git init -q #{workhorse_origin}],
+ %W[git -C #{workhorse_origin} checkout -q -b workhorse-move-notice],
+ %W[touch #{workhorse_origin}/proof-that-repo-got-cloned],
+ %W[git -C #{workhorse_origin} add .],
+ %W[git -C #{workhorse_origin} commit -q -m init],
+ %W[git -C #{workhorse_origin} checkout -q -b master]
+ ].each do |cmd|
+ raise "#{cmd.join(' ')} failed" unless system(*cmd)
end
- it 'calls gmake in the gitlab-workhorse directory' do
- expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
- expect(main_object).to receive(:run_command!).with(["gmake", "-C", workhorse_source, "install", "PREFIX=#{clone_path}"]).and_return(true)
-
- run_rake_task('gitlab:workhorse:install', clone_path)
- end
+ run_rake_task('gitlab:workhorse:install', clone_path, File.join(workhorse_origin, '.git'))
end
- context 'gmake is not available' do
- before do
- expect(main_object).to receive(:checkout_or_clone_version)
- allow(main_object).to receive(:run_command!).with(['make']).and_return(true)
- end
-
- it 'calls make in the gitlab-workhorse directory' do
- expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
- expect(main_object).to receive(:run_command!).with(["make", "-C", workhorse_source, "install", "PREFIX=#{clone_path}"]).and_return(true)
-
- run_rake_task('gitlab:workhorse:install', clone_path)
- end
- end
+ expect(File.exist?(File.join(clone_path, 'proof-that-repo-got-cloned'))).to be true
+ expect(File.executable?(File.join(clone_path, 'gitlab-workhorse'))).to be true
end
end
end